use super::encoding::Encoding;
use super::id::{Character, Item, Location, SavePoint, Snippet, Sound};
use super::lexeme::Lexeme;
use super::position::Position;
use super::style::Style;
use super::token::Token;
use crate::condition::Condition;
use crate::entity::Entity;
use crate::num_read::{NumRead, NumReader};
use std::convert::TryFrom;
use std::io::{Error, Read, Result, Seek, SeekFrom};

pub trait ScriptRead: Read + Seek {
    fn read_i8(&mut self) -> Result<i8> {
        NumReader::new(self).read_i8()
    }

    fn read_u8(&mut self) -> Result<u8> {
        NumReader::new(self).read_u8()
    }

    fn read_be_i16(&mut self) -> Result<i16> {
        NumReader::new(self).read_be_i16()
    }

    fn read_be_u16(&mut self) -> Result<u16> {
        NumReader::new(self).read_be_u16()
    }

    fn read_be_u32(&mut self) -> Result<u32> {
        NumReader::new(self).read_be_u32()
    }

    fn read_character(&mut self) -> Result<Character> {
        Character::try_from(self.read_u8()?)
            .map_err(|e| Error::other(format!("Invalid Character: 0x{:02x?}", e.number)))
    }

    fn read_condition(&mut self) -> Result<Condition> {
        Condition::try_from(self.read_be_u16()?)
            .map_err(|e| Error::other(format!("Invalid Condition: 0x{:04x?}", e.number)))
    }

    fn read_entity(&mut self) -> Result<Entity> {
        Ok(Entity::from(self.read_u8()?))
    }

    fn read_item(&mut self) -> Result<Item> {
        Item::try_from(self.read_u8()?)
            .map_err(|e| Error::other(format!("Invalid Item: 0x{:02x?}", e.number)))
    }

    fn read_some_item(&mut self) -> Result<Option<Item>> {
        Ok(Item::try_from(self.read_u8()?).ok())
    }

    fn read_location(&mut self) -> Result<Location> {
        Location::try_from(self.read_u8()?)
            .map_err(|e| Error::other(format!("Invalid Location: 0x{:02x?}", e.number)))
    }

    fn read_snippet(&mut self) -> Result<Snippet> {
        Snippet::try_from(self.read_u8()?)
            .map_err(|e| Error::other(format!("Invalid Snippet: 0x{:02x?}", e.number)))
    }

    fn read_save_point(&mut self) -> Result<SavePoint> {
        SavePoint::try_from(self.read_u8()?)
            .map_err(|e| Error::other(format!("Invalid SavePoint: 0x{:02x?}", e.number)))
    }

    fn read_spell(&mut self) -> Result<String> {
        let mut spell = String::new();
        loop {
            spell.push(match self.read_u8()? {
                0 => break,
                1 => '🌝',
                2 => '🪿',
                3 => '🪲',
                // FIXME Invalid value, should we throw an error maybe?
                _ => '�',
            });
        }
        Ok(spell)
    }

    fn read_sound(&mut self) -> Result<Sound> {
        Sound::try_from(self.read_u8()?)
            .map_err(|e| Error::other(format!("Invalid Sound: 0x{:02x?}", e.number)))
    }

    fn read_style(&mut self) -> Result<Style> {
        Style::try_from(self.read_u8()?)
            .map_err(|e| Error::other(format!("Invalid Style: 0x{:02x?}", e.number)))
    }

    /// Teads a relative i16 offset and returns the absolute u32 offset it points to.
    fn read_relative_position(&mut self) -> Result<Position> {
        let absolute_position = self.stream_position()? as u32;
        let relative_position = self.read_be_i16()? as i32;
        absolute_position
            .checked_add_signed(relative_position)
            .ok_or(Error::other(
                "Invalid relative offset {relative_position} from {absolute_position}",
            ))
            .map(|pos| Position::new(pos))
    }

    fn try_read_ideographic_space(&mut self) -> Result<(Option<char>, u8)> {
        const IDEOGRAPHIC_SPACE: char = '\u{3000}';
        match self.read_u8()? {
            0xba => Ok((Some(IDEOGRAPHIC_SPACE), 1)),
            _ => {
                self.seek(SeekFrom::Current(-1))?;
                Ok((None, 0))
            }
        }
    }

    fn try_read_diacritic(&mut self) -> Result<(Option<char>, u8)> {
        const DAKUTEN: char = '\u{3099}';
        const HANDAKUTEN: char = '\u{309a}';
        match self.read_u8()? {
            0xb8 => Ok((Some(DAKUTEN), 1)),
            0xb9 => Ok((Some(HANDAKUTEN), 1)),
            _ => {
                self.seek(SeekFrom::Current(-1))?;
                Ok((None, 0))
            }
        }
    }

    fn read_glyph(&mut self, encoding: Encoding) -> Result<(Option<u16>, u8)> {
        let byte = self.read_u8()?;
        match byte {
            0x00..=0xaf => Ok((Some(encoding.byte_to_glyph(byte)), 1)),
            0xb0..=0xb3 => Ok((
                Some(((byte as u16 - 0xb0) << 8) + self.read_u8()? as u16),
                2,
            )),
            _ => Ok((None, 1)),
        }
    }

    fn try_read_glyph(&mut self, encoding: Encoding) -> Result<(Option<u16>, u8)> {
        let position = self.stream_position()?;
        match self.read_glyph(encoding) {
            Ok((glyph, read)) => Ok((glyph, read)),
            Err(_) => {
                self.seek(SeekFrom::Start(position))?;
                Ok((None, 0))
            }
        }
    }

    fn try_read_symbol(&mut self, encoding: Encoding) -> Result<(Option<char>, u8)> {
        let (glyph, read) = self.try_read_glyph(encoding)?;
        if let Some(glyph) = glyph {
            if let Some(c) = encoding.try_glyph_into_char(glyph) {
                return Ok((Some(c), read));
            }
        }
        self.seek(SeekFrom::Current(-(read as i64)))?;
        Ok((None, 0))
    }

    fn try_read_char(&mut self, encoding: Encoding) -> Result<(Option<char>, u8)> {
        let (ideographic_space, ideographic_space_read) = self.try_read_ideographic_space()?;
        if let Some(ideographic_space) = ideographic_space {
            return Ok((Some(ideographic_space), ideographic_space_read));
        }

        let (diacritic, diacritic_read) = self.try_read_diacritic()?;
        let (c, c_read) = self.try_read_symbol(encoding)?;
        let read = diacritic_read + c_read;
        if let Some(c) = c {
            if let Some(diacritic) = diacritic {
                if let Some(normalized) = unicode_normalization::char::compose(c, diacritic) {
                    return Ok((Some(normalized), read));
                }
            } else {
                return Ok((Some(c), read));
            }
        }
        self.seek(SeekFrom::Current(-(read as i64)))?;
        Ok((None, 0))
    }

    fn try_read_string(&mut self, encoding: Encoding) -> Result<Option<String>> {
        let mut string = String::new();
        loop {
            let (c, read) = self.try_read_char(encoding)?;
            if let Some(c) = c {
                string.push(c);
            } else {
                self.seek(SeekFrom::Current(-(read as i64)))?;
                break;
            }
        }

        if string.is_empty() {
            Ok(None)
        } else {
            Ok(Some(string))
        }
    }

    // FIXME Drop this in favor of just using strings, which is mostly implemented.
    fn read_glyph_token(&mut self, encoding: Encoding) -> Result<Token> {
        match encoding {
            Encoding::English => {
                let (glyph, _) = self.try_read_glyph(encoding)?;
                let glyph = glyph.unwrap();
                Ok(match glyph {
                    0x80 => Token::MoonHieroglyph,
                    0x81 => Token::GooseHieroglyph,
                    0x82 => Token::BeetleHieroglyph,
                    0x83 => Token::FadedHieroglyph,
                    0x85..=0x87 => {
                        const CAPITAL_A: u8 = 0x41;
                        Token::LargeGlyph(char::from(glyph as u8 - 0x85 + CAPITAL_A))
                    }
                    invalid => Token::Error(format!("Invalid glyph: 0x{invalid:02x}.")),
                })
            }
            Encoding::Japanese => {
                let (glyph, _) = self.try_read_glyph(encoding)?;
                let glyph = glyph.unwrap();
                // FIXME The glyphs aren't the same.
                Ok(match glyph {
                    0x80 => Token::MoonHieroglyph,
                    0x81 => Token::GooseHieroglyph,
                    0x82 => Token::BeetleHieroglyph,
                    0x85..=0x87 => {
                        const CAPITAL_A: u8 = 0x41;
                        Token::LargeGlyph(char::from(glyph as u8 - 0x85 + CAPITAL_A))
                    }
                    invalid => Token::Error(format!("Invalid glyph: 0x{invalid:02x}.")),
                })
            }
        }
    }

    /// Returns some token or none if the end of script token if found.
    fn read_token(&mut self, encoding: Encoding) -> Result<Token> {
        if let Some(string) = self.try_read_string(encoding)? {
            return Ok(Token::String(string));
        }

        Ok(match self.read_u8()? {
            // FIXME Drop this in favor of just using strings, which is mostly implemented.
            byte if byte < 0xb4 => {
                self.seek(SeekFrom::Current(-1))?;
                self.read_glyph_token(encoding)?
            }
            0xb4 => Token::Character(self.read_character()?),
            0xb5 => Token::Item(self.read_item()?),
            0xb6 => Token::Location(self.read_location()?),
            0xb7 => Token::Snippet(self.read_snippet()?),
            0xb8 => Token::Dakuten,
            0xb9 => Token::Handakuten,
            0xba => Token::IdeographicSpace,
            0xbb => Token::WideSpace,
            0xbc => Token::NewLine,
            0xbd => Token::More,
            0xbe => Token::Speed(self.read_u8()?),
            0xbf => match self.read_u8()? {
                0x00 => Token::UnknownCommand00SetFlag1,
                0x01 => Token::UnknownCommand01,
                0x02 => Token::Clear,
                0x03 => Token::Style(self.read_style()?),
                // FIXME It doesn't read arguments but it is followed by weird looking value.
                0x04 => Token::Prompt(self.read_condition()?),
                0x05 => Token::ScrollOut(self.read_u8()?, self.read_u8()?),
                0x06 => Token::PromptStyle([self.read_style()?, self.read_style()?]),
                // Reads 3 bytes as the address of a script to jump to if the text condition is set.
                0x07 => Token::JumpIfNo(Position::new(u32::from_be_bytes([
                    0,
                    self.read_u8()?,
                    self.read_u8()?,
                    self.read_u8()?,
                ]))),
                0x08 => Token::ToggleCanSpeedUp,
                0x09 => Token::Wait(self.read_u8()?),
                0x0a => Token::SetOutline(self.read_i8()? >= 0),
                0x0b => Token::Save(self.read_save_point()?),
                0x0c => Token::PushItem,
                0x0d => Token::UseItem,
                0x0e => Token::SetPepelogooLevel(self.read_u8()?),
                0x0f => Token::Price(self.read_be_u16()?),
                0x10 => Token::CheckCanBuy(self.read_be_u16()?),
                0x11 => Token::Sell,
                0x12 => Token::Buy,
                0x13 => Token::IncantSpell(self.read_spell()?),
                0x14 => Token::StartQuizz(self.read_be_u32()?),
                0x15 => Token::CorrectAnswer(self.read_u8()?),
                0x16 => Token::PauseSound,
                0x17 => Token::ResumeSound,
                0x18 => Token::UnknownCommand18(self.read_be_u16()?, self.read_be_u16()?),
                0x19 => Token::AnimateAsha(self.read_u8()?, self.read_u8()?, self.read_u8()?),
                0x1a => Token::Animate(
                    self.read_entity()?,
                    self.read_u8()?,
                    self.read_u8()?,
                    self.read_u8()?,
                    self.read_u8()?,
                ),
                // FIXME This seems to read 7 bytes total but no idea what the data is like.
                0x1b => Token::Merchant(
                    [
                        self.read_some_item()?,
                        self.read_some_item()?,
                        self.read_some_item()?,
                    ],
                    self.read_be_i16()?,
                    self.read_be_i16()?,
                ),
                0x1c => Token::RemoveItem(self.read_item()?),
                0x1d => Token::GainHearts(self.read_u8()?),
                0x1e => Token::CheckCanGainHearts(self.read_be_u16()?),
                invalid => Token::Error(format!("Invalid command: 0x{invalid:02x}.")),
            },
            0xe1 => Token::SomethingAndPlaySound(self.read_sound()?),
            0xe2 => Token::UnknownE2,
            0xe3 => Token::UnknownE3,
            0xe4 => Token::SeekIfRand(self.read_be_u16()?, self.read_relative_position()?),
            0xe5 => Token::UnknownE5(
                self.read_u8()?,
                self.read_be_u16()?,
                self.read_be_u16()?,
                self.read_be_u16()?,
                self.read_be_u16()?,
            ),
            0xe6 => Token::UnknownE6(self.read_be_u16()?, self.read_be_u16()?),
            0xe7 => Token::SeekIfOwned(self.read_item()?, self.read_relative_position()?),
            0xe8 => Token::SeekIfUnowned(self.read_item()?, self.read_relative_position()?),
            0xe9 => Token::UnknownE9(self.read_be_u16()?),
            0xea => Token::UnknownEA(
                self.read_be_u16()?,
                self.read_be_u16()?,
                self.read_be_u16()?,
                self.read_be_u16()?,
                self.read_be_u16()?,
            ),
            0xeb => Token::UnknownEB(self.read_be_u16()?),
            0xec => Token::UnknownEC(
                self.read_u8()?,
                self.read_be_u16()?,
                self.read_be_u16()?,
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
            ),
            0xed => Token::UnknownED(self.read_be_u16()?, self.read_be_u16()?),
            0xee => Token::UnknownEE(self.read_be_u16()?),
            0xef => Token::UnknownEF(self.read_u8()?),
            0xf0 => Token::UnusedSeekIfOwned(self.read_item()?, self.read_relative_position()?),
            0xf1 => Token::UnknownF1(self.read_be_u16()?, self.read_u8()?, self.read_be_u16()?),
            0xf2 => Token::UnknownF2(
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
                self.read_be_i16()?,
            ),
            0xf3 => Token::UnknownF3(
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
            ),
            0xf4 => Token::UnknownF4(self.read_u8()?, self.read_be_u16()?),
            0xf5 => Token::UnknownF5(self.read_be_u16()?),
            0xf6 => Token::UnknownF6(
                self.read_be_u16()?,
                self.read_be_u16()?,
                self.read_be_u16()?,
                self.read_be_u16()?,
            ),
            0xf7 => Token::UnknownF7EntityPositionRelated(
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
                self.read_u8()?,
            ),
            0xf8 => Token::SetTrue(self.read_condition()?),
            0xf9 => Token::Nop,
            0xfa => Token::SetFalse(self.read_condition()?),
            0xfb => Token::PlaySound(self.read_sound()?),
            0xfc => Token::SeekIfFalse(self.read_condition()?, self.read_relative_position()?),
            0xfd => Token::SeekIfTrue(self.read_condition()?, self.read_relative_position()?),
            // FIXME Check what exactly the value is, it's a 16-bits script address but I'm not sure
            // about its specific shape. How can the script work as a 16-bits address in the first
            // place? Is it an address in RAM?
            0xfe => Token::Seek(self.read_relative_position()?),
            0xff => Token::End,
            invalid => Token::Error(format!("Invalid: 0x{invalid:02x}.")),
        })
    }

    /// Returns some token or none if the end of script token if found.
    fn read_lexeme(&mut self, encoding: Encoding) -> Result<Lexeme> {
        let position = Position::new(self.stream_position()? as u32);
        let token = match self.read_token(encoding) {
            Ok(token) => token,
            Err(error) => Token::Error(format!("{error:?}")),
        };
        let size = self.stream_position()? as u32 - position.position();
        Ok(Lexeme::new(token, position, size))
    }

    // FIXME Make it return Result<Script>.
    #[inline]
    fn read_script(&mut self, encoding: Encoding) -> Result<Vec<Lexeme>> {
        let mut lexemes = vec![];
        loop {
            let lexeme = self.read_lexeme(encoding)?;
            if lexeme.token().is_terminating() {
                lexemes.push(lexeme);
                break;
            }
            lexemes.push(lexeme);
        }
        Ok(lexemes)
    }
}

impl<T: Read + Seek> ScriptRead for T {}
