-- mod-version:3

-- The general idea is to check if the file opened is valid utf-8
-- since lite-xl only supports UTF8 text, others can be safely assumed
-- to be binary
local core = require "core"
local common = require "core.common"
local style = require "core.style"
local DocView = require "core.docview"
local RootView = require "core.rootview"
local View = require "core.view"


local function validate_utf8(s, max_len)
  local i, p, len = 1, 1, #s
  while p <= len and i <= max_len do
    if     p == s:find("[%z\1-\127]", p) then p = p + 1
    elseif p == s:find("[\194-\223][\128-\191]", p) then p = p + 2
    elseif p == s:find(       "\224[\160-\191][\128-\191]", p)
        or p == s:find("[\225-\236][\128-\191][\128-\191]", p)
        or p == s:find(       "\237[\128-\159][\128-\191]", p)
        or p == s:find("[\238-\239][\128-\191][\128-\191]", p) then p = p + 3
    elseif p == s:find(       "\240[\144-\191][\128-\191][\128-\191]", p)
        or p == s:find("[\241-\243][\128-\191][\128-\191][\128-\191]", p)
        or p == s:find(       "\244[\128-\143][\128-\191][\128-\191]", p) then p = p + 4
    else
      return false
    end
    i = i + 1
  end
  return true
end


local function replace_view(this, that)
  local node = core.root_view.root_node:get_node_for_view(this)
  local idx = node:get_view_idx(this)
  node:remove_view(core.root_view.root_node, this)
  node:add_view(that, idx)
  core.root_view.root_node:update_layout()
  core.redraw = true
end


local msg = "This file is not displayed because it is either binary or uses an unsupported text encoding."
local cmd -- here's evil code again...
if PLATFORM == "Windows" then
  cmd = "start %q"
elseif PLATFORM == "Linux" then
  cmd = "xdg-open %q"
else
  cmd = "open %q"
end

local opt = { "Open anyway", "Open with other program", "Close" }
local opt_actions = {
  function(self)
    -- open anyway
    local view = DocView(core.open_doc(self.filename))
    replace_view(self, view)
  end,
  function(self)
    -- open externally
    local node = core.root_view.root_node:get_node_for_view(self)
    node:close_view(core.root_view.root_node, self)
    system.exec(string.format(cmd, self.filename))
  end,
  function(self)
    local node = core.root_view.root_node:get_node_for_view(self)
    node:close_view(core.root_view.root_node, self)
  end
}


local OpenExtView = View:extend()


function OpenExtView:new(filename)
  OpenExtView.super.new(self)
  self.filename = filename
end


function OpenExtView:get_name()
  return common.basename(self.filename)
end


function OpenExtView:each_option()
  return coroutine.wrap(function()
    local w = (self.size.x - style.padding.x * (#opt + 1)) / #opt
    local h = style.font:get_height() + style.padding.y
    local x, y = self:get_content_offset()
    x = x + style.padding.x
    y = y + self.size.y / 2 + style.padding.y
    for i, o in ipairs(opt) do
      coroutine.yield(i, o, x, y, w, h)
      x = x + w + style.padding.x
    end
  end)
end


function OpenExtView:on_mouse_moved(px, py, dx, dy)
  OpenExtView.super.on_mouse_moved(self, px, py, dx, dy)
  self.hovered = nil
  for i, _, x, y, w, h in self:each_option() do
    if px > x and px <= x + w and py > y and py <= y + h then
      self.hovered = i
      break
    end
  end
end


function OpenExtView:on_mouse_pressed(button, x, y, clicks)
  if OpenExtView.super.on_mouse_pressed(self, button, x, y, clicks) then return end
  if self.hovered then
    opt_actions[self.hovered](self)
  end
end


function OpenExtView:draw()
  self:draw_background(style.background)
  local x, y = self:get_content_offset()
  local lh = style.font:get_height()
  y = y + self.size.y / 2 - style.padding.y - lh
  common.draw_text(style.font, style.text, msg, "center", x, y, self.size.x, lh)
  for i, opt, x, y, w, h in self:each_option() do
    local text_color = i == self.hovered and style.background or style.text
    renderer.draw_rect(x, y, w, h, style.accent)
    if i ~= self.hovered then
      renderer.draw_rect(x + 1, y + 1, w - 2, h - 2, style.background)
    end
    common.draw_text(style.font, text_color, opt, "center", x, y, w, h)
  end
end


local function validate_doc(doc)
  local f = io.open(doc.abs_filename or "")
  if not f then return true end
  local str = f:read(128 * 4) -- max bytes for 128 codepoints
  f:close()
  return validate_utf8(str or "", 128)
end


local rootview_open_doc = RootView.open_doc
function RootView:open_doc(doc)
  -- 128 * 4 == max number of bytes for 128 codepoints
  if validate_doc(doc) then
    return rootview_open_doc(self, doc)
  else
    local node = self:get_active_node_default()
    local view = OpenExtView(doc.abs_filename or doc.filename)
    node:add_view(view)
    self.root_node:update_layout()
    return view
  end
end
