// http://akelpad.sourceforge.net/forum/viewtopic.php?p=23439#23439
// Version: 2016-05-08
// Author: KDJ
// 
// *** A few operations on the bookmarks ***
// 
// Usage:
//   Call("Scripts::Main", 1, "LineBoardBookmarks.js", "Action LineLength")
//   Call("Scripts::Main", 1, "LineBoardBookmarks.js") - without argument - show menu on caret position
// 
// Arguments:
//   Action can be (not case sensitive):
//     XB  - cut all bookmarked lines
//     XU  - cut all unmarked lines
//     XBS - cut bookmarked lines in selection
//     XUS - cut unmarked lines in selection
//     CB  - copy all bookmarked lines
//     CU  - copy all unmarked lines
//     CBS - copy bookmarked lines in selection
//     CUS - copy unmarked lines in selection
//     OB  - open in new tab all bookmarked lines
//     OU  - open in new tab all unmarked lines
//     OBS - open in new tab bookmarked lines in selection
//     OUS - open in new tab unmarked lines in selection
//     VB  - paste to all bookmarked lines
//     VU  - paste to all unmarked lines
//     VBS - paste to bookmarked lines in selection
//     VUS - paste to unmarked lines in selection
//     RB  - replace all bookmarked lines
//     RU  - replace all unmarked lines
//     RBS - replace bookmarked lines in selection
//     RUS - replace unmarked lines in selection
//     DB  - delete all bookmarked lines
//     DU  - delete all unmarked lines
//     DBS - delete bookmarked lines in selection
//     DUS - delete unmarked lines in selection
//     S+  - expand selection to nearest bookmarks
//     S-  - reduce selection to nearest bookmarks
//     S+T - expand top of selection to previous bookmark
//     S-T - reduce top of selection to next bookmark
//     S+B - expand bottom of selection to next bookmark
//     S-B - reduce bottom of selection to previous bookmark
//     B   - bookmark all lines
//     BS  - bookmark selected lines
//     U   - unmark all lines
//     US  - unmark selected lines
//     I   - inverse all bookmarks
//     IS  - inverse bookmarks for selected lines
//     MC  - show menu on caret position (default)
//     MM  - show menu on mouse cursor position
//   LineLength
//     number of characters from line, which will be displayed on bookmarks list in option "Go to";
//     range is 0 - 512, default 128;
//     if 0, only numbers of lines are visible;
//     applies only to Action MC and MM
// 
// Remarks:
//   The script performs operations on entire unwrapped lines. "Word wrap" setting is ignored.
//   The script should be saved in Unicode format.

var hMainWnd    = AkelPad.GetMainWnd();
var hEditWnd    = AkelPad.GetEditWnd();
var sPluginFunc = "LineBoard::Main";

if (! (hEditWnd && AkelPad.IsPluginRunning(sPluginFunc)))
  WScript.Quit();

var oSys          = AkelPad.SystemFunction();
var bPanelVisible = IsPanelVisible();
var bRulerVisible = IsRulerdVisible();
var nRulerHeight  = GetRulerHeight();
var sAction       = "";
var nLineLen      = 128;
var sAction1, sAction2;

GetLangStrings();

if (WScript.Arguments.length)
{
  sAction = WScript.Arguments(0).toUpperCase().replace(/\s+/g, "");

  if (WScript.Arguments.length > 1)
  {
    nLineLen = parseInt(WScript.Arguments(1), 10);

    if (isNaN(nLineLen))
      nLineLen = 128;
    else if (nLineLen < 0)
      nLineLen = 0;
    else if (nLineLen > 512)
      nLineLen = 512;
  }
}

if ((! sAction) || (sAction.charAt(0) == "M"))
  sAction = Menu(sAction.charAt(1) == "M");

sAction1 = sAction.charAt(1);
sAction2 = sAction.charAt(2);

switch (sAction.charAt(0))
{
  case "":
    break;
  case "X":
    CopyOpenDeleteLines(sAction1 == "U", sAction2 == "S", true, false, true);
    break;
  case "C":
    CopyOpenDeleteLines(sAction1 == "U", sAction2 == "S", true, false, false);
    break;
  case "O":
    CopyOpenDeleteLines(sAction1 == "U", sAction2 == "S", false, true, false);
    break;
  case "V":
    PasteReplaceLines(sAction1 == "U", sAction2 == "S", true);
    break;
  case "R":
    PasteReplaceLines(sAction1 == "U", sAction2 == "S", false);
    break;
  case "D":
    CopyOpenDeleteLines(sAction1 == "U", sAction2 == "S", false, false, true);
    break;
  case "S":
    SetSelection(sAction1 == "+", sAction2);
    break;
  case "B":
    BookmarkLines(sAction1 == "S");
    break;
  case "U":
    UnmarkLines(sAction1 == "S");
    break;
  case "I":
    InverseBookmarks(sAction1 == "S");
    break;
  case "G":
    if (sAction1 == "N")
      AkelPad.Call(sPluginFunc, 18 /*go to next bookmark*/);
    else if (sAction1 == "P")
      AkelPad.Call(sPluginFunc, 19 /*go to previous bookmark*/);
    else
      GoToLine(parseInt(sAction.substr(1), 10));
    break;
  case "+":
    if (sAction1 == "P")
      ShowPanel();
    else if (sAction1 == "R")
      ShowRuler();
    break;
  case "L":
    AkelPad.Call("LineBoard::Settings");
    break;
  default:
    WScript.Echo(sTxtInvalidArg + sAction);
}

function CopyOpenDeleteLines(bUnmarked, bSelection, bCopy, bOpen, bDelete)
{
  if ((bOpen && (! AkelPad.IsMDI())) || (bDelete && AkelPad.GetEditReadOnly(0)))
    return;

  var aLine   = GetLines(bUnmarked, bSelection);
  var lpIndex = AkelPad.MemAlloc(_X64 ? 24 : 12 /*sizeof(AECHARINDEX)*/);
  var sText   = "";
  var nOffset1, nOffset2;
  var i;

  if (bDelete)
  {
    SetRedraw(false);
    BeginUndoAction();
  }

  for (i = aLine.length - 1; i >= 0; --i)
  {
    if (i in aLine)
    {
      AkelPad.SendMessage(hEditWnd, 3131 /*AEM_GETLINEINDEX*/, AkelPad.SendMessage(hEditWnd, 3142 /*AEM_GETWRAPLINE*/, i, 0), lpIndex);
      nOffset1 = AkelPad.SendMessage(hEditWnd, 3136 /*AEM_INDEXTORICHOFFSET*/, 0, lpIndex);

      AkelPad.SendMessage(hEditWnd, 3130 /*AEM_GETINDEX*/, 19 /*AEGI_WRAPLINEEND*/, lpIndex);
      nOffset2 = AkelPad.SendMessage(hEditWnd, 3136 /*AEM_INDEXTORICHOFFSET*/, 0, lpIndex) + 1;

      if (bCopy || bOpen)
        sText = AkelPad.GetTextRange(nOffset1, nOffset2, 0 /*new line as is*/) + sText;

      if (bDelete)
      {
        AkelPad.SetSel(nOffset1, nOffset2);
        AkelPad.Command(4156 /*IDM_EDIT_CLEAR*/);
      }
    }
  }

  AkelPad.MemFree(lpIndex);

  if (bCopy)
    AkelPad.SetClipboardText(sText);

  if (bDelete)
  {
    SetRedraw(true);
    StopUndoAction();
  }

  if (bOpen && sText)
  {
    AkelPad.Command(4101 /*IDM_FILE_NEW*/, 1 /*to eliminate conflict with Templates plugin: lParam=true*/);
    AkelPad.ReplaceSel(sText);
  }
}

function PasteReplaceLines(bUnmarked, bSelection, bPaste)
{
  if (AkelPad.GetEditReadOnly(0))
    return;

  var sText;

  if (bPaste)
    sText = AkelPad.GetClipboardText();
  else
    sText = GetReplaceText(bUnmarked, bSelection);

  if (sText)
  {
    var aLine   = GetLines(bUnmarked, bSelection);
    var lpIndex = AkelPad.MemAlloc(_X64 ? 24 : 12 /*sizeof(AECHARINDEX)*/);
    var nOffset1, nOffset2;
    var i;

    SetRedraw(false);
    BeginUndoAction();

    for (i = aLine.length - 1; i >= 0; --i)
    {
      if (i in aLine)
      {
        AkelPad.SendMessage(hEditWnd, 3131 /*AEM_GETLINEINDEX*/, AkelPad.SendMessage(hEditWnd, 3142 /*AEM_GETWRAPLINE*/, i, 0), lpIndex);
        nOffset1 = AkelPad.SendMessage(hEditWnd, 3136 /*AEM_INDEXTORICHOFFSET*/, 0, lpIndex);

        AkelPad.SendMessage(hEditWnd, 3130 /*AEM_GETINDEX*/, 19 /*AEGI_WRAPLINEEND*/, lpIndex);
        nOffset2 = AkelPad.SendMessage(hEditWnd, 3136 /*AEM_INDEXTORICHOFFSET*/, 0, lpIndex);

        AkelPad.SetSel(nOffset1, nOffset2);
        AkelPad.ReplaceSel(sText);
      }
    }

    AkelPad.MemFree(lpIndex);
    SetRedraw(true);
    StopUndoAction();
  }
}

function GetReplaceText(bUnmarked, bSelection)
{
  var sCaption;
  var sText;

  if (bUnmarked)
  {
    if (bSelection)
      sCaption = sTxtUnLinesSel;
    else
      sCaption = sTxtUnLinesAll;
  }
  else
  {
    if (bSelection)
      sCaption = sTxtBookLinesSel;
    else
      sCaption = sTxtBookLinesAll;
  }

  if (! bSelection)
    sText = AkelPad.GetSelText(0);

  sText = AkelPad.InputBox(hMainWnd, sCaption, sTxtReplaceWith, sText);

  return sText;
}

function SetSelection(bExpand, sTopBottom)
{
  var aNearBook = GetNearBookmarks(bExpand, sTopBottom);
  var nSelStart = AkelPad.GetSelStart();
  var nSelEnd   = AkelPad.GetSelEnd();
  var lpIndex   = AkelPad.MemAlloc(_X64 ? 24 : 12 /*sizeof(AECHARINDEX)*/);

  if (sTopBottom != "B")
  {
    if (aNearBook[0] >= 0)
    {
      AkelPad.SendMessage(hEditWnd, 3131 /*AEM_GETLINEINDEX*/, AkelPad.SendMessage(hEditWnd, 3142 /*AEM_GETWRAPLINE*/, aNearBook[0], 0), lpIndex);
      nSelStart = AkelPad.SendMessage(hEditWnd, 3136 /*AEM_INDEXTORICHOFFSET*/, 0, lpIndex);
    }
  }

  if (sTopBottom != "T")
  {
    if (aNearBook[1] >= 0)
    {
      AkelPad.SendMessage(hEditWnd, 3131 /*AEM_GETLINEINDEX*/, AkelPad.SendMessage(hEditWnd, 3142 /*AEM_GETWRAPLINE*/, aNearBook[1], 0), lpIndex);
      AkelPad.SendMessage(hEditWnd, 3130 /*AEM_GETINDEX*/, 19 /*AEGI_WRAPLINEEND*/, lpIndex);
      nSelEnd = AkelPad.SendMessage(hEditWnd, 3136 /*AEM_INDEXTORICHOFFSET*/, 0, lpIndex);
    }
  }

  AkelPad.MemFree(lpIndex);

  if ((nSelStart != AkelPad.GetSelStart()) || (nSelEnd != AkelPad.GetSelEnd()))
    AkelPad.SetSel(nSelStart, nSelEnd);
}

function BookmarkLines(bSelection)
{
  var nLines = GetLinesCount();
  var sBooks = "";
  var aBook;
  var aSelLine;
  var i;

  if (bSelection)
  {
    aBook    = GetBookmarksArray();
    aSelLine = GetSelLines();

    for (i = 0; i < nLines; ++i)
    {
      if ((i >= aSelLine[0]) && (i <= aSelLine[1]))
        sBooks += i + ",";
      else
      {
        if (i in aBook)
          sBooks += i + ",";
      }
    }
  }
  else
  {
    for (i = 0; i < nLines; ++i)
      sBooks += i + ",";
  }

  SetBookmarks(sBooks);
}

function UnmarkLines(bSelection)
{
  var sBooks = "";

  if (bSelection)
  {
    var aBook    = GetBookmarksArray();
    var nLines   = GetLinesCount();
    var aSelLine = GetSelLines();
    var i;

    for (i = 0; i < nLines; ++i)
    {
      if ((i < aSelLine[0]) || (i > aSelLine[1]))
      {
        if (i in aBook)
          sBooks += i + ",";
      }
    }
  }

  SetBookmarks(sBooks);
}

function InverseBookmarks(bSelection)
{
  var nLines = GetLinesCount();
  var sBooks = "";
  var aBook  = GetBookmarksArray();
  var aSelLine;
  var i;

  if (bSelection)
  {
    aSelLine = GetSelLines();

    for (i = 0; i < nLines; ++i)
    {
      if ((i >= aSelLine[0]) && (i <= aSelLine[1]))
      {
        if (! (i in aBook))
          sBooks += i + ",";
      }
      else
      {
        if (i in aBook)
          sBooks += i + ",";
      }
    }
  }
  else
  {
    for (i = 0; i < nLines; ++i)
    {
      if (! (i in aBook))
        sBooks += i + ",";
    }
  }

  SetBookmarks(sBooks);
}

function GetLines(bUnmarked, bSelection)
{
  var aBook = GetBookmarksArray();
  var aLine = [];
  var aSelLine;
  var nLines;
  var i;

  if (bSelection)
  {
    aSelLine = GetSelLines();

    if (bUnmarked)
    {
      for (i = aSelLine[0]; i <= aSelLine[1]; ++i)
      {
        if (! (i in aBook))
          aLine[i] = 0;
      }
    }
    else
    {
      for (i = aSelLine[0]; i <= aSelLine[1]; ++i)
      {
        if (i in aBook)
          aLine[i] = 0;
      }
    }
  }
  else
  {
    if (bUnmarked)
    {
      nLines = GetLinesCount();

      for (i = 0; i < nLines; ++i)
      {
        if (! (i in aBook))
          aLine[i] = 0;
      }
    }
    else
      aLine = aBook;
  }

  return aLine;
}

function GetLinesCount(bWrap)
{
  return AkelPad.SendMessage(hEditWnd, 3129 /*AEM_GETLINENUMBER*/, bWrap ? 0 /*AEGL_LINECOUNT*/ : 11 /*AEGL_LINEUNWRAPCOUNT*/, 0);
}

function GetSelLines()
{
  return [AkelPad.SendMessage(hEditWnd, 3143 /*AEM_GETUNWRAPLINE*/, AkelPad.SendMessage(hEditWnd, 3129 /*AEM_GETLINENUMBER*/, 1 /*AEGL_FIRSTSELLINE*/, 0), 0),
          AkelPad.SendMessage(hEditWnd, 3143 /*AEM_GETUNWRAPLINE*/, AkelPad.SendMessage(hEditWnd, 3129 /*AEM_GETLINENUMBER*/, 2 /*AEGL_LASTSELLINE*/, 0), 0)];
}

function GetNearBookmarks(bExpand, sTopBottom)
{
  var aBook1   = GetBookmarksArray();
  var aBook2   = [-1, -1];
  var aSelLine = GetSelLines();
  var nLines   = GetLinesCount();
  var i;

  if (bExpand)
  {
    if (sTopBottom != "B")
    {
      for (i = aSelLine[0] - 1; i >= 0; --i)
      {
        if (i in aBook1)
        {
          aBook2[0] = i;
          break;
        }
      }
    }
    if (sTopBottom != "T")
    {
      for (i = aSelLine[1] + 1; i < nLines; ++i)
      {
        if (i in aBook1)
        {
          aBook2[1] = i;
          break;
        }
      }
    }
  }
  else
  {
    if (sTopBottom != "B")
    {
      for (i = aSelLine[0] + 1; i <= aSelLine[1]; ++i)
      {
        if (i in aBook1)
        {
          aBook2[0] = i;
          break;
        }
      }
    }
    if (sTopBottom != "T")
    {
      for (i = aSelLine[1] - 1; i >= aSelLine[0]; --i)
      {
        if (i in aBook1)
        {
          aBook2[1] = i;
          break;
        }
      }
    }
  }

  if ((aBook2[1] >= 0) && (aBook2[0] > aBook2[1]))
    aBook2 = [-1, -1];

  return aBook2;
}

function GetBookmarksArray()
{
  //returns sparse array
  var aBook1    = [];
  var aBook2    = [];
  var lpBookLen = AkelPad.MemAlloc(4 /*sizeof(int)*/);
  var lpBookStr;
  var sBooks;
  var i;

  AkelPad.CallW(sPluginFunc, 12, hEditWnd, 0, 0, lpBookLen);
  lpBookStr = AkelPad.MemAlloc(AkelPad.MemRead(lpBookLen, 3 /*DT_DWORD*/) * 2 /*sizeof(wchar_t)*/);

  AkelPad.CallW(sPluginFunc, 12, hEditWnd, 0, lpBookStr, 0);
  sBooks = AkelPad.MemRead(lpBookStr, 1 /*DT_UNICODE*/);

  AkelPad.MemFree(lpBookLen);
  AkelPad.MemFree(lpBookStr);

  if (sBooks)
  {
    aBook1 = sBooks.split(",");

    for (i = 0; i < aBook1.length; ++i)
      aBook2[aBook1[i]] = 0;
  }

  return aBook2;
}

function SetBookmarks(sBooks)
{
  //delete all bookmarks
  AkelPad.CallW(sPluginFunc, 14, hEditWnd, 0);
  //set new bookmarks (works very slow, if amount > 10000)
  AkelPad.CallW(sPluginFunc, 13, hEditWnd, 0, sBooks);
}

function SetRedraw(bRedraw)
{
	AkelPad.SendMessage(hMainWnd, 11 /*WM_SETREDRAW*/, bRedraw, 0);
	AkelPad.SendMessage(hEditWnd, 11 /*WM_SETREDRAW*/, bRedraw, 0);
	if (bRedraw)
  {
    oSys.Call("User32::InvalidateRect", hMainWnd, 0, true);
	  oSys.Call("User32::InvalidateRect", hEditWnd, 0, true);
  }
}

function BeginUndoAction()
{
  AkelPad.SendMessage(hEditWnd, 3080 /*AEM_STOPGROUPTYPING*/, 0, 0);
  AkelPad.SendMessage(hEditWnd, 3081 /*AEM_BEGINUNDOACTION*/, 0, 0);
}

function StopUndoAction()
{
  AkelPad.SendMessage(hEditWnd, 3082 /*AEM_ENDUNDOACTION*/, 0, 0);
  AkelPad.SendMessage(hEditWnd, 3080 /*AEM_STOPGROUPTYPING*/, 0, 0);
}

function GoToLine(nLine)
{
  if (! isNaN(nLine))
  {
    var lpIndex = AkelPad.MemAlloc(_X64 ? 24 : 12 /*sizeof(AECHARINDEX)*/);

    AkelPad.SendMessage(hEditWnd, 3131 /*AEM_GETLINEINDEX*/, AkelPad.SendMessage(hEditWnd, 3142 /*AEM_GETWRAPLINE*/, nLine, 0), lpIndex);
    AkelPad.SendMessage(hEditWnd, 3124 /*AEM_EXSETSEL*/, lpIndex, lpIndex);

    AkelPad.MemFree(lpIndex);
  }
}

function Menu(bOnCursor)
{
  var aMenu1 = [[sTxtBookLinesAll, "B" ],
                [sTxtUnLinesAll,   "U" ],
                [""],
                [sTxtBookLinesSel, "BS"],
                [sTxtUnLinesSel,   "US"]];

  var aMenu2 = [[sTxtExpand,       "+" ],
                [sTxtExpandTop,    "+T"],
                [sTxtExpandBottom, "+B"],
                [""],
                [sTxtReduce,       "-" ],
                [sTxtReduceTop,    "-T"],
                [sTxtReduceBottom, "-B"]];

  var aMenu3 = [[sTxtAll,   "" ],
                [sTxtInSel, "S"]];

  var aMenu4 = GetBookmarksListArray();

  var aMenu = [[sTxtCut,           "X",  aMenu1, ! AkelPad.GetEditReadOnly(0)],
               [sTxtCopy,          "C",  aMenu1, true],
               [sTxtOpenInTab,     "O",  aMenu1, AkelPad.IsMDI()],
               [sTxtPasteTo,       "V",  aMenu1, AkelPad.SendMessage(hEditWnd, 3035 /*AEM_CANPASTE*/, 0, 0)],
               [sTxtReplace,       "R",  aMenu1, ! AkelPad.GetEditReadOnly(0)],
               [sTxtDelete,        "D",  aMenu1, ! AkelPad.GetEditReadOnly(0)],
               [""],
               [sTxtSelection,     "S",  aMenu2, true],
               [""],
               [sTxtBookmarkLines, "B",  aMenu3, true],
               [sTxtUnmarkLines,   "U",  aMenu3, true],
               [sTxtInverseBooks,  "I",  aMenu3, true],
               [""],
               [sTxtGoTo,          "G",  aMenu4, aMenu4.length],
               [sTxtNext,          "GN",      0, aMenu4.length],
               [sTxtPrevious,      "GP",      0, aMenu4.length],
               [""],
               [sTxtShowPanel,     "+P",      0, true, bPanelVisible],
               [sTxtShowRuler,     "+R",      0, true, bPanelVisible && bRulerVisible && nRulerHeight],
               [sTxtLBSettings,    "L",       0, true]];

  var sReturn = "";
  var hMenu;
  var nFlag;
  var nMenuID;
  var lpPoint;
  var nX, nY;
  var hWndHid;
  var nCmd, nCmd1, nCmd2;
  var i, n;

  for (i = 0; i < aMenu.length; ++i)
  {
    if (aMenu[i][0] && aMenu[i][2])
    {
      aMenu[i].Handle = oSys.Call("User32::CreatePopupMenu");
      for (n = 0; n < aMenu[i][2].length; ++n)
        oSys.Call("User32::AppendMenuW", aMenu[i].Handle, aMenu[i][2][n][0] ? 0 /*MF_STRING*/ : 0x0800 /*MF_SEPARATOR*/, ((i + 1) << 24) | n, aMenu[i][2][n][0]);
    }
  }

  hMenu = oSys.Call("User32::CreatePopupMenu");
  for (i = 0; i < aMenu.length; ++i)
  {
    if (aMenu[i].Handle)
    {
      nFlag = 0x10 /*MF_POPUP*/;

      if (! aMenu[i][3])
        nFlag |= 0x01 /*MF_GRAYED*/;

      nMenuID = aMenu[i].Handle;
    } 
    else if (aMenu[i][0])
    {
      nFlag = 0 /*MF_STRING*/;

      if (! aMenu[i][3])
        nFlag |= 0x01 /*MF_GRAYED*/;
      if (aMenu[i][4])
        nFlag |= 0x08 /*MF_CHECKED*/;

      nMenuID = (i + 1) << 24;
    }
    else
      nFlag = 0x0800 /*MF_SEPARATOR*/;

    oSys.Call("User32::AppendMenuW", hMenu, nFlag, nMenuID, aMenu[i][0]);
  }

  lpPoint = AkelPad.MemAlloc(8 /*sizeof(POINT)*/);

  if (bOnCursor)
  {
    oSys.Call("User32::GetCursorPos", lpPoint);
    nX = AkelPad.MemRead(_PtrAdd(lpPoint, 0), 3 /*DT_DWORD*/);
    nY = AkelPad.MemRead(_PtrAdd(lpPoint, 4), 3 /*DT_DWORD*/);
  }
  else
  {
    AkelPad.SendMessage(hEditWnd, 3190 /*AEM_GETCARETPOS*/, lpPoint, 0);
    oSys.Call("User32::ClientToScreen", hEditWnd, lpPoint);
    nX = AkelPad.MemRead(_PtrAdd(lpPoint, 0), 3 /*DT_DWORD*/);
    nY = AkelPad.MemRead(_PtrAdd(lpPoint, 4), 3 /*DT_DWORD*/) + AkelPad.SendMessage(hEditWnd, 3188 /*AEM_GETCHARSIZE*/, 0 /*AECS_HEIGHT*/, 0);
  }

  AkelPad.MemFree(lpPoint);

  hWndHid = oSys.Call("User32::CreateWindowExW", 0, "STATIC", 0, 0x50000000 /*WS_VISIBLE|WS_CHILD*/, 0, 0, 0, 0, hEditWnd, 0, AkelPad.GetInstanceDll(), 0);
  oSys.Call("User32::SetFocus", hWndHid);

  nCmd = oSys.Call("User32::TrackPopupMenu", hMenu, 0x0180 /*TPM_RETURNCMD|TPM_NONOTIFY*/, nX, nY, 0, hWndHid, 0);

  for (i = 0; i < aMenu.length; ++i)
    oSys.Call("User32::DestroyMenu", aMenu[i].Handle);
  oSys.Call("User32::DestroyMenu", hMenu);
  oSys.Call("User32::DestroyWindow", hWndHid);

  if (nCmd)
  {
    nCmd1   = ((nCmd >> 24) & 0xFFFFFF) - 1;
    nCmd2   = nCmd & 0xFFFFFF;
    sReturn = aMenu[nCmd1][1];

    if (aMenu[nCmd1][2])
      sReturn += aMenu[nCmd1][2][nCmd2][1];
  }

  return sReturn;
}

function GetBookmarksListArray()
{
  var aBook1    = GetBookmarksArray();
  var aBook2    = [];
  var bLineWrap =  AkelPad.SendMessage(hEditWnd, 3241 /*AEM_GETWORDWRAP*/, 0, 0) && (AkelPad.SendMessage(hMainWnd, 1222 /*AKD_GETMAININFO*/, 111 /*MI_STATUSPOSTYPE*/, 0) & 2 /*SPT_LINEWRAP*/);
  var nNumLen   = GetLinesCount(bLineWrap).toString().length;
  var lpIndex   = AkelPad.MemAlloc(_X64 ? 24 : 12 /*sizeof(AECHARINDEX)*/);
  var sNumber;
  var nOffset1, nOffset2;
  var i;

  for (i in aBook1)
  {
    sNumber = (bLineWrap ? AkelPad.SendMessage(hEditWnd, 3142 /*AEM_GETWRAPLINE*/, parseInt(i), 0) + 1 : parseInt(i) + 1).toString();

    while (sNumber.length < nNumLen)
      sNumber = "0" + sNumber;

    AkelPad.SendMessage(hEditWnd, 3131 /*AEM_GETLINEINDEX*/, AkelPad.SendMessage(hEditWnd, 3142 /*AEM_GETWRAPLINE*/, parseInt(i), 0), lpIndex);
    nOffset1 = AkelPad.SendMessage(hEditWnd, 3136 /*AEM_INDEXTORICHOFFSET*/, 0, lpIndex);

    AkelPad.SendMessage(hEditWnd, 3130 /*AEM_GETINDEX*/, 19 /*AEGI_WRAPLINEEND*/, lpIndex);
    nOffset2 = AkelPad.SendMessage(hEditWnd, 3136 /*AEM_INDEXTORICHOFFSET*/, 0, lpIndex);

    aBook2.push([sNumber + ":    " + AkelPad.GetTextRange(nOffset1, nOffset2).replace(/^\s+/, "").substr(0, nLineLen), i]);
  }

  AkelPad.MemFree(lpIndex);

  return aBook2;
}

function IsPanelVisible()
{
  var lpVisible = AkelPad.MemAlloc(4 /*sizeof(BOOL)*/);
  var bVisible;

  AkelPad.CallW(sPluginFunc, 4, lpVisible);
  bVisible = AkelPad.MemRead(lpVisible, 3 /*DT_DWORD*/);
  AkelPad.MemFree(lpVisible);

  return bVisible;
}

function IsRulerdVisible()
{
  var lpHeight  = AkelPad.MemAlloc(4 /*sizeof(int)*/);
  var lpVisible = AkelPad.MemAlloc(4 /*sizeof(BOOL)*/);
  var bVisible;

  AkelPad.CallW(sPluginFunc, 2, lpHeight, lpVisible);
  bVisible = AkelPad.MemRead(lpVisible, 3 /*DT_DWORD*/);
  AkelPad.MemFree(lpHeight);
  AkelPad.MemFree(lpVisible);

  return bVisible;
}

function GetRulerHeight()
{
  var lpHeight = AkelPad.MemAlloc(4 /*sizeof(int)*/);
  var nHeight;

  AkelPad.CallW(sPluginFunc, 2, lpHeight);
  nHeight = AkelPad.MemRead(lpHeight, 3 /*DT_DWORD*/);
  AkelPad.MemFree(lpHeight);

  return nHeight;
}

function SetRulerHeight(nHeight)
{
  //if nHeight==-1, then invert ruler visibility
  AkelPad.CallW(sPluginFunc, 3, nHeight);
}

function ShowPanel()
{
  AkelPad.CallW(sPluginFunc, 4); //show/hide panel
}

function ShowRuler()
{
  if (bPanelVisible && bRulerVisible && nRulerHeight)
    SetRulerHeight(-1);
  else
  {
    if (! bPanelVisible)
      ShowPanel();
    if (! bRulerVisible)
      SetRulerHeight(-1);
    if (! nRulerHeight)
      SetRulerHeight(17);
  }
}

function GetLangStrings()
{
  sTxtInvalidArg    = "无效参数: ";
  sTxtCut           = "剪切";
  sTxtCopy          = "复制";
  sTxtOpenInTab     = "在新标签页打开";
  sTxtPasteTo       = "粘贴到";
  sTxtReplace       = "替换";
  sTxtDelete        = "删除";
  sTxtSelection     = "选定区域";
  sTxtBookmarkLines = "添加书签";
  sTxtUnmarkLines   = "取消书签";
  sTxtInverseBooks  = "反转书签";
  sTxtGoTo          = "转到";
  sTxtNext          = "下一个";
  sTxtPrevious      = "上一个";
  sTxtShowPanel     = "显示面板";
  sTxtShowRuler     = "显示标尺";
  sTxtLBSettings    = "LineBoard 设置";
  sTxtBookLinesAll  = "所有已加书签的行";
  sTxtUnLinesAll    = "所有未加书签的行";
  sTxtBookLinesSel  = "选定区域中已加书签的行";
  sTxtUnLinesSel    = "选定区域中未加书签的行";
  sTxtExpand        = "扩展到最近的书签";
  sTxtExpandTop     = "向上扩展到上一书签";
  sTxtExpandBottom  = "向下扩展到下一书签";
  sTxtReduce        = "收缩到最近的书签";
  sTxtReduceTop     = "向上收缩到下一书签";
  sTxtReduceBottom  = "向下收缩到上一书签";
  sTxtAll           = "全部";
  sTxtInSel         = "在选定区域";
  sTxtReplaceWith   = "替换为:";
}
