/*
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>

This file is part of On the Grid.

On the Grid is free software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

On the Grid is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License along with On the Grid. If not, see <https://www.gnu.org/licenses/>.
*/

use crate::{
    database::Database,
    library, settings,
    types::{Config, LibraryPath},
};
use async_std::channel::{Receiver, Sender};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, RwLock, RwLockReadGuard};

pub trait Observable<T> {
    fn subscribe(&self) -> Receiver<T>;
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum CoreRequest {
    Library(library::LibraryRequest),
    Settings(settings::SettingsRequest),
    /*
    ChangeSetting(ChangeSettingRequest),
    CreateGame(CreateGameRequest),
    Home,
    OpenConfiguration,
    PlayingField,
    PlayStone(PlayStoneRequest),
    StartGame,
    */
}

/*
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ChangeSettingRequest {
    LibraryPath(String),
}

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

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

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

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
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)]
pub enum CoreResponse {
    Library(library::LibraryResponse),
    Settings(settings::SettingsResponse),
}

impl From<library::LibraryResponse> for CoreResponse {
    fn from(r: library::LibraryResponse) -> Self {
        Self::Library(r)
    }
}

impl From<settings::SettingsResponse> for CoreResponse {
    fn from(r: settings::SettingsResponse) -> Self {
        Self::Settings(r)
    }
}

#[derive(Clone, Debug)]
pub enum CoreNotification {
    ConfigurationUpdated(Config),
    BoardUpdated,
}

#[derive(Clone, Debug)]
pub struct Core {
    config: Arc<RwLock<Config>>,
    // state: Arc<RwLock<AppState>>,
    library: Arc<RwLock<Option<Database>>>,
    subscribers: Arc<RwLock<Vec<Sender<CoreNotification>>>>,
}

impl Core {
    pub fn new(config: Config) -> Self {
        println!("config: {:?}", config);

        let library = match config.get::<LibraryPath>() {
            Some(ref path) if path.to_path_buf().exists() => {
                Some(Database::open_path(path.to_path_buf()).unwrap())
            }
            _ => None,
        };

        Self {
            config: Arc::new(RwLock::new(config)),
            // state,
            library: Arc::new(RwLock::new(library)),
            subscribers: Arc::new(RwLock::new(vec![])),
        }
    }

    pub fn get_config(&self) -> Config {
        self.config.read().unwrap().clone()
    }

    /// Change the configuration of the Core. This function will update any relevant core
    /// functions, especially the contents of the library, and it will notify any subscribed objects
    /// that the configuration has changed.
    ///
    /// It will not handle persisting the new configuration, as the backing store for the
    /// configuration is not a decision for the core library.
    pub async fn set_config(&self, config: Config) {
        *self.config.write().unwrap() = config.clone();

        // let db = library::read_library(self.config.read().unwrap().get::<LibraryPath>()).await;
        let library_path = self.config.read().unwrap().get::<LibraryPath>();
        if let Some(ref path) = library_path {
            self.load_library(path);
        }

        self.notify(CoreNotification::ConfigurationUpdated(config.clone()))
            .await;
    }

    fn load_library(&self, path: &LibraryPath) {
        let db = Database::open_path(path.to_path_buf()).unwrap();
        *self.library.write().unwrap() = Some(db);
    }

    pub fn library(&self) -> RwLockReadGuard<'_, Option<Database>> {
        self.library.read().unwrap()
    }

    pub async fn dispatch(&self, request: CoreRequest) -> CoreResponse {
        match request {
            CoreRequest::Library(request) => library::handle(self, request).await.into(),
            CoreRequest::Settings(request) => settings::handle(self, request).await.into(),
        }
    }

    pub async fn notify(&self, notification: CoreNotification) {
        let subscribers = self.subscribers.read().unwrap().clone();
        for subscriber in subscribers {
            let subscriber = subscriber.clone();
            let _ = subscriber.send(notification.clone()).await;
        }
    }

    /*
    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))
                    unimplemented!()
                }
            },
            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))
                */
                unimplemented!()
            }
            CoreRequest::Home => {
                // CoreResponse::HomeView(home(self.state.read().unwrap().database.all_games()))
                unimplemented!()
            }
            CoreRequest::OpenConfiguration => {
                // CoreResponse::ConfigurationView(configuration(&self.config.read().unwrap()))
                unimplemented!()
            }
            CoreRequest::PlayingField => {
                // let app_state = self.state.read().unwrap();
                // let game = app_state.game.as_ref().unwrap();
                // CoreResponse::PlayingFieldView(playing_field(game))
                unimplemented!()
            }
            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))
                unimplemented!()
            }
            CoreRequest::StartGame => {
                unimplemented!()
            }
        }
    }
    */

    // pub async fn run(&self) {}
}

impl Observable<CoreNotification> for Core {
    fn subscribe(&self) -> Receiver<CoreNotification> {
        let mut subscribers = self.subscribers.write().unwrap();

        let (sender, receiver) = async_std::channel::unbounded();
        subscribers.push(sender);

        receiver
    }
}