use std::cell::RefCell;

use crate::common::{Color, Rect};
use crate::framework::backend::{BackendTexture, SpriteBatchCommand};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics;
use crate::game::player::Player;
use crate::game::scripting::tsc::text_script::TextScriptExecutionState;
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::graphics::font::Font;
use crate::input::touch_controls::TouchControlType;

#[derive(Copy, Clone, Eq, PartialEq)]
pub enum MapSystemState {
    Hidden,
    FadeInBox(u16),
    FadeInLine(u16),
    Visible,
    FadeOutBox(u16),
}

pub struct MapSystem {
    texture: RefCell<Option<Box<dyn BackendTexture>>>,
    has_map_data: RefCell<bool>,
    last_size: (u16, u16),
    tick: u16,
    state: MapSystemState,
}

impl MapSystem {
    pub fn new() -> MapSystem {
        MapSystem {
            texture: RefCell::new(None),
            has_map_data: RefCell::new(false),
            last_size: (0, 0),
            tick: 0,
            state: MapSystemState::Hidden,
        }
    }

    fn render_map(&self, state: &mut SharedGameState, ctx: &mut Context, stage: &Stage) -> GameResult {
        if self.texture.borrow().is_none() {
            *self.has_map_data.borrow_mut() = false;
            return Ok(());
        }

        *self.has_map_data.borrow_mut() = true;

        graphics::set_render_target(ctx, self.texture.borrow().as_ref())?;
        graphics::clear(ctx, Color::new(0.0, 0.0, 0.0, 1.0));

        let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;

        for y in 0..stage.map.height {
            for x in 0..stage.map.width {
                const RECTS: [Rect<u16>; 4] = [
                    Rect { left: 240, top: 24, right: 241, bottom: 25 },
                    Rect { left: 241, top: 24, right: 242, bottom: 25 },
                    Rect { left: 242, top: 24, right: 243, bottom: 25 },
                    Rect { left: 243, top: 24, right: 244, bottom: 25 },
                ];

                let attr = stage.map.get_attribute(x as _, y as _);

                let layer = match attr {
                    0 => 0,
                    0x01 | 0x02 | 0x40 | 0x44 | 0x51 | 0x52 | 0x55 | 0x56 | 0x60 | 0x71 | 0x72 | 0x75 | 0x76 | 0x80
                    | 0x81 | 0x82 | 0x83 | 0xA0 | 0xA1 | 0xA2 | 0xA3 => 1,
                    0x43 | 0x50 | 0x53 | 0x54 | 0x57 | 0x63 | 0x70 | 0x73 | 0x74 | 0x77 => 2,
                    _ => 3,
                };

                batch.add_rect(x as _, y as _, &RECTS[layer]);
            }
        }

        batch.draw(ctx)?;
        graphics::set_render_target(ctx, None)?;

        Ok(())
    }

    pub fn tick(
        &mut self,
        state: &mut SharedGameState,
        ctx: &mut Context,
        stage: &Stage,
        players: [&Player; 2],
    ) -> GameResult {
        let touch_rect = Rect::new_size(0, 0, state.canvas_size.0 as isize, state.canvas_size.1 as isize);

        if state.textscript_vm.state == TextScriptExecutionState::MapSystem {
            if self.state == MapSystemState::Hidden {
                state.touch_controls.control_type = TouchControlType::None;
                state.control_flags.set_control_enabled(false);
                self.state = MapSystemState::FadeInBox(0);
            }
        } else {
            self.state = MapSystemState::Hidden;
        }

        if self.state == MapSystemState::Hidden {
            self.tick = 0;
            *self.has_map_data.borrow_mut() = false;
            return Ok(());
        }

        self.tick = self.tick.wrapping_add(1);

        let width = (stage.map.width as f32 * state.scale) as u16;
        let height = (stage.map.height as f32 * state.scale) as u16;

        if self.last_size != (width, height) {
            self.last_size = (width, height);
            *self.texture.borrow_mut() = graphics::create_texture_mutable(ctx, width, height).ok();
            *self.has_map_data.borrow_mut() = false;
        }

        match self.state {
            MapSystemState::FadeInBox(tick) => {
                if tick >= 8 {
                    self.state = MapSystemState::FadeInLine(0);
                } else {
                    self.state = MapSystemState::FadeInBox(tick + 1);
                }
            }
            MapSystemState::FadeOutBox(tick) => {
                if tick == 0 {
                    state.control_flags.set_tick_world(true);
                    state.control_flags.set_control_enabled(true);
                    state.textscript_vm.state = TextScriptExecutionState::Ended;
                    self.state = MapSystemState::Hidden;
                } else {
                    self.state = MapSystemState::FadeOutBox(tick - 1);
                }
            }
            MapSystemState::FadeInLine(tick) => {
                if (tick + 2) < stage.map.height {
                    self.state = MapSystemState::FadeInLine(tick + 2);
                } else {
                    self.state = MapSystemState::Visible;
                }

                for player in &players {
                    if player.controller.trigger_jump()
                        || player.controller.trigger_shoot()
                        || state.touch_controls.consume_click_in(touch_rect)
                    {
                        self.state = MapSystemState::FadeOutBox(8);
                        break;
                    }
                }
            }
            MapSystemState::Visible => {
                for player in &players {
                    if player.controller.trigger_jump()
                        || player.controller.trigger_shoot()
                        || state.touch_controls.consume_click_in(touch_rect)
                    {
                        self.state = MapSystemState::FadeOutBox(8);
                        break;
                    }
                }
            }
            _ => (),
        }

        Ok(())
    }

    pub fn draw(
        &self,
        state: &mut SharedGameState,
        ctx: &mut Context,
        stage: &Stage,
        players: [&Player; 2],
    ) -> GameResult {
        if self.state == MapSystemState::Hidden {
            return Ok(());
        }

        if !*self.has_map_data.borrow() {
            self.render_map(state, ctx, stage)?;
        }

        let (scr_w, scr_h) = (state.canvas_size.0 * state.scale, state.canvas_size.1 * state.scale);
        let text_height = state.font.line_height();
        let rect_black_bar = Rect::new_size(
            0,
            (7.0 * state.scale) as _,
            state.screen_size.0 as _,
            ((text_height + 4.0) * state.scale) as _,
        );

        if !state.constants.is_switch {
            graphics::draw_rect(ctx, rect_black_bar, Color::new(0.0, 0.0, 0.0, 1.0))?;
        }

        let map_name = if state.constants.is_cs_plus && state.settings.locale == "jp" {
            stage.data.name_jp.as_str()
        } else {
            stage.data.name.as_str()
        };

        state.font.builder().center(state.canvas_size.0).y(9.0).draw(
            map_name,
            ctx,
            &state.constants,
            &mut state.texture_set,
        )?;

        let mut map_rect = Rect::new(0.0, 0.0, self.last_size.0 as f32, self.last_size.1 as f32);

        match self.state {
            MapSystemState::FadeInBox(tick) | MapSystemState::FadeOutBox(tick) => {
                let width = (state.scale * tick as f32 * stage.map.width as f32 / 16.0) as isize;
                let height = (state.scale * tick as f32 * stage.map.height as f32 / 16.0) as isize;

                let rect = Rect::new_size(
                    (scr_w / 2.0) as isize - width,
                    (scr_h / 2.0) as isize - height,
                    width * 2,
                    height * 2,
                );

                graphics::draw_rect(ctx, rect, Color::new(0.0, 0.0, 0.0, 1.0))?;

                return Ok(());
            }
            MapSystemState::FadeInLine(line) => {
                map_rect.bottom = state.scale * (line as f32 + 1.0);
            }
            _ => (),
        }

        let width_border = state.scale * (stage.map.width as f32 + 2.0);
        let height_border = state.scale * (stage.map.height as f32 + 2.0);

        let rect = Rect::new_size(
            ((scr_w - width_border) / 2.0) as isize,
            ((scr_h - height_border) / 2.0) as isize,
            width_border as isize,
            height_border as isize,
        );

        graphics::draw_rect(ctx, rect, Color::new(0.0, 0.0, 0.0, 1.0))?;

        if let Some(tex) = self.texture.borrow_mut().as_mut() {
            let width = state.scale * stage.map.width as f32;
            let height = state.scale * stage.map.height as f32;

            tex.clear();
            tex.add(SpriteBatchCommand::DrawRect(
                map_rect,
                Rect::new_size((scr_w - width) / 2.0, (scr_h - height) / 2.0, map_rect.width(), map_rect.height()),
            ));
            tex.draw()?;
        }

        if (self.tick & 8) != 0 {
            const PLAYER_RECT: Rect<u16> = Rect { left: 0, top: 57, right: 1, bottom: 58 };

            let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
            let x_offset = (state.canvas_size.0 - stage.map.width as f32) / 2.0;
            let y_offset = (state.canvas_size.1 - stage.map.height as f32) / 2.0;
            let tile_div = stage.map.tile_size.as_int() * 0x200;

            for player in &players {
                if !player.cond.alive() {
                    continue;
                }

                let plr_x = x_offset + (player.x / tile_div) as f32;
                let plr_y = y_offset + (player.y / tile_div) as f32;

                batch.add_rect(plr_x, plr_y, &PLAYER_RECT);
            }

            batch.draw(ctx)?;
        }

        Ok(())
    }
}
