import atexit
import time
from construct import *
from construct.protocols.layer3.ipv4 import IpAddress
from protocol import *
from common import *
from utils import *
from being import BeingsCache
from item import FloorItem
from inventory import (add_to_inventory, remove_from_inventory,
                       add_to_storage, remove_from_storage)
from trade import reset_trade_state
from loggers import netlog

server = None
beings_cache = None
party_info = []
party_members = {}
player_pos = {'map': 'unknown', 'x': 0, 'y': 0, 'dir': 0}
tick = 0
last_whisper = {'to': '', 'msg': ''}
player_inventory = {}
player_storage = {}
player_stats = {}
player_skills = {}
player_money = 0
player_attack_range = 0
trade_state = {'items_give': [], 'items_get': [],
               'zeny_give': 0, 'zeny_get': 0}
floor_items = {}

for s in range(255):
    player_stats[s] = 0


# --------------------------------------------------------------------
def smsg_ignore(data):
    pass


@extendable
def smsg_being_chat(data):
    cached_name = beings_cache.findName(data.id)
    real_name, _ = data.message.split(' : ', 1)
    if real_name != cached_name:
        cmsg_name_request(data.id)
    netlog.info("SMSG_BEING_CHAT id={} msg={}".format(data.id, data.message))


@extendable
def smsg_being_emotion(data):
    beings_cache.findName(data.id)
    netlog.info("SMSG_BEING_EMOTION {} : {}".format(data.id, data.emote))


@extendable
def smsg_being_move(data):
    global tick
    tick = data.tick
    beings_cache.findName(data.id, data.job)
    beings_cache[data.id].x = data.coor_pair.dst_x
    beings_cache[data.id].y = data.coor_pair.dst_y
    netlog.info("SMSG_BEING_MOVE {}".format(data))


@extendable
def smsg_being_action(data):
    global tick
    tick = data.tick
    netlog.info("SMSG_BEING_ACTION {}".format(data))


@extendable
def smsg_being_change_direction(data):
    netlog.info("SMSG_BEING_CHANGE_DIRECTION {}".format(data))


@extendable
def smsg_being_name_response(data):
    try:
        beings_cache[data.id].name = data.name
    except KeyError:
        pass
    netlog.info("SMSG_BEING_NAME_RESPONSE id={} name={}".format(
        data.id, data.name))


@extendable
def smsg_being_remove(data):
    try:
        del beings_cache[data.id]
    except KeyError:
        pass
    netlog.info("SMSG_BEING_REMOVE (id={}, deadflag={})".format(
        data.id, data.deadflag))


@extendable
def smsg_being_visible(data):
    beings_cache.findName(data.id, data.job)
    beings_cache[data.id].speed = data.speed
    beings_cache[data.id].x = data.coor.x
    beings_cache[data.id].y = data.coor.y
    netlog.info("SMSG_BEING_VISIBLE {}".format(data))


@extendable
def smsg_player_chat(data):
    netlog.info("SMSG_PLAYER_CHAT {}".format(data.message))


@extendable
def smsg_player_equipment(data):
    netlog.info("SMSG_PLAYER_EQUIPMENT {}".format(data))
    for item in data.equipment:
        player_inventory[item.index] = (item.id, 1)


@extendable
def smsg_player_inventory(data):
    netlog.info("SMSG_PLAYER_INVENTORY {}".format(data))
    for item in data.inventory:
        player_inventory[item.index] = (item.id, item.amount)


@extendable
def smsg_player_inventory_add(data):
    netlog.info("SMSG_PLAYER_INVENTORY_ADD index={} id={} amount={}".format(
        data.index, data.id, data.amount))
    add_to_inventory(data.index, data.id, data.amount)


@extendable
def smsg_player_inventory_remove(data):
    netlog.info("SMSG_PLAYER_INVENTORY_REMOVE index={} amount={}".format(
        data.index, data.amount))
    remove_from_inventory(data.index, data.amount)


@extendable
def smsg_player_inventory_use(data):
    netlog.info("SMSG_PLAYER_INVENTORY_USE {}".format(data))
    if data.amount > 0:
        player_inventory[data.index] = (data.item_id, data.amount)
    else:
        del player_inventory[data.index]


@extendable
def smsg_player_move(data):
    netlog.info("SMSG_PLAYER_MOVE {}".format(data))
    global tick
    tick = data.tick
    beings_cache.findName(data.id, data.job)
    beings_cache[data.id].x = data.coor_pair.dst_x
    beings_cache[data.id].y = data.coor_pair.dst_y


@extendable
def smsg_player_stop(data):
    netlog.info("SMSG_PLAYER_STOP id={} x={} y={}".format(
        data.id, data.x, data.y))
    beings_cache.findName(data.id)
    beings_cache[data.id].x = data.x
    beings_cache[data.id].y = data.y


@extendable
def smsg_player_update(data):
    netlog.info("SMSG_PLAYER_UPDATE_ {}".format(data))
    beings_cache.findName(data.id, data.job)
    beings_cache[data.id].speed = data.speed
    beings_cache[data.id].x = data.coor.x
    beings_cache[data.id].y = data.coor.y


@extendable
def smsg_player_warp(data):
    netlog.info("SMSG_PLAYER_WARP (map={}, x={}, y={}".format(
        data.map, data.x, data.y))
    player_pos['map'] = data.map
    player_pos['x'] = data.x
    player_pos['y'] = data.y
    beings_cache.clear()


@extendable
def smsg_ip_response(data):
    netlog.info("SMSG_IP_RESPONSE id={} ip={}".format(data.id, data.ip))


@extendable
def smsg_connection_problem(data):
    error_codes = {
        0 : "Authentification failed",
        1 : "No servers available",
        2 : "Account already in use",
        3 : "Speed hack detected",
        8 : "Duplicated login",
    }
    msg = error_codes.get(data.code, 'code ' + str(data.code))
    netlog.error("SMSG_CONNECTION_PROBLEM %d", data.code)
    raise Exception(msg)


@extendable
def smsg_gm_chat(data):
    netlog.info("SMSG_GM_CHAT {}".format(data.message))


@extendable
def smsg_party_info(data):
    global party_info, party_members
    party_info = data
    for m in data.members:
        party_members[m.id] = m.nick
    netlog.info("SMSG_PARTY_INFO {}".format(data))


@extendable
def smsg_party_chat(data):
    netlog.info("SMSG_PARTY_CHAT {} : {}".format(data.id, data.message))


@extendable
def smsg_trade_request(data):
    netlog.info("SMSG_TRADE_REQUEST {}".format(data.nick))
    # cmsg_trade_response("DECLINE")


@extendable
def smsg_trade_response(data):
    netlog.info("SMSG_TRADE_RESPONSE {}".format(data.code))


@extendable
def smsg_trade_item_add(data):
    netlog.info("SMSG_TRADE_ITEM_ADD id={} amount={}".format(
        data.id, data.amount))
    if data.id == 0:
        trade_state['zeny_get'] = data.amount
    else:
        trade_state['items_get'].append((data.id, data.amount))


@extendable
def smsg_trade_item_add_response(data):
    netlog.info(("SMSG_TRADE_ITEM_ADD_RESPONSE"
                 " index={} amount={} code={}").format(
        data.index, data.amount, data.code))

    index = data.index
    amount = data.amount
    code = data.code

    if code == 0 and amount > 0:
        if index > 0:
            item_id, _ = player_inventory[index]
            remove_from_inventory(index, amount)
            trade_state['items_give'].append((item_id, amount))
        elif index == 0:
            trade_state['zeny_give'] = amount


@extendable
def smsg_trade_cancel(data):
    netlog.info("SMSG_TRADE_CANCEL")
    reset_trade_state(trade_state)


@extendable
def smsg_trade_ok(data):
    netlog.info("SMSG_TRADE_OK who={}".format(data.who))


@extendable
def smsg_trade_complete(data):
    netlog.info("SMSG_TRADE_COMPLETE")
    global player_money
    player_money += trade_state['zeny_get'] - trade_state['zeny_give']
    # reset_trade_state(trade_state)


@extendable
def smsg_whisper(data):
    netlog.info("SMSG_WHISPER {} : {}".format(data.nick, data.message))


@extendable
def smsg_whisper_response(data):
    m = {0: "OK", 1: "Recepient is offline"}
    netlog.info("SMSG_WHISPER_RESPONSE {}".format(m.get(data.code, "error")))


@extendable
def smsg_server_ping(data):
    global tick
    tick = data.tick
    netlog.info("SMSG_SERVER_PING tick={}".format(data.tick))


@extendable
def smsg_map_login_success(data):
    netlog.info("SMSG_MAP_LOGIN_SUCCESS {}".format(data))
    global tick
    tick = data.tick
    player_pos['x'] = data.coor.x
    player_pos['y'] = data.coor.y
    player_pos['dir'] = data.coor.dir


@extendable
def smsg_walk_response(data):
    global tick
    tick = data.tick
    player_pos['x'] = data.coor_pair.dst_x
    player_pos['y'] = data.coor_pair.dst_y
    netlog.info("SMSG_WALK_RESPONSE {}".format(data))


@extendable
def smsg_item_visible(data):
    netlog.info("SMSG_ITEM_VISIBLE {}".format(data))
    item = FloorItem(data.id, data.type, data.amount, data.x, data.y)
    floor_items[data.id] = item


@extendable
def smsg_item_dropped(data):
    netlog.info("SMSG_ITEM_DROPPED {}".format(data))
    item = FloorItem(data.id, data.type, data.amount, data.x, data.y)
    floor_items[data.id] = item


@extendable
def smsg_item_remove(data):
    netlog.info("SMSG_ITEM_REMOVE id={}".format(data.id))
    if data.id in floor_items:
        del floor_items[data.id]


@extendable
def smsg_player_stat_update_x(data):
    netlog.info("SMSG_PLAYER_STAT_UPDATE_X type={} value={}".format(
        data.type, data.stat_value))
    player_stats[data.type] = data.stat_value


@extendable
def smsg_being_self_effect(data):
    netlog.info("SMSG_BEING_SELF_EFFECT id={} effect={}".format(
        data.id, data.effect))


@extendable
def smsg_being_status_change(data):
    netlog.info("SMSG_BEING_STATUS_CHANGE id={} status={} flag={}".format(
        data.id, data.status, data.flag))


@extendable
def smsg_player_status_change(data):
    netlog.info("SMSG_PLAYER_STATUS_CHANGE {}".format(data))


@extendable
def smsg_npc_message(data):
    netlog.info("SMSG_NPC_MESSAGE id={} message={}".format(
        data.id, data.message))


@extendable
def smsg_npc_choice(data):
    netlog.info("SMSG_NPC_CHOICE {}".format(data.select))


@extendable
def smsg_npc_close(data):
    netlog.info("SMSG_NPC_CLOSE id={}".format(data.id))


@extendable
def smsg_npc_next(data):
    netlog.info("SMSG_NPC_NEXT id={}".format(data.id))


@extendable
def smsg_npc_int_input(data):
    netlog.info("SMSG_NPC_INT_INPUT id={}".format(data.id))


@extendable
def smsg_npc_str_input(data):
    netlog.info("SMSG_NPC_STR_INPUT id={}".format(data.id))


@extendable
def smsg_npc_command(data):
    netlog.info("SMSG_NPC_COMMAND {}".format(data))


@extendable
def smsg_npc_buy(data):
    netlog.info("SMSG_NPC_BUY {}".format(data))


@extendable
def smsg_player_skills(data):
    netlog.info("SMSG_PLAYER_SKILLS {}".format(data))
    for skill in data.skills:
        player_skills[skill.id] = skill.level


@extendable
def smsg_player_skill_up(data):
    netlog.info("SMSG_PLAYER_SKILL_UP {}".format(data))
    player_skills.setdefault(data.id, data.level)


@extendable
def smsg_storage_status(data):
    netlog.info("SMSG_STORAGE_STATUS used={} max_size={}".format(
        data.used, data.max_size))


@extendable
def smsg_storage_add(data):
    netlog.info("SMSG_STORAGE_ADD {}".format(data))
    add_to_storage(data.index, data.id, data.amount)


@extendable
def smsg_storage_remove(data):
    netlog.info("SMSG_STORAGE_REMOVE {}".format(data))
    remove_from_storage(data.index, data.amount)


@extendable
def smsg_storage_close(data):
    netlog.info("SMSG_STORAGE_CLOSE")


@extendable
def smsg_storage_equip(data):
    netlog.info("SMSG_STORAGE_EQUIP {}".format(data))
    for item in data.equipment:
        player_storage[item.index] = (item.id, 1)


@extendable
def smsg_storage_items(data):
    netlog.info("SMSG_STORAGE_ITEMS {}".format(data))
    for item in data.storage:
        player_storage[item.index] = (item.id, item.amount)


@extendable
def smsg_player_attack_range(data):
    netlog.info("SMSG_PLAYER_ATTACK_RANGE %d", data.range)
    global player_attack_range
    player_attack_range = data.range


@extendable
def smsg_player_arrow_message(data):
    netlog.info("SMSG_PLAYER_ARROW_MESSAGE %d", data.code)


# --------------------------------------------------------------------
protodef = {
    0x8000 : (smsg_ignore, Field("data", 2)),      # ignore
    0x008a : (smsg_being_action,
              Struct("data",
                     ULInt32("src_id"),
                     ULInt32("dst_id"),
                     ULInt32("tick"),
                     ULInt32("src_speed"),
                     ULInt32("dst_speed"),
                     ULInt16("damage"),
                     Padding(2),
                     Byte("type"),
                     Padding(2))),
    0x009c : (smsg_being_change_direction,
              Struct("data",
                     ULInt32("id"),
                     Padding(2),
                     Byte("dir"))),
    0x00c3 : (smsg_ignore, Field("data", 6)),      # being-change-looks
    0x01d7 : (smsg_ignore, Field("data", 9)),      # being-change-looks2
    0x008d : (smsg_being_chat,
              Struct("data",
                     ULInt16("length"),
                     ULInt32("id"),
                     StringZ("message", lambda ctx: ctx.length - 8))),
    0x00c0 : (smsg_being_emotion,
              Struct("data",
                     ULInt32("id"),
                     Byte("emote"))),
    0x007b : (smsg_being_move,
              Struct("data",
                     ULInt32("id"),
                     ULInt16("speed"),
                     Padding(6),
                     ULInt16("job"),
                     Padding(6),
                     ULInt32("tick"),
                     Padding(10),
                     ULInt32("hp"),
                     ULInt32("max_hp"),
                     # Probe("debug", show_stream=False, show_stack=False),
                     Padding(6),
                     BitStruct("coor_pair",
                               BitField("src_x", 10),
                               BitField("src_y", 10),
                               BitField("dst_x", 10),
                               BitField("dst_y", 10)),
                     Padding(5))),
    0x0086 : (smsg_being_move,
              Struct("data",
                     ULInt32("id"),
                     # Padding(10)
                     BitStruct("coor_pair",
                               BitField("src_x", 10),
                               BitField("src_y", 10),
                               BitField("dst_x", 10),
                               BitField("dst_y", 10)),
                     ULInt32("tick"))),
    0x0095 : (smsg_being_name_response,
              Struct("data",
                     ULInt32("id"),
                     StringZ("name", 24))),
    0x0080 : (smsg_being_remove,
              Struct("data",
                     ULInt32("id"),
                     Byte("deadflag"))),
    0x0148 : (smsg_ignore, Field("data", 6)),   # being-resurrect
    0x019b : (smsg_being_self_effect,
              Struct("data",
                     ULInt32("id"),
                     ULInt32("effect"))),
    0x007c : (smsg_ignore, Field("data", 39)),  # spawn
    0x0196 : (smsg_being_status_change,
              Struct("data",
                     ULInt16("status"),
                     ULInt32("id"),
                     Flag("flag"))),
    0x0078 : (smsg_being_visible,
              Struct("data",
                     ULInt32("id"),
                     ULInt16("speed"),
                     Padding(6),
                     ULInt16("job"),
                     Padding(16),
                     ULInt32("hp"),
                     ULInt32("max_hp"),
                     Padding(6),
                     BitStruct("coor",
                               BitField("x", 10),
                               BitField("y", 10),
                               Nibble("dir")),
                     Padding(5))),
    0x013c : (smsg_ignore, Field("data", 2)),   # arrow-equip
    0x013b : (smsg_player_arrow_message,
              Struct("data",
                     ULInt16("code"))),
    0x013a : (smsg_player_attack_range,
              Struct("data",
                     ULInt16("range"))),
    0x008e : (smsg_player_chat,
              Struct("data",
                     ULInt16("length"),
                     StringZ("message", lambda ctx: ctx.length - 4))),
    0x00aa : (smsg_ignore,                      # player-equip
              Struct("data",
                     ULInt16("index"),
                     ULInt16("type"),
                     Byte("flag"))),
    0x00a4 : (smsg_player_equipment,
              Struct("data",
                     ULInt16("length"),
                     Array(lambda ctx: (ctx.length - 4) / 20,
                           Struct("equipment",
                                  ULInt16("index"),
                                  ULInt16("id"),
                                  Padding(16))))),
    0x0195 : (smsg_ignore, Field("data", 100)),  # guild-party-info
    0x01ee : (smsg_player_inventory,
              Struct("data",
                     ULInt16("length"),
                     Array(lambda ctx: (ctx.length - 4) / 18,
                           Struct("inventory",
                                  ULInt16("index"),
                                  ULInt16("id"),
                                  Padding(2),
                                  ULInt16("amount"),
                                  Padding(10))))),
    0x00a0 : (smsg_player_inventory_add,
              Struct("data",
                     ULInt16("index"),
                     ULInt16("amount"),
                     ULInt16("id"),
                     Padding(15))),
    0x00af : (smsg_player_inventory_remove,
              Struct("data",
                     ULInt16("index"),
                     ULInt16("amount"))),
    0x01c8 : (smsg_player_inventory_use,
              Struct("data",
                     ULInt16("index"),
                     ULInt16("item_id"),
                     Padding(4),
                     ULInt16("amount"),
                     Byte("type"))),
    0x01da : (smsg_player_move,
              Struct("data",
                     ULInt32("id"),
                     ULInt16("speed"),
                     Padding(6),
                     ULInt16("job"),
                     Padding(8),
                     ULInt32("tick"),
                     Padding(22),
                     BitStruct("coor_pair",
                               BitField("src_x", 10),
                               BitField("src_y", 10),
                               BitField("dst_x", 10),
                               BitField("dst_y", 10)),
                     Padding(5))),
    0x0139 : (smsg_ignore, Field("data", 14)),    # player-move-to-attack
    0x010f : (smsg_player_skills,
              Struct("data",
                     ULInt16("length"),
                     Array(lambda ctx: (ctx.length - 4) / 37,
                           Struct("skills",
                                  ULInt16("id"),
                                  Padding(4),
                                  ULInt16("level"),
                                  Padding(29))))),
    0x010e : (smsg_player_skill_up,
              Struct("data",
                     ULInt16("id"),
                     ULInt16("level"),
                     Padding(5))),
    0x00b0 : (smsg_player_stat_update_x,
              Struct("data",
                     ULInt16("type"),
                     ULInt32("stat_value"))),
    0x00b1 : (smsg_player_stat_update_x,
              Struct("data",
                     ULInt16("type"),
                     ULInt32("stat_value"))),
    0x0141 : (smsg_player_stat_update_x,
              Struct("data",
                     ULInt32("type"),
                     ULInt32("stat_value"),
                     ULInt32("bonus"))),
    0x00bc : (smsg_player_stat_update_x,
              Struct("data",
                     ULInt16("type"),
                     Flag("ok"),
                     Byte("stat_value"))),
    0x00bd : (smsg_ignore, Field("data", 42)),   # player-stat-update-5
    0x00be : (smsg_player_stat_update_x,
              Struct("data",
                     ULInt16("type"),
                     Byte("stat_value"))),
    0x0119 : (smsg_player_status_change,
              Struct("data",
                     ULInt32("id"),
                     ULInt16("stun"),
                     ULInt16("effect"),
                     ULInt16("effect_hi"),
                     Padding(1))),
    0x0088 : (smsg_player_stop,
              Struct("data",
                     ULInt32("id"),
                     ULInt16("x"),
                     ULInt16("y"))),
    0x00ac : (smsg_ignore, Field("data", 5)),    # player-unequip
    0x01d8 : (smsg_player_update,
              Struct("data",
                     ULInt32("id"),
                     ULInt16("speed"),
                     Padding(6),
                     ULInt16("job"),
                     Padding(30),
                     BitStruct("coor",
                               BitField("x", 10),
                               BitField("y", 10),
                               Nibble("dir")),
                     Padding(5))),
    0x01d9 : (smsg_player_update,
              Struct("data",
                     ULInt32("id"),
                     ULInt16("speed"),
                     Padding(6),
                     ULInt16("job"),
                     Padding(30),
                     BitStruct("coor",
                               BitField("x", 10),
                               BitField("y", 10),
                               Nibble("dir")),
                     Padding(4))),
    0x0091 : (smsg_player_warp,
              Struct("data",
                     StringZ("map", 16),
                     ULInt16("x"),
                     ULInt16("y"))),
    0x0020 : (smsg_ip_response,
              Struct("data",
                     ULInt32("id"),
                     IpAddress("ip"))),
    0x019a : (smsg_ignore, Field("data", 12)),    # pvp-set
    0x0081 : (smsg_connection_problem,
              Struct("data", Byte("code"))),
    0x009a : (smsg_gm_chat,
              Struct("data",
                     ULInt16("length"),
                     StringZ("message", lambda ctx: ctx.length - 4))),
    0x009e : (smsg_item_dropped,
              Struct("data",
                     ULInt32("id"),
                     ULInt16("type"),
                     Byte("identify"),
                     ULInt16("x"),
                     ULInt16("y"),
                     Byte("sub_x"),
                     Byte("sub_y"),
                     ULInt16("amount"))),
    0x00a1 : (smsg_item_remove,
              Struct("data", ULInt32("id"))),
    0x009d : (smsg_item_visible,
              Struct("data",
                     ULInt32("id"),
                     ULInt16("type"),
                     Byte("identify"),
                     ULInt16("x"),
                     ULInt16("y"),
                     ULInt16("amount"),
                     Byte("sub_x"),
                     Byte("sub_y"))),
    0x00fb : (smsg_party_info,
              Struct("data",
                     ULInt16("length"),
                     StringZ("name", 24),
                     Array(lambda ctx: (ctx.length - 28) / 46,
                           Struct("members",
                                  ULInt32("id"),
                                  StringZ("nick", 24),
                                  StringZ("map", 16),
                                  Flag("leader"),
                                  Flag("online"))))),
    0x00fe : (smsg_ignore, Field("data", 28)),    # party-invited
    0x0107 : (smsg_ignore, Field("data", 8)),     # party-update-coords
    0x0106 : (smsg_ignore, Field("data", 8)),     # party-update-hp
    0x0109 : (smsg_party_chat,
              Struct("data",
                     ULInt16("length"),
                     ULInt32("id"),
                     # Probe("debug", show_stream=False, show_stack=False),
                     StringZ("message", lambda ctx: ctx.length - 8))),
    0x01b9 : (smsg_ignore, Field("data", 4)),     # skill-cast-cancel
    0x013e : (smsg_ignore, Field("data", 22)),    # skill-casting
    0x01de : (smsg_ignore, Field("data", 31)),    # skill-damage
    0x011a : (smsg_ignore, Field("data", 13)),    # skill-no-damage
    0x00e5 : (smsg_trade_request,
              Struct("data",
                     StringZ("nick", 24))),
    0x00e7 : (smsg_trade_response,
              Struct("data", Byte("code"))),
    0x00e9 : (smsg_trade_item_add,
              Struct("data",
                     ULInt32("amount"),
                     ULInt16("id"),
                     Padding(11))),
    0x01b1 : (smsg_trade_item_add_response,
              Struct("data",
                     ULInt16("index"),
                     ULInt16("amount"),
                     Byte("code"))),
    0x00ee : (smsg_trade_cancel,
              StaticField("data", 0)),
    0x00f0 : (smsg_trade_complete,
              StaticField("data", 1)),
    0x00ec : (smsg_trade_ok,
              Struct("data", Byte("who"))),
    0x0097 : (smsg_whisper,
              Struct("data",
                     ULInt16("length"),
                     StringZ("nick", 24),
                     StringZ("message", lambda ctx: ctx.length - 28))),
    0x0098 : (smsg_whisper_response,
              Struct("data", Byte("code"))),
    0x0073 : (smsg_map_login_success,
              Struct("data",
                     ULInt32("tick"),
                     # ULInt24("coor"),
                     BitStruct("coor",
                               BitField("x", 10),
                               BitField("y", 10),
                               Nibble("dir")),
                     Padding(2))),
    0x007f : (smsg_server_ping,
              Struct("data",
                     ULInt32("tick"))),
    0x0087 : (smsg_walk_response,
              Struct("data",
                     ULInt32("tick"),
                     BitStruct("coor_pair",
                               BitField("src_x", 10),
                               BitField("src_y", 10),
                               BitField("dst_x", 10),
                               BitField("dst_y", 10)),
                     Padding(1))),
    0x00b5 : (smsg_ignore, Field("data", 6)),          # npc-next
    0x00b4 : (smsg_npc_message,
              Struct("data",
                     ULInt16("length"),
                     ULInt32("id"),
                     StringZ("message", lambda ctx: ctx.length - 8))),
    0x00b7 : (smsg_npc_choice,
              Struct("data",
                     ULInt16("length"),
                     ULInt32("id"),
                     StringZ("select", lambda ctx: ctx.length - 8))),
    0x00b6 : (smsg_npc_close,
              Struct("data",
                     ULInt32("id"))),
    0x00b5 : (smsg_npc_next,
              Struct("data",
                     ULInt32("id"))),
    0x0142 : (smsg_npc_int_input,
              Struct("data",
                     ULInt32("id"))),
    0x01d4 : (smsg_npc_str_input,
              Struct("data",
                     ULInt32("id"))),
    0x0212 : (smsg_npc_command,
              Struct("data",
                     ULInt32("id"),
                     ULInt16("command"),
                     ULInt32("target_id"),
                     ULInt16("x"),
                     ULInt16("y"))),
    0x00c6 : (smsg_npc_buy,
              Struct("data",
                     ULInt16("length"),
                     Array(lambda ctx: (ctx.length - 4) / 11,
                           Struct("items",
                                  ULInt32("price"),
                                  Padding(4),
                                  ULInt16("type"),
                                  ULInt32("id"))))),
    0x00f2 : (smsg_storage_status,
              Struct("data",
                     ULInt16("used"),
                     ULInt16("max_size"))),
    0x00f4 : (smsg_storage_add,
              Struct("data",
                     ULInt16("index"),
                     ULInt32("amount"),
                     ULInt16("id"),
                     Padding(11))),
    0x00f6 : (smsg_storage_remove,
              Struct("data",
                     ULInt16("index"),
                     ULInt32("amount"))),
    0x00f8 : (smsg_storage_close,
              Struct("data")),        # ?????
    0x00a6 : (smsg_storage_equip,
              Struct("data",
                     ULInt16("length"),
                     Array(lambda ctx: (ctx.length - 4) / 20,
                           Struct("equipment",
                                  ULInt16("index"),
                                  ULInt16("id"),
                                  Padding(16))))),
    0x01f0 : (smsg_storage_items,
              Struct("data",
                     ULInt16("length"),
                     Array(lambda ctx: (ctx.length - 4) / 18,
                           Struct("storage",
                                  ULInt16("index"),
                                  ULInt16("id"),
                                  Padding(2),
                                  ULInt16("amount"),
                                  Padding(10))))),
}


# --------------------------------------------------------------------
def cmsg_map_server_connect(account, char_id, session1, session2, gender):
    netlog.info(("CMSG_MAP_SERVER_CONNECT account={} char_id={} "
                 "session1={} session2={} gender={}").format(account,
                    char_id, session1, session2, gender))

    send_packet(server, CMSG_MAP_SERVER_CONNECT,
                (ULInt32("account"), account),
                (ULInt32("char_id"), char_id),
                (ULInt32("session1"), session1),
                (ULInt32("session2"), session2),
                (Gender("gender"), gender))


def cmsg_map_loaded():
    netlog.info("CMSG_MAP_LOADED")
    ULInt16("opcode").build_stream(CMSG_MAP_LOADED, server)


def cmsg_map_server_ping(tick_=-1):
    netlog.info("CMSG_MAP_SERVER_PING tick={}".format(tick))
    if tick_ < 0:
        tick_ = tick + 1
    send_packet(server, CMSG_MAP_SERVER_PING,
                (ULInt32("tick"), tick_))


def cmsg_trade_response(answer):
    if answer in ("OK", "ACCEPT", "YES", True):
        answer = 3
    elif answer in ("DECLINE", "CANCEL", "NO", False):
        answer = 4

    s = {3: "ACCEPT", 4: "DECLINE"}

    netlog.info("CMSG_TRADE_RESPONSE {}".format(s[answer]))
    send_packet(server, CMSG_TRADE_RESPONSE,
                (Byte("answer"), answer))


def cmsg_trade_request(player_id):
    netlog.info("CMSG_TRADE_REQUEST id={}".format(player_id))
    send_packet(server, CMSG_TRADE_REQUEST,
                (ULInt32("id"), player_id))


def cmsg_trade_item_add_request(index, amount):
    netlog.info("CMSG_TRADE_ITEM_ADD_REQUEST index={} amount={}".format(
        index, amount))

    # Workaround for TMWA, I'm pretty sure it has a bug related to
    # not sending back the amount of GP player added to trade
    if index == 0 and amount <= player_money:
        trade_state['zeny_give'] = amount

    send_packet(server, CMSG_TRADE_ITEM_ADD_REQUEST,
                (ULInt16("index"), index),
                (ULInt32("amount"), amount))


def cmsg_trade_ok():
    netlog.info("CMSG_TRADE_OK")
    ULInt16("opcode").build_stream(CMSG_TRADE_OK, server)


def cmsg_trade_add_complete():
    netlog.info("CMSG_TRADE_ADD_COMPLETE")
    ULInt16("opcode").build_stream(CMSG_TRADE_ADD_COMPLETE, server)


def cmsg_trade_cancel_request():
    netlog.info("CMSG_TRADE_CANCEL_REQUEST")
    ULInt16("opcode").build_stream(CMSG_TRADE_CANCEL_REQUEST, server)


def cmsg_name_request(id_):
    netlog.info("CMSG_NAME_REQUEST id={}".format(id_))
    send_packet(server, CMSG_NAME_REQUEST,
                (ULInt32("id"), id_))


def cmsg_chat_message(msg):
    netlog.info("CMSG_CHAT_MESSAGE {}".format(msg))
    l = len(msg)
    send_packet(server, CMSG_CHAT_MESSAGE,
                (ULInt16("len"), l + 5),
                (StringZ("msg", l + 1), msg))


def cmsg_chat_whisper(to_, msg):
    netlog.info("CMSG_CHAT_WHISPER to {} : {}".format(to_, msg))
    last_whisper['to'] = to_
    last_whisper['msg'] = msg
    l = len(msg)
    send_packet(server, CMSG_CHAT_WHISPER,
                (ULInt16("len"), l + 29),
                (StringZ("nick", 24), to_),
                (StringZ("msg", l + 1), msg))


def cmsg_party_message(msg):
    netlog.info("CMSG_PARTY_MESSAGE {}".format(msg))
    l = len(msg)
    send_packet(server, CMSG_PARTY_MESSAGE,
                (ULInt16("len"), l + 4),
                (String("msg", l), msg))


def cmsg_player_change_dest(x_, y_):
    netlog.info("CMSG_PLAYER_CHANGE_DEST x={} y={}".format(x_, y_))

    class C:
        opcode = CMSG_PLAYER_CHANGE_DEST

        class coor:
            x = x_
            y = y_
            dir = 0

    d = Struct("packet",
               ULInt16("opcode"),
               BitStruct("coor", BitField("x", 10),
                         BitField("y", 10), Nibble("dir")))

    d.build_stream(C, server)


def cmsg_player_change_dir(new_dir):
    netlog.info("CMSG_PLAYER_CHANGE_DIR {}".format(new_dir))
    send_packet(server, CMSG_PLAYER_CHANGE_DIR,
                (ULInt16("unused"), 0),
                (ULInt8("dir"), new_dir))


def cmsg_player_change_act(id_, action):
    netlog.info("CMSG_PLAYER_CHANGE_ACT id={} action={}".format(id_, action))
    send_packet(server, CMSG_PLAYER_CHANGE_ACT,
                (ULInt32("id"), id_),
                (Byte("action"), action))


def cmsg_player_respawn():
    netlog.info("CMSG_PLAYER_RESPAWN")
    send_packet(server, CMSG_PLAYER_RESPAWN,
                (Byte("action"), 0))


def cmsg_player_emote(emote):
    netlog.info("CMSG_PLAYER_EMOTE {}".format(emote))
    send_packet(server, CMSG_PLAYER_EMOTE,
                (Byte("emote"), emote))


def cmsg_item_pickup(item_id):
    netlog.info("CMSG_ITEM_PICKUP id={}".format(item_id))
    send_packet(server, CMSG_ITEM_PICKUP,
                (ULInt32("id"), item_id))


def cmsg_player_stop_attack():
    netlog.info("CMSG_PLAYER_STOP_ATTACK")
    ULInt16("opcode").build_stream(CMSG_PLAYER_STOP_ATTACK, server)


def cmsg_player_equip(index):
    netlog.info("CMSG_PLAYER_EQUIP index={}".format(index))
    send_packet(server, CMSG_PLAYER_EQUIP,
                (ULInt16("index"), index),
                (ULInt16("unused"), 0))


def cmsg_player_unequip(index):
    netlog.info("CMSG_PLAYER_UNEQUIP index={}".format(index))
    send_packet(server, CMSG_PLAYER_UNEQUIP,
                (ULInt16("index"), index))


def cmsg_player_inventory_use(index, item_id):
    netlog.info("CMSG_PLAYER_INVENTORY_USE index={} id={}".format(
        index, item_id))
    send_packet(server, CMSG_PLAYER_INVENTORY_USE,
                (ULInt16("index"), index),
                (ULInt32("id"), item_id))


def cmsg_player_inventory_drop(index, amount):
    netlog.info("CMSG_PLAYER_INVENTORY_DROP index={} amount={}".format(
        index, amount))
    send_packet(server, CMSG_PLAYER_INVENTORY_DROP,
                (ULInt16("index"), index),
                (ULInt16("amount"), amount))


# --------------- NPC ---------------------
def cmsg_npc_talk(npcId):
    netlog.info("CMSG_NPC_TALK id={}".format(npcId))
    send_packet(server, CMSG_NPC_TALK,
                (ULInt32("npcId"), npcId),
                (Byte("unused"), 0))


def cmsg_npc_next_request(npcId):
    netlog.info("CMSG_NPC_NEXT_REQUEST id={}".format(npcId))
    send_packet(server, CMSG_NPC_NEXT_REQUEST,
                (ULInt32("npcId"), npcId))


def cmsg_npc_close(npcId):
    netlog.info("CMSG_NPC_CLOSE id={}".format(npcId))
    send_packet(server, CMSG_NPC_CLOSE,
                (ULInt32("npcId"), npcId))


def cmsg_npc_list_choice(npcId, choice):
    netlog.info("CMSG_NPC_LIST_CHOICE id={} choice={}".format(npcId, choice))
    send_packet(server, CMSG_NPC_LIST_CHOICE,
                (ULInt32("npcId"), npcId),
                (Byte("choice"), choice))


def cmsg_npc_int_response(npcId, value):
    netlog.info("CMSG_NPC_INT_RESPONSE id={} value={}".format(npcId, value))
    send_packet(server, CMSG_NPC_INT_RESPONSE,
                (ULInt32("npcId"), npcId),
                (SLInt32("value"), value))


def cmsg_npc_str_response(npcId, value):
    netlog.info("CMSG_NPC_STR_RESPONSE id={} value={}".format(npcId, value))
    l = len(value)
    send_packet(server, CMSG_NPC_STR_RESPONSE,
                (ULInt16("len"), l + 9),
                (ULInt32("npcId"), npcId),
                (StringZ("value", l + 1), value))


def cmsg_npc_buy_sell_request(npcId, action):
    netlog.info("CMSG_NPC_BUY_SELL_REQUEST id={} action={}".format(
        npcId, action))
    send_packet(server, CMSG_NPC_BUY_SELL_REQUEST,
                (ULInt32("npcId"), npcId),
                (Byte("action"), action))


def cmsg_npc_buy_request(itemId, amount):
    netlog.info("CMSG_NPC_BUY_REQUEST itemId={} amount={}".format(
        itemId, amount))
    send_packet(server, CMSG_NPC_BUY_REQUEST,
                (ULInt16("len"), 8),
                (ULInt16("amount"), amount),
                (ULInt16("itemId"), itemId))


def cmsg_npc_sell_request(index, amount):
    netlog.info("CMSG_NPC_SELL_REQUEST index={} amount={}".format(
        index, amount))
    send_packet(server, CMSG_NPC_SELL_REQUEST,
                (ULInt16("len"), 8),
                (ULInt16("index"), index),
                (ULInt16("amount"), amount))


# --------------- STATS, SKILLS --------------------------
def cmsg_stat_update_request(stat, value):
    netlog.info("CMSG_STAT_UPDATE_REQUEST stat={} value={}".format(
        stat, value))
    send_packet(server, CMSG_STAT_UPDATE_REQUEST,
                (ULInt16("stat"), stat),
                (Byte("value"), value))


def cmsg_skill_levelup_request(skillId):
    netlog.info("CMSG_SKILL_LEVELUP_REQUEST skillId={}".format(skillId))
    send_packet(server, CMSG_SKILL_LEVELUP_REQUEST,
                (ULInt16("id"), skillId))


# ------------------- STORAGE -------------------------------
def cmsg_move_to_storage(index, amount):
    netlog.info("CMSG_MOVE_TO_STORAGE index={} amount={}".format(
        index, amount))
    send_packet(server, CMSG_MOVE_TO_STORAGE,
                (ULInt16("index"), index),
                (ULInt32("amount"), amount))


def cmsg_move_from_storage(index, amount):
    netlog.info("CMSG_MOVE_FROM_STORAGE index={} amount={}".format(
        index, amount))
    send_packet(server, CMSG_MOVE_FROM_STORAGE,
                (ULInt16("index"), index),
                (ULInt32("amount"), amount))


def cmsg_storage_close():
    netlog.info("CMSG_CLOSE_STORAGE")
    ULInt16("opcode").build_stream(CMSG_CLOSE_STORAGE, server)


# --------------------------------------------------------------------

last_ping_ts = 0


def connect(host, port):

    def ping_logic(ts):
        global last_ping_ts
        if last_ping_ts and ts > last_ping_ts + 15:
            last_ping_ts = time.time()
            cmsg_map_server_ping()

    global server, beings_cache
    beings_cache = BeingsCache(cmsg_name_request)
    #server = SocketWrapper(host=host, port=port, protodef=protodef)
    server = SocketWrapper(host="52.174.196.146", port=port, protodef=protodef)

    import logicmanager
    logicmanager.logic_manager.add_logic(ping_logic)


@atexit.register
def cleanup():
    if server is not None:
        server.close()