// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
pub mod model;
pub mod view;
mod controller;

use std::{time::Instant, any::Any, fmt::Write};

use glium::Frame;
use winit::{event::Event, window::CursorIcon};

use model::{Model, ModelOutcome, client::{ClientRes, ClientError, LevelInfo}};
use view::{View, ResetType, StoryTextInfo};
use controller::Controller;

use crate::app::{App, config::action::{Action, ActionEvent, ActionState}, state::{GameState, Status}};
use crate::protocol::message::stream::MsError;

#[derive(Default)]
struct TimeArgs {
	dt: f32,
	steps: u32,
	time_diff: f32,
}

pub struct Playing<'a> {
	model: Option<Model>,
	view: View<'a>,
	controller: Controller,

	leave: bool,
	story_text: StoryTextInfo,
}

impl<'a> Playing<'a> {
	pub fn new(app: &mut App) -> Playing<'a> {
		let view = View::new(app);
		let controller = Controller::new(Instant::now());

		Playing { model: None, view, controller, leave: false, story_text: StoryTextInfo::empty() }
	}

	fn action_text_input_closed(&mut self, action: ActionEvent) {
		if matches!(action, ActionEvent::Action(Action::PlayingLeave, ActionState::Pressed)) {
			self.leave = true;
		}

		if matches!(action, ActionEvent::Action(Action::PrintPos, ActionState::Pressed)) {
			let world_pos = self.view.camera.pixel_to_abs_world_coords(self.controller.cursor_pos);

			let mut msg = String::from("Cursor: World = ");
			let _ = write!(msg, "{world_pos}");

			if let Some(model) = &self.model {
				let blocks = model.get_world().get_blocks();
				let block_pos = blocks.world_to_block_pos(world_pos);
				let in_bounds = blocks.block_pos_in_bounds(block_pos);
				let _ = write!(msg, ", Block = {block_pos}");
				if !in_bounds {
					let _ = write!(msg, " (out of bounds)");
				}
			}

			log::info!("{msg}");
		}

		self.view.action(action);
		self.controller.action(action);
	}
}

impl GameState for Playing<'_> {
	fn enable(&mut self, app: &mut App) {
		app.gui.do_extra_pass();
		self.view.sounds.set_muted(false);
	}

	fn push(&mut self, app: &mut App, msg: Option<Box<dyn Any>>) {
		let mut model = Model::new(*msg.unwrap().downcast::<ClientRes>().unwrap());
		self.view.reset(app, &model, ResetType::Full);
		self.controller.reset(Instant::now());
		self.story_text = StoryTextInfo::new(model.get_client_mut().get_level_mut().map_or_else(Vec::new, LevelInfo::take_story_text));
		self.model = Some(model);
	}

	fn disable(&mut self, app: &mut App) {
		self.model = None;
		self.view.sounds.set_muted(true);
		app.window.set_cursor(CursorIcon::Default);
	}

	fn action(&mut self, action: ActionEvent) {
		if self.story_text.open() {
			if matches!(action, ActionEvent::Action(Action::StoryTextClose, ActionState::Pressed)) {
				self.story_text.next();
				if self.story_text.open() {
					return;
				}
			} else {
				return;
			}
		}

		let prev_open = self.view.get_hud().text_input_open();
		self.view.text_input_action(action);

		if self.view.get_hud().text_input_open() {
			if !prev_open {
				self.action_text_input_closed(ActionEvent::AllReleased);
			}
		} else {
			self.action_text_input_closed(action);
		}
	}

	fn event(&mut self, app: &mut App, event: &Event<()>) {
		match event {
			Event::WindowEvent { event, .. } => {
				self.view.window_event(event, self.story_text.open());
				if self.story_text.open() {
					let _ = app.gui.window_event(&app.window, event);
				} else {
					self.controller.window_event(event);
				}
			},
			Event::AboutToWait => app.window.request_redraw(),
			_ => (),
		}
	}

	fn loop_iter(&mut self, app: &mut App, frame: &mut Frame) -> Status {
		let time = self.controller.get_time();

		let model = self.model.as_mut().unwrap();

		if !self.story_text.open() {
			match model.update(app, &mut self.view, &mut self.controller, &time) {
				Ok(ModelOutcome::Continue) => model.sanity_check(),
				Ok(ModelOutcome::Exit) => self.leave = true,
				Err(err) => {
					match err {
						ClientError::Io(MsError::Disconnected(Some(reason))) => log::warn!("server disconnected: {reason}"),
						ClientError::Io(MsError::Disconnected(None)) => log::warn!("server disconnected"),
						ClientError::Io(MsError::Other(msg)) => log::warn!("server error and disconnection: {msg}"),
						ClientError::Protocol(msg) => log::warn!("disconnecting after server violated protocol: {msg}"),
						ClientError::Limit(msg) => log::warn!("disconnecting after client limit violated: {msg}"),
					}

					self.leave = true;
				},
			}
		}

		app.window.set_cursor(
			if self.story_text.open() { CursorIcon::Default }
			else if self.view.following_player() { CursorIcon::Crosshair }
			else if self.view.spectating_fixed_camera() { CursorIcon::Default }
			else if self.controller.is_dragging() { CursorIcon::Grabbing }
			else { CursorIcon::Grab }
		);

		self.view.render(app, frame, model, &mut self.controller, &if self.story_text.open() { TimeArgs::default() } else { time }, &mut self.story_text);

		if self.leave {
			self.leave = false;
			Status::PopState
		} else { Status::Ok }
	}
}
