local buf, ref_buf

--==============================================================================
local min, max = math.min, math.max
local ceil, floor = math.ceil, math.floor

function minmax(x, minv, maxv)
  return min(max(x, minv),maxv)
end
----------------------------
function round(x)
  if x < 0 then return ceil(x - 0.5) else return floor(x + 0.5) end
end
----------------------------
function round_to(x, step)
  if x < 0 then return ceil(x/step - 0.5)*step else return floor(x/step + 0.5)*step end
end

--==============================================================================
function pointIN(px, py, x,y,w,h)
  return px >= x and px <= x + w and py >= y and py <= y + h
end
--------
function mouseIN(x,y,w,h)
  return pointIN(gfx.mouse_x, gfx.mouse_y, x,y,w,h)
end

-- LEFT ----------------------
function mouseDown(x,y,w,h)
  return mouse_down and mouseIN(x,y,w,h)
end
--------
function mouseUp(x,y,w,h)
  return mouse_up and mouseIN(x,y,w,h)
end
--------
function mouseClick(x,y,w,h)
  return mouseUp(x,y,w,h) and pointIN(mouse_down_x,mouse_down_y, x,y,w,h)
end

-- RIGHT ---------------------
function mouseRDown(x,y,w,h)
  return mouse_rdown and mouseIN(x,y,w,h)
end

--===========================================================================
function GetLinesFromString(str) -- with "\n"
  local lines = {}
  for line in str:gmatch(".-\n") do
    lines[#lines + 1] = line
  end
  -- Get last line or full string(if "\n" not found)
  if #lines == 0 then lines[#lines + 1] = str
    else lines[#lines + 1] = str:match(".*\n(.-)$")
  end
    -- Simple variant, but may be slowly for long monolit strings?
    -- lines[#lines + 1] = str:match(".*\n(.-)$") or text
  -------------
  return lines
end

--===========================================================================
-- Get track chunk(allow > 4MB)
function GetTrackChunk(track)
  if not track then return end
  -- Try standart function -----
  local ret, track_chunk = reaper.GetTrackStateChunk(track, "", false) -- isundo = false
  if ret and track_chunk and #track_chunk < 4194303 then return track_chunk end
  -- If chunk_size >= max_size, use wdl fast string --
  local fast_str = reaper.SNM_CreateFastString("")
  if reaper.SNM_GetSetObjectState(track, fast_str, false, false) then
    track_chunk = reaper.SNM_GetFastString(fast_str)
  end
  reaper.SNM_DeleteFastString(fast_str)
  return track_chunk
end

--===========================================================================
function draw_line2(str1, str2) -- if ref_line ~= line
  str2 = str2 or ""
  local textw  = gfx.measurestr(str1)/(#str1-1) -- -"\n"
  for i = 1, #str1 do
    local c1, c2 = str1:sub(i, i), str2:sub(i, i)
    if c1~=c2 then gfx.set(1, 0, 0) else gfx.set(0.1) end
    gfx.x = gfx.texth + textw*(i-1) -- addition align
    gfx.drawstr(c1)  
  end
  gfx.set(1, 0, 0)
  gfx.line(gfx.texth, gfx.y + gfx.texth, gfx.x, gfx.y + gfx.texth)
  gfx.set(0)
end
--===========================================================================
function Draw1()
  if buf then
    gfx.setfont(1,"Courier New",16)
    gfx.x, gfx.y = gfx.texth, gfx.texth -- start coords
    gfx.set(0.1)
    for i = s_pos, #buf do
      if buf[i] == ref_buf[i] then gfx.drawstr(buf[i])
        else draw_line2(buf[i], ref_buf[i])
      end
      gfx.x = gfx.texth
      gfx.y = gfx.y + gfx.texth
      if gfx.y > gfx.h - gfx.texth then break end 
    end
  end
end

--===========================================================================
function ScrollLimits() -- globals!
  if not buf then return end
  max_lines = minmax(floor(gfx.h/gfx.texth), 1, #buf)
  max_s_pos = max(1, #buf - max_lines)
  s_pos = minmax(s_pos, 1, max_s_pos)
end
--===========================================================================
function ScrollBar(x,y,w,h)
  gfx.set(0.94) 
  gfx.rect(x,y,w,h)
  gfx.set(0.68); 
  gfx.rect(x,y,w,h,0)
  -------------------
  local normval, sh, sy
  -------------------
  if s_pos == 1 then normval = 0 
  else normval = (s_pos - 1)/(max_s_pos - 1)
  end
  -------------------
  sh = max_lines/#buf * h      -- scroll bar h
  sh = max(sh, 20)
  sy = y + (h - sh) * normval  -- scroll bar y
  gfx.set(0.8)
  gfx.rect(x+1, sy, w-2, sh, 1)
  -------------------
  if gfx.mouse_cap&1==1 and pointIN(mouse_down_x, mouse_down_y, x,y,w,h ) and 
    (mouse_down or mouse_move) then 
    s_pos = round(max_s_pos * (gfx.mouse_y - y - sh/2) / (h - sh) )
  end
  
end
--===========================================================================
function Button(x,y,w,h, lbl)
  gfx.set(0.94) 
  gfx.rect(x,y,w,h)
  gfx.set(0.68) 
  gfx.rect(x,y,w,h,0)
  gfx.set(0.1) 
  gfx.x, gfx.y = x, y
  gfx.setfont(1,"Courier New",16) 
  gfx.drawstr(lbl, 5, x+w, y+h)
  return mouseDown(x,y,w,h)
end

--===========================================================================
function GoToNextPrev()
  local w, h = 20, gfx.texth
  local x1, x2, x3 = gfx.w - w*3, gfx.w - w*2, gfx.w - w 
  local y = 0
  ----------------------------
  if Button(x1,y,w,h, utf8.char(0x25C0)) and buf then
    for i = s_pos - 1, 1, -1 do
      if buf[i] ~= ref_buf[i] then s_pos = i; break end
    end
  end
  ------------------
  if Button(x2,y,w,h, utf8.char(0x25B6)) and buf then
    for i = s_pos + 1, #buf do
      if buf[i] ~= ref_buf[i] then s_pos = i; break end
    end
  end
  ------------------
  if Button(x3,y,w,h, utf8.char(0x25CF)) and buf then s_pos = 1 end
  ------------------
  if Button(x3,gfx.h-h,w,h, utf8.char(0x25CF)) and buf then s_pos = max_s_pos end
end

--===========================================================================

function main()
  ----------------------------
  -- chunk upd rate linked to bufsize
  if upd_cnt >= 1 then 
    local track, track_chunk
    track = reaper.GetSelectedTrack(0, 0)
    if track then track_chunk = GetTrackChunk(track) end
    if track_chunk then buf = GetLinesFromString(track_chunk) end
    if not ref_buf then ref_buf = buf end
    upd_cnt = 0 -- reset update counter
  elseif buf then
    upd_cnt = upd_cnt + 200/#buf
  end
  
  ---------------
  Draw1()
  ---------------
  
  local x,y,w,h
  if buf then
    -------------
    x,y,w,h = gfx.w - 220, 0, 160, gfx.texth, 1
    if Button(x, y, w, h, "Update Reference") then ref_buf = buf end
    -------------
    GoToNextPrev()
    -------------
    x,y,w,h = gfx.w - 20, gfx.texth, 20, gfx.h - gfx.texth*2 
    ScrollBar(x,y,w,h)
    -------------
    if gfx.mouse_wheel ~= 0 then 
      s_pos = s_pos - round(gfx.mouse_wheel/20)
      gfx.mouse_wheel = 0
    end
    -------------
    ScrollLimits()
  else
    w, h = 200, 30
    x, y = (gfx.w - w)/2, (gfx.h - h)/2 
    Button(x,y,w,h, "No track selected!")
  end

end

--------------------------------------------------------------------------------
--   INIT   --------------------------------------------------------------------
--------------------------------------------------------------------------------
function Init()
  -- Init window ------
  gfx.clear = 0xF0F0F0
  gfx.clear = 0xDEDEDC
  gui = {w = 860, h = 500 , dock = 0, x = 100, y = 300}         
  gfx.init("TEST", gui.w, gui.h, gui.dock, gui.x, gui.y)
  mouse_last_cap = 0
  mouse_down_x, mouse_down_y = 0, 0
  mouse_last_x, mouse_last_y = 0, 0
  ---------
  s_pos, max_s_pos, max_lines = 1, 1, 1
  upd_cnt = 1
end

----------------------------------------
--   Mainloop   ------------------------
----------------------------------------
function mainloop()
  -- mouse state -----------------------
  mouse_down = gfx.mouse_cap&1==1 and mouse_last_cap&1==0
  mouse_rdown = gfx.mouse_cap&2==2 and mouse_last_cap&2==0
  mouse_up = gfx.mouse_cap&1==0 and mouse_last_cap&1==1
  mouse_rup = gfx.mouse_cap&2==0 and mouse_last_cap&2==2
  if mouse_down then mouse_down_x, mouse_down_y = gfx.mouse_x, gfx.mouse_y end
  mouse_move = (mouse_last_x ~= gfx.mouse_x) or (mouse_last_y ~= gfx.mouse_y)
  -- modkeys state ---------------------
  Ctrl  = gfx.mouse_cap&4==4
  Shift = gfx.mouse_cap&8==8
  Alt   = gfx.mouse_cap&16==16
  -------------------------
  -- DRAW,MAIN functions --
  main()
  
  -- update mouse last state -----------
  gfx.mouse_wheel = 0
  gfx.mouse_hwheel = 0
  mouse_last_cap = gfx.mouse_cap
  mouse_last_x = gfx.mouse_x
  mouse_last_y = gfx.mouse_y
  
  --------------------------------------  
  gfx.update() -- Update gfx window
  --------------------------------------
  char = gfx.getchar()
  if char==32 then reaper.Main_OnCommand(40044, 0) end -- play 
  if char~=-1 then reaper.defer(mainloop) end          -- defer
end

----------------------------------------
Init()
mainloop()