use psopacket::{pso_packet, PSOPacketData};
use crate::{PSOPacket, PacketParseError, PSOPacketData};
use crate::utf8_to_utf16_array;
use crate::packet::messages::GameMessage;
//use character::character::FullCharacter;
use crate::character::character as character;
use crate::ConsumingBlob;

use std::io::Read;

pub const BLOCK_MENU_ID: u32 = 1;
pub const ROOM_MENU_ID: u32 = 2;
pub const LOBBY_MENU_ID: u32 = 3;

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

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


#[derive(Debug, PartialEq, Clone)]
pub struct BlockEntry {
    menu: u32,
    item: u32,
    flags: u16,
    name: [u16; 0x11],
}

impl PSOPacketData for BlockEntry {
    fn from_bytes<R: Read>(_cursor: &mut R) -> Result<Self, PacketParseError> {
        unimplemented!();
    }

    fn as_bytes(&self) -> Vec<u8> {
        let mut bytes = Vec::new();
        bytes.extend_from_slice(&u32::to_le_bytes(self.menu));
        bytes.extend_from_slice(&u32::to_le_bytes(self.item));
        bytes.extend_from_slice(&u16::to_le_bytes(self.flags));
        bytes.extend_from_slice(&unsafe { std::mem::transmute::<[u16; 0x11], [u8; 0x11*2]>(self.name) });
        bytes
    }
}

#[pso_packet(0xA1)]
pub struct ShipBlockList {
    shipblock: BlockEntry,
    blocks: Vec<BlockEntry>
}

impl ShipBlockList {
    pub fn new(shipname: &str, num_blocks: usize) -> ShipBlockList {
        ShipBlockList {
            shipblock: BlockEntry {
                menu: BLOCK_MENU_ID,
                item: 0,
                flags: 0,
                name: utf8_to_utf16_array!(shipname, 0x11)
            },
            blocks: (0..num_blocks).map(|i| BlockEntry {
                menu: BLOCK_MENU_ID,
                item: i as u32 + 1,
                flags: 0,
                name: utf8_to_utf16_array!(format!("Block {}", i+1), 0x11)
            }).collect()
        }
    }
}

// TODO: menu should be an enum
// TODO: or perhaps MenuSelect should be broken up into different structs based on menu
// TODO: i.e. ShipMenuSelect, BlockMenuSelect, etc
#[pso_packet(0x10)]
pub struct MenuSelect {
    pub menu: u32,
    pub item: u32,
}

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

#[pso_packet(0x10)]
pub struct RoomPasswordReq {
    pub menu: u32,
    pub item: u32,
    pub password: [u16; 16],
}

#[pso_packet(0x84)]
pub struct LobbySelect {
    pub menu: u32,
    pub lobby: u32,
}

#[pso_packet(0xE7)]
pub struct FullCharacter {
    #[no_debug]
    character: character::FullCharacter,
}


#[pso_packet(0x95)]
pub struct CharDataRequest {
}


// TODO: what does this even do?
#[pso_packet(0x61)]
pub struct CharData {
    _unknown: [u8; 0x828]
}


#[pso_packet(0x60)]
pub struct BurstDone72 {
    msg: u8,
    len: u8,
    client: u8,
    target: u8,
}

impl BurstDone72 {
    pub fn new() -> BurstDone72 {
        BurstDone72 {
            msg: 0x72,
            len: 3,
            client: 0x18,
            target: 0x08,
        }
    }
}


#[pso_packet(0x60)]
pub struct Message {
    msg: GameMessage,
}

impl Message {
    pub fn new(msg: GameMessage) -> Message {
        Message {
            msg: msg,
        }
    }
}

#[pso_packet(0x62, manual_flag)]
pub struct DirectMessage {
    flag: u32,
    msg: GameMessage
}

impl DirectMessage {
    pub fn new(target: u32, msg: GameMessage) -> DirectMessage {
        DirectMessage {
            flag: target,
            msg: msg,
        }
    }
}

#[pso_packet(0x6D, manual_flag)]
struct Like62ButCooler {
    pub flag: u32,
    pub blob: ConsumingBlob,
}


#[derive(PSOPacketData, Clone, Copy, Default)]
pub struct PlayerHeader {
    pub tag: u32,
    pub guildcard: u32,
    pub _unknown1: [u32; 5],
    pub client_id: u32,
    pub name: [u16; 16],
    pub _unknown2: u32,
}

#[derive(PSOPacketData, Clone)]
pub struct PlayerInfo {
    pub header: PlayerHeader,
    pub inventory: character::Inventory,
    pub character: character::Character,
}

#[pso_packet(0x67)]
pub struct JoinLobby {
    pub client: u8,
    pub leader: u8,
    pub one: u8,
    pub lobby: u8,
    pub block: u16,
    pub event: u16,
    pub padding: u32,
    pub playerinfo: Vec<PlayerInfo>,
}

#[pso_packet(0x68, manual_flag)]
pub struct AddToLobby {
    flag: u32,
    pub client: u8,
    pub leader: u8,
    pub one: u8,
    pub lobby: u8,
    pub block: u16,
    pub event: u16,
    pub padding: u32,
    pub playerinfo: PlayerInfo,
}

#[pso_packet(0xC1)]
pub struct CreateRoom {
    unknown: [u32; 2],
    name: [u16; 16],
    password: [u16; 16],
    difficulty: u8,
    battle: u8,
    challenge: u8,
    episode: u8,
    single_player: u8,
    padding: [u8; 3],
}


#[pso_packet(0x01)]
pub struct SmallDialog {
    padding: [u32; 0x02],
    msg: String,
}

impl SmallDialog {
    pub fn new(msg: String) -> SmallDialog {
        SmallDialog {
            padding: [0; 0x02],
            msg: msg,
        }
    }
}

#[pso_packet(0x64, manual_flag)]
pub struct JoinRoom {
    pub flag: u32, // # of elements in players
    pub maps: [u32; 0x20],
    pub players: [PlayerHeader; 4],
    pub client: u8,
    pub leader: u8,
    pub one: u8,
    pub difficulty: u8, // TODO: enum
    pub battle: u8,
    pub event: u8,
    pub section: u8,
    pub challenge: u8,
    pub random_seed: u32,
    pub episode: u8,
    pub one2: u8,
    pub single_player: u8,
    pub unknown: u8,
}

#[pso_packet(0x65, manual_flag)]
pub struct AddToRoom {
    pub flag: u32,
    pub client: u8,
    pub leader: u8,
    pub one: u8,
    pub lobby: u8,
    pub block: u16,
    pub event: u16,
    pub padding: u32,
    pub playerinfo: PlayerInfo,
}

#[pso_packet(0x69)]
pub struct LeaveLobby {
    client: u8,
    leader: u8,
    _padding: u16,
}

impl LeaveLobby {
    pub fn new(client: u8, leader: u8) -> LeaveLobby {
        LeaveLobby {
            client: client,
            leader: leader,
            _padding: 0,
        }
    }
}

#[pso_packet(0x66)]
pub struct LeaveRoom {
    client: u8,
    leader: u8,
    _padding: u16,
}

impl LeaveRoom {
    pub fn new(client: u8, leader: u8) -> LeaveRoom {
        LeaveRoom {
            client: client,
            leader: leader,
            _padding: 0,
        }
    }
}

#[pso_packet(0x06)]
pub struct PlayerChat {
    pub unknown: u32,
    pub guildcard: u32,
    pub message: String,
}

impl PlayerChat {
    pub fn new(guildcard: u32, message: String) -> PlayerChat {
        PlayerChat {
            unknown: 0x00010000,
            guildcard: guildcard,
            message: message,
        }
    }
}


#[pso_packet(0x8A)]
pub struct RoomNameRequest {
}

#[pso_packet(0x8A)]
pub struct RoomNameResponse {
    pub name: String,
}

#[pso_packet(0x7ED)]
pub struct UpdateConfig{
    pub config: [u8; 0xE8],
}

#[pso_packet(0xD8)]
pub struct ViewInfoboardRequest {
}

#[derive(PSOPacketData, Clone)]
pub struct InfoboardResponse {
    pub name: [u16; 16],
    pub message: [u16; 172],
}

#[pso_packet(0xD8)]
pub struct ViewInfoboardResponse {
    pub response: Vec<InfoboardResponse>,
}

#[pso_packet(0xD9)]
pub struct WriteInfoboard {
    pub message: String,
}

#[pso_packet(0x08)]
pub struct RoomListRequest {

}

#[derive(PSOPacketData, Clone)]
pub struct RoomList {
    pub menu_id: u32,
    pub item_id: u32,
    pub difficulty: u8,
    pub players: u8,
    pub name: [u16; 16],
    pub episode: u8,
    pub flags: u8,
}

#[pso_packet(0x08)]
pub struct RoomListResponse {
    pub baseroom: RoomList,
    pub rooms: Vec<RoomList>,
}

#[derive(PSOPacketData, Clone, Copy, Default)]
pub struct LobbyEntry {
    menu_id: u32,
    item_id: u32,
    padding: u32,
}

impl LobbyEntry {
    pub fn new(menu_id: u32, lobby_id: u32) -> LobbyEntry {
        LobbyEntry {
            menu_id: menu_id,
            item_id: lobby_id,
            padding: 0,
        }
    }
}

#[pso_packet(0x83, manual_flag)]
pub struct LobbyList {
    flag: u32,
    entries: [LobbyEntry; 16],
}

impl LobbyList {
    pub fn new() -> LobbyList {
        let lobbies = (0..16).fold([LobbyEntry::default(); 16],
            |mut acc, index| {
                acc[index].menu_id = LOBBY_MENU_ID;
                acc[index].item_id = index as u32;
                acc
            });
        LobbyList {
            flag: 0x0F,
            entries: lobbies,
        }
    }
}

#[pso_packet(0x6F)]
pub struct DoneBursting {
}

#[pso_packet(0x16F)]
pub struct DoneBursting2 {
}

#[pso_packet(0x98)]
pub struct ClientCharacterData {
    pub data: [u8; 2088],
}

#[pso_packet(0xA2)]
pub struct RequestQuestList {
}

#[derive(PSOPacketData, Clone, Copy)]
pub struct QuestCategory {
    pub menu_id: u32,
    pub option_id: u32,
    pub name: [u16; 32],
    pub description: [u16; 122],
}

#[pso_packet(0xA2)]
pub struct QuestCategoryList {
    pub quest_categories: Vec<QuestCategory>,
}


#[derive(PSOPacketData, Clone, Copy)]
pub struct QuestEntry {
    pub menu_id: u32,
    pub category_id: u16,
    pub quest_id: u16,
    pub name: [u16; 32],
    pub description: [u16; 122],
}

#[pso_packet(0xA2)]
pub struct QuestOptionList {
    pub quests: Vec<QuestEntry>,
}

#[pso_packet(0xA3)]
pub struct QuestDetail {
    description: [u16; 288]
}

#[pso_packet(0x09)]
pub struct QuestDetailRequest {
    pub menu: u32,
    pub category: u16,
    pub quest: u16,
}

#[pso_packet(0x10)]
pub struct QuestMenuSelect {
    pub menu: u32,
    pub category: u16,
    pub quest: u16,
}

#[pso_packet(0x44)]
pub struct QuestHeader {
    pub unknown1: [u8; 0x24],
    pub filename: [u8; 16],
    pub length: u32,
    pub name: [u8; 16],
    pub unknown2: [u8; 8],
}

#[pso_packet(0x44)]
pub struct QuestFileRequest {
    pub filename: [u8; 16],
}

#[pso_packet(0x13, no_flag)]
pub struct QuestChunk {
    pub chunk_num: u32,
    pub filename: [u8; 16],
    pub blob: [u8; 0x400],
    pub blob_length: u32,
    pub unknown: u32,
}

#[pso_packet(0x13, no_flag)]
pub struct QuestChunkAck {
    pub chunk_num: u32,
    filename: [u8; 16],
}

#[pso_packet(0xAC)]
pub struct DoneLoadingQuest {
}