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::new();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").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
                        }
                    ),
                    location: item::ItemLocation::Inventory {
                        character_id: char1.id,
                    }
                }).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().for_each(drop);

    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::new();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").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
                        }
                    ),
                    location: item::ItemLocation::Inventory {
                        character_id: char1.id,
                    }
                }).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().for_each(drop);
    ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10000,
    })))).await.unwrap().for_each(drop);

    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::new();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").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
                    }
                ),
                location: item::ItemLocation::Inventory {
                    character_id: char1.id,
                }
            }).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().for_each(drop);


    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::new();

    let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").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::MagicStoneIritista,
                }
            ),
            location: item::ItemLocation::Inventory {
                character_id: char1.id,
            }
        }).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().for_each(drop);

    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::new();

    let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").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
                        }
                    ),
                    location: item::ItemLocation::Inventory {
                        character_id: char1.id,
                    }
                }).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().for_each(drop);
    ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10001,
    })))).await.unwrap().for_each(drop);
    ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
        client: 0,
        target: 0,
        item_id: 0x10001,
    })))).await.unwrap().for_each(drop);

    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]
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() {}