local reap = require("lib.reap")

local BASE = reap.base_path(...)

local easeFN = require(BASE .. ".ease")
local json = require("lib.json")
local QuadCache = require("love_src.wrapper.lappy.new.quad").obj
local ImageCache = require("love_src.wrapper.lappy.new.image").obj

local utils = {}

local function loadJSON(path)
  local data = json.decode(love.filesystem.read(path))
  return data
end

local ROAD = {
  LENGTH= { NONE= 0, SHORT=  25, MEDIUM=  50, LONG=  100 }, -- num segments
  HILL=   { NONE= 0, LOW=    20, MEDIUM=  40, HIGH=   60 },
  CURVE=  { NONE= 0, EASY=    2, MEDIUM=   4, HARD=    6 }
};

-- function utils.getRumbleColor(index, rumbleLength)
--   local colorDark = hex2color("#1a1d33")
--   local colorBright = hex2color("#c8d0fa")
--   if (math.floor(index/rumbleLength)%2 == 0) then
--     return colorBright
--   else
--     return colorDark
--   end
-- end

-- function utils.getFinishStartColor()
--   local colorDark = hex2color("#CCFFCC")
--   local colorBright = hex2color("#FFCCFF")

--   return colorDark, colorBright
-- end

local function lastY(segments)
  if (#segments == 0) then
    return 0
  else
    return segments[#segments].p2.world.y
  end
end

local function lastWidth(segments, fallback)
  if (#segments == 0) then
    return fallback
  else
    return segments[#segments].p2.world.w
  end
end

function utils.findSegment(zPosition, segments, segmentLength)
  local index = (math.floor(zPosition/segmentLength) % #segments) + 1
  if (index >= #segments) then
    index = #segments
  end
  return segments[index];
end

--- add quad and image
---@param path string
---@param slice "horizontal" | "vertical"
---@param w1 number
---@param w2 number
function utils.addQuads(path, slice, w1, w2)
  local image = ImageCache:load_to(path, path)

  local sw, sh = image:getDimensions()

  local quads = {}
  if (slice == "vertical") then
    for i = 0, sh - 1 do
      local percent = easeFN.percentRemaining(i, sh - 1)
      local destW = easeFN.interpolate(w1, w2, percent)
      table.insert(quads,
        QuadCache:load_to(string.format("%s_%s_%s_%s_%s_%s_%s",
          path, 0, i, sw, 1, sw, sh),
          0, i, sw, 1, sw, sh)
      )
    end
  elseif (slice == "horizontal") then
    for i = 0, sw - 1 do
      local percent = easeFN.percentRemaining(i, sw - 1)
      local destH = easeFN.interpolate(w1, w2, percent)
      table.insert(quads,
        QuadCache:load_to(
        string.format("%s_%s_%s_%s_%s_%s_%s",
        path, i, 0, 1, sh, sw, sh),
        i, 0, 1, sh, sw, sh)
      )
    end
  end
  return quads, image
end

--- add new segment to segments
---@param segments any
---@param segmentLength number z coord length
---@param rumbleLength number for debug purpose only
---@param curve number curve degree
---@param y number height
---@param width number
---@return table segment it's basically part that are drawn
function utils.addSegment(segments, segmentLength, rumbleLength, curve, y, width)
  local index = #segments

  local w1 = lastWidth(segments, width)
  local w2 = width

  local y1 = lastY(segments)
  local y2 = y

  local fQuads, fImage = utils.addQuads("love_src/asset/image/sample/javascript-racer-master/images/sprites/column.png", "vertical", w1, w2)
  local cQuads, cImage = utils.addQuads("love_src/asset/image/sample/javascript-racer-master/images/sprites/column.png", "vertical", w1, w2)

  local lQuads, lImage = utils.addQuads("love_src/asset/image/sample/javascript-racer-master/images/sprites/column.png", "horizontal", y1, y2)
  local rQuads, rImage = utils.addQuads("love_src/asset/image/sample/javascript-racer-master/images/sprites/column.png", "horizontal", y1, y2)

  return {
    --- index for seeking
    index= index + 1,
    --- part 1 start
    p1 = {
      world =
        {
          w = w1,
          y = y1,
          z =  index   * segmentLength,
        },
      camera = {},
      screen = {}
    },
    --- part 2 end
    p2 = {
      world = {
        w = w2,
        y = y2,
        z = (index+1)* segmentLength,
      },
      camera = {},
      screen = {}
    },
    --- curve to follow along
    curve = curve,
    sprites= {},
    ---p1 -> p2 quads
    texture = {
      floor = {
        image = fImage,
        quads = fQuads
      },
      ceil = {
        image = cImage,
        quads = cQuads
      },
      wallL = {
        image = lImage,
        quads = lQuads
      },
      wallR = {
        image = rImage,
        quads = rQuads
      },
    }
  }
end

function utils.addRoad(segments, segmentLength, rumbleLength, enter, hold, leave, curve, y, roadWidth)
  local startY   = lastY(segments);
  local endY     = startY + ((y or 0) * segmentLength);

  local startWidth = lastWidth(segments, (roadWidth or 0));
  local endWidth = (roadWidth or 0)

  local total    = enter + hold + leave;
  for n = 1, enter do
    table.insert(
      segments,
      utils.addSegment(
        segments,
        segmentLength,
        rumbleLength,
        easeFN.easeIn(0, curve, n/enter),
        easeFN.easeInOut(startY, endY, n/total),
        easeFN.easeInOut(startWidth, endWidth, n/total)
      )
    )
  end
  for n = 1, hold do
    table.insert(
      segments,
      utils.addSegment(
        segments,
        segmentLength,
        rumbleLength,
        curve,
        easeFN.easeInOut(startY, endY, (enter + n)/total),
        easeFN.easeInOut(startWidth, endWidth, (enter + n)/total)
      )
    )
  end
  for n = 1, leave do
    table.insert(
      segments,
      utils.addSegment(
        segments,
        segmentLength,
        rumbleLength,
        easeFN.easeInOut(curve, 0, n/leave),
        easeFN.easeInOut(startY, endY, (enter+hold+n)/total),
        easeFN.easeInOut(startWidth, endWidth, (enter + hold + n)/total)
      )
    )
  end
  return segments
end

function utils.addStraight(segments, segmentLength, rumbleLength, num)
  num = num or ROAD.LENGTH.MEDIUM;
  return utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, 2000);
end

function utils.addHill(segments, segmentLength, rumbleLength, num, height)
  num    = num    or ROAD.LENGTH.MEDIUM;
  height = height or ROAD.HILL.MEDIUM;
  return utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height, 2000);
end

function utils.addCurve(segments, segmentLength, rumbleLength, num, curve, height)
  num    = num    or ROAD.LENGTH.MEDIUM;
  curve  = curve  or ROAD.CURVE.MEDIUM;
  height = height or ROAD.HILL.NONE;
  return utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, curve, height, 2000);
end

function utils.addLowRollingHills(segments, segmentLength, rumbleLength, num, height)
  num    = num    or ROAD.LENGTH.SHORT;
  height = height or ROAD.HILL.LOW;
  utils.addRoad(segments, segmentLength, rumbleLength, num, num, num,  0,  height/2, 2000);
  utils.addRoad(segments, segmentLength, rumbleLength, num, num, num,  0, -height, 2000);
  utils.addRoad(segments, segmentLength, rumbleLength, num, num, num,  0,  height, 2000);
  utils.addRoad(segments, segmentLength, rumbleLength, num, num, num,  0,  0, 2000);
  utils.addRoad(segments, segmentLength, rumbleLength, num, num, num,  0,  height/2, 2000);
  utils.addRoad(segments, segmentLength, rumbleLength, num, num, num,  0,  0, 2000);
  return segments
end

function utils.addSCurves(segments, segmentLength, rumbleLength)
  utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM,  -ROAD.CURVE.EASY, 2000);
  utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM,   ROAD.CURVE.MEDIUM, 2000);
  utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM,   ROAD.CURVE.EASY, 2000);
  utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM,  -ROAD.CURVE.EASY, 2000);
  utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM,  -ROAD.CURVE.MEDIUM, 2000);
  return segments
end

function utils.addDownhillToEnd(segments, segmentLength, rumbleLength, num)
  num = num or 200;
  utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, -ROAD.CURVE.EASY, -lastY(segments)/segmentLength, 2000);
  return segments
end

function utils.resetCars(cars, segments, segmentLength, totalCars, maxSpeed)
  local n, car, segment, offset, z, sprite, speed;
  for n = 0, totalCars do
    offset = math.random() * easeFN.randomChoice({-0.8, 0.8});
    z      = math.floor(math.random() * segments.length) * segmentLength;
    sprite = easeFN.randomChoice({
      ImageCache:load_to("asset/image/sample/javascript-racer-master/images/sprites/bush1.png",
        "asset/image/sample/javascript-racer-master/images/sprites/bush1.png")
    });

    speed  = maxSpeed/4 + math.random() * maxSpeed;
    car = { offset = offset, z = z, sprite = sprite, speed = speed };
    segment = utils.findSegment(car.z, segments, segmentLength);
    table.insert(segment.cars, car);
    table.insert(cars, car);
  end
end

function utils.loadRoad(segments, segmentLength, rumbleLength, path)
  local segmentData = loadJSON(path)
  for _, v in ipairs(segmentData) do
    local enter = v.enter
    local hold = v.hold
    local leave = v.leave
    local curve = v.curve
    local height = v.height
    local width = v.width
    utils.addRoad(segments, segmentLength, rumbleLength, enter, hold, leave, curve, height, width);
  end
end

function utils.resetRoad(path, pathSprite, segmentLength, rumbleLength, playerZ)
  local segments = {}
  utils.loadRoad(segments, segmentLength, rumbleLength, path)
  if (pathSprite) then
    utils.loadSpriteMapData(segments, pathSprite);
  end
  local trackLength = #segments * segmentLength;
  return segments, trackLength
end

function utils.loadSpriteMapData(segments, path)
  local segmentData = loadJSON(path)
  for _, v in ipairs(segmentData) do
    local index = v.index
    local texture = v.texture
    local offset = v.offset
    utils.addSprite(segments, index, ImageCache:load_to(texture, texture), offset, texture);
  end
end

function utils.addSprite(segments, index, sprite, offset, key)
  table.insert(segments[index].sprites, {
    source= sprite,
    offset= offset,
    data = {}
  })
  local _sIndex = #segments[index].sprites
    segments[index].sprites[_sIndex].data = {
      path = key,
      type = "static"
    }
end


return utils
