local profile = require("core.profile")
local tbl_ValidCameraAttributes = {
  Distance = "Distance",
  fov = "fov",
  DepthOfField = "DepthOfField",
  FocusDistance = "FocusDistance",
  FStop = "FStop",
  focalLength = "focalLength",
  ["Position.X"] = "Position.X",
  ["Position.Y"] = "Position.Y",
  ["Position.Z"] = "Position.Z",
  Pitch = "Pitch",
  Yaw = "Yaw",
  Roll = "Roll",
  PanLeft = "PanLeft",
  PanRight = "PanRight",
  TiltUp = "TiltUp",
  TiltDown = "TiltDown",
  ["PlayerFrame.Top"] = "PlayerFrame.Top",
  ["PlayerFrame.Bottom"] = "PlayerFrame.Bottom",
  ["PlayerFrame.Left"] = "PlayerFrame.Left",
  ["PlayerFrame.Right"] = "PlayerFrame.Right",
  ["TargetFrame.Top"] = "TargetFrame.Top",
  ["TargetFrame.Bottom"] = "TargetFrame.Bottom",
  ["TargetFrame.Left"] = "TargetFrame.Left",
  ["TargetFrame.Right"] = "TargetFrame.Right",
  ["OrbitConstraint.RotationMax"] = "OrbitConstraint.RotationMax",
  ["OrbitConstraint.RotationLeft"] = "OrbitConstraint.RotationLeft",
  ["OrbitConstraint.TriggerLeft"] = "OrbitConstraint.TriggerLeft",
  ["OrbitConstraint.TriggerRight"] = "OrbitConstraint.TriggerRight",
  ["OrbitConstraint.RotationRight"] = "OrbitConstraint.RotationRight",
  ["OrbitConstraint.ElevationMaxUp"] = "OrbitConstraint.ElevationMaxUp",
  ["OrbitConstraint.ElevationMaxDown"] = "OrbitConstraint.ElevationMaxDown",
  ["OrbitConstraint.YawRange"] = "OrbitConstraint.YawRange"
}
local tbl_ValidBlendMembers = {
  elapsedTime = "elapsedTime",
  bIsInProgress = "bIsInProgress",
  bComplete = "bComplete",
  callback = "callback",
  duration = "duration"
}
local GetProgress = function(table)
  return table.bIsInProgress
end
local SetProgress = function(table, bValue)
  table.bIsInProgress = bValue
end
local GetAttributeStrings = function(name)
  local attribute = name
  local extra
  if attribute == "fov" then
    attribute = "AngleOfView"
  elseif attribute == "Position.X" then
    attribute = "Position"
    extra = "X"
  elseif attribute == "Position.Y" then
    attribute = "Position"
    extra = "Y"
  elseif attribute == "Position.Z" then
    attribute = "Position"
    extra = "Z"
  elseif attribute == "PlayerFrame.Top" then
    attribute = "PlayerFrame"
    extra = "Top"
  elseif attribute == "PlayerFrame.Bottom" then
    attribute = "PlayerFrame"
    extra = "Bottom"
  elseif attribute == "PlayerFrame.Left" then
    attribute = "PlayerFrame"
    extra = "Left"
  elseif attribute == "PlayerFrame.Right" then
    attribute = "PlayerFrame"
    extra = "Right"
  elseif attribute == "OrbitConstraint.RotationMax" then
    attribute = "OrbitConstraint"
    extra = "RotationMax"
  elseif attribute == "OrbitConstraint.RotationLeft" then
    attribute = "OrbitConstraint"
    extra = "RotationLeft"
  elseif attribute == "OrbitConstraint.RotationRight" then
    attribute = "OrbitConstraint"
    extra = "RotationRight"
  elseif attribute == "OrbitConstraint.TriggerLeft" then
    attribute = "OrbitConstraint"
    extra = "TriggerLeft"
  elseif attribute == "OrbitConstraint.TriggerRight" then
    attribute = "OrbitConstraint"
    extra = "TriggerRight"
  elseif attribute == "OrbitConstraint.ReturnLeft" then
    attribute = "OrbitConstraint"
    extra = "ReturnLeft"
  elseif attribute == "OrbitConstraint.ReturnRight" then
    attribute = "OrbitConstraint"
    extra = "ReturnRight"
  elseif attribute == "OrbitConstraint.ElevationMaxUp" then
    attribute = "OrbitConstraint"
    extra = "ElevationMaxUp"
  elseif attribute == "OrbitConstraint.ElevationMaxDown" then
    attribute = "OrbitConstraint"
    extra = "ElevationMaxDown"
  elseif attribute == "OrbitConstraint.YawRange" then
    attribute = "OrbitConstraint"
    extra = "YawRange"
  end
  return attribute, extra
end
local SetInitialValue = function(cam, table, name)
  local attribute, extra
  attribute, extra = GetAttributeStrings(name)
  if not extra then
    cam[attribute] = cam.initial[attribute]
  else
    cam[attribute][extra] = cam.initial[attribute][extra]
  end
  table.bIsInProgress = false
  table.callback = nil
end
local SetupEaseTable = function(table)
  table.elapsedTime = 0
  table.bIsInProgress = false
  table.bComplete = false
  table.callback = nil
end
local SetCallback = function(table, functioncall)
  table.callback = functioncall
end
local IsValidCameraAttribute = function(name)
  if tbl_ValidCameraAttributes[name] then
    return true
  end
  return false
end
local IsValidBlendMember = function(name)
  if tbl_ValidBlendMembers[name] then
    return true
  end
  return false
end
local Reset = function(cam, table)
  if table then
    for _, member in pairs(table) do
      if IsValidCameraAttribute(_) then
        SetInitialValue(cam, table, member.name)
      end
    end
    table.bComplete = false
    table.elapsedTime = 0
    table.bIsInProgress = false
    table.callback = nil
  end
end
local RemoveTheAttributes = function(table)
  if table then
    for attribute, _ in pairs(table) do
      if IsValidCameraAttribute(attribute) then
        table[attribute] = nil
      end
    end
  end
end
local SetBlendValue = function(cam, table, n, e)
  local attribute, extra
  attribute, extra = GetAttributeStrings(n)
  local s
  if not extra then
    s = cam[attribute]
  else
    s = cam[attribute][extra]
  end
  local member = {
    name = n,
    startValue = s,
    endValue = e
  }
  table[n] = member
end
local AdjustBlendValue = function(cam, table, n, e)
  assert(table, "AdjustBlendValue(table)")
  table[n].endValue = e
end
local SetDuration = function(table, durationvalue)
  assert(table, "AdjustBlendValue(table)")
  table.duration = durationvalue
end
local SetStartValue = function(camera, table)
  assert(table, "SetStartValue()")
  for _, member in pairs(table) do
    if IsValidCameraAttribute(_) then
      local attribute, extra
      attribute, extra = GetAttributeStrings(member.name)
      if not extra then
        table[member.name].startValue = camera[attribute]
      else
        table[member.name].startValue = camera[attribute][extra]
      end
      table[member.name].bCompleted = false
    end
  end
  table.elapsedTime = 0
end
local PrintBlendTable = function(table)
  local t = table
  assert(t, "PrintBlendTable()")
  print("/////////////////////////////////")
  print("::PrintBlendTable() ")
  print("/////////////////////////////////")
  for _, member in pairs(t) do
    if IsValidCameraAttribute(_) then
      print("---------------------------------")
      print("name is: " .. member.name)
      print("---------------------------------")
      print("startValue is: " .. member.startValue)
      print("endValue is: " .. member.endValue)
      print("duration is: " .. table.duration)
    end
  end
end
local CopyBlendTable = function(from, to)
  assert(from, "CopyBlendTable()")
  assert(to, "CopyBlendTable()")
  local original = from
  for key, value in pairs(original) do
    if IsValidCameraAttribute(key) then
      local table = {}
      for k, v in pairs(value) do
        table[k] = v
      end
      to[key] = table
    elseif IsValidBlendMember(key) then
      to[key] = value
    end
  end
end
local TakeSnapshot = function(cam, table)
  assert(table, "TakeSnapshot()")
  local camera = cam
  local t = table
  for key, member in pairs(t) do
    if IsValidCameraAttribute(key) then
      local attribute, extra
      attribute, extra = GetAttributeStrings(member.name)
      if not extra then
        t[member.name].endValue = camera[attribute]
      else
        t[member.name].endValue = camera[attribute][extra]
      end
    end
  end
end
local EaseInAndOutQuad = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local result
  local delta = c - b
  local t = et / (d / 2)
  if t < 1 then
    result = delta / 2 * t * t + b
  else
    t = t - 1
    result = -delta / 2 * (t * (t - 2) - 1) + b
  end
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseInAndOutCubic = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local result
  local delta = c - b
  local t = et / (d / 2)
  if t < 1 then
    result = delta / 2 * t * t * t + b
  else
    t = t - 2
    result = delta / 2 * (t * t * t + 2) + b
  end
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseInAndOutCircular = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local result
  local delta = c - b
  local t = et / (d / 2)
  if t < 1 then
    result = -delta / 2 * (math.sqrt(1 - t * t) - 1) + b
  else
    t = t - 2
    result = delta / 2 * (math.sqrt(1 - t * t) + 1) + b
  end
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseInAndOutQuintic = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local result
  local delta = c - b
  local t = et / (d / 2)
  if t < 1 then
    result = delta / 2 * t * t * t * t + b
  else
    t = t - 2
    result = -delta / 2 * (t * t * t * t - 2) + b
  end
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseInLinear = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local delta = c - b
  local t = et / d
  local result = delta * t + b
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseInQuad = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local result
  local delta = c - b
  local t = et / d
  result = delta * t * t + b
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseInCubic = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local result
  local delta = c - b
  local t = et / d
  result = delta * t * t * t + b
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseInQuartic = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local result
  local delta = c - b
  local t = et / d
  t = t - 1
  result = -delta * (t * t * t * t - 1) + b
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseInQuintic = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local result
  local delta = c - b
  local t = et / d
  result = delta * t * t * t * t * t + b
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseOutQuad = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local result
  local delta = c - b
  local t = et / d
  result = -delta * t * (t - 2) + b
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseOutCubic = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local result
  local delta = c - b
  local t = et / d
  t = t - 1
  result = delta * (t * t * t + 1) + b
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseOutQuartic = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local result
  local delta = c - b
  local t = et / d
  t = t - 1
  result = -delta * (t * t * t * t - 1) + b
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseOutQuintic = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local result
  local delta = c - b
  local t = et / d
  t = t - 1
  result = delta * (t * t * t * t * t + 1) + b
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result, false
end
local EaseOutBounce = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local t = et / d
  local delta = c - b
  local result
  if t < 0.36363637 then
    result = delta * (7.5625 * t * t) + b
  elseif t < 0.72727275 then
    t = t - 0.54545456
    result = delta * (7.5625 * t * t + 0.75) + b
  elseif t < 0.90909094 then
    t = t - 0.8181818
    result = delta * (7.5625 * t * t + 0.9375) + b
  else
    t = t - 0.95454544
    result = delta * (7.5625 * t * t + 0.984375) + b
  end
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result
end
local EaseInBounce = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local delta = c - b
  local result = delta - EaseOutBounce(d - et, 0, delta, d) + b
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result
end
local EaseInAndOutBounce = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local delta = c - b
  local result
  if et < d / 2 then
    result = EaseInBounce(et * 2, 0, delta, d) * 0.5 + b
  else
    result = EaseOutBounce(et * 2 - d, 0, delta, d) * 0.5 + delta * 0.5 + b
  end
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result
end
local EaseOutAndInBounce = function(et, b, c, d)
  if d < et then
    return c, true
  end
  local delta = c - b
  local result
  if et < d / 2 then
    result = EaseOutBounce(et * 2, b, delta / 2, d)
  else
    result = EaseInBounce(et * 2 - d, b + delta / 2, delta / 2, d)
  end
  if 0 < delta then
    if c < result then
      return c, true
    end
  elseif c > result then
    return c, true
  end
  return result
end
local EaseFunctions = {
  EaseInLinear = EaseInLinear,
  EaseInQuad = EaseInQuad,
  EaseInCubic = EaseInCubic,
  EaseInQuartic = EaseInQuartic,
  EaseInQuintic = EaseInQuintic,
  EaseOutQuad = EaseOutQuad,
  EaseOutCubic = EaseOutCubic,
  EaseOutQuartic = EaseOutQuartic,
  EaseOutQuintic = EaseOutQuintic,
  EaseInAndOutQuad = EaseInAndOutQuad,
  EaseInAndOutCubic = EaseInAndOutCubic,
  EaseInAndOutCircular = EaseInAndOutCircular,
  EaseInAndOutQuintic = EaseInAndOutQuintic,
  EaseInBounce = EaseInBounce,
  EaseOutBounce = EaseOutBounce,
  EaseInAndOutBounce = EaseInAndOutBounce,
  EaseOutAndInBounce = EaseOutAndInBounce
}
local EaseMode = function(move, formulatype)
  local funcname = "Ease" .. move .. formulatype
  return EaseFunctions[funcname]
end
local PrintDoneBlock = function()
  print("#######################################################")
  print("#######################################################")
  print("#######################################################")
  print("#######################################################")
  print("##########                                   ##########")
  print("##########                                   ##########")
  print("##########                                   ##########")
  print("##########                                   ##########")
  print("##########                                   ##########")
  print("##########                                   ##########")
  print("##########               DONE                ##########")
  print("##########                                   ##########")
  print("##########                                   ##########")
  print("##########                                   ##########")
  print("##########                                   ##########")
  print("##########                                   ##########")
  print("#######################################################")
  print("#######################################################")
  print("#######################################################")
  print("#######################################################")
end
local EaseByValue = function(easemove, easestyle, startvalue, endvalue, elapsedvalue, maxvalue)
  local s = startvalue
  local e = endvalue
  local easefunction = EaseMode(easemove, easestyle)
  if easefunction == nil then
    assert(easefunction, "EaseByDistance() .. assert(easefunction) .." .. "Ease" .. easemove .. easestyle)
  end
  local curVal = easefunction(elapsedvalue, s, e, maxvalue)
  return curVal
end
local EaseEverything = function(lvl, table, cam, easemove, easeformula)
  assert(table, "::EaseEverything(table)")
  if table.bIsInProgress and not table.bComplete then
    local unitTime = lvl:GetUnitTime()
    local curVal
    local easefunction = EaseMode(easemove, easeformula)
    for _, member in pairs(table) do
      if IsValidCameraAttribute(_) then
        local n = member.name
        local s = member.startValue
        local e = member.endValue
        local d = table.duration
        if easefunction == nil then
          assert(easefunction, "EaseEverything() .. assert(easefunction) .." .. "Ease" .. easemove .. easeformula)
        end
        curVal, table.bComplete = easefunction(table.elapsedTime, s, e, d)
        local attribute, extra
        attribute, extra = GetAttributeStrings(n)
        if not extra then
          cam[attribute] = curVal
        else
          cam[attribute][extra] = curVal
        end
      end
    end
    if table.bComplete then
      table.elapsedTime = 0
      table.bIsInProgress = false
      table.bComplete = false
      if table.callback then
        table.callback()
      end
      return true
    else
      table.elapsedTime = table.elapsedTime + unitTime
    end
    return false
  end
end
return profile.WrapLibrary({
  SetupEaseTable = SetupEaseTable,
  SetCallback = SetCallback,
  IsValidCameraAttribute = IsValidCameraAttribute,
  Reset = Reset,
  GetProgress = GetProgress,
  SetProgress = SetProgress,
  PrintDoneBlock = PrintDoneBlock,
  SetInitialValue = SetInitialValue,
  RemoveTheAttributes = RemoveTheAttributes,
  SetBlendValue = SetBlendValue,
  AdjustBlendValue = AdjustBlendValue,
  SetDuration = SetDuration,
  SetStartValue = SetStartValue,
  PrintBlendTable = PrintBlendTable,
  CopyBlendTable = CopyBlendTable,
  TakeSnapshot = TakeSnapshot,
  EaseInAndOutQuad = EaseInAndOutQuad,
  EaseInAndOutCubic = EaseInAndOutCubic,
  EaseInAndOutCircular = EaseInAndOutCircular,
  EaseInAndOutQuintic = EaseInAndOutQuintic,
  EaseInLinear = EaseInLinear,
  EaseInQuad = EaseInQuad,
  EaseInCubic = EaseInCubic,
  EaseInQuartic = EaseInQuartic,
  EaseInQuintic = EaseInQuintic,
  EaseOutQuad = EaseOutQuad,
  EaseOutCubic = EaseOutCubic,
  EaseOutQuartic = EaseOutQuartic,
  EaseOutQuintic = EaseOutQuintic,
  EaseOutBounce = EaseOutBounce,
  EaseInBounce = EaseInBounce,
  EaseInAndOutBounce = EaseInAndOutBounce,
  EaseOutAndInBounce = EaseOutAndInBounce,
  EaseByValue = EaseByValue,
  EaseEverything = EaseEverything
})
