local _requireModuleCache = {}
local requireModule = function(moduleRelativePath)
  local m = _requireModuleCache[moduleRelativePath]
  if not m then
    m = require("ui/" .. moduleRelativePath)
    _requireModuleCache[moduleRelativePath] = m
  end
  return m
end
local bindDataMeta = class("core.ui.bindDataMeta")
local valueBase = class("core.ui.bindDataMeta.valueBase")

function valueBase:ctor(owner, key, meth)
  self._bindDataMeta = owner
  self._bindKey = key
  self._bindMeth = meth
  self._uiInst = self._bindDataMeta._uiInst
  self._modules = self._bindDataMeta._uiInst.modules
  self._uiBinding = self._bindDataMeta._uiBinding
end

local valueToggleModule = class("core.ui.bindDataMeta.valueToggleModule", valueBase)

function valueToggleModule:itemBind(oldK, newK, uiExp)
  local mds = self._modules[self._bindKey] or {}
  if mds[oldK] ~= nil then
    mds[oldK]:unBind()
  end
  if mds[newK] ~= nil then
    mds[newK]:toBind(uiExp)
  end
end

function valueToggleModule:itemAssetName(k)
  return self[k].assetName
end

local valueDynamicModule = class("core.ui.bindDataMeta.valueDynamicModule", valueBase)

function valueDynamicModule:setModuleConfig(cfg)
  self._uiBinding:LinkerSetSystemObject(self._bindKey, cfg)
end

function valueDynamicModule:itemBind(oldK, newCfg, uiExp)
  local newK = newCfg.moduleKey
  local newModuleName = newCfg.moduleName
  local m
  if newModuleName == nil then
    m = require("core/ui/base/emptyModule").new()
  else
    m = requireModule(newModuleName).new(G_UIModuleBase.ModuleType.Dynamic)
  end
  if m.created then
    m:created(newCfg, self._bindMeth, self._uiInst)
  end
  local mds = self._modules[self._bindKey] or {}
  mds[newK] = m
  if mds[oldK] ~= nil then
    mds[oldK]:unBind()
  end
  m:toBind(uiExp)
end

local valueArray = class("core.ui.bindDataMeta.valueArray", valueBase)

function valueArray:ctor(owner, key, meth)
  valueArray.super.ctor(self, owner, key, meth)
  self._filterMap = {}
end

function valueArray:getIdentify(id)
  if #self <= 0 then
    return ""
  end
  if id > #self then
    return nil
  end
  local i = self[id].identify
  if i then
    return i
  else
    return nil
  end
end

function valueArray:getIdentifyByMapId(id)
  if #self <= 0 then
    return ""
  end
  if id then
    local realId = id
    local newId = self._filterMap[realId]
    if newId then
      realId = newId
    end
    return self[realId].identify
  else
    return nil
  end
end

function valueArray:getItemCls(id)
  local mds = self._modules[self._bindKey] or {}
  if id then
    return mds[id]
  else
    return mds
  end
end

function valueArray:getItemClsByMapId(id)
  local mds = self._modules[self._bindKey] or {}
  if id then
    local realId = id
    local newId = self._filterMap[realId]
    if newId then
      realId = newId
    end
    return mds[realId]
  else
    return mds
  end
end

function valueArray:getCellSize(id)
  if #self <= 0 then
    return 0
  end
  local v = self[id]
  if v == nil then
    return 0
  end
  if v.size ~= nil then
    if type(v.size) == "number" then
      return v.size
    else
      return 0
    end
  else
    return 0
  end
end

function valueArray:getLength()
  return #self
end

function valueArray:getMapLength()
  return #self._filterMap
end

function valueArray:getValueByMapId(id)
  return self[self._filterMap[id]]
end

function valueArray:insert(index, v)
  if type(index) ~= "number" then
    v = index
    index = #self + 1
  end
  local mds = self._modules[self._bindKey] or {}
  local moduleName = self.moduleName
  if v.moduleName then
    moduleName = v.moduleName
  end
  local m
  if moduleName == nil then
    m = require("core/ui/base/emptyModule").new()
  else
    m = requireModule(moduleName).new(G_UIModuleBase.ModuleType.TableItem)
  end
  if m.created then
    m:created(v, self._bindMeth, self._uiInst)
  end
  table.insert(self, index, v)
  local currentMd = mds[index]
  mds[index] = m
  for i = index + 1, #self do
    local tmp = mds[i]
    mds[i] = currentMd
    currentMd = tmp
  end
  self._modules[self._bindKey] = mds
  self:filter(nil, false)
  self:sort(nil, false)
  self._uiBinding:LinkerSetSystemObject(self._bindKey, self)
  return m
end

function valueArray:insert_array(v, filter, sort)
  local mds = self._modules[self._bindKey] or {}
  local arrayLength = #self
  local moduleName = self.moduleName
  for i = 1, #v do
    if v[i].moduleName then
      moduleName = v[i].moduleName
    end
    local m
    if moduleName == nil then
      m = require("core/ui/base/emptyModule").new()
    else
      m = requireModule(moduleName).new(G_UIModuleBase.ModuleType.TableItem)
    end
    if m.created then
      m:created(v[i], self._bindMeth, self._uiInst)
    end
    table.insert(self, v[i])
    mds[arrayLength + i] = m
  end
  self._modules[self._bindKey] = mds
  self:filter(filter, false)
  self:sort(sort, false)
  self._uiBinding:LinkerSetSystemObject(self._bindKey, self)
end

function valueArray:refresh_array(dataList, filter, sort)
  self:clear()
  self:insert_array(dataList, filter, sort)
  local count = self:getLength()
  for i = 1, count do
    local module = self:getItemCls(i)
    module:onSetData(dataList[i])
  end
end

function valueArray:remove(index)
  local mds = self._modules[self._bindKey]
  if mds == nil or mds[index] == nil then
    return
  end
  table.remove(self, index)
  local num = #self
  local removeId = index
  for i = index + 1, num + 1 do
    mds[removeId] = mds[i]
    removeId = i
  end
  mds[num + 1] = nil
  self._modules[self._bindKey] = mds
  self:filter(nil, false)
  self:sort(nil, false)
  self._uiBinding:LinkerSetSystemObject(self._bindKey, self)
end

function valueArray:change(index, value)
  if self[index] == nil then
    return
  end
  local mds = self._modules[self._bindKey] or {}
  if mds[index] == nil then
    return
  end
  for i, v in pairs(value) do
    self[index][i] = v
  end
  if mds[index].refresh and mds[index].isBind then
    mds[index]:refresh()
  end
end

function valueArray:freshAll(v)
  local mds = self._modules[self._bindKey] or {}
  local arrayLength = #self
  local moduleName = self.moduleName
  for i = 1, #v do
    local m = mds[i]
    if not m then
      if moduleName == nil then
        m = require("core/ui/base/emptyModule").new()
      else
        m = requireModule(moduleName).new(G_UIModuleBase.ModuleType.TableItem)
      end
      if m.created then
        m:created(v[i], self._bindMeth, self._uiInst)
      end
      arrayLength = arrayLength + 1
      self[arrayLength] = v[i]
      mds[arrayLength] = m
    else
      self:change(i, v[i])
    end
  end
  for i = #v + 1, arrayLength do
    self[i] = nil
  end
  self._modules[self._bindKey] = mds
  self:filter(nil, false)
  self._uiBinding:LinkerSetSystemObject(self._bindKey, self)
  for i = #v + 1, arrayLength do
    mds[i] = nil
  end
end

function valueArray:clear(linker)
  local mds = self._modules[self._bindKey] or {}
  local num = #self
  for i = num, 1, -1 do
    table.remove(self, i)
  end
  if linker == nil then
    linker = true
  end
  table.clear(self._filterMap)
  if linker == true then
    self._uiBinding:LinkerSetSystemObject(self._bindKey, self)
  end
  for i = num, 1, -1 do
    mds[i] = nil
  end
end

function valueArray:getValue(index, theKey)
  if self[index] == nil then
    return nil
  end
  if not theKey then
    return self[index]
  end
  return self[index][theKey]
end

function valueArray:_compare(val1, val2)
  for k = 1, #self.ruler do
    if val1[self.ruler[k].key] ~= val2[self.ruler[k].key] then
      if self.ruler[k].order == "DESC" and val1[self.ruler[k].key] < val2[self.ruler[k].key] then
        return true
      elseif self.ruler[k].order == "ASC" and val1[self.ruler[k].key] > val2[self.ruler[k].key] then
        return true
      end
      return false
    end
  end
  return false
end

function valueArray:sort(ruler, linker)
  self.ruler = ruler or self.ruler or {}
  local isSort = not table.isEmpty(self.ruler)
  if not isSort then
    return
  end
  if linker == nil then
    linker = true
  end
  if linker then
    self:filter(nil, false)
  end
  local mds = self._modules[self._bindKey] or {}
  for i = 1, #self._filterMap do
    if mds[i] then
      mds[i]:unBind()
    end
    for j = i + 1, #self._filterMap do
      if self:_compare(self[self._filterMap[i]], self[self._filterMap[j]]) then
        local tmp = self._filterMap[j]
        self._filterMap[j] = self._filterMap[i]
        self._filterMap[i] = tmp
      end
    end
  end
  if linker then
    self._uiBinding:LinkerSetSystemObject(self._bindKey, self)
  end
end

function valueArray:filter(condition, linker)
  if linker == nil then
    linker = true
  end
  if condition ~= nil then
    self.condition = condition
  end
  local filterMap = self._filterMap or {}
  table.clear(filterMap)
  local mds = self._modules[self._bindKey] or {}
  for i = 1, #self do
    if self.condition == nil or self:condition(self[i]) then
      table.insert(filterMap, i)
    else
      mds[i]:unBind()
    end
  end
  self._filterMap = filterMap
  if linker then
    self:sort(nil, false)
    self._uiBinding:LinkerSetSystemObject(self._bindKey, self)
  end
end

bindDataMeta.cls_valueToggleModule = valueToggleModule
bindDataMeta.cls_valueDynamicModule = valueDynamicModule
bindDataMeta.cls_valueArray = valueArray

function bindDataMeta:ctor(uiInst)
  self._values = {}
  self._methods = {}
  self._removeMethods = {}
  self._handlers = {}
  self._objectHandlers = {}
  self._setupBindings = {}
  self._uiInst = uiInst
  self._uiBinding = self._uiInst.uiBinding
  self._values.insert = self._values_insert
  self._values.remove = self._values_remove
  self._values.clear = self._values_clear
  self.__index = self.__index
  self.__newindex = self.__newindex
end

function bindDataMeta:setupBindings()
  if self._uiInst.bindConfigs == nil then
    return
  end
  for i = 0, self._uiInst.bindConfigs.Length - 1 do
    local linker = self._uiInst.bindConfigs[i]
    local key = linker.label
    if not self._setupBindings[key] then
      self._setupBindings[key] = linker
      local t = linker.attributeType
      if t == 11 then
        self:_setObjectValue(key, self._uiInst.bind[key], self._uiInst.methods[key])
      elseif t < 100 then
        local dataValue = self._uiInst.bind[key]
        local dataType = type(dataValue)
        if dataType == "string" and dataValue == "nil" then
        elseif dataType == "function" then
          self:_setValue(key, self._uiInst.bind[key]())
        else
          self:_setValue(key, self._uiInst.bind[key])
        end
        self._handlers[key] = self._setValue
      elseif t == 200 then
        local keySplits = string.split(key, "#")
        local dataKey = key
        local objIdx = 0
        if 1 < #keySplits then
          dataKey = keySplits[1]
          objIdx = tonumber(keySplits[2])
        end
        self:_setModules(key, dataKey, self._uiInst.bind[dataKey], self._uiInst.methods[dataKey], objIdx)
      else
        self:_setMethods(key, t, self._uiInst.methods[key])
      end
      if self._uiInst.bind[key] ~= nil then
        self._uiInst.bind[key] = nil
      end
    else
      print("dont support multi key " .. key)
    end
  end
end

function bindDataMeta:_setObjectValue(key, val, meth)
  if val == nil then
    return
  end
  local valType
  if val.type then
    valType = val.type
  else
    valType = "array"
  end
  if valType == "obj" then
  end
  if valType == "array" then
    local valSuper = self.cls_valueArray.new(self, key, meth)
    setmetatable(val, {__index = valSuper})
    if self._uiInst.modules[key] == nil then
      local mds = {}
      for i = 1, #val do
        local v = val[i]
        local moduleName = val.moduleName
        if v.moduleName then
          moduleName = v.moduleName
        end
        local m
        if moduleName == nil then
          m = require("core/ui/base/emptyModule").new()
        else
          m = requireModule(moduleName).new(G_UIModuleBase.ModuleType.TableItem)
        end
        if m.created then
          m:created(v, meth, self._uiInst)
        end
        mds[i] = m
      end
      self._uiInst.modules[key] = mds
    end
    self._values[key] = val
    self._uiBinding:LinkerSetSystemObject(key, val)
  elseif valType == "toggleModule" then
    local mds = {}
    for k, v in pairs(val) do
      local m
      if type(v) == "table" then
        if v.assetName ~= nil then
          if v.moduleName == nil then
            m = require("core/ui/base/emptyModule").new()
          else
            m = requireModule(v.moduleName).new(G_UIModuleBase.ModuleType.Dynamic)
          end
          if m.created then
            m:created(v, meth and meth[k] or {}, self._uiInst)
          end
          mds[k] = m
        else
          errorf("dont has assetName: " .. k)
        end
      end
    end
    self._uiInst.modules[key] = mds
    local valSuper = self.cls_valueToggleModule.new(self, key, meth)
    setmetatable(val, {__index = valSuper})
    self._uiBinding:LinkerSetSystemObject(key, val)
    self._values[key] = val
  elseif valType == "dynamicModule" then
    local mds = {}
    self._uiInst.modules[key] = mds
    local valSuper = self.cls_valueDynamicModule.new(self, key, meth)
    setmetatable(val, {__index = valSuper})
    self._uiBinding:LinkerSetSystemObject(key, val)
    self._values[key] = val
  else
    errorf("dont support this type in key:" .. key .. "  " .. valType)
  end
end

function bindDataMeta:_setValue(key, val)
  if val == nil then
    return
  end
  local bindConfig = self._setupBindings[key]
  if bindConfig == nil then
    errorf("failed to find bindConfig: " .. key)
    return
  end
  local attrType = bindConfig.attributeType
  if attrType == 0 then
    self._uiBinding:LinkerSetVector2(key, val)
  elseif attrType == 1 then
    self._uiBinding:LinkerSetVector3(key, val)
  elseif attrType == 2 then
    self._uiBinding:LinkerSetQuaternion(key, val)
  elseif attrType == 3 then
    if type(val) ~= "boolean" then
      errorf("data value must boolean:" .. key .. "  val: " .. table.tostring(val))
    end
    self._uiBinding:LinkerSetBoolean(key, val)
  elseif attrType == 4 then
    if type(val) ~= "number" then
      errorf("data value must number:" .. key .. " val: " .. table.tostring(val))
    end
    self._uiBinding:LinkerSetInt32(key, val)
  elseif attrType == 5 then
    if type(val) ~= "string" then
      errorf("data value must string:  " .. key .. "  val: " .. table.tostring(val))
    end
    self._uiBinding:LinkerSetString(key, val)
  elseif attrType == 6 then
    if type(val) ~= "number" then
      errorf("data value must number:" .. key .. "  val: " .. table.tostring(val))
    end
    self._uiBinding:LinkerSetSingle(key, val)
  elseif attrType == 7 then
    self._uiBinding:LinkerSetColor(key, val)
  elseif attrType == 8 then
    self._uiBinding:LinkerSetSprite(key, val)
  elseif attrType == 9 then
    self._uiBinding:LinkerSetChar(key, val)
  elseif attrType == 10 then
    self._uiBinding:LinkerSetRect(key, val)
  elseif attrType == 12 then
    self._uiBinding:LinkerSetSystemObject(key, val)
  else
    errorf("setValue dont has this type >" .. attrType)
    return
  end
  self._values[key] = val
end

function bindDataMeta:_toRmMethod(key)
  if self._removeMethods[key] then
    self._uiBinding:RemoveAction(key)
    self._removeMethods[key] = nil
    self._methods[key] = nil
  end
end

function bindDataMeta:_setMethods(key, t, val)
  if val == nil then
    return
  end
  if self._methods[key] then
    error([[
dont support multi bind methods
 method only support bind once:  ]] .. key)
  end
  if type(val) ~= "function" then
    error("methods must a function: " .. key)
  end
  local actionWrap = function(arg)
    val(self._uiInst, arg)
  end
  if t == 100 then
    self._uiBinding:LinkerSetAction(key, actionWrap)
  elseif t == 101 then
    self._uiBinding:LinkerSetActionBoolean(key, actionWrap)
  elseif t == 102 then
    self._uiBinding:LinkerSetActionSingle(key, actionWrap)
  elseif t == 103 then
    self._uiBinding:LinkerSetActionInt32(key, actionWrap)
  elseif t == 104 then
    self._uiBinding:LinkerSetActionString(key, actionWrap)
  elseif t == 105 then
    self._uiBinding:LinkerSetActionVector2(key, actionWrap)
  else
    errorf("dont has this type:" .. t)
  end
  self._methods[key] = val
end

function bindDataMeta._setModules_setUIBinding(this, uib)
  local mdl = this.module
  if mdl ~= nil then
    mdl:setUIBinding(uib)
  end
end

function bindDataMeta:_setModules(bindKey, dataKey, val, mths, objIdx)
  if val == nil then
    return
  end
  local moduleName = val.moduleName
  local ms
  if mths ~= nil and type(mths) == "table" then
    ms = mths
  end
  local isArray = val.count and val.count > 0
  local realVal
  if isArray then
    if val.childModules == nil then
      val.childModules = {}
    end
    realVal = {index = objIdx}
    val.childModules[objIdx] = realVal
  else
    realVal = val
  end
  realVal.setUIBinding = self._setModules_setUIBinding
  local mdl
  if moduleName == nil then
    mdl = require("core/ui/base/emptyModule").new()
  else
    mdl = requireModule(moduleName).new(G_UIModuleBase.ModuleType.Static)
  end
  if isArray then
    if self._uiInst.modules[dataKey] == nil then
      self._uiInst.modules[dataKey] = {}
    end
    self._uiInst.modules[dataKey][objIdx] = mdl
  else
    self._uiInst.modules[dataKey] = mdl
  end
  realVal.module = mdl
  self._uiBinding:LinkerSetSystemObject(bindKey, realVal)
  if mdl.created then
    mdl:created(realVal, ms, self._uiInst)
  end
  self._values[dataKey] = val
end

function bindDataMeta._values_insert(t, k, v)
  local self = getmetatable(t)
  table.insert(self._values[k], v)
  self._uiBinding:LinkerSetSystemObject(k, self._values[k])
end

function bindDataMeta._values_remove(t, k, v)
  local self = getmetatable(t)
  if v > #self._values[k] then
    return
  end
  table.remove(self._values[k], v)
  self._uiBinding:LinkerSetSystemObject(k, self._values[k])
end

function bindDataMeta._values_clear(t, k, linker)
  local self = getmetatable(t)
  if linker == nil then
    linker = true
  end
  local v = self._values[k]
  if v then
    for i, x in ipairs(v) do
      v[i] = nil
    end
  end
  if linker == true then
    self._uiBinding:LinkerSetSystemObject(k, self._values[k])
  end
end

function bindDataMeta.__index(t, k)
  local self = getmetatable(t)
  if self == nil then
    errorf("[bindDataMeta.__index] self is nil")
    return nil
  end
  return self._values[k]
end

function bindDataMeta.__newindex(t, k, v)
  local self = getmetatable(t)
  if self == nil then
    errorf("[bindDataMeta.__index] self is nil")
    return
  end
  if type(v) == "table" and self._uiInst.linkerDataDict[k] == 11 then
    errorf(string.format("[bindDataMeta.__newindex] \230\163\128\230\159\165\229\175\185BindingField(%s)\231\154\132\228\189\191\231\148\168\230\150\185\229\188\143\230\152\175\228\184\141\230\152\175\230\156\137\233\151\174\233\162\152\239\188\140\230\175\148\229\166\130\229\186\148\232\175\165insert_array\228\189\134\228\189\160\231\148\168\228\186\134\232\181\139\229\128\188\232\175\173\229\143\165\227\128\130\n\229\166\130\230\158\156\230\156\137\231\150\145\233\151\174\239\188\140\232\129\148\231\179\187Jovi", k))
  else
    local h = self._handlers[k]
    if h then
      h(self, k, v)
    else
      self._values[k] = v
    end
  end
end

return bindDataMeta
