-- common_window.inc
--
-- Common functions for handling windows: opening, arranging, unpinning
--
-- DO NOT INCLUDE THIS FILE! Include common.inc instead
--

TOP_LEFT = 0;
TOP_RIGHT = 1;
BOTTOM_LEFT = 2;
BOTTOM_RIGHT = 3;

ARRANGE_STASH = 0;
ARRANGE_GRID = 1;
ARRANGE_CASCADE = 2;

-------------------------------------------------------------------------------
-- stashWindow(sourceX, sourceY, corner, bounds)
--
-- Drags a window from a given location to a corner of the screen.
--
-- sourceX, sourceY -- location of anchor point to drag window
-- corner -- destination corner (TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT,
--           or BOTTOM_RIGHT)
-- bounds (optional) -- bounds of window
-- offset (optional) -- Offset from the corner to stash the windows. Useful when
--                      the state of the window can change before being arranged.
--                      This will be a hard offset, ignoring the current window
--                      size entirely (so always supply both x and y).
--                      Note: This does not auto-correct to keep things off the game
--                      window borders. Factor that space in when using this.
--                      object structure: offset.x, offset.y (as positive values).
-------------------------------------------------------------------------------

local stashBit = 0;

function stashWindow(sourceX, sourceY, corner, bounds, offset)
  if not sourceX or not sourceY or not corner then
    error("Incorrect number of arguments for stashWindow()");
  end
  stashBit = (stashBit + 2) % 4;
  if not bounds then
    srReadScreen();
    bounds = srGetWindowBorders(sourceX, sourceY);
  end
  local size = {};
  size[0] = bounds[2] - bounds[0];
  size[1] = bounds[3] - bounds[1];
  local screen = srGetWindowSize();
  local dest = {};


  if offset ~= nill then
    -- There's an offset. Override to place the window at the exact offset from the
    -- specified corner.
    if corner == TOP_LEFT then
      dest[0] = offset.x;
      dest[1] = offset.y + stashBit;
    elseif corner == TOP_RIGHT then
      dest[0] = screen[0] - offset.x;
      dest[1] = offset.y + stashBit;
    elseif corner == BOTTOM_LEFT then
      dest[0] = offset.x;
      dest[1] = screen[1] - offset.y - stashBit;
    elseif corner == BOTTOM_RIGHT then
      dest[0] = screen[0] - offset.x;
      dest[1] = screen[1] - offset.y - stashBit;
    end
  else
    -- No offset, normal logic.
    dest[0] = screen[0] - size[0] + (sourceX - bounds[0]) - 5;
    if corner == TOP_LEFT or corner == BOTTOM_LEFT then
      dest[0] = sourceX - bounds[0] + 5;
    end
    dest[1] = screen[1] - size[1] + (sourceY - bounds[1]) - stashBit - 5;
    if corner == TOP_LEFT or corner == TOP_RIGHT then
      dest[1] = sourceY - bounds[1] + stashBit + 5;
    end
  end

  safeDrag(sourceX, sourceY, dest[0], dest[1]);
  srSetMousePos(sourceX, sourceY);
  lsSleep(click_delay);
  return dest;
end

-------------------------------------------------------------------------------
-- stashAllWindows(corner)
--
-- Move all windows not in the same quadrant as corner into the given
-- corner.  Use this twice on different corners to make sure that you
-- grab all windows.
--
-- corner -- Destination corner
-------------------------------------------------------------------------------

function stashAllWindows(corner)
  if not corner then
    error("Incorrect number of arguments for stashAllWindows()");
  end
  local top = (corner == TOP_LEFT or corner == TOP_RIGHT);
  local left = (corner == TOP_LEFT or corner == BOTTOM_LEFT);
  local screen = srGetWindowSize();
  local done = false;
  local stashes = {};
  srReadScreen();
  local anchors = findAllImages("ThisIs.png");
  while not done do
    done = true;
    for i=1,#anchors do
      if not wasStashed(anchors[i], stashes) then
        safeClick(anchors[i][0], anchors[i][1]);
        lsSleep(click_delay);
        local lastPos = stashWindow(anchors[i][0], anchors[i][1], corner);
        stashes[#stashes + 1] = lastPos;
        done = false;
      end
    end
    lsSleep(tick_delay);
    srReadScreen();
    anchors = findAllImages("ThisIs.png");
    statusScreen("Stashing Windows");
  end
end

function wasStashed(pos, stashes)
  local result = false;
  for i=1,#stashes do
    local diffX = math.abs(stashes[i][0] - pos[0]);
    local diffY = math.abs(stashes[i][1] - pos[1]);
    if diffX < 20 and diffY < 20 then
      result = true;
      break;
    end
  end
  return result;
end

-------------------------------------------------------------------------------
-- openAndPin(x, y, timeout)
--
-- Click at the given position to open a window, then pin that window.
--
-- x, y -- position to click
-- timeout -- time to wait before giving up
--
-- Returns true on success.
-------------------------------------------------------------------------------

function openAndPin(x, y, timeout)
  if not x or not y or not timeout then
    error("Incorrect number of arguments for openAndPin()");
  end
  local success = false;
  srReadScreen();
  local anchors = findAllImages("ThisIs.png");
  local startTime = lsGetTimer();
  safeBegin();
  srSetMousePos(x, y);
  lsSleep(50);
  srClickMouse(x, y);
  lsDoFrame();
  statusScreen("Clicking to open window");
  local done = false;
  local anchor = nil;
  while not done do
    checkBreak();
    srReadScreen();
    local newAnchors = findAllImages("ThisIs.png");
    done = false;
    local limit = math.min(#anchors, #newAnchors);
    for i=1,limit do
      if anchors[i][0] ~= newAnchors[i][0]
        or anchors[i][1] ~= newAnchors[i][1]
      then
        anchor = newAnchors[i];
        done = true;
        break;
      end
    end
    if not done and #anchors ~= #newAnchors then
      done = true;
      anchor = newAnchors[#newAnchors];
    end
    if not done and lsGetTimer() > startTime + timeout then
      done = true;
    end
    anchors = newAnchors;
    statusScreen("Waiting for window to open");
    lsSleep(tick_delay);
  end
  if anchor then
    success = true;
    if findImageInWindow("unpinnedPin.png", x+5, y) then
      safeClick(anchor[0], anchor[1], 1);
      statusScreen("Pinning window");
    end
  end
  return success;
end

-------------------------------------------------------------------------------
-- arrangeInGrid(cascade, uiGap, varWidth, varHeight, sizeRight, offsetWidth, offsetHeight, visibleStats)
--
-- Arrange all open windows into a grid on the screen. Stashes them first.
--
-- cascade (optional) -- arrange windows into an overlapping cascade
-- uiGap (optional) -- leave a gap at the top for the ui
-- varWidth, varHeight (optional) -- specify window width/height - instead of detecting.
-- sizeRight (optional) -- amount of space to right of screen, defaults to automato width
-- offsetWidth (optional) -- adds an additional width offset
-- offsetHeight (optional) -- adds an additional height offset
-- visibleStats (optional) -- adds additional space below top ui to make sure stat bubbles are visible
-------------------------------------------------------------------------------

function arrangeInGrid(cascade, uiGap, varWidth, varHeight, sizeRight, offsetWidth, offsetHeight, visibleStats)
  stashAllWindows(BOTTOM_LEFT);
  stashAllWindows(BOTTOM_RIGHT);
  arrangeStashed(cascade, uiGap, varWidth, varHeight, sizeRight, offsetWidth, offsetHeight, windowCap, bottomRightOffset, visibleStats);
end

-------------------------------------------------------------------------------
-- arrangeStashed(cascade, uiGap, varWidth, varHeight, sizeRight, offsetWidth, offsetHeight, visibleStats)
--
-- Arrange all open windows into a grid on the screen. Assumes they
-- are pre-stashed.
--
-- cascade (optional) -- arrange windows into a overlapping cascade
-- uiGap (optional) -- leave a 50px gap at the top (for water or similar)
-- varWidth (optional) -- width of windows, if not set width will be initial window width
-- varHeight (optional) --  height of windows, if not set height will be initial window height
-- sizeRight (optional) -- amount of space to right of screen, defaults to automato width
-- offsetWidth (optional) -- adds an additional width offset
-- offsetHeight (optional) -- adds an additional height offset
-- windowCap (optional) -- a hard limit to the amount of windows to arrange. Will not arrange
--                         more if provided.
-- bottomRightOffset -- overrides the default -20 x -20 from bottom right corner offset when
--                      looking for a stashed window to arrange. Used when stashing with an
--                      offset as the window may not be present at -20 x -20.
--                      data structure: bottomRightOffset.x, bottomRightOffset.y
-- visibleStats (optional) -- adds additional space below top ui to make sure stat bubbles are visible
-------------------------------------------------------------------------------

function arrangeStashed(cascade, uiGap, varWidth, varHeight, sizeRight, offsetWidth, offsetHeight, windowCap, bottomRightOffset, visibleStats)
  local screen = srGetWindowSize();

  local bottomRightX = screen[0] - 20;
  local bottomRightY = screen[1] - 20;

  -- Override bottomRight stuff when that's provided.
  if bottomRightOffset ~= nil then
    bottomRightX = screen[0] - bottomRightOffset.x;
    bottomRightY = screen[1] - bottomRightOffset.y;
  end

 if sizeRight then
  screen[0] = screen[0] - sizeRight;
 else
  screen[0] = screen[0] - lsGetWindowSize()[0];
 end

  local currentX = 8;
  local currentY = 10;
    if uiGap then
      if visibleStats then
        currentY = 130;
      else
        currentY = 95;
      end
    end
  local initY = currentY;
  local lastX = 0;
  local lastY = 0;
  local xMax = 0;
  local yMax = 0;
  local counter = 0
  srReadScreen();

  local window = findImageInWindow("windowCorner.png", bottomRightX, bottomRightY, 100);
  while window and (window[0] > lastX or window[1] > lastY) and window[1] > initY do
    counter = counter + 1

    if (windowCap ~= nil and counter > windowCap) then
      break;
    end

    safeClick(window[0]+5, window[1]+5);
    lsSleep(click_delay);
    srReadScreen();
    local bounds = srGetWindowBorders(window[0] + 5, window[1] + 5);
    local width, height;
    if not varWidth then
      width = bounds[2] - bounds[0] + 7; -- The +7 offset is due to bigger borders in T9. Prevent touching/overlapping.
    else
      width = varWidth;
    end
    if not varHeight then
      height = bounds[3] - bounds[1] + 14; -- The +14 offset is due to bigger borders in T9. Prevent touching/overlapping.
    else
      height = varHeight;
    end
    if offsetWidth then
     width = width + offsetWidth;
    end
    if offsetHeight then
     height = height + offsetHeight;
    end

    if cascade then
      if currentY + height >= screen[1] then
        currentX = currentX + xMax;
        currentY = 5;
        xMax = 0;
      end
      if currentX + width >= screen[0] then
        error("Cannot arrange these windows into a cascade.");
      end
    else
      if currentX + width >= screen[0] then
        currentX = 8;
        currentY = currentY + yMax;
        yMax = 0;
      end
      if currentY + height >= screen[1] then
        error("Cannot arrange these windows into a grid.");
      end
    end
    lastX = window[0] - bounds[0] + currentX;
    lastY = window[1] - bounds[1] + currentY;
    safeDrag(window[0]+5, window[1]+5, lastX, lastY);
    lsSleep(tick_delay);
    if cascade then
      currentX = currentX + 8;
      currentY = currentY + 25;

      -- This fixes a bug and I'm not sure why it does this.
      -- The 2nd window on a cascade paritally covers This Is on first window. Left border is touching on Window 1 and 2.
      if counter == 1 then
        currentX = currentX + 4 -- Add an additional 4 on currentX and Y on 2nd window
        currentY = currentY + 4
      end

    else
      currentX = currentX + width + 5;
    end
    xMax = math.max(xMax, width);
    yMax = math.max(yMax, height);
    lsSleep(tick_delay);
    srReadScreen();
    window = findImageInWindow("windowCorner.png", bottomRightX, bottomRightY, 100);
    statusScreen("Arranging Windows " .. counter);
  end
end

-------------------------------------------------------------------------------
-- closeAllWindows(x, y, width, height)
--
-- Close all open windows.
--
-- x, y (optional) -- origin of box to search
-- width, height (optional) -- size of box to search
-------------------------------------------------------------------------------

function closeAllWindows(x, y, width, height)
  if not x then
    x = 0;
  end
  if not y then
    y = 0;
  end
  if not width then
    width = srGetWindowSize()[0];
  end
  if not height then
    height = srGetWindowSize()[1];
  end

  local closeImages = {"ThisIs.png", "UnPin.png"};
  local closeRight = {1, 1};

  local found = true;
  while found do
    found = false;
    for i=1,#closeImages do
      local image = closeImages[i];
      local right = closeRight[i];
      srReadScreen();
      local images = findAllImagesInRange(image, x, y, width, height);
      while #images >= 1 do
        done = true;
        if image == "UnPin.png" then
          safeClick(images[#images][0]-15, images[#images][1], right);
          lsSleep(75);
        else
          safeClick(images[#images][0], images[#images][1], right);
          lsSleep(75);
        end
        sleepWithStatus(10, "Please wait ...", nil, nil, "Closing Windows");
        srReadScreen();
        images = findAllImagesInRange(image, x, y, width, height);
      end
    end
  end
end

-------------------------------------------------------------------------------
-- closeEmptyRegions()
--
-- Closes all empty windows on the screen
--
-------------------------------------------------------------------------------

function closeEmptyRegions()
  allRegs = findAllTextRegions();
  if allRegs then
    for i = 1, #allRegs do
      local p = parseRegion(allRegs[i]);
      if p == nil then
        unpinRegion(allRegs[i]);
      end
    end
  end
end

-------------------------------------------------------------------------------
-- windowManager(title, message, allowCascade, allowUIGap, varWidth, varHeight, sizeRight, offsetWidth, offsetHeight, default_focus, default_uiGap)
--
-- Run a window manager interface to let the user open, pin, arrange,
-- or clear windows.
--
-- title (optional) -- title of window
-- message (optional) -- extra instructions for the user
-- allowCascade (optional) -- allow the user to arrange in a cascade
-- allowUIGap (optional) -- allow the user to add a gap to skip the UI
-- varWidth, varHeight (optional) -- specify window width/height - instead of detecting
-- sizeRight (optional) -- amount of space to right of screen, defaults to automato width
-- offsetWidth (optional) -- adds an additional width offset
-- offsetHeight (optional) -- adds an additional height offset
-- default_focus (optional) -- bool: force 'Pause for Focus' box to be checked
-- default_uiGap (optional) -- bool: force 'Leave Gap for the UI' box to be checked

-------------------------------------------------------------------------------

pauseForFocus = false;

function windowManager(title, message, allowCascade, allowUIGap, varWidth, varHeight, sizeRight, offsetWidth, offsetHeight, default_focus, default_uiGap)
  if not title then
    title = "Window Manager";
  end
  if not message then
    message = "Tap Ctrl to open and pin a window.\nTap Alt to open, pin and stash."
  end
  local uiGap = false;
  local visibleStats = false;

  if default_uiGap then
    uiGap = true;
  end

  if default_focus then
    pauseForFocus = true;
  end

  local arrangeMode = nil;
  local shift = false;
  local done = false;
  while not done do
    local y = 10;
    lsPrint(10, y, 0, 1.0, 1.0, 0xFFFFFFff, title);
    y = y + 50;

    if lsButtonText(25, y, 0, 250, 0x80D080ff, "Form Grid") then
      lsDoFrame();
      if pauseForFocus then
        askForWindow(focusMessage);
      end
      arrangeInGrid(false, uiGap, varWidth, varHeight, sizeRight, offsetWidth, offsetHeight, visibleStats);
      arrangeMode = ARRANGE_GRID;
    end
    y = y + 30;

    if allowCascade then
      if lsButtonText(25, y, 0, 250, 0x80D080ff, "Form Cascade") then
        lsDoFrame();
        if pauseForFocus then
          askForWindow(focusMessage);
        end
        arrangeInGrid(true, uiGap, varWidth, varHeight, sizeRight, offsetWidth, offsetHeight);
        arrangeMode = ARRANGE_CASCADE;
      end
      y = y + 30;
    end

    if lsButtonText(25, y, 0, 250, 0x80D080ff, "Stash Windows (Right)") then
      lsDoFrame();
      if pauseForFocus then
        askForWindow(focusMessage);
      end
      stashAllWindows(BOTTOM_RIGHT);
      arrangeMode = ARRANGE_STASH;
    end
    y = y + 30;

    if lsButtonText(25, y, 0, 250, 0x80D080ff, "Stash Windows (Left)") then
      lsDoFrame();
      if pauseForFocus then
        askForWindow(focusMessage);
      end
      stashAllWindows(BOTTOM_LEFT);
      arrangeMode = ARRANGE_STASH;
    end
    y = y + 30;

    if lsButtonText(25, y, 0, 250, 0x80D080ff, "Un-Pin Windows") then
      lsDoFrame();
      if pauseForFocus then
        askForWindow(focusMessage);
      end
      closeAllWindows();
    end
    y = y + 40;

    pauseForFocus = CheckBox(25, y, 10, 0xffffffff, " Pause for Focus",
                            pauseForFocus);
    if allowUIGap then
      y = y + 20;
      uiGap = CheckBox(25, y, 10, 0xffffffff, " Leave Gap for the UI",
                            uiGap);
        if uiGap then
          y = y + 20;
          visibleStats = CheckBox(25, y, 10, 0xffffffff, " Leave space for stat bubbles",
                                visibleStats);
        end
    end
      y = y + 30;

    lsPrintWrapped(10, y, 0, lsScreenY - 20, 0.7, 0.7, 0xd0d0d0ff,
                   message);
    if lsButtonText(lsScreenX - 110, lsScreenY - 30, 0, 100,
                    0xffffffff, "End Script") then
      error(quit_message);
    end
    while lsControlHeld() do
      checkBreak();
      shift = true;
    end
    if shift then
      shift = false;
      local x = 0;
      local y = 0;
      x, y = srMousePos();
      openAndPin(x, y, 500);
    end
    while lsAltHeld() do
      checkBreak();
      shift = true;
    end
    if shift then
      shift = false;
      local x = 0;
      local y = 0;
      x, y = srMousePos();
      openAndPin(x, y, 500);
      stashWindow(x, y, BOTTOM_RIGHT);
    end
    if lsButtonText(10, lsScreenY - 30, 0, 100, 0xFFFFFFff, "Done") then
      done = true;
    end
    checkBreak();
    lsDoFrame();
    lsSleep(tick_delay);
  end
  
  return arrangeMode;
end

-------------------------------------------------------------------------------
-- unpinManager(title, message)
--
-- Interface to help the user clear away all windows when a macro completes.
--
-- title (optional)  -- title of window
-- message (optional) -- extra instructions for the user
-------------------------------------------------------------------------------

function unpinManager(title, message)
  if not title then
    title = "Unpin Manager";
  end
  if not message then
    message = "";
  end
  while lsShiftHeld() and lsControlHeld() do
    lsPrintWrapped(10, 80, 0, lsScreenX - 20, 0.8, 0.8, 0x808080ff,
                  "Please release Ctrl+Shift");
    lsSleep(tick_delay);
    lsDoFrame();
  end
  local done = false;
  while not done do
    lsPrint(10, 10, 0, 1.0, 1.0, 0xFFFFFFff, title);
    lsPrintWrapped(10, 60, 0, lsScreenX - 20, 0.7, 0.7,
                   0xd0d0d0ff, message);
    if lsButtonText(30, 160, 0, 250, 0x80D080ff, "Un-Pin Windows") then
      lsDoFrame();
      if pauseForFocus then
        askForWindow(focusMessage);
      end
      closeAllWindows();
    end
    if lsButtonText(lsScreenX - 110, lsScreenY - 30, 0, 100,
                    0xffffffff, "Done") then
      done = true;
    end
    if lsShiftHeld() and lsControlHeld() then
      done = true;
    end
    lsDoFrame();
    lsSleep(tick_delay);
  end
end

-------------------------------------------------------------------------------
-- unpinRegion(region)
--
-- Unpins a region
--
-- region - region object to unpin
-------------------------------------------------------------------------------

function unpinRegion(region)
  if region then
    lsPrintln("Unpinning at: " .. region[0] + region[2] - 15 .. ", " .. region[1] + region[3] + 15);
    safeClick(region[0] + region[2] - 15, region[1] + 15, 0);
  end
end

-------------------------------------------------------------------------------
-- unpinOnExit(f)
--
-- Execute function f and present an unpin manager window when it
-- completes or if an error is thrown.
--
-- f -- function to execute with no arguments
-------------------------------------------------------------------------------

function unpinOnExit(f)
  if not f then
    error("Incorrect number of arguments for f");
  end
  local status, error = pcall(f);
  unpinManager("Complete", error);
end

-------------------------------------------------------------------------------
-- closeEmptyAndErrorWindows()
--
-- Closes all empty windows and error windows on the screen
--
-------------------------------------------------------------------------------

function closeEmptyAndErrorWindows()
  local windows = findAllWindows();
  local closed = 0;
  if windows then
    for i = 1, #windows do
      temp = makeBox(windows[i].x + 7, windows[i].y + 4,
          windows[i].width -35, windows[i].height - 7);
      fr = parseWindow(temp);

      if #fr == 0 then
        unpinWindow(windows[i]);
        closed = closed + 1;
      end
    end
  end
  return closed;
end

-------------------------------------------------------------------------------
-- unpinWindow(window)
--
-- Unpins a window
--
-- window - window object to unpin
-------------------------------------------------------------------------------

function unpinWindow(window)
  if window then
    safeClick(window.x + window.width - 15, window.y + 15, 0);
  end
end
