use chrono::{DateTime, Utc};

use psopacket::{pso_packet, PSOPacketData};
use crate::{PSOPacket, PacketParseError, PSOPacketData, utf8_to_utf16_array};

use crate::character::character::SelectScreenCharacter;

use std::io::Read;

pub const PATCH_FILE_CHUNK_SIZE: u16 = 0x8000; // 32kb
pub const GUILD_CARD_CHUNK_SIZE: usize = 0x6800;
pub const PARAM_DATA_CHUNK_SIZE: usize = 0x6800;

#[pso_packet(0x03)]
pub struct LoginWelcome {
    #[utf8]
    copyright: [u8; 0x60],
    server_key: [u8; 48],
    client_key: [u8; 48],
}

impl LoginWelcome {
    pub fn new(server_key: [u8; 48], client_key: [u8; 48]) -> LoginWelcome {
        let mut copyright = [0u8; 0x60];
        copyright[..0x4B].clone_from_slice(b"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.");
        LoginWelcome {
            copyright: copyright,
            server_key: server_key,
            client_key: client_key,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SessionAction {
    None,
    SelectCharacter,
    NewCharacter,
    DressingRoom,
}

impl PSOPacketData for SessionAction {
    fn from_bytes<R: Read>(cursor: &mut R) -> Result<Self, PacketParseError> {
        let mut bytes = [0u8; 1];
        let len = cursor.read(&mut bytes).map_err(|_| PacketParseError::ReadError)?;
        if len != 1 {
            return Err(PacketParseError::NotEnoughBytes)
        }
        match bytes[0] {
            0 => Ok(SessionAction::None),
            1 => Ok(SessionAction::SelectCharacter),
            2 => Ok(SessionAction::NewCharacter),
            3 => Ok(SessionAction::DressingRoom),
            _ => Err(PacketParseError::InvalidValue)
        }
    }

    fn as_bytes(&self) -> Vec<u8> {
        vec![match self {
            SessionAction::None => 0,
            SessionAction::SelectCharacter => 1,
            SessionAction::NewCharacter => 2,
            SessionAction::DressingRoom => 3,
        }]
    }
}



#[derive(PSOPacketData, Clone, Copy)]
pub struct Session {
    pub version: [u8; 30],
    pub session_id: u32,
    pub interserver_checksum: u32,
    pub action: SessionAction,
    pub character_slot: u8, // 1..=4
}

impl Session {
    pub fn new() -> Session {
        Session {
            version: [0; 30],
            session_id: 0,
            interserver_checksum: 0,
            action: SessionAction::None,
            character_slot: 0,
        }
    }
}


#[pso_packet(0x93)]
pub struct Login {
    pub tag: u32,
    pub guildcard: u32,
    pub version: u16,
    pub unknown1: [u8; 6],
    pub team: u32,
    #[utf8]
    pub username: [u8; 16],
    pub unknown2: [u8; 32],
    #[utf8]
    pub password: [u8; 16],
    pub unknown3: [u8; 40],
    pub hwinfo: [u8; 8],
    pub session: Session
    //pub security_data: [u8; 40],
}

#[derive(Debug, Clone, PartialEq)]
pub enum AccountStatus {
    Ok,
    Error,
    InvalidPassword,
    InvalidPassword2,
    Maintenance,
    AlreadyOnline,
    Banned,
    Banned2,
    InvalidUser,
    PayUp,
    Locked,
    BadVersion,
}

impl PSOPacketData for AccountStatus {
    fn from_bytes<R: Read>(cursor: &mut R) -> Result<Self, PacketParseError> {
        let mut bytes = [0u8; 4];
        let len = cursor.read(&mut bytes).map_err(|_| PacketParseError::ReadError)?;
        if len != 4 {
            return Err(PacketParseError::NotEnoughBytes)
        }
        match bytes[0] {
            0 => Ok(AccountStatus::Ok),
            1 => Ok(AccountStatus::Error),
            2 => Ok(AccountStatus::InvalidPassword),
            3 => Ok(AccountStatus::InvalidPassword2),
            4 => Ok(AccountStatus::Maintenance),
            5 => Ok(AccountStatus::AlreadyOnline),
            6 => Ok(AccountStatus::Banned),
            7 => Ok(AccountStatus::Banned2),
            8 => Ok(AccountStatus::InvalidUser),
            9 => Ok(AccountStatus::PayUp),
            10 => Ok(AccountStatus::Locked),
            11 => Ok(AccountStatus::BadVersion),
            _ => Err(PacketParseError::InvalidValue),
        }
    }

    fn as_bytes(&self) -> Vec<u8> {
        vec![match self {
            AccountStatus::Ok => 0,
            AccountStatus::Error => 1,
            AccountStatus::InvalidPassword => 2,
            AccountStatus::InvalidPassword2 => 3,
            AccountStatus::Maintenance => 4,
            AccountStatus::AlreadyOnline => 5,
            AccountStatus::Banned => 6,
            AccountStatus::Banned2 => 7,
            AccountStatus::InvalidUser => 8,
            AccountStatus::PayUp => 9,
            AccountStatus::Locked => 10,
            AccountStatus::BadVersion => 11,
        },0,0,0]
    }
}


#[pso_packet(0xE6)]
pub struct LoginResponse {
    pub status: AccountStatus,
    pub tag: u32,
    pub guildcard: u32,
    pub team_id: u32,
    //pub security_data: [u8; 40],
    pub session: Session,
    pub caps: u32,
}

impl LoginResponse {
    pub fn by_status(status: AccountStatus, session: Session) -> LoginResponse {
        LoginResponse {
            status: status,
            tag: 0x00010000,
            //tag: 0x00000100,
            guildcard: 0,
            team_id: 0,
            session: session,
            caps: 0x00000102,
        }
    }
    pub fn by_char_select(guildcard: u32, team_id: u32, session: Session) -> LoginResponse {
        LoginResponse {
            status: AccountStatus::Ok,
            tag: 0x00010000,
            //tag: 0x00000100,
            guildcard: guildcard,
            team_id: team_id,
            session: session,
            caps: 0x00000102,
        }
    }
}


#[pso_packet(0xE0)]
pub struct RequestSettings {
}

#[pso_packet(0xE2)]
pub struct SendKeyAndTeamSettings {
    unknown: [u8; 0x114],
    key_config: [u8; 0x16C],
    joystick_config: [u8; 0x38],
    guildcard: u32,
    team_id: u32,
    //team_info: [u32; 2],
    team_info: [u8; 8],
    team_priv: u16,
    unknown2: u16,
    //team_name: [u16; 16],
    team_name: [u8; 32],
    #[nodebug]
    team_flag: [u8; 2048],
    team_rewards: [u8; 8],
}

impl SendKeyAndTeamSettings {
    pub fn new(key_config: [u8; 0x16C], joystick_config: [u8; 0x38], guildcard: u32, team_id: u32) -> SendKeyAndTeamSettings {
        SendKeyAndTeamSettings {
            unknown: [0; 0x114],
            key_config: key_config,
            joystick_config: joystick_config,
            guildcard: guildcard,
            team_id: team_id,
            //team_info: [0; 2],
            team_info: [0; 8],
            team_priv: 0,
            unknown2: 0,
            //team_name: [0; 16],
            team_name: [0; 32],
            team_flag: [0; 2048],
            team_rewards: [0; 8]
        }
    }
}

#[pso_packet(0x19)]
pub struct RedirectClient {
    pub ip: u32,
    pub port: u16,
    pub padding: u16,
}

impl RedirectClient {
    pub fn new(ip: u32, port: u16) -> RedirectClient {
        RedirectClient {
            ip: ip,
            port: port,
            padding: 0,
        }
    }
}

#[pso_packet(0x1E8)]
pub struct Checksum {
    pub checksum: u32,
    pub padding: u32,
}

#[pso_packet(0x2E8)]
pub struct ChecksumAck {
    pub ack: u32,
}

impl ChecksumAck {
    pub fn new(ack: u32) -> ChecksumAck {
        ChecksumAck {
            ack: ack,
        }
    }
}

#[pso_packet(0xE3)]
pub struct CharSelect {
    pub slot: u32,
    pub reason: u32, // TODO: enum?
}

#[pso_packet(0xE4)]
pub struct CharAck {
    pub slot: u32,
    pub code: u32, // TODO: enum?
}

impl PSOPacketData for SelectScreenCharacter {
    fn from_bytes<R: Read>(cursor: &mut R) -> Result<Self, PacketParseError> {
        let mut buf = [0u8; SelectScreenCharacter::SIZE];
        cursor.read(&mut buf).map_err(|_| PacketParseError::ReadError)?;
        SelectScreenCharacter::from_le_bytes(buf)
    }

    fn as_bytes(&self) -> Vec<u8> {
        self.to_le_bytes().to_vec()
    }
}

#[pso_packet(0xE5)]
pub struct CharacterPreview {
    pub slot: u32,
    pub character: SelectScreenCharacter,
}

#[pso_packet(0x3E8)]
pub struct GuildcardDataRequest {
}

#[pso_packet(0x1DC)]
pub struct GuildcardDataHeader {
    one: u32,
    len: u32,
    checksum: u32,
}

impl GuildcardDataHeader {
    pub fn new(len: usize, checksum: u32) -> GuildcardDataHeader {
        GuildcardDataHeader {
            one: 1,
            len: len as u32,
            checksum: checksum
        }
    }
}

#[pso_packet(0x3DC)]
pub struct GuildcardDataChunkRequest {
    _unknown: u32,
    pub chunk: u32,
    pub again: u32,
}

pub struct GuildcardDataChunk {
    _unknown: u32,
    chunk: u32,
    pub buffer: [u8; GUILD_CARD_CHUNK_SIZE],

    len: usize,
}

impl GuildcardDataChunk {
    pub fn new(chunk: u32, buffer: [u8; GUILD_CARD_CHUNK_SIZE], len: usize) -> GuildcardDataChunk {
        GuildcardDataChunk {
            _unknown: 0,
            chunk: chunk as u32,
            buffer: buffer,
            len: len,
        }
    }
}

impl PSOPacket for GuildcardDataChunk {
    fn from_bytes(_data: &[u8]) -> Result<GuildcardDataChunk, PacketParseError> {
        unimplemented!();
    }

    fn as_bytes(&self) -> Vec<u8> {
        let mut buf: Vec<u8> = Vec::new();
        buf.extend_from_slice(&u32::to_le_bytes(0));
        buf.extend_from_slice(&u32::to_le_bytes(self._unknown));
        buf.extend_from_slice(&u32::to_le_bytes(self.chunk));
        buf.extend_from_slice(&self.buffer[0..self.len]);
        while buf.len() % 4 != 0 {
            buf.push(0);
        }

        let pkt_len = (buf.len() + 4) as u16;
        let mut prebuf: Vec<u8> = Vec::new();
        prebuf.extend_from_slice(&u16::to_le_bytes(pkt_len));
        prebuf.extend_from_slice(&u16::to_le_bytes(0x2DC));
        prebuf.append(&mut buf);
        prebuf
    }
}

impl std::fmt::Debug for GuildcardDataChunk {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "packet GuildcardDataChunk {{\n").unwrap();
        write!(f, "    flag: {:?}\n", 0).unwrap();
        write!(f, "    _unknown: {:X?}\n", self._unknown).unwrap();
        write!(f, "    chunk: {:X?}\n", self.chunk).unwrap();
        write!(f, "    buffer: [0..{:X}]\n", self.len).unwrap();
        write!(f, "}}")
    }
}


#[pso_packet(0x4EB)]
pub struct ParamDataRequest {
}

#[derive(Clone)]
pub struct ParamFile {
    pub size: u32,
    pub checksum: u32,
    pub offset: u32,
    pub filename: [u8; 0x40],
}

#[derive(Clone)]
pub struct ParamDataHeader {
    pub files: Vec<ParamFile>
}

impl PSOPacket for ParamDataHeader {
    fn from_bytes(_data: &[u8]) -> Result<ParamDataHeader, PacketParseError> {
        unimplemented!();
    }

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

        buf.extend_from_slice(&u32::to_le_bytes(self.files.len() as u32));
        for f in &self.files {
            buf.extend_from_slice(&u32::to_le_bytes(f.size));
            buf.extend_from_slice(&u32::to_le_bytes(f.checksum));
            buf.extend_from_slice(&u32::to_le_bytes(f.offset));
            buf.extend_from_slice(&f.filename[..]);
        }
        while buf.len() % 4 != 0 {
            buf.push(0);
        }

        let pkt_len = (buf.len() + 4) as u16;
        let mut prebuf: Vec<u8> = Vec::new();
        prebuf.extend_from_slice(&u16::to_le_bytes(pkt_len));
        prebuf.extend_from_slice(&u16::to_le_bytes(0x1EB));
        prebuf.append(&mut buf);
        prebuf
    }
}

impl std::fmt::Debug for ParamDataHeader {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "packet ParamDataHeader{{\n").unwrap();
        write!(f, "    files: [..]\n").unwrap();
        write!(f, "}}")
    }
}

#[pso_packet(0x3EB)]
pub struct ParamDataChunkRequest {
}

#[pso_packet(0x2EB)]
pub struct ParamDataChunk {
    pub chunk: u32,
    #[nodebug]
    pub data: [u8; 0x6800], // TODO: why wont the const work here? (blame macros?)
}


#[pso_packet(0xEC)]
pub struct SetFlag {
    pub flags: u32,
}


#[pso_packet(0xB1)]
pub struct Timestamp {
    #[utf8]
    timestamp: [u8; 28],
}

impl Timestamp {
    pub fn new(time: DateTime<Utc>) -> Timestamp {
        let timestr = time.format("%Y:%m:%d: %H:%M:%S").to_string();
        let timebytes = timestr.as_bytes();
        let mut timebuf = [0u8; 28];
        timebuf[..timebytes.len()].clone_from_slice(timebytes);
        Timestamp {
            timestamp: timebuf
        }
    }
}


#[derive(PSOPacketData, Copy, Clone)]
pub struct ShipListEntry {
    pub menu: u32,
    pub item: u32,
    pub flags: u16,
    pub name: [u16; 0x11],
}

#[pso_packet(0xA0)]
pub struct ShipList {
    baseship: ShipListEntry,
    pub ships: Vec<ShipListEntry>,
}

impl ShipList {
    pub fn new(ships: Vec<ShipListEntry>) -> ShipList {
        ShipList {
            baseship: ShipListEntry {
                menu: ships.get(0).map(|s| s.menu).unwrap_or(0),
                item: 0,
                flags: 0,
                name: utf8_to_utf16_array!("Ship", 0x11),
            },
            ships: ships,
        }
    }
}

#[pso_packet(0x10)]
pub struct MenuSelect {
    pub menu: u32,
    pub item: u32,
}


#[cfg(test)]
mod tests {
    #[test]
    fn test_account_status_enum() {
        use super::PSOPacket;
        use super::Session;
        let pkt = super::LoginResponse {
            status: super::AccountStatus::InvalidPassword,
            tag: 0,
            guildcard: 0,
            team_id: 0,
            session: Session::new(),
            caps: 0,
        };

        let mut bytes = pkt.as_bytes();
        assert!(bytes[8] == 2);

        bytes[8] = 8;

        let pkt = super::LoginResponse::from_bytes(&bytes).unwrap();
        assert!(pkt.status == super::AccountStatus::InvalidUser);
    }

    #[test]
    fn test_key_settings_reply() {
        use super::PSOPacket;
        use rand::{Rng};

        let mut rng = rand::thread_rng();

        let mut key_config = [0u8; 0x16C];
        let mut joystick_config = [0u8; 0x38];

        rng.fill(&mut key_config[..]);
        rng.fill(&mut joystick_config[..]);
        let pkt = super::SendKeyAndTeamSettings::new(key_config, joystick_config, 123, 456);
        let bytes = pkt.as_bytes();

        assert!(bytes[2] == 0xe2);
        assert!(bytes[8 + 0x114] == key_config[0]);
        assert!(bytes[8 + 0x114 + 0x16C] == joystick_config[0]);
    }

    #[test]
    fn test_login_checksum_ack() {
        use super::PSOPacket;
        let pkt = super::ChecksumAck::new(1);
        assert!(pkt.as_bytes() == [0xC, 0, 0xE8, 0x02, 0,0,0,0, 1,0,0,0]);
    }

    #[test]
    fn test_session_size() {
        use super::PSOPacketData;
        let session = super::Session::new();
        assert!(session.as_bytes().len() == 40);
    }
}