class PacketHandler:
    data = b''
    stopping = False

    def __init__(self, table, callback):
        self.table = table
        self.callback = callback

    def feed(self, data):
        self.data += data
        reader = ByteReader(self.data)
        try:
            while True:
                pos = reader.tell()
                if pos >= len(self.data):
                    break
                packet = read_packet(reader, self.table)
                self.callback(packet)
                if self.stopping:
                    return
        except OutOfData as e:
            if e.reader is not reader:
                raise e
        self.data = self.data[pos:]

    def stop(self):
        self.stopping = True
        self.feed = lambda x: None def write_packet(packet):
    writer = ByteWriter()
    writer.write_uint32(packet.packet_id)
    packet.write(writer)
    return writer.get() class ServerChatMessage(Packet):
    def read(self, reader):
        self.entity_id = reader.read_uint64()
        size = reader.read_uint32()
        data = reader.read(size * 2)
        self.value = data.decode(ENCODING)

    def write(self, writer):
        writer.write_uint64(self.entity_id)
        data = self.value.encode(ENCODING)
        writer.write_uint32(len(data) / 2)
        writer.write(data) CS_PACKETS = {
    0: EntityUpdate,
    6: InteractPacket,
    7: HitPacket,
    8: PassivePacket,
    9: ShootPacket,
    10: ClientChatMessage,
    11: ChunkDiscovered,
    12: SectorDiscovered,
    17: ClientVersion
} SC_PACKETS = {
    0: EntityUpdate,
    1: MultipleEntityUpdate,  # not used
    2: UpdateFinished,
    3: AirshipUpdate,  # not used
    4: ServerUpdate,
    5: CurrentTime,
    10: ServerChatMessage,
    18: ServerFull,
    17: ServerMismatch,
    16: JoinPacket,
    15: SeedData
} class EntityUpdate(Packet):
    def read(self, reader):
        size = reader.read_uint32()
        self.data = zlib.decompress(reader.read(size))
        reader = ByteReader(self.data)
        self.entity_id = reader.read_uint64()

    def update_entity(self, entity):
        reader = ByteReader(self.data)
        reader.skip(8)
        return read_masked_data(entity, reader)

    def set_entity(self, entity, entity_id, mask=None):
        if mask is None:
            mask = FULL_MASK
        writer = ByteWriter()
        writer.write_uint64(entity_id)
        write_masked_data(entity, writer, mask)
        self.data = writer.get()

    def write(self, writer):
        data = zlib.compress(self.data)
        writer.write_uint32(len(data))
        writer.write(data) class JoinPacket(Packet):
    data = None

    def read(self, reader):
        if reader.read_uint32() != 0:
            raise NotImplementedError()
            return
        self.entity_id = reader.read_uint64()  # must be > 1 and < 10
        self.data = EntityData()
        self.data.read(reader)

    def write(self, writer):
        writer.write_uint32(0)
        writer.write_uint64(self.entity_id)
        if self.data is None:
            writer.write(b'\x00' * 0x1168)
        else:
            self.data.write(writer) class CurrentTime(Packet):
    def read(self, reader):
        self.day = reader.read_uint32()
        self.time = reader.read_uint32()

    def write(self, writer):
        writer.write_uint32(self.day)
        writer.write_uint32(self.time) class ClientChatMessage(Packet):
    def read(self, reader):
        size = reader.read_uint32()
        data = reader.read(size * 2)
        self.value = data.decode(ENCODING)

    def write(self, writer):
        data = self.value.encode(ENCODING)
        writer.write_uint32(len(data) / 2)
        writer.write(data) class UpdateFinished(Packet):
    # called when entity update (packet 0) is finished
    pass class ServerUpdate(Packet):
    def reset(self):
        self.block_actions = []
        self.player_hits = []
        self.particles = []
        self.sound_actions = []
        self.shoot_actions = []
        self.static_entities = []
        self.chunk_items = []
        self.items_8 = []
        self.pickups = []
        self.kill_actions = []
        self.damage_actions = []
        self.passive_actions = []
        self.missions = []

    def is_empty(self):
        for l in (self.block_actions, self.player_hits, self.particles,
                  self.sound_actions, self.shoot_actions, self.static_entities,
                  self.chunk_items, self.items_8, self.pickups,
                  self.kill_actions, self.damage_actions, self.passive_actions,
                  self.missions):
            if l:
                return False
        return True

    def read(self, reader):
        size = reader.read_uint32()
        decompressed_data = zlib.decompress(reader.read(size))
        reader = ByteReader(decompressed_data)

        self.block_actions = read_list(reader, BlockAction)
        self.player_hits = read_list(reader, HitPacket)
        self.particles = read_list(reader, ParticleData)
        self.sound_actions = read_list(reader, SoundAction)
        self.shoot_actions = read_list(reader, ShootPacket)
        self.static_entities = read_list(reader, StaticEntityPacket)
        self.chunk_items = read_list(reader, ChunkItems)

        self.items_8 = []
        for _ in range(reader.read_uint32()):
            something = reader.read_uint64()
            sub_items = []
            for _ in range(reader.read_uint32()):
                sub_items.append(reader.read(16))
            self.items_8.append((something, sub_items))

        self.pickups = read_list(reader, PickupAction)
        self.kill_actions = read_list(reader, KillAction)
        self.damage_actions = read_list(reader, DamageAction)

        # EXT: used when NPC wizards/creatures use a right-click targeted
        # action such as a spell cast. (NPC rclick action?)
        self.passive_actions = read_list(reader, PassivePacket)

        self.missions = read_list(reader, MissionPacket)

        debug = False
        if debug:
            v = vars(self).copy()
            # del v['kill_actions']
            # del v['player_hits']
            del v['pickups']
            del v['damage_actions']
            del v['sound_actions']
            del v['shoot_actions']
            del v['chunk_items']
            for k, v in v.items():
                if not v:
                    continue
                print(k, v)

        if len(decompressed_data) != reader.tell():
            raise NotImplementedError()

    def write(self, writer):
        data = ByteWriter()
        write_list(data, self.block_actions)
        write_list(data, self.player_hits)
        write_list(data, self.particles)
        write_list(data, self.sound_actions)
        write_list(data, self.shoot_actions)
        write_list(data, self.static_entities)
        write_list(data, self.chunk_items)

        data.write_uint32(len(self.items_8))
        for item in self.items_8:
            something, sub_items = item
            data.write_uint64(something)
            data.write_uint32(len(sub_items))
            for item in sub_items:
                data.write(item)

        write_list(data, self.pickups)
        write_list(data, self.kill_actions)
        write_list(data, self.damage_actions)
        write_list(data, self.passive_actions)

        write_list(data, self.missions)

        compressed_data = zlib.compress(data.get())
        writer.write_uint32(len(compressed_data))
        writer.write(compressed_data) class InteractPacket(Packet):
    def read(self, reader):
        self.item_data = ItemData()
        self.item_data.read(reader)
        self.chunk_x = reader.read_int32()
        self.chunk_y = reader.read_int32()
        # index of item in ChunkItems, -1 when drop
        self.item_index = reader.read_int32()
        self.something4 = reader.read_uint32()
        self.interact_type = reader.read_uint8()
        self.something6 = reader.read_uint8()
        self.something7 = reader.read_uint16()

    def write(self, writer):
        self.item_data.write(writer)
        writer.write_int32(self.chunk_x)
        writer.write_int32(self.chunk_y)
        writer.write_int32(self.item_index)
        writer.write_uint32(self.something4)
        writer.write_uint8(self.interact_type)
        writer.write_uint8(self.something6)
        writer.write_uint16(self.something7) CLIENT_VERSION = 3
SERVER_PORT = 12345
FULL_MASK = 0x0000FFFFFFFFFFFF
MAX_TIME = 24 * 60 * 60 * 1000
NORMAL_TIME_SPEED = 10.0
SLEEP_TIME_SPEED = 100.0
BLOCK_SCALE = 0x10000
ZONE_SCALE = BLOCK_SCALE * 256
REGION_SCALE = ZONE_SCALE * 64
MISSION_SCALE = REGION_SCALE // 8
MISSIONS_IN_REGION = 8
MAX_POS = REGION_SCALE * 1024
MAX_ZONE = MAX_POS // ZONE_SCALE
CHUNK_SCALE = ZONE_SCALE
MAX_CHUNK = MAX_ZONE
SECTOR_SCALE = REGION_SCALE
AIR_BLOCK = 0
SOLID_BLOCK = 1
WATER_BLOCK = 2
FLAT_WATER_BLOCK = 3
HOSTILE_FLAG = 1 << 5
FRIENDLY_PLAYER_TYPE = 0
HOSTILE_TYPE = 1
FRIENDLY_TYPE = 2 # also 4, 5
NAMED_FRIENDLY_TYPE = 3
TARGET_TYPE = 6
CLIMBING_FLAG = 1 << 0
ATTACKING_FLAG = 1 << 2
GLIDER_FLAG = 1 << 4
LANTERN_FLAG = 1 << 9
STEALTH_FLAG = 1 << 10
IMMOVABLE_FLAG = 1 << 8
SOLID_PARTICLE = 0
BOMB_PARTICLE = 1
NO_SPREAD_PARTICLE = 2
NO_ACCELERATION_PARTICLE = 3
NO_GRAVITY_PARTICLE = 4
WARRIOR_CLASS = 1
BERSERKER = 0
GUARDIAN = 1
RANGER_CLASS = 2
SNIPER = 0
SCOUT = 1
MAGE_CLASS = 3
FIRE_MAGE = 0
WATER_MAGE = 1
ROGUE_CLASS = 4
ASSASSIN = 0
NINJA = 1
CLASS_NAMES = {
    WARRIOR_CLASS: 'Warrior',
    RANGER_CLASS: 'Ranger',
    MAGE_CLASS: 'Mage',
    ROGUE_CLASS: 'Rogue'
}
CLASS_SPECIALIZATIONS = {
    WARRIOR_CLASS: {
        BERSERKER: 'Berserker',
        GUARDIAN: 'Guardian'
    },
    RANGER_CLASS: {
        SNIPER: 'Sniper',
        SCOUT: 'Scout'
    },
    MAGE_CLASS: {
        FIRE_MAGE: 'Fire',
        WATER_MAGE: 'Water'
    },
    ROGUE_CLASS: {
        ASSASSIN: 'Assassin',
        NINJA: 'Ninja'
    }
}
MATERIAL_NAMES = {
    1: 'Iron',
    2: 'Wood',
    5: 'Obsidian',
    7: 'Bone',
    11: 'Gold',
    12: 'Silver',
    13: 'Emerald',
    14: 'Sapphire',
    15: 'Ruby',
    16: 'Diamond',
    17: 'Sandstone',
    18: 'Saurian',
    19: 'Parrot',
    20: 'Mammoth',
    21: 'Plant',
    22: 'Ice',
    23: 'Licht',
    24: 'Glass',
    25: 'Silk',
    26: 'Linen',
    27: 'Cotton',
    128: 'Fire',
    129: 'Unholy',
    130: 'Ice',
    131: 'Wind'
}
from cuwo.packet import (PacketHandler, write_packet, ServerChatMessage,
                         CS_PACKETS, SC_PACKETS, EntityUpdate,
                         JoinPacket, CurrentTime, ClientChatMessage,
                         UpdateFinished, ServerUpdate, InteractPacket)
from cuwo import entity as entitydata
from cuwo import constants
        from cuwo.win32 import WindowsEventLoop
import sys
import asyncio
import signal
# Copyright (c) Mathias Kaerlev 2013-2017.
#
# This file is part of cuwo.
#
# cuwo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cuwo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with cuwo.  If not, see <http://www.gnu.org/licenses/>.



class BackendProtocol(asyncio.Protocol):
    def __init__(self, protocol):
        self.protocol = protocol
        protocol.relay_client = self

    def connection_made(self, transport):
        self.transport = transport
        self.protocol.got_relay_client()

    def data_received(self, data):
        self.protocol.server_data_received(data)


class FrontendProtocol(asyncio.Protocol):
    relay_client = None
    relay_packets = None
    entity_id = None
    disconnected = False
    update_index = 0

    def __init__(self, loop):
        self.loop = loop
        self.start_time = self.loop.time()
        self.client_handler = PacketHandler(CS_PACKETS,
                                            self.on_client_packet)
        self.server_handler = PacketHandler(SC_PACKETS,
                                            self.on_server_packet)
        self.entities = {}
        self.relay_packets = []
        self.print_stats()
        self.times = []

    def print_stats(self):
        if self.disconnected:
            return
        self.loop.call_later(15, self.print_stats)

        if self.entity_id is None:
            return
        entity = self.entities[self.entity_id]
        print('Info:')
        print('Pos:', entity.pos.x, entity.pos.y, entity.pos.z)

    def send_chat(self, value):
        packet = ServerChatMessage()
        packet.entity_id = 0
        packet.value = value
        self.transport.write(write_packet(packet))

    def send_client_chat(self, value):
        packet = ClientChatMessage()
        packet.value = value
        self.relay_client.transport.write(write_packet(packet))

    def on_entity_update(self, packet):
        return
        if packet.entity_id not in self.entities:
            entity = entitydata.EntityData()
            self.entities[packet.entity_id] = entity
            print('Created new entity:', packet.entity_id)
        else:
            entity = self.entities[packet.entity_id]
        packet.update_entity(entity)

    def on_server_update(self, packet):
        if packet.items_8:
            print(packet.items_8)
        if packet.missions:
            print(packet.missions)
        packet.missions = []
        # for static_entity in packet.static_entities:
        #     print(vars(static_entity.header))

    def on_client_packet(self, packet):
        if packet.packet_id == EntityUpdate.packet_id:
            self.on_entity_update(packet)
        if self.relay_client is None:
            self.relay_packets.append(write_packet(packet))
            return
        if packet.packet_id == InteractPacket.packet_id:
            print(vars(packet))
        self.relay_client.transport.write(write_packet(packet))
        if packet.packet_id not in (0,):
            print('Got client packet:', packet.packet_id)

    def on_server_packet(self, packet):
        if packet.packet_id == EntityUpdate.packet_id:
            self.on_entity_update(packet)

        elif packet.packet_id == JoinPacket.packet_id: