use super::*;

/// A monologue containing a list of optional lines
#[derive(Asset, TypePath, Debug, Deserialize, Default, Clone, PartialEq)]
pub(crate) struct Monologue {
    pub batches: Vec<MonologueLineBatch>,
}

impl Monologue {
    pub fn get(&self, idx: usize) -> Option<&MonologueLineBatch> {
        self.batches.get(idx)
    }

    pub fn add_batch(&mut self, group: MonologueLineBatch) {
        self.batches.push(group);
    }
}

#[derive(Debug, Error)]
pub struct MonologueParseError;

impl std::fmt::Display for MonologueParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "Could not parse monologue file")
    }
}

impl TryFrom<&str> for Monologue {
    type Error = MonologueParseError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        // Create a blank monologue to populate
        let mut monologue = Monologue::default();

        // Add an initial batch that may end up being empty
        monologue.add_batch(MonologueLineBatch::default());

        // Iterate over raw string lines in the .mono file
        for line in value.lines() {
            // Break up into batches by --- separator
            if line.starts_with("---") {
                monologue.add_batch(MonologueLineBatch::default());
            // Skip any empty lines or comments
            } else if line.starts_with("#") || line.is_empty() {
                // Skip comments and blank lines
                // everything else we read as a monologue line
            } else {
                monologue.batches.last_mut().unwrap().add_line(line.into());
            }
        }

        // Clear empty batches
        monologue.batches.retain(|batch| !batch.lines.is_empty());

        Ok(monologue)
    }
}

/// A set of possible lines in a monologue
#[derive(Debug, Deserialize, Default, Clone, PartialEq)]
pub(crate) struct MonologueLineBatch {
    pub lines: Vec<MonologueLine>,
}

impl MonologueLineBatch {
    fn add_line(&mut self, line: MonologueLine) {
        self.lines.push(line);
    }
}

/// A single monologue line
#[derive(Debug, Deserialize, Default, Clone, PartialEq)]
pub(crate) struct MonologueLine {
    pub value: String,
}

impl Display for MonologueLine {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.value)
    }
}

impl From<String> for MonologueLine {
    fn from(value: String) -> Self {
        MonologueLine { value }
    }
}

impl From<MonologueLine> for String {
    fn from(val: MonologueLine) -> Self {
        val.value
    }
}

impl From<&str> for MonologueLine {
    fn from(value: &str) -> Self {
        MonologueLine {
            value: value.into(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use indoc::*;

    #[test]
    fn empty_monologue() {
        const MONO: &str = "";

        let parsed: Monologue = MONO.try_into().unwrap();

        let expected = Monologue::default();

        assert_eq!(parsed, expected);
    }

    #[test]
    fn complicated_empty_monologue() {
        const MONO: &str = indoc! {"
            ---
            ---
            # a comment
            ---
            --- # more stuff
            # another comment
            ---

            ---
        "};

        let parsed: Monologue = MONO.try_into().unwrap();

        let expected = Monologue::default();

        assert_eq!(parsed, expected);
    }

    #[test]
    fn basic_monologue() {
        const MONO: &str = indoc! {"
            ---
            hello
            ---
            world
            ---
        "};

        let parsed: Monologue = MONO.try_into().unwrap();

        let expected = Monologue {
            batches: vec![
                MonologueLineBatch {
                    lines: vec!["hello".into()],
                },
                MonologueLineBatch {
                    lines: vec!["world".into()],
                },
            ],
        };

        assert_eq!(parsed, expected);
    }

    #[test]
    fn basic_with_comments_monologue() {
        const MONO: &str = indoc! {"
            # Some stuff before we get started
            ---
            # I really like this line
            hello
            ---
            # I'm skeptical about this one...
            world
            # This line needs work
            ---
            # More notes after the lines
        "};

        let parsed: Monologue = MONO.try_into().unwrap();

        let expected = Monologue {
            batches: vec![
                MonologueLineBatch {
                    lines: vec!["hello".into()],
                },
                MonologueLineBatch {
                    lines: vec!["world".into()],
                },
            ],
        };

        assert_eq!(parsed, expected);
    }

    #[test]
    fn batches_monologue() {
        const MONO: &str = indoc! {"
            a

            b

            c
            ---

            d

            e

            ---

            # with comment before...
            f
            # ...and after
        "};

        let parsed: Monologue = MONO.try_into().unwrap();

        let expected = Monologue {
            batches: vec![
                MonologueLineBatch {
                    lines: vec!["a".into(), "b".into(), "c".into()],
                },
                MonologueLineBatch {
                    lines: vec!["d".into(), "e".into()],
                },
                MonologueLineBatch {
                    lines: vec!["f".into()],
                },
            ],
        };

        assert_eq!(parsed, expected);
    }
}

#[derive(Default)]
struct MonologueLoader;

#[derive(Debug, Error)]
enum MonologueLoaderError {
    #[error("Could not load asset: {0}")]
    Io(#[from] std::io::Error),
    #[error("Could not parse utf8")]
    Utf8(#[from] std::string::FromUtf8Error),
    // TODO: Real errors
    #[error("Could not as monologue")]
    Parse(#[from] MonologueParseError),
}

impl AssetLoader for MonologueLoader {
    type Asset = Monologue;
    type Settings = ();
    type Error = MonologueLoaderError;
    async fn load(
        &self,
        reader: &mut dyn Reader,
        _settings: &(),
        _load_context: &mut LoadContext<'_>,
    ) -> Result<Self::Asset, Self::Error> {
        let mut bytes: Vec<u8> = Vec::new();
        reader.read_to_end(&mut bytes).await?;

        let raw_string = String::from_utf8(bytes)?;

        Ok(Monologue::try_from(raw_string.as_str())?)
    }

    fn extensions(&self) -> &[&str] {
        &["mono"]
    }
}

pub struct MonologueAssetsPlugin;

impl Plugin for MonologueAssetsPlugin {
    fn build(&self, app: &mut App) {
        app.init_asset::<Monologue>()
            .init_asset_loader::<MonologueLoader>();
    }
}
