use crate::model::*;
use crate::persistency::*;
use crate::protocol::*;
use crate::search::*;

use std::path::Path;
use std::sync::*;

#[derive(Clone)]
pub struct ServerState {
    data_store: Arc<RwLock<DataStore>>,
}

impl ServerState {
    #[allow(dead_code)]
    pub fn new(storage_path: &Path) -> std::io::Result<Self> {
        Ok(Self {
            data_store: Arc::new(RwLock::new(DataStore::new(&storage_path)?)),
        })
    }
}

impl ProtocolHandler for ServerState {
    fn new_connection(&mut self, con: &ClientConnection) {
        println!("{}: New Connection", con);
    }

    fn query(&mut self, con: &ClientConnection, query: &[String]) -> ProtocolResult<Vec<Texture>> {
        let q = Query::parse(query)
            .map_err(|e| ProtocolError::BadRequest(format!("Invalid Query String: {:?}", e)))?;

        let data_store = self
            .data_store
            .read()
            .unwrap_or_else(|_| handle_broken_rwlock());

        let mut textures = data_store.borrow_textures();

        let found = q.search(&mut textures);

        println!("{}: Query '{:?}' found: {}", con, query, found.len());

        Ok(found)
    }

    fn get_texture_by_id(
        &mut self,
        con: &ClientConnection,
        id: &str,
    ) -> ProtocolResult<Option<Texture>> {
        let data_store = self
            .data_store
            .read()
            .unwrap_or_else(|_| handle_broken_rwlock());

        let found = data_store.texture_by_id(id);

        println!(
            "{}: Texture by id: '{}' found: {}",
            con,
            id,
            found.is_some()
        );

        Ok(found)
    }

    fn get_texture_by_name(
        &mut self,
        con: &ClientConnection,
        name: &str,
    ) -> ProtocolResult<Option<Texture>> {
        let data_store = self
            .data_store
            .read()
            .unwrap_or_else(|_| handle_broken_rwlock());

        let found = data_store.texture_by_name(name);

        println!(
            "{}: Texture by name: '{}' found: {}",
            con,
            name,
            found.is_some()
        );

        Ok(found)
    }

    fn get_texture_file(
        &mut self,
        con: &ClientConnection,
        hash: Sha256,
    ) -> ProtocolResult<Vec<u8>> {
        let data_store = self
            .data_store
            .read()
            .unwrap_or_else(|_| handle_broken_rwlock());

        let data = data_store.read_texture_file_by_hash(&hash);

        println!("{}: Texture File: '{}' found: {}", con, &hash, data.is_ok());

        Ok(data?)
    }

    fn get_texture_preview(
        &mut self,
        con: &ClientConnection,
        hash: Sha256,
        format: TextureFormat,
    ) -> ProtocolResult<Vec<u8>> {
        let mut data_store = self
            .data_store
            .write()
            .unwrap_or_else(|_| handle_broken_rwlock());

        let preview = data_store.get_texture_preview(&hash, format);

        println!(
            "{}: Texture Preview: '{}' found: {}",
            con,
            &hash,
            preview.is_ok()
        );

        Ok(preview?)
    }

    fn replace_texture(
        &mut self,
        con: &ClientConnection,
        delete: Option<Texture>,
        insert: Option<Texture>,
        insert_texture_data: Option<Vec<u8>>,
    ) -> ProtocolResult<ReplaceTextureStatus> {
        println!("{}: Replace Texture Request", con);

        let mut data_store = self
            .data_store
            .write()
            .unwrap_or_else(|_| handle_broken_rwlock());

        match insert_texture_data {
            Some(data) => {
                data_store.store_texture_file(&data)?;
            }
            None => (),
        }

        match (delete, insert) {
            (Some(delete), Some(insert)) => {
                if !data_store.is_texture_file_on_disk(&insert.texture_hash) {
                    return Ok(ReplaceTextureStatus::NeedTextureData(insert.texture_hash));
                }

                if !data_store.delete(&delete) {
                    return Err(ProtocolError::Conflict(
                        "Delete Texture was modified!".to_string(),
                    ));
                }

                if !data_store.insert(insert) {
                    // undo delete
                    // panics if texture file is delete during delete and reinsert.
                    // unlikely to happen.
                    assert!(data_store.insert(delete));
                    return Err(ProtocolError::Conflict(
                        "Name or Id already taken.".to_string(),
                    ));
                }

                data_store.flush_metadata()?;

                Ok(ReplaceTextureStatus::Ok)
            }
            (Some(delete), None) => {
                if !data_store.delete(&delete) {
                    return Err(ProtocolError::Conflict(
                        "Delete Texture was modified!".to_string(),
                    ));
                }

                data_store.flush_metadata()?;

                Ok(ReplaceTextureStatus::Ok)
            }
            (None, Some(insert)) => {
                if !data_store.is_texture_file_on_disk(&insert.texture_hash) {
                    return Ok(ReplaceTextureStatus::NeedTextureData(insert.texture_hash));
                }

                if !data_store.insert(insert) {
                    // undo delete
                    return Err(ProtocolError::Conflict(
                        "Name or Id already taken.".to_string(),
                    ));
                }

                data_store.flush_metadata()?;

                Ok(ReplaceTextureStatus::Ok)
            }
            (None, None) => Ok(ReplaceTextureStatus::Ok),
        }
    }

    fn disconnected(&mut self, con: &ClientConnection) {
        println!("{}: Disconnected", con);
    }
}

fn handle_broken_rwlock() -> ! {
    eprintln!("Panic while data_store lock was acquired.");
    eprintln!("Representation in memory could be invalid.");
    eprintln!("Can't recover. Please restart.");
    std::process::exit(13);
}
