use super::*;

use std::io::*;
use std::net::*;

use serde::{Deserialize, Serialize};

pub struct Connection<R: Read + Sized, W: Write + Sized> {
    reader: R,
    writer: W,
}

const KIB: u32 = 1024;
const MIB: u32 = 1024 * 1024;

const PACKAGE_TYPE_ERROR: u8 = 0;
const PACKAGE_TYPE_JSON: u8 = 1;
const PACKAGE_TYPE_BIN: u8 = 2;

impl Connection<BufReader<TcpStream>, TcpStream> {
    pub fn from_tcp(connection: TcpStream) -> Result<Self> {
        let reader = BufReader::new(connection.try_clone()?);
        Ok(Connection {
            reader,
            writer: connection,
        })
    }
}

impl<R: Read + Sized, W: Write + Sized> Connection<R, W> {
    #[cfg(test)]
    pub fn new(reader: R, writer: W) -> Self {
        Connection { reader, writer }
    }

    #[cfg(test)]
    pub fn destruct(self) -> (R, W) {
        let Connection { reader, writer } = self;
        (reader, writer)
    }

    pub fn receive(&mut self) -> Result<Package> {
        let mut payload_type_buffer = [0u8; 1];
        let mut reserved_buffer = [0u8; 3];
        let mut payload_length_buffer = [0u8; 4];

        self.reader.read_exact(&mut payload_type_buffer)?;
        self.reader.read_exact(&mut reserved_buffer)?;
        self.reader.read_exact(&mut payload_length_buffer)?;

        let payload_type = payload_type_buffer[0];
        let payload_length = u32::from_be_bytes(payload_length_buffer);

        // Check length.
        match payload_type {
            PACKAGE_TYPE_ERROR => {
                if payload_length > 1 * KIB {
                    return Err(Error::new(
                        ErrorKind::InvalidData,
                        "Maximum length of Error Package is 1 KiB.",
                    ));
                }
            }
            PACKAGE_TYPE_JSON => {
                if payload_length > 16 * MIB {
                    return Err(Error::new(
                        ErrorKind::InvalidData,
                        "Maximum length of JSON Package is 16 MiB.",
                    ));
                }
            }
            PACKAGE_TYPE_BIN => {
                if payload_length > 512 * MIB {
                    return Err(Error::new(
                        ErrorKind::InvalidData,
                        "Maximum length of Binary Package is 512 MiB.",
                    ));
                }
            }
            _ => {
                return Err(Error::new(ErrorKind::InvalidData, "Unknown Package Type."));
            }
        }

        let mut payload = vec![0u8; payload_length as usize];
        self.reader.read_exact(&mut payload[..])?;

        match payload_type {
            PACKAGE_TYPE_ERROR => {
                let contents = String::from_utf8(payload)
                    .map_err(|_| Error::new(ErrorKind::InvalidData, "Invalid UTF-8."))?;

                let mut parts = contents.splitn(2, " ");

                match (parts.next(), parts.next()) {
                    (Some(code), Some(info)) => {
                        let code: u16 = code.parse().map_err(|_| {
                            Error::new(ErrorKind::InvalidData, "Status code in error expected!")
                        })?;
                        Ok(Package::Error(code, info.to_string()))
                    }

                    _ => Err(Error::new(
                        ErrorKind::InvalidData,
                        "Status code in error expected!",
                    )),
                }
            }
            PACKAGE_TYPE_JSON => {
                // try special packages first.
                match serde_json::from_slice::<Option<bool>>(&payload[..]) {
                    Ok(Some(true)) => {
                        return Ok(Package::Json(JsonValue::True));
                    }

                    Ok(Some(false)) => {
                        return Ok(Package::Json(JsonValue::False));
                    }

                    Ok(None) => {
                        return Ok(Package::Json(JsonValue::Null));
                    }
                    _ => (), // else try other
                }

                // try single texture
                match serde_json::from_slice::<Texture>(&payload[..]) {
                    Ok(texture) => {
                        return Ok(Package::Json(JsonValue::Texture(texture)));
                    }
                    _ => (), // else try other
                }

                // try texture vec
                match serde_json::from_slice::<Vec<Texture>>(&payload[..]) {
                    Ok(textures) => {
                        return Ok(Package::Json(JsonValue::TextureArray(textures)));
                    }
                    _ => (), // else try other
                }

                let json: Command = serde_json::from_slice(&payload[..]).map_err(|e| {
                    #[cfg(test)]
                    dbg!(&e);

                    Error::new(ErrorKind::InvalidData, "Invalid JSON.")
                })?;

                Ok(Package::Command(json))
            }
            PACKAGE_TYPE_BIN => Ok(Package::Binary(payload)),
            _ => {
                // Covered in the match above.
                unreachable!();
            }
        }
    }

    pub fn send(&mut self, pkg: &Package) -> Result<()> {
        match pkg {
            Package::Json(JsonValue::Null) => self.send_json(&Option::<bool>::None),
            Package::Json(JsonValue::True) => self.send_json(&true),
            Package::Json(JsonValue::False) => self.send_json(&false),

            Package::Json(JsonValue::Texture(texture)) => self.send_json(texture),
            Package::Json(JsonValue::TextureArray(textures)) => self.send_json(textures),

            Package::Command(cmd) => self.send_json(cmd),

            Package::Binary(bin) => self.send_binary(bin),
            Package::Error(code, msg) => self.send_error(*code, msg),
        }
    }

    fn send_json<J: 'static + Serialize>(&mut self, msg: &J) -> Result<()> {
        let w = &mut self.writer;

        let payload = serde_json::to_vec(msg).unwrap();

        w.write_all(&[PACKAGE_TYPE_JSON, 42, 42, 42])?;
        w.write_all(&(payload.len() as u32).to_be_bytes())?;

        w.write_all(&payload[..])?;

        w.flush()
    }

    fn send_binary(&mut self, payload: &[u8]) -> Result<()> {
        let w = &mut self.writer;

        w.write_all(&[PACKAGE_TYPE_BIN, 42, 42, 42])?;
        w.write_all(&(payload.len() as u32).to_be_bytes())?;

        w.write_all(payload)?;

        w.flush()
    }

    fn send_error(&mut self, code: u16, msg: &str) -> Result<()> {
        let w = &mut self.writer;

        let payload = format!("{:03} {}", code, msg).into_bytes();

        w.write_all(&[PACKAGE_TYPE_ERROR, 42, 42, 42])?;
        w.write_all(&(payload.len() as u32).to_be_bytes())?;

        w.write_all(&payload[..])?;

        w.flush()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    use crate::model::*;

    // Utility fn
    fn demo_texture() -> Texture {
        const HASH_BYTES: [u8; 32] = [
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
            24, 25, 26, 27, 28, 29, 30, 31,
        ];

        Texture {
            id: "d32e1f80-a17a-4dd7-8ed7-c3a2de1de1c9".to_string(),
            name: "texture.png".to_string(),
            tags: vec!["Wood".to_string(), "Hair".to_string()],
            format: TextureFormat::PNG,
            resolution: (512, 512),
            texture_hash: Sha256(HASH_BYTES),
        }
    }

    fn test_read_back(pkg: &Package) {
        let dummy_read_data = [255u8];

        // generate write
        let mut c = Connection::new(&dummy_read_data[..], Vec::new());
        c.send(&pkg).unwrap();

        let (_, written) = c.destruct();

        // read back in
        let mut c = Connection::new(&written[..], Vec::new() /*dummy*/);
        let read_back_pkg = c.receive().unwrap();

        assert_eq!(&read_back_pkg, pkg)
    }

    #[test]
    fn read_error_good() {
        let mut read_data = Vec::new();

        let msg = b"42 TEST";
        read_data.extend_from_slice(&[0, 42, 42, 42, 0, 0, 0, msg.len() as u8]);
        read_data.extend_from_slice(msg);

        let mut c = Connection::new(&read_data[..], Vec::new());

        assert_eq!(c.receive().unwrap(), Package::Error(42, "TEST".to_string()))
    }

    #[test]
    fn read_error_bad() {
        let mut read_data = Vec::new();

        let msg = b"TEST XXXX";
        read_data.extend_from_slice(&[0, 42, 42, 42, 0, 0, 0, msg.len() as u8]);
        read_data.extend_from_slice(msg);

        let mut c = Connection::new(&read_data[..], Vec::new());

        assert_eq!(c.receive().is_err(), true)
    }

    #[test]
    fn read_binary_good() {
        let mut read_data = Vec::new();

        let msg = b"Hello World";
        read_data.extend_from_slice(&[2, 42, 42, 42, 0, 0, 0, msg.len() as u8]);
        read_data.extend_from_slice(msg);

        let mut c = Connection::new(&read_data[..], Vec::new());

        assert_eq!(c.receive().unwrap(), Package::Binary(Vec::from(&msg[..])));
    }

    #[test]
    fn read_binary_bad() {
        let mut read_data = Vec::new();

        let msg = b"Hello World";
        // to large (size).
        read_data.extend_from_slice(&[2, 42, 42, 42, 255, 0, 0, 0]);
        read_data.extend_from_slice(msg);

        let mut c = Connection::new(&read_data[..], Vec::new());

        assert_eq!(c.receive().is_err(), true);
    }

    // we don't test the actual json parsing here,
    // since serde_json is well tested.

    #[test]
    fn read_json_good() {
        let mut read_data = Vec::new();

        let msg = br#"{
                "query" : {
                    "query" : ["Hallo",   "Welt!"]
                }
            }"#;
        read_data.extend_from_slice(&[1, 42, 42, 42, 0, 0, 0, msg.len() as u8]);
        read_data.extend_from_slice(msg);

        let mut c = Connection::new(&read_data[..], Vec::new());

        assert_eq!(
            c.receive().unwrap(),
            Package::Command(Command::Query {
                query: vec!["Hallo".to_string(), "Welt!".to_string()],
            })
        );
    }

    #[test]
    fn read_json_multiple_special() {
        let mut read_data = Vec::new();

        let msg = br#"null"#;
        read_data.extend_from_slice(&[1, 42, 42, 42, 0, 0, 0, msg.len() as u8]);
        read_data.extend_from_slice(msg);

        let msg = br#"true"#;
        read_data.extend_from_slice(&[1, 42, 42, 42, 0, 0, 0, msg.len() as u8]);
        read_data.extend_from_slice(msg);

        let msg = br#"false"#;
        read_data.extend_from_slice(&[1, 42, 42, 42, 0, 0, 0, msg.len() as u8]);
        read_data.extend_from_slice(msg);

        let mut c = Connection::new(&read_data[..], Vec::new());

        assert_eq!(c.receive().unwrap(), Package::Json(JsonValue::Null));

        assert_eq!(c.receive().unwrap(), Package::Json(JsonValue::True));

        assert_eq!(c.receive().unwrap(), Package::Json(JsonValue::False));
    }

    #[test]
    fn read_json_bad() {
        let mut read_data = Vec::new();

        let msg = br#"{
                "big foot" : {
                    "query" : ["Hallo",   "Welt!"]
                }
            }"#;
        read_data.extend_from_slice(&[1, 42, 42, 42, 0, 0, 0, msg.len() as u8]);
        read_data.extend_from_slice(msg);

        let mut c = Connection::new(&read_data[..], Vec::new());

        assert_eq!(c.receive().is_err(), true);
    }

    #[test]
    fn writes() {
        test_read_back(&Package::Json(JsonValue::Null));
        test_read_back(&Package::Json(JsonValue::True));
        test_read_back(&Package::Json(JsonValue::False));

        test_read_back(&Package::Json(JsonValue::Texture(demo_texture())));
        test_read_back(&Package::Json(JsonValue::TextureArray(vec![
            demo_texture(),
            demo_texture(),
        ])));

        test_read_back(&Package::Command(Command::Pong {}));

        test_read_back(&Package::Error(500, "I bims 1 Error".to_string()));

        test_read_back(&Package::Binary(vec![42u8; 50000]));
    }
}
