local M = {}

-- Credits: https://sites.google.com/site/nullauahdark/getimagewidthheight
--[[
	Get Image Size
	By: MikuAuahDark
	Allows you to get image/video size from most files.
	Supported Image/Video Files(by priority order):
	1. Portable Network Graphics
	2. Windows Bitmap
	3. JPEG
	4. GIF
	5. Photoshop Document
	6. Truevision TGA
	7. JPEG XR/TIFF*
	8. MP4
	9. AVI
	(*) = experimental
]]

local get_image_width_height = function(file)
  local fileinfo = type(file)
  if type(file) == "string" then
    file = assert(io.open(file, "rb"))
  else
    fileinfo = file:seek("cur")
  end
  local function refresh()
    if type(fileinfo) == "number" then
      file:seek("set", fileinfo)
    else
      file:close()
    end
  end
  local width, height = 0, 0
  file:seek("set", 1)
  -- Detect if PNG
  if file:read(3) == "PNG" then
    --[[
			The strategy is
			1. Seek to position 0x10
			2. Get value in big-endian order
		]]
    file:seek("set", 16)
    local widthstr, heightstr = file:read(4), file:read(4)
    if type(fileinfo) == "number" then
      file:seek("set", fileinfo)
    else
      file:close()
    end
    width = widthstr:sub(1, 1):byte() * 16777216
      + widthstr:sub(2, 2):byte() * 65536
      + widthstr:sub(3, 3):byte() * 256
      + widthstr:sub(4, 4):byte()
    height = heightstr:sub(1, 1):byte() * 16777216
      + heightstr:sub(2, 2):byte() * 65536
      + heightstr:sub(3, 3):byte() * 256
      + heightstr:sub(4, 4):byte()
    return width, height
  end
  file:seek("set")
  -- Detect if BMP
  if file:read(2) == "BM" then
    --[[
			The strategy is:
			1. Seek to position 0x12
			2. Get value in little-endian order
		]]
    file:seek("set", 18)
    local widthstr, heightstr = file:read(4), file:read(4)
    refresh()
    width = widthstr:sub(4, 4):byte() * 16777216
      + widthstr:sub(3, 3):byte() * 65536
      + widthstr:sub(2, 2):byte() * 256
      + widthstr:sub(1, 1):byte()
    height = heightstr:sub(4, 4):byte() * 16777216
      + heightstr:sub(3, 3):byte() * 65536
      + heightstr:sub(2, 2):byte() * 256
      + heightstr:sub(1, 1):byte()
    return width, height
  end
  -- Detect if JPG/JPEG
  file:seek("set")
  if file:read(2) == "\255\216" then
    --[[
			The strategy is
			1. Find necessary markers
			2. Store biggest value in variable
			3. Return biggest value
		]]
    local lastb, curb = 0, 0
    local sstr = file:read(1)
    while sstr ~= nil do
      lastb = curb
      curb = sstr:byte()
      if (curb == 194 or curb == 192) and lastb == 255 then
        file:seek("cur", 3)
        local sizestr = file:read(4)
        local h = sizestr:sub(1, 1):byte() * 256 + sizestr:sub(2, 2):byte()
        local w = sizestr:sub(3, 3):byte() * 256 + sizestr:sub(4, 4):byte()
        if w > width and h > height then
          width = w
          height = h
        end
      end
      sstr = file:read(1)
    end
    if width > 0 and height > 0 then
      refresh()
      return width, height
    end
  end
  file:seek("set")
  -- Detect if GIF
  if file:read(4) == "GIF8" then
    --[[
			The strategy is
			1. Seek to 0x06 position
			2. Extract value in little-endian order
		]]
    file:seek("set", 6)
    width, height = file:read(1):byte() + file:read(1):byte() * 256, file:read(1):byte() + file:read(1):byte() * 256
    refresh()
    return width, height
  end
  -- More image support
  file:seek("set")
  -- Detect if Photoshop Document
  if file:read(4) == "8BPS" then
    --[[
			The strategy is
			1. Seek to position 0x0E
			2. Get value in big-endian order
		]]
    file:seek("set", 14)
    local heightstr, widthstr = file:read(4), file:read(4)
    refresh()
    width = widthstr:sub(1, 1):byte() * 16777216
      + widthstr:sub(2, 2):byte() * 65536
      + widthstr:sub(3, 3):byte() * 256
      + widthstr:sub(4, 4):byte()
    height = heightstr:sub(1, 1):byte() * 16777216
      + heightstr:sub(2, 2):byte() * 65536
      + heightstr:sub(3, 3):byte() * 256
      + heightstr:sub(4, 4):byte()
    return width, height
  end
  file:seek("end", -18)
  -- Detect if Truevision TGA file
  if file:read(10) == "TRUEVISION" then
    --[[
			The strategy is
			1. Seek to position 0x0C
			2. Get image width and height in little-endian order
		]]
    file:seek("set", 12)
    width = file:read(1):byte() + file:read(1):byte() * 256
    height = file:read(1):byte() + file:read(1):byte() * 256
    refresh()
    return width, height
  end
  file:seek("set")
  -- Detect if JPEG XR/Tagged Image File (Format)
  if file:read(2) == "II" then
    -- It would slow, tell me how to get it faster
    --[[
			The strategy is
			1. Read all file contents
			2. Find "Btomlong" and "Rghtlong" string
			3. Extract values in big-endian order(strangely, II stands for Intel byte
			ordering(little-endian) but it's in big-endian)
		]]
    local temp = file:read("*a")
    local btomlong = { temp:find("Btomlong") }
    local rghtlong = { temp:find("Rghtlong") }
    if #btomlong == 2 and #rghtlong == 2 then
      local heightstr = temp:sub(btomlong[2] + 1, btomlong[2] + 5)
      local widthstr = temp:sub(rghtlong[2] + 1, rghtlong[2] + 5)
      refresh()
      width = widthstr:sub(1, 1):byte() * 16777216
        + widthstr:sub(2, 2):byte() * 65536
        + widthstr:sub(3, 3):byte() * 256
        + widthstr:sub(4, 4):byte()
      height = heightstr:sub(1, 1):byte() * 16777216
        + heightstr:sub(2, 2):byte() * 65536
        + heightstr:sub(3, 3):byte() * 256
        + heightstr:sub(4, 4):byte()
      return width, height
    end
  end
  -- Video support
  file:seek("set", 4)
  -- Detect if MP4
  if file:read(7) == "ftypmp4" then
    --[[
			The strategy is
			1. Seek to 0xFB
			2. Get value in big-endian order
		]]
    file:seek("set", 0xFB)
    local widthstr, heightstr = file:read(4), file:read(4)
    refresh()
    width = widthstr:sub(1, 1):byte() * 16777216
      + widthstr:sub(2, 2):byte() * 65536
      + widthstr:sub(3, 3):byte() * 256
      + widthstr:sub(4, 4):byte()
    height = heightstr:sub(1, 1):byte() * 16777216
      + heightstr:sub(2, 2):byte() * 65536
      + heightstr:sub(3, 3):byte() * 256
      + heightstr:sub(4, 4):byte()
    return width, height
  end
  file:seek("set", 8)
  -- Detect if AVI
  if file:read(3) == "AVI" then
    file:seek("set", 0x40)
    width = file:read(1):byte()
      + file:read(1):byte() * 256
      + file:read(1):byte() * 65536
      + file:read(1):byte() * 16777216
    height = file:read(1):byte()
      + file:read(1):byte() * 256
      + file:read(1):byte() * 65536
      + file:read(1):byte() * 16777216
    refresh()
    return width, height
  end
  refresh()

  -- default to window height (for svgs, usually)
  local _height = vim.api.nvim_win_get_height(0)
  return _height, _height
end

M.calculate_ascii_width_height = function(buf_id, buf_path, opts)
  local padding = opts.render.min_padding
  local win_width = vim.api.nvim_win_get_width(buf_id)
  local win_height = vim.api.nvim_win_get_height(buf_id)

  -- PERF: Possibly expensive operation
  local img_width, img_height = get_image_width_height(buf_path)

  -- x2 for terminal fonts being 2/1 in aspect ratio
  img_width = img_width * 2

  local scale = math.min((win_width - padding * 2) / img_width, (win_height - padding * 2) / img_height)

  local width = math.floor(img_width * scale)
  local height = math.floor(img_height * scale)

  local horizntal_padding = math.floor((win_width - width) / 2)
  local vertical_padding = math.floor((win_height - height) / 2)

  return width, height, horizntal_padding, vertical_padding
end

M.create_label = function(buf_path, ascii_width, horizontal_padding)
  local label = buf_path:match("^.+/(.+)$")
  local win_width = ascii_width + horizontal_padding * 2

  local padding_left_size = math.floor((win_width - #label) / 2)
  label = string.rep(" ", padding_left_size) .. label

  return label
end

M.buf_clear = function(buf_id)
  vim.api.nvim_buf_set_lines(buf_id, 0, -1, true, { "" })
end

local insert_blank_lines = function(data, i)
  for _ = 1, i do
    table.insert(data, "")
  end
end

M.buf_insert_data_with_padding = function(buf_id, data, horizontal_padding, vertical_padding, label, opts)
  local data_with_padding = {}

  local horizontal_string = string.rep(" ", horizontal_padding)

  insert_blank_lines(data_with_padding, vertical_padding)
  for _, line in ipairs(data) do
    table.insert(data_with_padding, horizontal_string .. line)
  end

  table.insert(data_with_padding, "")

  if opts.render.show_label then
    table.insert(data_with_padding, label)
  else
    table.insert(data_with_padding, "")
  end

  insert_blank_lines(data_with_padding, vertical_padding - 2)

  local baleia = require("baleia").setup()
  baleia.buf_set_lines(buf_id, 0, -1, false, data_with_padding)
end

return M
