use super::id::{Character, Item, Location, SavePoint, Snippet, Sound};
use super::position::Position;
use super::style::Style;
use crate::condition::Condition;
use crate::entity::Entity;
use std::cmp::Ord;

/// A parsed text token.
///
/// While the hieroglyphs could be converted into Unicode, that would make the text harder to handle
/// as they can't easily be typed and most fonts don't support them.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum Token {
    /// A Unicode string.
    String(String),
    // FIXME Drop it as it's a regular glyph, but still document what it is.
    /// An egyptian hieroglyph representing a moon on a lake.
    ///
    /// It looks a lot but not completely like '\u{131fa}' (classifier moon), on top of something
    /// aking to '\u{1321b}' (lake).
    MoonHieroglyph,
    // FIXME Drop it as it's a regular glyph, but still document what it is.
    /// An egyptian hieroglyph representing a right-facing goose and the sun.
    ///
    /// It could be '\u{13166}' (ostrich), '\u{1316c}' (goose), or '\u{1316d}' (duck). It also
    /// contains '\u{131f3}' (sun) in the to-left corner.
    GooseHieroglyph,
    // FIXME Drop it as it's a regular glyph, but still document what it is.
    /// An egyptian hieroglyph representing a beetle.
    ///
    /// It could be '\u{131a3}' (to become).
    BeetleHieroglyph,
    // FIXME Drop it as it's a regular glyph, but still document what it is.
    /// A faded out variant of the moon on a lake hieroglyph.
    FadedHieroglyph,
    // FIXME Drop it as it's a regular glyph, but still document what it is.
    LargeGlyph(char),
    Character(Character),
    Item(Item),
    Location(Location),
    Snippet(Snippet),
    /// The dakuten diacritic.
    ///
    /// This should not show up as a token but should be parsed into a string.
    Dakuten,
    /// The handakuten diacritic.
    ///
    /// This should not show up as a token but should be parsed into a string.
    Handakuten,
    /// The regular space used in Japanese text. It is rendered as 12px wide.
    ///
    /// This should not show up as a token but should be parsed into a string.
    IdeographicSpace,
    /// A 16px wide space used in Japanese to center text.
    WideSpace,
    NewLine,
    More,
    /// The number of frames to wait between printing each glyph.
    Speed(u8),
    UnknownCommand00SetFlag1,
    UnknownCommand01,
    Clear,
    Style(Style),
    /// When followed by value 39, it's a Yes/No prompt. All the prompts found so far have the 39
    /// value except one which has 30.
    Prompt(Condition),
    ScrollOut(u8, u8),
    /// The style to apply depending on the index of the currently selected item.
    PromptStyle([Style; 2]),
    /// If the condition is true, makes the script jump to the given location, continue otherwise.
    ///
    /// A Yes/No prompt sets the condition to be true when replying No, so the boolean logic is
    /// flipped.
    ///
    /// The location is a 3 bytes address.
    JumpIfNo(Position),
    /// Toggles whether the script can be sped up by pressing B.
    ///
    /// Speeding up is enabled by default.
    ToggleCanSpeedUp,
    Wait(u8),
    SetOutline(bool),
    /// The data likely is the save point.
    Save(SavePoint),
    PushItem,
    UseItem,
    // FIXME Could benefit from a dedicated type.
    /// Set Pepelogoo's level.
    ///
    /// Valid values are 0, 1 and 2 for levels 1, 2 and 3.
    SetPepelogooLevel(u8),
    Price(u16),
    CheckCanBuy(u16),
    Sell,
    Buy,
    // FIXME Could benefit from a dedicated type.
    /// Incant the expected spell. Valid values are 1, 2 and 3 representing the first three
    /// hieroglyphs, and 0 to terminate the spell.
    IncantSpell(String),
    // FIXME It doesn't read arguments but it is followed by four weird looking bytes.
    StartQuizz(u32),
    CorrectAnswer(u8),
    PauseSound,  // Related to ResumeSound.
    ResumeSound, // Related to PauseSound.
    // In FUN_000077ce, the first param is used as an index into DAT_0007c8f8.
    UnknownCommand18(u16, u16),
    AnimateAsha(u8, u8, u8),
    /// Related to animation, the las arguments are the entity's state to set.
    Animate(Entity, u8, u8, u8, u8),
    // FIXME What are the last two arguments for? They don't look like prices.
    /// Triggers a merchant.
    Merchant([Option<Item>; 3], i16, i16),
    RemoveItem(Item),
    GainHearts(u8),
    CheckCanGainHearts(u16),
    // FIXME What is that "something"?
    // Does something and plays the given sound.
    SomethingAndPlaySound(Sound),
    UnknownE2,
    UnknownE3,
    /// Seek to the given position if the first argument is greater than a random number.
    SeekIfRand(u16, Position),
    // FIXME Check its fields.
    UnknownE5(u8, u16, u16, u16, u16),
    // FIXME Check its fields.
    UnknownE6(u16, u16),
    SeekIfOwned(Item, Position),
    SeekIfUnowned(Item, Position),
    UnknownE9(u16),
    UnknownEA(u16, u16, u16, u16, u16),
    UnknownEB(u16),
    UnknownEC(u8, u16, u16, u8, u8, u8, u8),
    // FIXME May have 8 bytes in total, but I don't unserstand where they are read.
    UnknownED(u16, u16),
    UnknownEE(u16),
    UnknownEF(u8),
    /// Exactly like `SeekIfOwned`, but unused.
    UnusedSeekIfOwned(Item, Position),
    UnknownF1(u16, u8, u16),
    // Depending on Asha's x position, either use the first 5 arguments or seek by the last one.
    UnknownF2(u8, u8, u8, u8, u8, i16),
    UnknownF3(u8, u8, u8, u8, u8),
    UnknownF4(u8, u16),
    UnknownF5(u16),
    UnknownF6(u16, u16, u16, u16),
    UnknownF7EntityPositionRelated(u8, u8, u8, u8, u8, u8, u8, u8),
    /// Sets a bit to 1 in the global script state. The upper 13 bits are the byte's index, and the
    /// 3 lower bits are the bit's index, 0 for the least significant one.
    SetTrue(Condition),
    Nop,
    SetFalse(Condition),
    // Plays the given sound, the argument being its ID.
    PlaySound(Sound),
    /// The first argument is a condition ID, the second is a relative jump do do from the position
    /// of the address, aka from after the token minus 2.
    ///
    /// Checks the condition and either skip or continue.
    ///
    /// FIXME Ensure the destination is correct, either by checking it's inside the script, by
    /// parsing it, or by using labels depending on which values are encountered.
    SeekIfFalse(Condition, Position),
    SeekIfTrue(Condition, Position),
    /// Relative jump from the current position.
    Seek(Position),
    End,
    Label(Position),
    Error(String),
}

impl Token {
    pub fn is_terminating(&self) -> bool {
        matches!(self, Token::Seek(_) | Token::End | Token::Error(_))
    }

    pub fn position(&self) -> Option<Position> {
        match self {
            Token::JumpIfNo(position) => Some(*position),
            Token::SeekIfRand(_, position) => Some(*position),
            Token::SeekIfOwned(_, position) => Some(*position),
            Token::SeekIfUnowned(_, position) => Some(*position),
            Token::SeekIfFalse(_, position) => Some(*position),
            Token::SeekIfTrue(_, position) => Some(*position),
            Token::Seek(position) => Some(*position),
            _ => None,
        }
    }
}
