use crate::{
    types::{AppState, Config, ConfigOption, DatabasePath, GameState, Player, Rank},
    ui::{configuration, home, playing_field, ConfigurationView, HomeView, PlayingFieldView},
};
use serde::{Deserialize, Serialize};
use std::{
    path::PathBuf,
    sync::{Arc, RwLock},
};
use typeshare::typeshare;

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
#[serde(tag = "type", content = "content")]
pub enum CoreRequest {
    ChangeSetting(ChangeSettingRequest),
    CreateGame(CreateGameRequest),
    Home,
    OpenConfiguration,
    PlayingField,
    PlayStone(PlayStoneRequest),
    StartGame,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
#[serde(tag = "type", content = "content")]
pub enum ChangeSettingRequest {
    LibraryPath(String),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
pub struct PlayStoneRequest {
    pub column: u8,
    pub row: u8,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
pub struct CreateGameRequest {
    pub black_player: PlayerInfoRequest,
    pub white_player: PlayerInfoRequest,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
pub enum PlayerInfoRequest {
    Hotseat(HotseatPlayerRequest),
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
pub struct HotseatPlayerRequest {
    pub name: String,
    pub rank: Option<String>,
}

impl From<HotseatPlayerRequest> for Player {
    fn from(p: HotseatPlayerRequest) -> Self {
        Self {
            name: p.name,
            rank: p.rank.and_then(|r| Rank::try_from(r.as_ref()).ok()),
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
#[serde(tag = "type", content = "content")]
pub enum CoreResponse {
    ConfigurationView(ConfigurationView),
    HomeView(HomeView),
    PlayingFieldView(PlayingFieldView),
    UpdatedConfigurationView(ConfigurationView),
}

#[derive(Clone, Debug)]
pub struct CoreApp {
    config: Arc<RwLock<Config>>,
    state: Arc<RwLock<AppState>>,
}

impl CoreApp {
    pub fn new(config_path: std::path::PathBuf) -> Self {
        let config = Config::from_path(config_path).expect("configuration to open");

        let db_path: DatabasePath = config.get().unwrap();
        let state = Arc::new(RwLock::new(AppState::new(db_path)));

        Self {
            config: Arc::new(RwLock::new(config)),
            state,
        }
    }

    pub async fn dispatch(&self, request: CoreRequest) -> CoreResponse {
        match request {
            CoreRequest::ChangeSetting(request) => match request {
                ChangeSettingRequest::LibraryPath(path) => {
                    let mut config = self.config.write().unwrap();
                    config.set(ConfigOption::DatabasePath(DatabasePath(PathBuf::from(
                        path,
                    ))));
                    CoreResponse::UpdatedConfigurationView(configuration(&config))
                }
            },
            CoreRequest::CreateGame(create_request) => {
                let mut app_state = self.state.write().unwrap();
                let white_player = {
                    match create_request.white_player {
                        PlayerInfoRequest::Hotseat(request) => Player::from(request),
                    }
                };
                let black_player = {
                    match create_request.black_player {
                        PlayerInfoRequest::Hotseat(request) => Player::from(request),
                    }
                };
                app_state.game = Some(GameState {
                    white_player,
                    black_player,
                    ..GameState::default()
                });
                let game_state = app_state.game.as_ref().unwrap();
                CoreResponse::PlayingFieldView(playing_field(game_state))
            }
            CoreRequest::Home => {
                CoreResponse::HomeView(home(self.state.read().unwrap().database.all_games()))
            }
            CoreRequest::OpenConfiguration => {
                CoreResponse::ConfigurationView(configuration(&self.config.read().unwrap()))
            }
            CoreRequest::PlayingField => {
                let app_state = self.state.read().unwrap();
                let game = app_state.game.as_ref().unwrap();
                CoreResponse::PlayingFieldView(playing_field(game))
            }
            CoreRequest::PlayStone(request) => {
                let mut app_state = self.state.write().unwrap();
                app_state.place_stone(request);

                let game = app_state.game.as_ref().unwrap();
                CoreResponse::PlayingFieldView(playing_field(game))
            }
            CoreRequest::StartGame => {
                unimplemented!()
            }
        }
    }

    pub async fn run(&self) {}
}
