-- v1.3.0

local operator = {}

local redstone = require("redstone")
local npcManager = require("npcManager")

local min, max, clamp, abs = math.min, math.max, math.clamp, math.abs

operator.name = "operator"
operator.id = NPC_ID
operator.order = 0.44

operator.onRedPower = function(n, c, power, dir, hitbox)
  local data = n.data
  data.pow = data.pow or {[0] = 0, [1] = 0, [2] = 0, [3] = 0}

  if dir == -1 then
    dir = n.data.frameX
  else
    dir = (dir + 2)%4
  end

  data.pow[dir] = max(data.pow[dir], power)
end

operator.config = npcManager.setNpcSettings({
	id = operator.id,

  width = 32,
  height = 32,

	gfxwidth = 32,
	gfxheight = 32,
	gfxoffsetx = 0,
	gfxoffsety = 0,

	frames = 4,
	framespeed = 8,
	framestyle = 0,
  invisible = false,

  nogravity = true,
  notcointransformable = true,
  noblockcollision = true,
	jumphurt = true,
	nohurt = true,
	noyoshi = true,
  blocknpc = true,
  playerblock = true,
  playerblocktop = true,
  npcblock = true,
  disabledespawn = false,

  hasnooul = true,
})


operator.operations = {}
local operations = operator.operations

local mouthList, plusList, minusList = {}, {}, {}
function operator.registerOperation(id, t)
  if id then
    operations[id] = t
    if t.mouth then table.insert(mouthList, id) end
    if t.plus then table.insert(plusList, id) end
    if t.minus then table.insert(minusList, id) end
  end
end
local registerOperation = operator.registerOperation

local mouthAI = function(n, c, power, dir, hitbox)
  if operations[n.id] and operations[n.id].mouth then
    return operations[n.id].mouth(n, c, c.data.power, dir, hitbox)
  else
    return power
  end
end

local plusAI = function(n, c, power, dir, hitbox)
  return operations[n.id].plus(n, c, c.data.power, dir, hitbox)
end

local minusAI = function(n, c, power, dir, hitbox)
  return operations[n.id].minus(n, c, c.data.power, dir, hitbox)
end

local dirlist = {['eyes'] = 0, ['minus'] = 1, ['mouth'] = 2, ['plus'] = 3}
local function powerSide(n, dir)
  return n.data.pow[(n.data.frameX + dirlist[dir])%4]
end


function operator.prime(n)
  local data = n.data

  data.animFrame = data.animFrame or 0
  data.animTimer = data.animTimer or 0

  data.frameX = data._settings.dir or 0
  data.frameY = data.frameY or 0

  n.powerSide = powerSide
  data.pow = data.pow or {[0] = 0, [1] = 0, [2] = 0, [3] = 0}
  data.diff = data.diff or 0
  data.timer = data.timer or 0

  data.redhitbox = redstone.basicRedHitBox(n)
end

function operator.onRedLoad()
  registerOperation(redstone.id.repeater, {
    -- AND GATE
    ["plus"] = function(n, c, power, dir, hitbox)
      if c:powerSide('eyes') > 0 and c:powerSide('minus') > 0 then
        return 15
      else
        return 0
      end
    end,

    -- XOR GATE
    ["minus"] = function(n, c, power, dir, hitbox)
      local power_eyes = c:powerSide('eyes') > 0
      local power_plus = c:powerSide('plus') > 0
      if (power_eyes or power_plus) and not (power_eyes and power_plus) then
        return 15
      else
        return 0
      end
    end,
  })

  registerOperation(redstone.id.alternator, {
    -- TURNOUT
    ["mouth"] = function(n, c, power, dir, hitbox)
      if c.data.powerPrev == 0 and c.data.power > 0 then
        n.data.facing = -n.data.facing
      end
      return 15
    end
  })

  registerOperation(redstone.id.capacitor, {
    -- EQUAL TO
    ["mouth"] = function(n, c, power, dir, hitbox)
      if power == (n.data.maxcapacitance - 1) then
        n.data.unlocked = true
        return power
      else
        return 0
      end
    end,

    -- GREATER THAN
    ["plus"] = function(n, c, power, dir, hitbox)
      if power > (n.data.maxcapacitance - 1) then
        n.data.unlocked = true
        return power
      else
        return 0
      end
    end,

    -- LESS THAN
    ["minus"] = function(n, c, power, dir, hitbox)
      if power < (n.data.maxcapacitance - 1) then
        n.data.unlocked = true
        return power
      else
        return 0
      end
    end,
  })

  registerOperation(redstone.id.fuse, {
    ["plus"] = function(n, c, power, dir, hitbox)
      return power % n.data.limit
    end,
    ["minus"] = function(n, c, power, dir, hitbox)
      return power % n.data.limit
    end
  })

  registerOperation(redstone.id.spyblock, {
    ["mouth"] = function(n, c, power, dir, hitbox)
      -- MEDIAN COMPARE
      if n.data.type == 0 then
        if (c:powerSide('eyes') > c:powerSide('plus') and c:powerSide('plus') > c:powerSide('minus')) or (c:powerSide('minus') > c:powerSide('plus') and c:powerSide('plus') > c:powerSide('eyes')) then
          return c:powerSide('plus')
        elseif (c:powerSide('eyes') > c:powerSide('minus') and c:powerSide('minus') > c:powerSide('plus')) or (c:powerSide('plus') > c:powerSide('minus') and c:powerSide('minus') > c:powerSide('eyes')) then
          return c:powerSide('minus')
        else
          return c:powerSide('eyes')
        end
      -- ALL DIFF
      elseif n.data.type == 2 then
        return abs(c.data.diff)

      -- COUNT
      elseif n.data.type == 3 then
        local out = 0
        if c:powerSide('eyes') ~= 0 then out = out + 5 end
        if c:powerSide('plus') ~= 0 then out = out + 5 end
        if c:powerSide('minus') ~= 0 then out = out + 5 end
        return out
      end

      return 0
    end,

    ["plus"] = function(n, c, power, dir, hitbox)
      -- MAX COMPARE
      if n.data.type == 0 then
        return max(c:powerSide('eyes'), c:powerSide('minus'))

      -- POS DIFF
      elseif n.data.type == 2 then
        if c.data.diff > 0 then
          return c.data.diff
        end
      
      -- COUNT
      elseif n.data.type == 3 then
        local out = 0
        if c:powerSide('eyes') ~= 0 then out = out + 8 end
        if c:powerSide('minus') ~= 0 then out = min(15, out + 8) end
        return out
      end

      return 0
    end,

    ["minus"] = function(n, c, power, dir, hitbox)
      -- MIN COMPARE
      if n.data.type == 0 then
        return min(c:powerSide('eyes'), c:powerSide('plus'))

      -- NEG DIFF
      elseif n.data.type == 2 then
        if c.data.diff < 0 then
          return -c.data.diff
        end
      
      -- COUNT
      elseif n.data.type == 3 then
        local out = 0
        if c:powerSide('eyes') ~= 0 then out = out + 8 end
        if c:powerSide('plus') ~= 0 then out = min(15, out + 8) end
        return out
      end

      return 0
    end
  })

  registerOperation(redstone.id.reaper, {
    ["mouth"] = function(n, c, power, dir, hitbox)
      return 15 - power
    end
  })
end

function operator.onRedTick(n)
  local data = n.data
  data.observ = false

  data.power = clamp(n:powerSide('eyes') + n:powerSide('plus') - n:powerSide('minus'), 0, 15)

  if n.data.powerPrev ~= n.data.power then
    n.data.diff = n.data.power - n.data.powerPrev
    n.data.timer = 5
  end

  if n.data.timer <= 0 then
    n.data.diff = 0
  else
    n.data.timer = n.data.timer - 1
  end


  redstone.updateRedHitBox(n)
  redstone.passDirectionEnergy{source = n, power = data.power, hitbox = data.redhitbox[(data.frameX + 2)%4 + 1], powerAI = mouthAI}
  redstone.passDirectionEnergy{source = n, power = 0, hitbox = data.redhitbox[(data.frameX + 1)%4 + 1], npcList = minusList, powerAI = minusAI}
  redstone.passDirectionEnergy{source = n, power = 0, hitbox = data.redhitbox[(data.frameX - 1)%4 + 1], npcList = plusList, powerAI = plusAI}

  if (data.power == 0 and data.powerPrev ~= 0) or (data.power ~= 0 and data.powerPrev == 0) then
    data.observ = true
  end

  if n:powerSide('eyes') == 0 and n:powerSide('plus') == 0 and n:powerSide('minus') == 0 then
    data.frameY = 0
  else
    data.frameY = 1
  end

  data.pow = {[0] = 0, [1] = 0, [2] = 0, [3] = 0}
  redstone.resetPower(n)
end

operator.onRedDraw = redstone.drawNPC

redstone.register(operator)

return operator
