---- #########################################################################
---- #                                                                       #
---- # Copyright (C) OpenTX                                                  #
-----#                                                                       #
---- # License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html               #
---- #                                                                       #
---- # This program is free software; you can redistribute it and/or modify  #
---- # it under the terms of the GNU General Public License version 2 as     #
---- # published by the Free Software Foundation.                            #
---- #                                                                       #
---- # This program is distributed in the hope that it will be useful        #
---- # but WITHOUT ANY WARRANTY; without even the implied warranty of        #
---- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         #
---- # GNU General Public License for more details.                          #
---- #                                                                       #
---- #########################################################################


--###############################################################################
-- Multi buffer for Config description
-- To start operation:
--   Write 0xFF at address 4 will request the buffer to be cleared
--   Write "Conf" at address 0..3
-- Read
--   Read at address 12 gives the current config page
--   Read at address 13..172 gives the current data of the page = 8 lines * 20 caracters
-- Write
--   Write at address 5..11 the command
--   Write 0x01 at address 4 will send the command to the module
-- !! Before exiting the script must write 0 at address 0 for normal operation !!
--###############################################################################

local Version = "v0.2"
local Focus = -1
local Page = 0
local Edit = -1
local Edit_pos = 1
local Menu = { {text = "", field_type = 0, field_len = 0, field_value = {}, field_text = ""},
               {text = "", field_type = 0, field_len = 0, field_value = {}, field_text = ""},
               {text = "", field_type = 0, field_len = 0, field_value = {}, field_text = ""},
               {text = "", field_type = 0, field_len = 0, field_value = {}, field_text = ""},
               {text = "", field_type = 0, field_len = 0, field_value = {}, field_text = ""},
               {text = "", field_type = 0, field_len = 0, field_value = {}, field_text = ""},
               {text = "", field_type = 0, field_len = 0, field_value = {}, field_text = ""} }
local Menu_value = {}
local Blink = 0
local ModuleNumber = 0
local ModuleType = ""
local Module = {}
local InitialProtocol = 0
local InitialSubProtocol = 0

function bitand(a, b)
    local result = 0
    local bitval = 1
    while a > 0 and b > 0 do
      if a % 2 == 1 and b % 2 == 1 then -- test the rightmost bits
          result = result + bitval      -- set the current bit
      end
      bitval = bitval * 2 -- shift left
      a = math.floor(a/2) -- shift right
      b = math.floor(b/2)
    end
    return result
end

local function Config_Send(page, line, value)
  local i
  i = (page*16) + line
  multiBuffer( 5, i )
  for i = 1 , 6 , 1 do
    multiBuffer( 5+i, value[i] )
  end
  multiBuffer( 4, 1 )
end

local function Config_Release()
  --Set the protocol back to what it was
  Module.protocol = InitialProtocol
  Module.subProtocol = InitialSubProtocol
  model.setModule(ModuleNumber, Module)

  --Stop requesting updates
  local i
  for i = 3 , 0 , -1 do
    multiBuffer( i, 0 )
  end
end

local function Config_Page( )
  Config_Send(Page, 0, { 0, 0, 0, 0, 0, 0 })
end

local function Config_Draw_Edit( event )
  local i
  local text

  if Menu[Focus].field_type == 0xD0 then
  -- Editable Hex value
    if Edit == -1 then
    -- Init
      Edit = 0
      Edit_pos = 1
      Blink = 0
      for i = 1, Menu[Focus].field_len, 1 do
        Menu_value[i] = Menu[Focus].field_value[i]
      end
    end
    if Edit == 0 then
    -- Not editing value
      if event == EVT_VIRTUAL_ENTER then
        if Edit_pos > Menu[Focus].field_len then
        -- Save or Cancel
          Edit = -1
          if Edit_pos == Menu[Focus].field_len + 1 then
          -- Save
            Config_Send(Page, Focus, Menu_value)
          end
          return
        else
        -- Switch to edit mode
          Edit = 1
        end
      elseif event == EVT_VIRTUAL_PREV and Edit_pos > 1 then
      -- Move cursor
        Edit_pos = Edit_pos - 1
      elseif event == EVT_VIRTUAL_NEXT and Edit_pos < Menu[Focus].field_len + 2 then
      -- Move cursor
        Edit_pos = Edit_pos + 1
      end
    else
    -- Editing value
      if event == EVT_VIRTUAL_ENTER then
      -- End edit
        Edit = 0
      elseif event == EVT_VIRTUAL_PREV then
      -- Change value
        if Menu_value[Edit_pos] > 0 then
          Menu_value[Edit_pos] = Menu_value[Edit_pos] - 1
        end
      elseif event == EVT_VIRTUAL_NEXT then
      -- Change value
        if Menu_value[Edit_pos] < 255 then
          Menu_value[Edit_pos] = Menu_value[Edit_pos] + 1
        end
      end
      --Blink
      Blink = Blink + 1
      if Blink > 30 then
        Blink = 0
      end
    end
    --Display
    if LCD_W == 480 then
      lcd.drawRectangle(160-1, 100-1, 160+2, 55+2, TEXT_COLOR)
      lcd.drawFilledRectangle(160, 100, 160, 55, TEXT_BGCOLOR)
    else
      lcd.clear()
    end
    for i = 1, Menu[Focus].field_len, 1 do
      if i==Edit_pos and (Edit ~= 1 or Blink > 15) then
        attrib = INVERS
      else
        attrib = 0
      end
      if LCD_W == 480 then
        lcd.drawText(170+12*2*(i-1), 110, string.format('%02X', Menu_value[i]), attrib)
      else
        lcd.drawText(17+6*2*(i-1), 10, string.format('%02X', Menu_value[i]), attrib + SMLSIZE)
      end
    end
    if Edit_pos == Menu[Focus].field_len + 1 then
      attrib = INVERS
    else
      attrib = 0
    end
    if LCD_W == 480 then
      lcd.drawText(170, 130, "Save", attrib)
    else
      lcd.drawText(17, 30, "Save", attrib + SMLSIZE)
    end
    if Edit_pos == Menu[Focus].field_len + 2 then
      attrib = INVERS
    else
      attrib = 0
    end
    if LCD_W == 480 then
      lcd.drawText(260, 130, "Cancel", attrib)
    else
      lcd.drawText(77, 30, "Cancel", attrib + SMLSIZE)
    end

  elseif Menu[Focus].field_type == 0x90 then
  -- Action text
    if Edit == -1 then
    -- Init
      Edit = 0
      Edit_pos = 2
    end
    if event == EVT_VIRTUAL_ENTER then
      -- Exit
      Edit = -1
      if Edit_pos == 1 then
      -- Yes
        Config_Send(Page, Focus, { 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA } )
      end
      return
    elseif event == EVT_VIRTUAL_PREV and Edit_pos > 1 then
    -- Switch to Yes
      Edit_pos = Edit_pos - 1
    elseif event == EVT_VIRTUAL_NEXT and Edit_pos < 2 then
    -- Switch to No
      Edit_pos = Edit_pos + 1
    end
    -- Display
    if LCD_W == 480 then
      lcd.drawRectangle(160-1, 100-1, 160+2, 55+2, TEXT_COLOR)
      lcd.drawFilledRectangle(160, 100, 160, 55, TEXT_BGCOLOR)
    else
      lcd.clear()
    end
    if LCD_W == 480 then
      lcd.drawText(170, 110, Menu[Focus].field_text .. "?")
    else
      lcd.drawText(17, 10, Menu[Focus].field_text .. "?", SMLSIZE)
    end
    if Edit_pos == 1 then
      attrib = INVERS
    else
      attrib = 0
    end
    if LCD_W == 480 then
      lcd.drawText(170, 130, "Yes", attrib)
    else
      lcd.drawText(17, 30, "Yes", attrib + SMLSIZE)
    end
    if Edit_pos == 2 then
      attrib = INVERS
    else
      attrib = 0
    end
    if LCD_W == 480 then
      lcd.drawText(260, 130, "No", attrib)
    else
      lcd.drawText(77, 30, "No", attrib)
    end
  end
end

local function Config_Next_Prev( event )
-- Next Prev on main menu
  local line
  if event == EVT_VIRTUAL_PREV then
    for line = Focus - 1, 1, -1 do
      if Menu[line].field_type >= 0x80 and Menu[line].field_type ~= 0xA0 and Menu[line].field_type ~= 0xC0 then
        Focus = line
        break
      end
    end
  elseif event == EVT_VIRTUAL_NEXT then
    for line = Focus + 1, 7, 1 do
      if Menu[line].field_type >= 0x80 and Menu[line].field_type ~= 0xA0 and Menu[line].field_type ~= 0xC0 then
        Focus = line
        break
      end
    end
  end
end

local function Config_Draw_Menu()
-- Main menu
  local i
  local value
  local line
  local length
  local text
  
  lcd.clear()

  if LCD_W == 480 then
    --Draw title
    lcd.drawFilledRectangle(0, 0, LCD_W, 30, TITLE_BGCOLOR)
    lcd.drawText(1, 5, "Multi Config " .. Version, MENU_TITLE_COLOR)
    if multiBuffer(13) == 0x00 then
      lcd.drawText(10,50,"No Config telemetry...", BLINK)
    end
  else
    --Draw on LCD_W=128
    lcd.drawText(1, 0, "Multi Config " .. Version, SMLSIZE)
    if multiBuffer(13) == 0x00 then
      lcd.drawText(2,17,"No Config telemetry...",SMLSIZE)
    end
  end

  if multiBuffer(13) ~= 0x00 then
    if LCD_W == 480 then
      --Draw firmware version and channels order
      local ch_order = multiBuffer(17)
      local channel_names = {}
      channel_names[bitand(ch_order,3)+1] = "A"
      ch_order = math.floor(ch_order/4)
      channel_names[bitand(ch_order,3)+1] = "E"
      ch_order = math.floor(ch_order/4)
      channel_names[bitand(ch_order,3)+1] = "T"
      ch_order = math.floor(ch_order/4)
      channel_names[bitand(ch_order,3)+1] = "R"
      lcd.drawText(150, 5, ModuleType.." v" .. multiBuffer(13) .. "." .. multiBuffer(14) .. "." .. multiBuffer(15) .. "." .. multiBuffer(16) .. " " .. channel_names[1] .. channel_names[2] .. channel_names[3] .. channel_names[4], MENU_TITLE_COLOR)
    else
      lcd.drawText(76, 0, "/Fw" .. multiBuffer(13) .. "." .. multiBuffer(14) .. "." .. multiBuffer(15) .. "." .. multiBuffer(16),SMLSIZE) -- .. " " .. channel_names[1] .. channel_names[2] .. channel_names[3] .. channel_names[4])
    end

    --Draw Menu
    for line = 1, 7, 1 do
      --Clear line info
      Menu[line].text = ""
      Menu[line].field_type = 0
      Menu[line].field_len = 0
      for i = 1, 7, 1 do
        Menu[line].field_value[i] = 0
      end
      Menu[line].field_text = ""
      length = 0
      --Read line from buffer
      for i = 0, 20-1, 1 do
        value=multiBuffer( line*20+13+i )
        if value > 0x80 and Menu[line].field_type == 0 then
        -- Read field type
          Menu[line].field_type = bitand(value, 0xF0)
          Menu[line].field_len = bitand(value, 0x0F)
          length = Menu[line].field_len
          if Menu[line].field_type ~= 0xA0 and Menu[line].field_type ~= 0xC0 and Focus == -1 then
          -- First actionnable field if nothing was selected
            Focus = line;
          end
        else
          if Menu[line].field_type == 0 then
          -- Text
          if value == 0 then
            break   -- end of line
          end
            Menu[line].text = Menu[line].text .. string.char(value)
          else
          -- Menu specific fields
            length = length - 1
            if Menu[line].field_type == 0x80 or Menu[line].field_type == 0x90 then
              Menu[line].field_text = Menu[line].field_text .. string.char(value)
            else
              Menu[line].field_value[Menu[line].field_len-length] = value
            end
            if length == 0 then
            -- End of fields
              break
            end
          end
        end
      end
      -- Display menu text
      if Menu[line].text ~= "" then
        if Menu[line].field_type == 0xA0 or Menu[line].field_type == 0xB0 or Menu[line].field_type == 0xC0 or Menu[line].field_type == 0xD0 then
          Menu[line].text = Menu[line].text .. ":"
        end
        if LCD_W == 480 then
          lcd.drawText(10,32+20*line,Menu[line].text )
        else
          lcd.drawText(2,1+8*line,Menu[line].text,SMLSIZE)
        end
      end
      -- Display specific fields
      if line == Focus then
        attrib = INVERS
      else
        attrib = 0
      end
      if Menu[line].field_type == 0x80 or Menu[line].field_type == 0x90 then
      -- Text
        if LCD_W == 480 then
          lcd.drawText(10+9*#Menu[line].text, 32+20*line, Menu[line].field_text, attrib)
        else
          lcd.drawText(2+5*#Menu[line].text, 1+8*line, Menu[line].field_text, SMLSIZE + attrib)
        end
      elseif Menu[line].field_type == 0xA0 or Menu[line].field_type == 0xB0 then
      -- Decimal value
        value = 0
        for i = 1, Menu[line].field_len, 1 do
          value = value*256 + value
        end
        if LCD_W == 480 then
          lcd.drawText(10+9*#Menu[line].text, 32+20*line, value, attrib)
        else
          lcd.drawText(2+5*#Menu[line].text, 1+8*line, value, SMLSIZE + attrib)
        end
      elseif Menu[line].field_type == 0xC0 or Menu[line].field_type == 0xD0 then
      -- Hex value
        text=""
        for i = 1, Menu[line].field_len, 1 do
          text = text .. string.format('%02X ', Menu[line].field_value[i])
        end
        if LCD_W == 480 then
          lcd.drawText(10+9*#Menu[line].text, 32+20*line, text, attrib)
        else
          lcd.drawText(2+5*#Menu[line].text, 1+8*line, text, SMLSIZE + attrib)
        end
      end
    end
  end
end

-- Init
local function Config_Init()
  --Find Multi module
  Module_int = model.getModule(0)
  Module_ext = model.getModule(1)
  if Module_int["Type"] ~= 6 and Module_ext["Type"] ~= 6 then
    error("No Multi module detected...")
    return 2
  end
  if Module_int["Type"] == 6 and Module_ext["Type"] == 6 then
    error("Two Multi modules detected, turn on only the one to be configured.")
    return 2
  end
  if Module_int["Type"] == 6 then
    ModuleNumber = 0
    ModuleType = "Internal"
  else
    ModuleNumber = 1
    ModuleType = "External"
  end
  --Get Module settings and set it to config protocol
  Module = model.getModule(ModuleNumber)
  InitialProtocol = Module.protocol
  InitialSubProtocol = Module.subProtocol
  Module.protocol = 86
  Module.subProtocol = 0
  model.setModule(ModuleNumber, Module)
  --pause while waiting for the module to switch to config
  for i = 0, 10, 1 do end

  --Set protocol to talk to
  multiBuffer( 0, string.byte('C') )
  --test if value has been written
  if multiBuffer( 0 ) ~=  string.byte('C') then
    error("Not enough memory!")
    return 2
  end

  --Request init of the buffer
  multiBuffer( 4, 0xFF )
  multiBuffer(13, 0x00 )

  --Continue buffer init
  multiBuffer( 1, string.byte('o') )
  multiBuffer( 2, string.byte('n') )
  multiBuffer( 3, string.byte('f') )

  -- Test set
  -- multiBuffer( 12, 0 )
  -- multiBuffer( 13, 1 )
  -- multiBuffer( 14, 3 )
  -- multiBuffer( 15, 2 )
  -- multiBuffer( 16, 62 )
  -- multiBuffer( 17, 0 + 1*4 + 2*16 + 3*64)

  -- multiBuffer( 33, string.byte('G') )
  -- multiBuffer( 34, string.byte('l') )
  -- multiBuffer( 35, string.byte('o') )
  -- multiBuffer( 36, string.byte('b') )
  -- multiBuffer( 37, string.byte('a') )
  -- multiBuffer( 38, string.byte('l') )
  -- multiBuffer( 39, string.byte(' ') )
  -- multiBuffer( 40, string.byte('I') )
  -- multiBuffer( 41, string.byte('D') )
  -- multiBuffer( 42, 0xD0 + 4 )
  -- multiBuffer( 43, 0x12 )
  -- multiBuffer( 44, 0x34 )
  -- multiBuffer( 45, 0x56 )
  -- multiBuffer( 46, 0x78 )
  -- multiBuffer( 47, 0x9A )
  -- multiBuffer( 48, 0xBC )

  -- multiBuffer( 53, 0x90 + 9 )
  -- multiBuffer( 54, string.byte('R') )
  -- multiBuffer( 55, string.byte('e') )
  -- multiBuffer( 56, string.byte('s') )
  -- multiBuffer( 57, string.byte('e') )
  -- multiBuffer( 58, string.byte('t') )
  -- multiBuffer( 59, string.byte(' ') )
  -- multiBuffer( 60, string.byte('G') )
  -- multiBuffer( 61, string.byte('I') )
  -- multiBuffer( 62, string.byte('D') )
  -- multiBuffer( 63, 0x00 )
end

-- Main
local function Config_Run(event)
  if event == nil then
    error("Cannot be run as a model script!")
    return 2
  elseif event == EVT_VIRTUAL_EXIT then
    Config_Release()
    return 2
  else
    Config_Draw_Menu()
    if ( event == EVT_VIRTUAL_PREV_PAGE or event == EVT_VIRTUAL_NEXT_PAGE ) and Edit < 1 then
    -- Not editing, ok to change page
      if event == EVT_VIRTUAL_PREV_PAGE then
        killEvents(event)
        if Page > 0 then
          --Page = Page - 1
          --Config_Page()
        end
      else
        --Page = Page + 1
        --Config_Page()
      end
    end
    if Focus > 0 then
    -- At least one line has an action
      if Edit >= 0 then
      -- Currently editing
        Config_Draw_Edit( event )
      elseif event == EVT_VIRTUAL_ENTER then
      -- Switch to edit
        Config_Draw_Edit( 0 )
      elseif event == EVT_VIRTUAL_PREV or event == EVT_VIRTUAL_NEXT then
      -- Main menu selection
        Config_Next_Prev( event )
      end
    end
    return 0
  end
end

return { init=Config_Init, run=Config_Run }
