import socket
import ssl
import time

import struct


class Type(object):
    T_END = 0
    T_INT = 1
    T_STR = 2
    T_COORD = 3
    T_UINT8 = 4
    T_UINT16 = 5
    T_COLOR = 6
    T_TTOL = 8
    T_INT8 = 9
    T_INT16 = 10
    T_NIL = 12
    T_UID = 13
    T_BYTES = 14
    T_FLOAT32 = 15
    T_FLOAT64 = 16


class Coord(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @staticmethod
    def mul(lhs, rhs):
        return Coord(lhs.x * rhs.x, lhs.y * rhs.y)

    @staticmethod
    def floor(lhs, rhs):
        return Coord(int(lhs.x / rhs.x), int(lhs.y / rhs.y))  # TODO: math.floor?

    @staticmethod
    def diff(lhs, rhs):
        return Coord(abs(lhs.x - rhs.x), abs(lhs.y - rhs.y))


class Coords(object):
    Z = Coord(0, 0)
    POSRES = Coord.mul(
        Coord(9.765625E-4, 9.765625E-4),
        Coord(11, 11)
    )


class Color(object):
    def __init__(self, r, g, b, a):
        self.r = r
        self.g = g
        self.b = b
        self.a = a


class MessageBuf(object):
    def __init__(self, buf=bytearray()):
        self._buf = bytearray(buf)
        #self._buf = buf
        self.pos = 0

    @property
    def buf(self):
        return self._buf

    # NOTE: Auth server uses BE, game server uses LE
    def __end(self, be):
        return '>' if be else '<'

    def add_string(self, s, enc='utf-8'):
        self._buf.extend(s.encode(enc))
        self._buf.append(0)
        self.pos = len(self._buf)

    def add_bytes(self, b):
        self._buf.extend(b)
        self.pos = len(self._buf)

    def add_uint8(self, val):
        self._buf.append(val)
        self.pos = len(self._buf)

    def add_int16(self, val, be=False):
        self._buf.extend(struct.pack(self.__end(be) + 'h', val))
        self.pos = len(self._buf)

    def add_uint16(self, val, be=False):
        self._buf.extend(struct.pack(self.__end(be) + 'H', val))
        self.pos = len(self._buf)

    def add_int32(self, val, be=False):
        self._buf.extend(struct.pack(self.__end(be) + 'i', val))
        self.pos = len(self._buf)

    def add_int64(self, val, be=False):
        self._buf.extend(struct.pack(self.__end(be) + 'q', val))
        self.pos = len(self._buf)

    def add_uint32(self, val, be=False):
        self._buf.extend(struct.pack(self.__end(be) + 'I', val))
        self.pos = len(self._buf)

    def add_float32(self, val, be=False):
        self._buf.extend(struct.pack(self.__end(be) + 'f', val))
        self.pos = len(self._buf)

    def add_float64(self, val, be=False):
        self._buf.extend(struct.pack(self.__end(be) + 'd', val))
        self.pos = len(self._buf)

    def add_list(self, l, be=False):
        for e in l:
            t = type(e)
            if e is None:
                self.add_uint8(0)
            elif t is int:
                self.add_uint8(Type.T_INT)
                self.add_int32(e, be)
            elif t is str or t is unicode:
                self.add_uint8(Type.T_STR)
                self.add_string(e)
            elif t is Coord:
                self.add_uint8(Type.T_COORD)
                self.add_int32(e.x, be)
                self.add_int32(e.y, be)
            elif t is bytearray:
                self.add_uint8(Type.T_BYTES)
                if len(e) < 128:
                    self.add_uint8(len(e))
                else:
                    self.add_uint8(0x80)
                    self.add_int32(len(e), be)
                self.add_bytes(e)
            elif t is Color:
                self.add_uint8(Type.T_COLOR)
                self.add_uint8(e.r)
                self.add_uint8(e.g)
                self.add_uint8(e.b)
                self.add_uint8(e.a)
            elif t is float:
                self.add_uint8(Type.T_FLOAT32)
                self.add_float32(e, be)
            else:
                pass  # TODO: Add `double` support

    # TODO: Add `enc` parameter
    def get_string(self):
        s = ''
        while True:
            c = self.get_uint8()
            if c == 0:
                break
            s += chr(c)
        return s

    def get_bytes(self, len):
        res = self._buf[self.pos:self.pos + len]
        self.pos += len
        return res

    def get_remaining(self):
        res = self._buf[self.pos:]
        self.pos = len(self._buf)
        return res

    def get_uint8(self):
        res = self._buf[self.pos]
        self.pos += 1
        return res

    def get_int16(self, be=False):
        res = struct.unpack(
            self.__end(be) + 'h',
            str(self._buf[self.pos:self.pos + 2])
        )[0]
        self.pos += 2
        return res

    def get_uint16(self, be=False):
        res = struct.unpack(
            self.__end(be) + 'H',
            str(self._buf[self.pos:self.pos + 2])
        )[0]
        self.pos += 2
        return res

    def get_int32(self, be=False):
        res = struct.unpack(
            self.__end(be) + 'i',
            str(self._buf[self.pos:self.pos + 4])
        )[0]
        self.pos += 4
        return res

    def get_uint32(self, be=False):
        res = struct.unpack(
            self.__end(be) + 'I',
            str(self._buf[self.pos:self.pos + 4])
        )[0]
        self.pos += 4
        return res

    def get_int64(self, be=False):
        res = struct.unpack(
            self.__end(be) + 'q',
            str(self._buf[self.pos:self.pos + 8])
        )[0]
        self.pos += 8
        return res

    def get_float32(self, be=False):
        res = struct.unpack(
            self.__end(be) + 'f',
            str(self._buf[self.pos:self.pos + 4])
        )[0]
        self.pos += 4
        return res

    def get_float64(self, be=False):
        res = struct.unpack(
            self.__end(be) + 'd',
            str(self._buf[self.pos:self.pos + 8])
        )[0]
        self.pos += 8
        return res

    def get_coords(self, be=False):
        x = self.get_int32(be)
        y = self.get_int32(be)
        return Coord(x, y)

    def get_color(self):
        r = self.get_uint8()
        g = self.get_uint8()
        b = self.get_uint8()
        a = self.get_uint8()
        return Color(r, g, b, a)

    def get_list(self, be=False):
        res = []
        while True:
            if self.eom():
                break
            t = self.get_uint8()
            if t == Type.T_END:
                break
            elif t == Type.T_INT:
                res.append(self.get_int32(be))
            elif t == Type.T_STR:
                res.append(self.get_string())
            elif t == Type.T_COORD:
                res.append(self.get_coords(be))
            elif t == Type.T_UINT8:
                res.append(self.get_uint8())
            elif t == Type.T_UINT16:
                res.append(self.get_uint16(be))
            elif t == Type.T_INT8:
                res.append(self.get_uint8())  # TODO
            elif t == Type.T_INT16:
                res.append(self.get_int16(be))
            elif t == Type.T_COLOR:
                res.append(self.get_color())
            elif t == Type.T_TTOL:
                res.append(self.get_list(be))
            elif t == Type.T_NIL:
                res.append(0)
            elif t == Type.T_UID:
                res.append(self.get_int64(be))
            elif t == Type.T_BYTES:
                l = self.get_uint8()
                if (l & 128) != 0:
                    l = self.get_int32(be)
                res.append(self.get_bytes(l))
            elif t == Type.T_FLOAT32:
                res.append(self.get_float32(be))
            elif t == Type.T_FLOAT64:
                res.append(self.get_float64(be))
            else:
                pass  # TODO
        return res

    def skip(self, count):
        self.pos += count

    def eom(self):
        return self.pos >= len(self._buf)

    def __str__(self):
        return ', '.join([hex(c) + '/' + chr(c) for c in self._buf])


G_server = {'1':1}

class AuthException(Exception):
    pass


#class AuthClient(SimpleLogger):
class AuthClient():
    def __init__(self, verbose=False):
        #SimpleLogger.__init__(self)
        self.verbose = verbose

    def connect(self, host, port, cert_path):
        #try:
        if 1==1:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            #self.ws = ssl.wrap_socket(s, ca_certs=cert_path)
            #self.ws.connect((host, port))
            sock = ssl.wrap_socket(s, ca_certs=cert_path)
            G_server['sock'] = sock
            #print(G_server)
            #print(sock)
            #print(host)
            #print(port)
            print('connected')
            G_server['sock'].connect((host, port))
        #except Exception as e:
            #raise AuthException(e)

    def login(self, username, password):
        #self.__recv_bytes(2)
        msg = MessageBuf()
        msg.add_string('pw')
        msg.add_string(username)
        #msg.add_bytes(password.decode('hex'))
        msg.add_bytes(password)
        #msg.add_string(password)
        #print('MSG:',msg,'type',type(msg))
        self.__send_msg(msg)

        self.__recv_bytes(1,200)

        #print('LOOKING FOR COOKIE')

        msg = MessageBuf()
        msg.add_string('cookie')
        self.__send_msg(msg)
        #time.sleep(5)
        res = self.__recv_bytes(1,200)
        print('RES:',res)
        G_server['cookie'] = res
        
        #time.sleep(5)
        #self.__recv_bytes(2)
        #rpl = self.__recv_msg()
        #print('rpl',rpl)
        #status = rpl.get_string()
        #if status == 'ok':
            #acc = rpl.get_string()  # This is normally the same thing as `username`
            #print(acc)
        #    return
        #elif status == 'no':
            #print('NO')
        #    err = rpl.get_string()
        #    raise AuthException(err)
        #else:
        #    raise AuthException('Unexpected reply: "' + status + '"')
        return G_server['cookie']

    def get_cookie(self):
        #print('- trying to get cookie')
        msg = MessageBuf()
        msg.add_string('cookie')
        self.__send_msg(msg)
        time.sleep(5)
        self.__recv_bytes(2)
        time.sleep(5)
        self.__recv_bytes(2)

        #rpl = self.__recv_msg()
        #status = rpl.get_string()
        #if status == 'ok':
        #    cookie = rpl.get_bytes(32)
        #    return cookie
        #else:
        #    raise AuthException('Unexpected reply: "' + status + '"')

    def __prepend_header(self, msg):
        tmp = MessageBuf()
        tmp.add_uint16(len(msg.buf), be=True)
        tmp.add_bytes(msg.buf)
        return tmp

    def __send_msg(self, msg):
        msg = self.__prepend_header(msg)
        if self.verbose:
            self.info('> ' + str(msg))
        #self.ws.sendall(msg.buf)
        #print('MSG:',msg.buf,'type',type(msg.buf))
        G_server['sock'].sendall(msg.buf)

    def __recv_msg(self):
        #print('trying to get header')        
        header = MessageBuf(self.__recv_bytes(2))
        #print('set header >', header)
        #data_len = header.get_uint16(be=True)
        #print('get data')
        #msg = MessageBuf(self.__recv_bytes(data_len))
        #if self.verbose:
        #    self.info('< ' + str(msg))
        #return msg

    def __recv_bytes(self, max_count, bytes_size):
        #res = []
        count= 0
        
        while True:
            #print('count',count)
            count = count + 1
            #bytes_to_read = l - len(res)
            #if bytes_to_read < 1:
                #break
            if count > max_count:
                break

            #data = self.ws.recv(bytes_to_read)
            #print('+')
            data = G_server['sock'].recv(bytes_size)
            #print('-')
            if not data:
                print('there is no data')
                break

            #res += data
            #data = int.from_bytes(data, 'little')
            #res.append(data)
            #v = data.decode("utf-8")
            #v = data.decode("latin-1")
            #res.extend(data)
            #print('-',data,'>',v)
        #res = bytearray(res)
        print('type',type(data))
        print(data)
        return data
