use elseware::common::serverstate::{ClientId, ServerState};
use elseware::entity::gateway::{EntityGateway, InMemoryGateway};
use elseware::entity::item;
use elseware::ship::ship::{ShipServerState, RecvShipPacket};
//use elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType, FloorItemType};

use libpso::packet::ship::*;
use libpso::packet::messages::*;

#[path = "common.rs"]
mod common;
use common::*;


#[async_std::test]
async fn test_use_monomate() {
    let mut entity_gateway = InMemoryGateway::default();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let mut p1_items = Vec::new();
    for tool in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter() {
        let mut item = Vec::new();
        for _ in 0..2usize {
            item.push(entity_gateway.create_item(
                item::NewItemEntity {
                    item: item::ItemDetail::Tool(
                        item::tool::Tool {
                            tool: tool
                        }
                    ),
                }).await.unwrap());
        }
        p1_items.push(item::InventoryItemEntity::Stacked(item));
    }

    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_items)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();

    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    assert_eq!(inventory_items.items.len(), 2);
    inventory_items.items[0].with_stacked(|items| {
        assert_eq!(items.len(), 1)
    }).unwrap();
    inventory_items.items[1].with_stacked(|items| {
        assert_eq!(items.len(), 2)
    }).unwrap();
}

#[async_std::test]
async fn test_use_monomate_twice() {
    let mut entity_gateway = InMemoryGateway::default();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let mut p1_items = Vec::new();
    for tool in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter() {
        let mut item = Vec::new();
        for _ in 0..3usize {
            item.push(entity_gateway.create_item(
                item::NewItemEntity {
                    item: item::ItemDetail::Tool(
                        item::tool::Tool {
                            tool: tool
                        }
                    ),
                }).await.unwrap());
        }
        p1_items.push(item::InventoryItemEntity::Stacked(item));
    }

    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_items)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();
    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();

    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    assert_eq!(inventory_items.items.len(), 2);
    inventory_items.items[0].with_stacked(|items| {
        assert_eq!(items.len(), 1)
    }).unwrap();
    inventory_items.items[1].with_stacked(|items| {
        assert_eq!(items.len(), 3)
    }).unwrap();
}

#[async_std::test]
async fn test_use_last_monomate() {
    let mut entity_gateway = InMemoryGateway::default();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let mut p1_inv = Vec::new();
    for tool in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter() {
        p1_inv.push(item::InventoryItemEntity::Stacked(vec![entity_gateway.create_item(
            item::NewItemEntity {
                item: item::ItemDetail::Tool(
                    item::tool::Tool {
                        tool: tool
                    }
                ),
            }).await.unwrap()]));
    }

    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();


    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    assert_eq!(inventory_items.items.len(), 1);
    inventory_items.items[0].with_stacked(|items| {
        assert_eq!(items.len(), 1);
        assert_eq!(items[0].item.item_type(), item::ItemType::Tool(item::tool::ToolType::Monofluid));
    }).unwrap();
}

#[async_std::test]
async fn test_use_nonstackable_tool() {
    let mut entity_gateway = InMemoryGateway::default();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let mut p1_items = Vec::new();
    p1_items.push(entity_gateway.create_item(
        item::NewItemEntity {
            item: item::ItemDetail::Tool(
                item::tool::Tool {
                    tool: item::tool::ToolType::HuntersReport,
                }
            ),
        }).await.unwrap());

    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_items)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();

    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    assert_eq!(inventory_items.items.len(), 0);
}

#[async_std::test]
async fn test_use_materials() {
    let mut entity_gateway = InMemoryGateway::default();

    let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let mut p1_inv = Vec::new();
    for tool in vec![item::tool::ToolType::PowerMaterial, item::tool::ToolType::LuckMaterial].into_iter() {
        let mut item = Vec::new();
        for _ in 0..5usize {
            item.push(entity_gateway.create_item(
                item::NewItemEntity {
                    item: item::ItemDetail::Tool(
                        item::tool::Tool {
                            tool: tool
                        }
                    ),
                }).await.unwrap());
        }
        p1_inv.push(item::InventoryItemEntity::Stacked(item));
    }

    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();
    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10001,
    })))).await.unwrap();
    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10001,
    })))).await.unwrap();

    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    assert_eq!(inventory_items.items.len(), 2);
    inventory_items.items[0].with_stacked(|items| {
        assert_eq!(items.len(), 4)
    }).unwrap();
    inventory_items.items[1].with_stacked(|items| {
        assert_eq!(items.len(), 3)
    }).unwrap();

    let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap();
    let char = characters[0].as_ref().unwrap();

    assert!(char.materials.power == 1);
    assert!(char.materials.luck == 2);
}

#[async_std::test]
async fn test_jackolantern() {
    let mut entity_gateway = InMemoryGateway::default();
    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;

    let p1_inv = vec![
        item::InventoryItemEntity::Stacked(
            vec![
                entity_gateway.create_item(
                    item::NewItemEntity {
                        item: item::ItemDetail::Tool(
                            item::tool::Tool {
                                tool: item::tool::ToolType::JackOLantern,
                            }
                        ),
                    }).await.unwrap(),
                entity_gateway.create_item(item::NewItemEntity {
                    item: item::ItemDetail::Tool(
                        item::tool::Tool {
                            tool: item::tool::ToolType::JackOLantern,
                        }
                    ),
                }).await.unwrap(),
            ])];
    entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();

    let mut ship = Box::new(ShipServerState::builder()
        .gateway(entity_gateway.clone())
        .build());
    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
    join_lobby(&mut ship, ClientId(1)).await;
    create_room(&mut ship, ClientId(1), "room", "").await;

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();

    ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap();

    let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
    for item in inventory_items.items {
        for sitem in item.stacked().unwrap() {
            assert!(sitem.item.clone().as_tool().unwrap().tool.is_mag_cell());
        }
    }
}

// TODO: tests for ALL ITEMS WOW

/*
#[async_std::test]
pub async fn test_learn_new_tech() {}

#[async_std::test]
pub async fn test_new_fo_has_foie_1() {}

#[async_std::test]
pub async fn test_char_cannot_use_lower_level_tech() {}

#[async_std::test]
pub async fn test_char_cannot_learn_wrong_tech() {}

#[async_std::test]
pub async fn test_char_cannot_learn_high_level_tech() {}

#[async_std::test]
pub async fn test_android_cannot_learn_tech() {}
*/