#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

from thriftpy3.Thrift import *
import six


class TProtocolException(TException):
  """Custom Protocol Exception class"""

  UNKNOWN = 0
  INVALID_DATA = 1
  NEGATIVE_SIZE = 2
  SIZE_LIMIT = 3
  BAD_VERSION = 4
  NOT_IMPLEMENTED = 5
  DEPTH_LIMIT = 6

  def __init__(self, type=UNKNOWN, message=None):
    TException.__init__(self, message)
    self.type = type


class TProtocolBase:
  """Base class for Thrift protocol driver."""

  def __init__(self, trans):
    self.trans = trans

  def writeMessageBegin(self, name, ttype, seqid):
    pass

  def writeMessageEnd(self):
    pass

  def writeStructBegin(self, name):
    pass

  def writeStructEnd(self):
    pass

  def writeFieldBegin(self, name, ttype, fid):
    pass

  def writeFieldEnd(self):
    pass

  def writeFieldStop(self):
    pass

  def writeMapBegin(self, ktype, vtype, size):
    pass

  def writeMapEnd(self):
    pass

  def writeListBegin(self, etype, size):
    pass

  def writeListEnd(self):
    pass

  def writeSetBegin(self, etype, size):
    pass

  def writeSetEnd(self):
    pass

  def writeBool(self, bool_val):
    pass

  def writeByte(self, byte):
    pass

  def writeI16(self, i16):
    pass

  def writeI32(self, i32):
    pass

  def writeI64(self, i64):
    pass

  def writeDouble(self, dub):
    pass

  def writeString(self, str_val):
    pass

  def readMessageBegin(self):
    pass

  def readMessageEnd(self):
    pass

  def readStructBegin(self):
    pass

  def readStructEnd(self):
    pass

  def readFieldBegin(self):
    pass

  def readFieldEnd(self):
    pass

  def readMapBegin(self):
    pass

  def readMapEnd(self):
    pass

  def readListBegin(self):
    pass

  def readListEnd(self):
    pass

  def readSetBegin(self):
    pass

  def readSetEnd(self):
    pass

  def readBool(self):
    pass

  def readByte(self):
    pass

  def readI16(self):
    pass

  def readI32(self):
    pass

  def readI64(self):
    pass

  def readDouble(self):
    pass

  def readString(self):
    pass

  def skip(self, ttype):
    if ttype == TType.STOP:
      return
    elif ttype == TType.BOOL:
      self.readBool()
    elif ttype == TType.BYTE:
      self.readByte()
    elif ttype == TType.I16:
      self.readI16()
    elif ttype == TType.I32:
      self.readI32()
    elif ttype == TType.I64:
      self.readI64()
    elif ttype == TType.DOUBLE:
      self.readDouble()
    elif ttype == TType.STRING:
      self.readString()
    elif ttype == TType.STRUCT:
      name = self.readStructBegin()
      while True:
        (name, ttype, id) = self.readFieldBegin()
        if ttype == TType.STOP:
          break
        self.skip(ttype)
        self.readFieldEnd()
      self.readStructEnd()
    elif ttype == TType.MAP:
      (ktype, vtype, size) = self.readMapBegin()
      for i in range(size):
        self.skip(ktype)
        self.skip(vtype)
      self.readMapEnd()
    elif ttype == TType.SET:
      (etype, size) = self.readSetBegin()
      for i in range(size):
        self.skip(etype)
      self.readSetEnd()
    elif ttype == TType.LIST:
      (etype, size) = self.readListBegin()
      for i in range(size):
        self.skip(etype)
      self.readListEnd()

  # tuple of: ( 'reader method' name, is_container bool, 'writer_method' name )
  _TTYPE_HANDLERS = (
       (None, None, False),  # 0 TType.STOP
       (None, None, False),  # 1 TType.VOID # TODO: handle void?
       ('readBool', 'writeBool', False),  # 2 TType.BOOL
       ('readByte',  'writeByte', False),  # 3 TType.BYTE and I08
       ('readDouble', 'writeDouble', False),  # 4 TType.DOUBLE
       (None, None, False),  # 5 undefined
       ('readI16', 'writeI16', False),  # 6 TType.I16
       (None, None, False),  # 7 undefined
       ('readI32', 'writeI32', False),  # 8 TType.I32
       (None, None, False),  # 9 undefined
       ('readI64', 'writeI64', False),  # 10 TType.I64
       ('readString', 'writeString', False),  # 11 TType.STRING and UTF7
       ('readContainerStruct', 'writeContainerStruct', True),  # 12 *.STRUCT
       ('readContainerMap', 'writeContainerMap', True),  # 13 TType.MAP
       ('readContainerSet', 'writeContainerSet', True),  # 14 TType.SET
       ('readContainerList', 'writeContainerList', True),  # 15 TType.LIST
       (None, None, False),  # 16 TType.UTF8 # TODO: handle utf8 types?
       (None, None, False)  # 17 TType.UTF16 # TODO: handle utf16 types?
      )

  def readFieldByTType(self, ttype, spec):
    try:
      (r_handler, w_handler, is_container) = self._TTYPE_HANDLERS[ttype]
    except IndexError:
      raise TProtocolException(type=TProtocolException.INVALID_DATA,
                               message='Invalid field type %d' % (ttype))
    if r_handler is None:
      raise TProtocolException(type=TProtocolException.INVALID_DATA,
                               message='Invalid field type %d' % (ttype))
    reader = getattr(self, r_handler)
    if not is_container:
      return reader()
    return reader(spec)

  def readContainerList(self, spec):
    results = []
    ttype, tspec = spec[0], spec[1]
    r_handler = self._TTYPE_HANDLERS[ttype][0]
    reader = getattr(self, r_handler)
    (list_type, list_len) = self.readListBegin()
    if tspec is None:
      # list values are simple types
      for idx in range(list_len):
        results.append(reader())
    else:
      # this is like an inlined readFieldByTType
      container_reader = self._TTYPE_HANDLERS[list_type][0]
      val_reader = getattr(self, container_reader)
      for idx in range(list_len):
        val = val_reader(tspec)
        results.append(val)
    self.readListEnd()
    return results

  def readContainerSet(self, spec):
    results = set()
    ttype, tspec = spec[0], spec[1]
    r_handler = self._TTYPE_HANDLERS[ttype][0]
    reader = getattr(self, r_handler)
    (set_type, set_len) = self.readSetBegin()
    if tspec is None:
      # set members are simple types
      for idx in range(set_len):
        results.add(reader())
    else:
      container_reader = self._TTYPE_HANDLERS[set_type][0]
      val_reader = getattr(self, container_reader)
      for idx in range(set_len):
        results.add(val_reader(tspec))
    self.readSetEnd()
    return results

  def readContainerStruct(self, spec):
    (obj_class, obj_spec) = spec
    obj = obj_class()
    obj.read(self)
    return obj

  def readContainerMap(self, spec):
    results = dict()
    key_ttype, key_spec = spec[0], spec[1]
    val_ttype, val_spec = spec[2], spec[3]
    (map_ktype, map_vtype, map_len) = self.readMapBegin()
    # TODO: compare types we just decoded with thrift_spec and
    # abort/skip if types disagree
    key_reader = getattr(self, self._TTYPE_HANDLERS[key_ttype][0])
    val_reader = getattr(self, self._TTYPE_HANDLERS[val_ttype][0])
    # list values are simple types
    for idx in range(map_len):
      if key_spec is None:
        k_val = key_reader()
      else:
        k_val = self.readFieldByTType(key_ttype, key_spec)
      if val_spec is None:
        v_val = val_reader()
      else:
        v_val = self.readFieldByTType(val_ttype, val_spec)
      # this raises a TypeError with unhashable keys types
      # i.e. this fails: d=dict(); d[[0,1]] = 2
      results[k_val] = v_val
    self.readMapEnd()
    return results

  def readStruct(self, obj, thrift_spec):
    self.readStructBegin()
    while True:
      (fname, ftype, fid) = self.readFieldBegin()
      if ftype == TType.STOP:
        break
      try:
        field = thrift_spec[fid]
      except IndexError:
        self.skip(ftype)
      else:
        if field is not None and ftype == field[1]:
          fname = field[2]
          fspec = field[3]
          val = self.readFieldByTType(ftype, fspec)
          setattr(obj, fname, val)
        else:
          self.skip(ftype)
      self.readFieldEnd()
    self.readStructEnd()

  def writeContainerStruct(self, val, spec):
    val.write(self)

  def writeContainerList(self, val, spec):
    self.writeListBegin(spec[0], len(val))
    r_handler, w_handler, is_container = self._TTYPE_HANDLERS[spec[0]]
    e_writer = getattr(self, w_handler)
    if not is_container:
      for elem in val:
        e_writer(elem)
    else:
      for elem in val:
        e_writer(elem, spec[1])
    self.writeListEnd()

  def writeContainerSet(self, val, spec):
    self.writeSetBegin(spec[0], len(val))
    r_handler, w_handler, is_container = self._TTYPE_HANDLERS[spec[0]]
    e_writer = getattr(self, w_handler)
    if not is_container:
      for elem in val:
        e_writer(elem)
    else:
      for elem in val:
        e_writer(elem, spec[1])
    self.writeSetEnd()

  def writeContainerMap(self, val, spec):
    k_type = spec[0]
    v_type = spec[2]
    ignore, ktype_name, k_is_container = self._TTYPE_HANDLERS[k_type]
    ignore, vtype_name, v_is_container = self._TTYPE_HANDLERS[v_type]
    k_writer = getattr(self, ktype_name)
    v_writer = getattr(self, vtype_name)
    self.writeMapBegin(k_type, v_type, len(val))
    for m_key, m_val in six.iteritems(val):
      if not k_is_container:
        k_writer(m_key)
      else:
        k_writer(m_key, spec[1])
      if not v_is_container:
        v_writer(m_val)
      else:
        v_writer(m_val, spec[3])
    self.writeMapEnd()

  def writeStruct(self, obj, thrift_spec):
    self.writeStructBegin(obj.__class__.__name__)
    for field in thrift_spec:
      if field is None:
        continue
      fname = field[2]
      val = getattr(obj, fname)
      if val is None:
        # skip writing out unset fields
        continue
      fid = field[0]
      ftype = field[1]
      fspec = field[3]
      # get the writer method for this value
      self.writeFieldBegin(fname, ftype, fid)
      self.writeFieldByTType(ftype, val, fspec)
      self.writeFieldEnd()
    self.writeFieldStop()
    self.writeStructEnd()

  def writeFieldByTType(self, ttype, val, spec):
    r_handler, w_handler, is_container = self._TTYPE_HANDLERS[ttype]
    writer = getattr(self, w_handler)
    if is_container:
      writer(val, spec)
    else:
      writer(val)

def checkIntegerLimits(i, bits):
    if bits == 8 and (i < -128 or i > 127):
        raise TProtocolException(TProtocolException.INVALID_DATA,
                                 "i8 requires -128 <= number <= 127")
    elif bits == 16 and (i < -32768 or i > 32767):
        raise TProtocolException(TProtocolException.INVALID_DATA,
                                 "i16 requires -32768 <= number <= 32767")
    elif bits == 32 and (i < -2147483648 or i > 2147483647):
        raise TProtocolException(TProtocolException.INVALID_DATA,
                                 "i32 requires -2147483648 <= number <= 2147483647")
    elif bits == 64 and (i < -9223372036854775808 or i > 9223372036854775807):
         raise TProtocolException(TProtocolException.INVALID_DATA,
                                  "i64 requires -9223372036854775808 <= number <= 9223372036854775807")

class TProtocolFactory:
  def getProtocol(self, trans):
    pass
