--
-- 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.
--

require 'TProtocol'
local libluabpack = require 'libluabpack'
local libluabitwise = require 'libluabitwise'

TBinaryProtocol = __TObject.new(TProtocolBase, {
  __type = 'TBinaryProtocol',
  VERSION_MASK = -65536, -- 0xffff0000
  VERSION_1    = -2147418112, -- 0x80010000
  TYPE_MASK    = 0x000000ff,
  strictRead   = false,
  strictWrite  = true
})

function TBinaryProtocol:writeMessageBegin(name, ttype, seqid)
  if self.strictWrite then
    self:writeI32(libluabitwise.bor(TBinaryProtocol.VERSION_1, ttype))
    self:writeString(name)
    self:writeI32(seqid)
  else
    self:writeString(name)
    self:writeByte(ttype)
    self:writeI32(seqid)
  end
end

function TBinaryProtocol:writeMessageEnd()
end

function TBinaryProtocol:writeStructBegin(name)
end

function TBinaryProtocol:writeStructEnd()
end

function TBinaryProtocol:writeFieldBegin(name, ttype, id)
  self:writeByte(ttype)
  self:writeI16(id)
end

function TBinaryProtocol:writeFieldEnd()
end

function TBinaryProtocol:writeFieldStop()
  self:writeByte(TType.STOP);
end

function TBinaryProtocol:writeMapBegin(ktype, vtype, size)
  self:writeByte(ktype)
  self:writeByte(vtype)
  self:writeI32(size)
end

function TBinaryProtocol:writeMapEnd()
end

function TBinaryProtocol:writeListBegin(etype, size)
  self:writeByte(etype)
  self:writeI32(size)
end

function TBinaryProtocol:writeListEnd()
end

function TBinaryProtocol:writeSetBegin(etype, size)
  self:writeByte(etype)
  self:writeI32(size)
end

function TBinaryProtocol:writeSetEnd()
end

function TBinaryProtocol:writeBool(bool)
  if bool then
    self:writeByte(1)
  else
    self:writeByte(0)
  end
end

function TBinaryProtocol:writeByte(byte)
  local buff = libluabpack.bpack('c', byte)
  self.trans:write(buff)
end

function TBinaryProtocol:writeI16(i16)
  local buff = libluabpack.bpack('s', i16)
  self.trans:write(buff)
end

function TBinaryProtocol:writeI32(i32)
  local buff = libluabpack.bpack('i', i32)
  self.trans:write(buff)
end

function TBinaryProtocol:writeUI32(i32)
  local buff = libluabpack.bpack('I', i32)
  self.trans:write(buff)
end

function TBinaryProtocol:writeI64(i64)
  local buff = libluabpack.bpack('l', i64)
  self.trans:write(buff)
end

function TBinaryProtocol:writeDouble(dub)
  local buff = libluabpack.bpack('d', dub)
  self.trans:write(buff)
end

function TBinaryProtocol:writeString(str)
  -- Should be utf-8
  self:writeI32(string.len(str))
  self.trans:write(str)
end

function TBinaryProtocol:writeUuid(uuid)
  self:writeUI32(uuid.two)
  self:writeUI32(uuid.three)
  self:writeUI32(uuid.zero)
  self:writeUI32(uuid.one)
end

function TBinaryProtocol:readMessageBegin()
  local sz, ttype, name, seqid = self:readI32()
  if sz < 0 then
    local version = libluabitwise.band(sz, TBinaryProtocol.VERSION_MASK)
    if version ~= TBinaryProtocol.VERSION_1 then
      terror(TProtocolException:new{
        message = 'Bad version in readMessageBegin: ' .. sz
      })
    end
    ttype = libluabitwise.band(sz, TBinaryProtocol.TYPE_MASK)
    name = self:readString()
    seqid = self:readI32()
  else
    if self.strictRead then
      terror(TProtocolException:new{message = 'No protocol version header'})
    end
    name = self.trans:readAll(sz)
    ttype = self:readByte()
    seqid = self:readI32()
  end
  return name, ttype, seqid
end

function TBinaryProtocol:readMessageEnd()
end

function TBinaryProtocol:readStructBegin()
  return nil
end

function TBinaryProtocol:readStructEnd()
end

function TBinaryProtocol:readFieldBegin()
  local ttype = self:readByte()
  if ttype == TType.STOP then
    return nil, ttype, 0
  end
  local id = self:readI16()
  return nil, ttype, id
end

function TBinaryProtocol:readFieldEnd()
end

function TBinaryProtocol:readMapBegin()
  local ktype = self:readByte()
  local vtype = self:readByte()
  local size = self:readI32()
  return ktype, vtype, size
end

function TBinaryProtocol:readMapEnd()
end

function TBinaryProtocol:readListBegin()
  local etype = self:readByte()
  local size = self:readI32()
  return etype, size
end

function TBinaryProtocol:readListEnd()
end

function TBinaryProtocol:readSetBegin()
  local etype = self:readByte()
  local size = self:readI32()
  return etype, size
end

function TBinaryProtocol:readSetEnd()
end

function TBinaryProtocol:readBool()
  local byte = self:readByte()
  if byte == 0 then
    return false
  end
  return true
end

function TBinaryProtocol:readByte()
  local buff = self.trans:readAll(1)
  local val = libluabpack.bunpack('c', buff)
  return val
end

function TBinaryProtocol:readI16()
  local buff = self.trans:readAll(2)
  local val = libluabpack.bunpack('s', buff)
  return val
end

function TBinaryProtocol:readI32()
  local buff = self.trans:readAll(4)
  local val = libluabpack.bunpack('i', buff)
  return val
end

function TBinaryProtocol:readUI32()
  local buff = self.trans:readAll(4)
  local val = libluabpack.bunpack('I', buff)
  return val
end

function TBinaryProtocol:readI64()
  local buff = self.trans:readAll(8)
  local val = libluabpack.bunpack('l', buff)
  return val
end

function TBinaryProtocol:readDouble()
  local buff = self.trans:readAll(8)
  local val = libluabpack.bunpack('d', buff)
  return val
end

function TBinaryProtocol:readString()
  local len = self:readI32()
  local str = self.trans:readAll(len)
  return str
end

function TBinaryProtocol:readUuid()
  local a = self:readUI32()
  local b = self:readUI32()
  local c = self:readUI32()
  local d = self:readUI32()
  return TUUID:new {
    zero = c,
    one = d,
    two = a,
    three = b
  }
end

TBinaryProtocolFactory = TProtocolFactory:new{
  __type = 'TBinaryProtocolFactory',
  strictRead = false
}

function TBinaryProtocolFactory:getProtocol(trans)
  -- TODO Enforce that this must be a transport class (ie not a bool)
  if not trans then
    terror(TProtocolException:new{
      message = 'Must supply a transport to ' .. ttype(self)
    })
  end
  return TBinaryProtocol:new{
    trans = trans,
    strictRead = self.strictRead,
    strictWrite = true
  }
end
