--
-- 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 'Thrift'
require 'TFramedTransport'
require 'TBinaryProtocol'

-- TServer
TServer = __TObject:new{
  __type = 'TServer'
}

-- 2 possible constructors
--   1. {processor, serverTransport}
--   2. {processor, serverTransport, transportFactory, protocolFactory}
function TServer:new(args)
  if ttype(args) ~= 'table' then
    error('TServer must be initialized with a table')
  end
  if args.processor == nil then
    terror('You must provide ' .. ttype(self) .. ' with a processor')
  end
  if args.serverTransport == nil then
    terror('You must provide ' .. ttype(self) .. ' with a serverTransport')
  end

  -- Create the object
  local obj = __TObject.new(self, args)

  if obj.transportFactory then
    obj.inputTransportFactory = obj.transportFactory
    obj.outputTransportFactory = obj.transportFactory
    obj.transportFactory = nil
  else
    obj.inputTransportFactory = TFramedTransportFactory:new{}
    obj.outputTransportFactory = obj.inputTransportFactory
  end

  if obj.protocolFactory then
    obj.inputProtocolFactory = obj.protocolFactory
    obj.outputProtocolFactory = obj.protocolFactory
    obj.protocolFactory = nil
  else
    obj.inputProtocolFactory = TBinaryProtocolFactory:new{}
    obj.outputProtocolFactory = obj.inputProtocolFactory
  end

  -- Set the __server variable in the handler so we can stop the server
  obj.processor.handler.__server = self

  return obj
end

function TServer:setServerEventHandler(handler)
  self.serverEventHandler = handler
end

function TServer:_clientBegin(content, iprot, oprot)
  if self.serverEventHandler and
    type(self.serverEventHandler.clientBegin) == 'function' then
    self.serverEventHandler:clientBegin(iprot, oprot)
  end
end

function TServer:_preServe()
  if self.serverEventHandler and
    type(self.serverEventHandler.preServe) == 'function' then
    self.serverEventHandler:preServe(self.serverTransport:getSocketInfo())
  end
end

function TServer:setExceptionHandler(exceptionHandler)
  self.exceptionHandler = exceptionHandler
end

function TServer:_handleException(err)
  if string.find(err, 'TTransportException') == nil then
    if self.exceptionHandler then
      self.exceptionHandler(err)
    else
      print(err)
    end
  end
end

function TServer:serve() end
function TServer:handle(client)
  local itrans, otrans =
    self.inputTransportFactory:getTransport(client),
    self.outputTransportFactory:getTransport(client)
  local iprot, oprot =
    self.inputProtocolFactory:getProtocol(itrans),
    self.outputProtocolFactory:getProtocol(otrans)

  self:_clientBegin(iprot, oprot)
  while true do
    local ret, err = pcall(self.processor.process, self.processor, iprot, oprot)
    if ret == false and err then
      if not string.find(err, "TTransportException") then
        self:_handleException(err)
      end
      break
    end
  end
  itrans:close()
  otrans:close()
end

function TServer:close()
  self.serverTransport:close()
end

-- TSimpleServer
--  Single threaded server that handles one transport (connection)
TSimpleServer = __TObject:new(TServer, {
  __type = 'TSimpleServer',
  __stop = false
})

function TSimpleServer:serve()
  self.serverTransport:listen()
  self:_preServe()
  while not self.__stop do
    client = self.serverTransport:accept()
    self:handle(client)
  end
  self:close()
end

function TSimpleServer:stop()
  self.__stop = true
end
