------------------------------------------------------------------
--
--  Author: Alexey Melnichuk <alexeymelnichuck@gmail.com>
--
--  Copyright (C) 2015-2016 Alexey Melnichuk <alexeymelnichuck@gmail.com>
--
--  This file is part of lua-rs232 library.
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
-- 
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
-- 
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
------------------------------------------------------------------

local rs232 = require "rs232.core"

local function class(base)
  local t = base and setmetatable({}, base) or {}
  t.__index = t
  t.__class = t
  t.__base  = base

  function t.new(...)
    local o = setmetatable({}, t)
    if o.__init then
      if t == ... then -- we call as Class:new()
        return o:__init(select(2, ...))
      else             -- we call as Class.new()
        return o:__init(...)
      end
    end
    return o
  end

  return t
end

local function select_by_prefix(pfx, p)
  local names  = {}
  local values = {}
  p = p or ''

  for k, v in pairs(rs232) do
    if type(k) == 'string' and type(v) == 'number' then
      if k:sub(1, #pfx) == pfx then
        local name = p .. k:sub(#pfx+1)
        assert(not names[v])
        names[v]     = name
        values[name] = v
      end
    end
  end

  return values, names
end

local ERROR, ERROR_NAMES = select_by_prefix'RS232_ERR_'

local BAUD_NAMES, BAUD do
local tmp = select_by_prefix'RS232_BAUD_'
BAUD_NAMES, BAUD = {}, {}

for k, v in pairs(tmp) do
  BAUD_NAMES[ '_'    .. k ] = v
  BAUD_NAMES['BAUD_' .. k ] = v
  BAUD[v] =  'BAUD_' .. k
end

end

local DATA_NAMES, DATA do

local tmp = select_by_prefix'RS232_DATA_'
DATA_NAMES, DATA = {}, {}

for k, v in pairs(tmp) do
  DATA_NAMES[ '_' .. k    ] = v
  DATA_NAMES['DATA_' .. k ] = v
  DATA[v] =  'DATA_' .. k
end

end

local DTR_NAMES, DTR = select_by_prefix'RS232_DTR_'

local RTS_NAMES, RTS = select_by_prefix'RS232_RTS_'

local FLOW_NAMES, FLOW = select_by_prefix'RS232_FLOW_'

local PARITY_NAMES, PARITY = select_by_prefix'RS232_PARITY_'

local STOP_NAMES, STOP do

local tmp = select_by_prefix'RS232_STOP_'
STOP_NAMES, STOP = {}, {}

for k, v in pairs(tmp) do
  STOP_NAMES[ '_' .. k  ] = v
  STOP_NAMES['STOP_' .. k ] = v
  STOP[v] =  'STOP_' .. k
end

end

local RS232Error = class() do

function RS232Error:__init(no, ext)
  self._no   = no
  self._name = assert(ERROR_NAMES[no])
  self._ext  = ext
  return self
end

function RS232Error:cat()  return 'RS232'     end

function RS232Error:no()   return self._no    end

function RS232Error:name() return self._name end

function RS232Error:msg()  return rs232.error_tostring(self._no) end

function RS232Error:ext()  return self._ext   end

function RS232Error:__eq(rhs)
  return self._no == rhs._no
end

function RS232Error:__tostring()
  local err = string.format("[%s][%s] %s (%d)",
    self:cat(), self:name(), self:msg(), self:no()
  )
  if self:ext() then
    err = string.format("%s - %s", err, self:ext())
  end
  return err
end

end

local ERRORS = {
  [rs232.RS232_ERR_TIMEOUT] = RS232Error.new(rs232.RS232_ERR_TIMEOUT);
}

local function Error(e)
  return ERRORS[e] or RS232Error.new(e)
end

local function F(e, ...)
  if e ~= rs232.RS232_ERR_NOERROR then
    return nil, Error(e)
  end

  return true, ...
end

local Port = class() do

function Port:__init(name, opt)
  self._name = assert(name)
  self._opt = {}
  for k,v in pairs(opt or {})do
    self._opt[k] = v
  end

  return self
end

function Port:open()
  local ok, ret = F(rs232.open(self._name))
  if not ok then return nil, ret end
  self._p = ret

  ok, ret = self:set(self._opt)
  if not ok then
    self._p:close()
	self._p = nil
    return nil, err
  end

  return self, tostring(self._p)
end

function Port:close(...)
  if not self._p then return nil, "Already closed"  end

  local p = self._p
  self._p = nil

  return p:close()
end

function Port:set(port_opt)
  local p = self._p

  local ok, err

  if port_opt.baud ~= nil then
    ok, err = self:set_baud_rate(port_opt.baud)
    if not ok then return nil, err end
  end

  if port_opt.baud_rate ~= nil then
    ok, err = self:set_baud_rate(port_opt.baud_rate)
    if not ok then return nil, err end
  end

  if port_opt.data_bits ~= nil then
    ok, err = self:set_data_bits(port_opt.data_bits)
    if not ok then return nil, err end
  end

  if port_opt.parity ~= nil then
    ok, err = self:set_parity(port_opt.parity)
    if not ok then return nil, err end
  end

  if port_opt.stop_bits ~= nil then
    ok, err = self:set_stop_bits(port_opt.stop_bits)
    if not ok then return nil, err end
  end

  if port_opt.flow_control ~= nil then
    ok, err = self:set_flow_control(port_opt.flow_control)
    if not ok then return nil, err end
  end

  if port_opt.rts ~= nil then
    ok, err = self:set_rts(port_opt.rts)
    if not ok then return nil, err end
  end

  if port_opt.dtr ~= nil then
    ok, err = self:set_dtr(port_opt.dtr)
    if not ok then return nil, err end
  end

  return self
end

function Port:read(...)
  local e, data, len = self._p:read(...)

  if e ~= rs232.RS232_ERR_NOERROR then
    if e == rs232.RS232_ERR_TIMEOUT then
      return data or ''
    end
    return data, Error(e)
  end

  return data
end

function Port:write(...)
  local ok, len = F(self._p:write(...))
  if not ok then return nil, len end
  return len
end

function Port:flush(...)
  local ok, err = F(self._p:flush(...))
  if not ok then return nil, err end
  return self
end

function Port:in_queue_clear(...)
  local ok, err = F(self._p:in_queue_clear(...))
  if not ok then return nil, err end
  return self
end

function Port:in_queue(...)
  local ok, len = F(self._p:in_queue(...))
  if not ok then return nil, len end
  return len
end

function Port:device(...)
  local ok, name = F(self._p:in_queue(...))
  if not ok then return nil, name end
  return name
end

function Port:fd(...)
  local ok, fd = F(self._p:fd(...))
  if not ok then return nil, fd end
  return fd
end

local function check_val(value, NAMES, VALUES)
  local v
  if type(value) == 'string' then
    v = assert( NAMES[value:upper()], 'Unsupported value:' .. tostring(value) )
  else
    v = value
    assert( VALUES[value], 'Unsupported value:' .. tostring(value) )
  end
  return v
end

function Port:set_baud_rate(value)
  local v = check_val(value, BAUD_NAMES, BAUD)

  local ok, err = F(self._p:set_baud_rate(v))
  if not ok then return nil, err end

  return self
end

function Port:baud_rate()
  local val = self._p:baud_rate()
  return val, BAUD[val]
end

function Port:set_data_bits(value)
  local v = check_val(value, DATA_NAMES, DATA)

  local ok, err = F(self._p:set_data_bits(v))
  if not ok then return nil, err end

  return self
end

function Port:data_bits()
  local val = self._p:data_bits()
  return val, DATA[val]
end

function Port:set_stop_bits(value)
  local v = check_val(value, STOP_NAMES, STOP)

  local ok, err = F(self._p:set_stop_bits(v))
  if not ok then return nil, err end

  return self
end

function Port:stop_bits()
  local val = self._p:stop_bits()
  return val, STOP[val]
end

function Port:set_parity(value)
  local v = check_val(value, PARITY_NAMES, PARITY)

  local ok, err = F(self._p:set_parity(v))
  if not ok then return nil, err end

  return self
end

function Port:parity()
  local val = self._p:parity()
  return val, PARITY[val]
end

function Port:set_flow_control(value)
  local v = check_val(value, FLOW_NAMES, FLOW)

  local ok, err = F(self._p:set_flow_control(v))
  if not ok then return nil, err end

  return self
end

function Port:flow_control()
  local val = self._p:flow_control()
  return val, FLOW[val]
end

function Port:set_dtr(value)
  local v = check_val(value, DTR_NAMES, DTR)

  local ok, err = F(self._p:set_dtr(v))
  if not ok then return nil, err end

  return self
end

function Port:dtr()
  local val = self._p:dtr()
  return val, DTR[val]
end

function Port:set_rts(value)
  local v = check_val(value, RTS_NAMES, RTS)

  local ok, err = F(self._p:set_rts(v))
  if not ok then return nil, err end

  return self
end

function Port:rts()
  local val = self._p:rts()
  return val, RTS[val]
end

function Port:__tostring()
  return "RS232 Port " .. tostring(self._p)
end

end

return setmetatable({
  _NAME      = "rs232";
  _VERSION   = "0.1.1-dev";
  _COPYRIGHT = "Copyright (C) 2015-2016 Alexey Melnichuk";
  _LICENSE   = "MIT";

  port  = Port.new;
  error = RS232Error.new;
  ERROR = ERROR;
},{__index = rs232})
