/*
ddtu - digital data transferring utility
Copyright (C) 2022  Kasyanov Nikolay Alexeyevich (Unbewohnte)

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

This program 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 Affero General Public License for more details.
*/

use crate::util::error::Error;
use crate::util::buf_read::{read_u128_slice, read_utf8_string_slice};
use crate::protocol::specs::*;
use crate::fsys::file::ChunkedFile;


pub struct TextPacket {
    pub text: String,
}

impl TextPacket {
    pub fn empty() -> TextPacket {
        return TextPacket{
            text: "".to_string(),
        };
    }

    pub fn new(txt: &str) -> TextPacket {
        return TextPacket {
            text: txt.to_string(),
        }
    }
}

impl Packet for TextPacket {
    fn get_type(&self) -> PacketType {
        return PacketType::TextData;
    } 

    fn as_bytes(&self) -> Vec<u8> {
        let mut packet_as_bytes: Vec<u8> = Vec::<u8>::new();

        packet_as_bytes.extend_from_slice(&TEXT_PACKET_ID.to_be_bytes());

        let text_length: u128 = self.text.len() as u128;
        packet_as_bytes.extend_from_slice(&text_length.to_be_bytes());
        packet_as_bytes.extend_from_slice(&self.text.as_bytes());

        return packet_as_bytes;
    }

    fn from_bytes(&mut self, packet_bytes: Vec<u8>) -> Option<Error> {
        if packet_bytes.len() < 18 {
            return Some(
                Error::new(format!("{} bytes is too small for a text packet to be valid", packet_bytes.len()).as_str()
            ));
        }

        // retrieve and check for packet type byte
        match packet_bytes[0].to_be() {
            TEXT_PACKET_ID => {}
            _ => {
                return Some(Error::new("packet type is not of a text packet"));
            }
        }

        // get text length
        let text_length: u128 = read_u128_slice(&packet_bytes[1..17]);
        
        if text_length as usize > packet_bytes[17..].len() {
            return Some(
                Error::new(
                    format!(
                        "text length ({}) is bigger than provided packet bytes length ({})",
                         text_length,
                         packet_bytes[16..].len()
                        ).as_str()
                    )
                );
        } 

        // extract text
        self.text = read_utf8_string_slice(&packet_bytes[17..]);

        return None;
    }
}


pub struct FileInfoPacket {
    pub file_id: u128,
    pub filename: String,
    pub filesize: u128,
    pub relative_path: String,
}

impl FileInfoPacket {
    pub fn empty() -> FileInfoPacket {
        return FileInfoPacket{
            file_id: 0,
            filename: String::new(),
            filesize: 0,
            relative_path: String::new(),
        };
    }

    pub fn new(file: &ChunkedFile, file_id: u128, rel_path: String) -> Result<FileInfoPacket, Error> {
        let mut new_fileinfo_packet: FileInfoPacket = FileInfoPacket::empty();
        
        // id
        new_fileinfo_packet.file_id = file_id;
        
        //filename
        match file.path.file_name() {
            Some(filename) => {
                match filename.to_owned().into_string() {
                    Ok(filename_string) => {
                        new_fileinfo_packet.filename = filename_string;
                    }
                    Err(filename_os_string) => {
                        return Err(
                            Error::new(format!("could not convert \'{:?}\' (OsString) to String", filename_os_string).as_str())
                        );
                    }
                }
            }
            None => {
                return Err(
                    Error::new(format!("could not get filename from \'{}\'", file.path.display()).as_str())
                );
            }
        }

        // size
        new_fileinfo_packet.filesize =  file.size;

        // rel path
        new_fileinfo_packet.relative_path = rel_path;

        return Ok(new_fileinfo_packet);
    }
}

impl Packet for FileInfoPacket {
    fn get_type(&self) -> PacketType {
        return PacketType::FileInfo;
    }

    fn as_bytes(&self) -> Vec<u8> {
        let mut packet_as_bytes: Vec<u8> = Vec::<u8>::new();
        
        // file id
        packet_as_bytes.extend_from_slice(&self.file_id.to_be_bytes());

        // filename
        let filename_bytes_length: u128 = self.filename.len() as u128;
        packet_as_bytes.extend_from_slice(&filename_bytes_length.to_be_bytes());
        packet_as_bytes.extend_from_slice(&self.filename.as_bytes());

        // filesize
        packet_as_bytes.extend_from_slice(&self.filesize.to_be_bytes());

        // relative path
        let relative_path_bytes_length: u128 = self.relative_path.len() as u128;
        packet_as_bytes.extend_from_slice(&relative_path_bytes_length.to_be_bytes());
        packet_as_bytes.extend_from_slice(&self.relative_path.as_bytes());

        return packet_as_bytes;
    }

    fn from_bytes(&mut self, packet_bytes: Vec<u8>) -> Option<Error> {
        if packet_bytes.len() < 16+16+16+16+1+1 {
            return Some(
                Error::new(format!("{} bytes is too small for a fileinfo packet to be valid", packet_bytes.len()).as_str()
            ));
        }

        // file id
        self.file_id = read_u128_slice(&packet_bytes[0..16]);

        // get filename
        let filename_length: u128 = read_u128_slice(&packet_bytes[16..32]);
        self.filename = read_utf8_string_slice(&packet_bytes[32..(filename_length+32) as usize]);

        // filesize
        self.filesize = read_u128_slice(&packet_bytes[(filename_length+32) as usize..(filename_length+32+16) as usize]);

        // relative path
        let rel_path_length: u128 = read_u128_slice(
            &packet_bytes[(filename_length+32+16) as usize..(filename_length+32+16+16)as usize]
        );
        self.relative_path = read_utf8_string_slice(
            &packet_bytes[(filename_length+32+16+16) as usize..(filename_length+32+16+16+rel_path_length) as usize]
        );

        return None;
    }
}


pub struct FileDataPacket {
    pub file_id: u128,
    pub chunk_no: u128,
    pub chunk: [u8; CHUNK_SIZE],
}

impl FileDataPacket {
    pub fn empty() -> FileDataPacket {
        return FileDataPacket {
            file_id: 0,
            chunk_no: 0,
            chunk: [0; CHUNK_SIZE],
        }
    }
}

impl Packet for FileDataPacket {
    fn get_type(&self) -> PacketType {
        return PacketType::FileData;
    }

    fn as_bytes(&self) -> Vec<u8> {
        let mut packet_bytes: Vec<u8> = Vec::<u8>::new();
        
        // file id
        packet_bytes.extend_from_slice(&self.file_id.to_be_bytes());

        // chunk number
        packet_bytes.extend_from_slice(&self.chunk_no.to_be_bytes());

        // chunk
        packet_bytes.extend_from_slice(&self.chunk.len().to_be_bytes());
        packet_bytes.extend_from_slice(&self.chunk);

        return packet_bytes;
    }

    fn from_bytes(&mut self, packet_bytes: Vec<u8>) -> Option<Error> {
        if packet_bytes.len() < 16+16+16+1 {
            return Some(
                Error::new(format!("{} bytes is too small for a fileinfo packet to be valid", packet_bytes.len()).as_str()
            ));
        }

        // file id
        self.file_id = read_u128_slice(&packet_bytes[0..16]);
        
        // chunk number
        self.chunk_no = read_u128_slice(&packet_bytes[16..32]);

        // chunk bytes
        let chunk_length = read_u128_slice(&packet_bytes[32..48]);
        for i in 48..(48+chunk_length) as usize {
            self.chunk[i-48] = packet_bytes[i];
        } 
        
        return None;
    }
}

pub struct ConnectionShutdownPacket {}

impl Packet for ConnectionShutdownPacket {
    fn get_type(&self) -> PacketType {
        return PacketType::ConnectionShutdown;
    }

    fn as_bytes(&self) -> Vec<u8> {
        return vec!(CONNECTION_SHUTDOWN_PACKET_ID.to_be_bytes()[0]);
    }

    fn from_bytes(&mut self, _packet_bytes: Vec<u8>) -> Option<Error> {
        return None;
    }
}

pub struct HandshakePaket {
    pub protocol_version: u8,
    pub use_encryption: bool,
    pub encryption_type: u8,
    pub encryption_key: Vec<u8>,
}

impl HandshakePaket {
    fn empty() -> HandshakePaket {
        return HandshakePaket{
            protocol_version: PROTOCOL_LATEST_VERSION,
            use_encryption: false,
            encryption_type: ECRYPTION_XOR,
            encryption_key: vec!(0, 0, 0, 0, 0, 0, 0, 0),
        };
    }
}

impl Packet for HandshakePaket {
    fn get_type(&self) -> PacketType {
        return PacketType::Handshake;
    }

    fn as_bytes(&self) -> Vec<u8> {
        return vec!(HANDSHAKE_PACKET_ID.to_be_bytes()[0]);
    }

    fn from_bytes(&mut self, _packet_bytes: Vec<u8>) -> Option<Error> {
        return None;
    }
}