// http://akelpad.sourceforge.net/forum/viewtopic.php?p=10988#10988
// Version: 2018-02-01
// Author: KDJ
//
// *** Sort lines ***
//
// Sort lines according to two keys: by line length and by string, alphabetically or numerically; removes duplicate lines, reverses the order of lines
//
// Usage:
//   Call("Scripts::Main", 1, "SortLines.js")
//
// Shortcut keys in dialog box:
//   Enter - Sort
//   Esc   - Close
//   Shift+Alt+Right (Left, Down, Up, End, Home, PgDn, PgUp) - move dialog box.

var sTxtCaption   = "行排序";
var sTxtRange     = "排序范围";
var sTxtFrom      = "从";
var sTxtTo        = "到";
var sTxtLines     = "行:";
var sTxtColumns   = "列(&C):";
var sTxtKeys      = "排序关键字";
var sTxtKey1      = "关键字 &1 - 行长度";
var sTxtKey2      = "关键字 &2 - 行内容";
var sTxtRTL       = "从右向左读取";
var sTxtEntLine   = "整行(&E)";
var sTxtDesc      = "降序(&D)";
var sTxtIgCase    = "忽略大小写(&I)";
var sTxtString    = "字串(&S)";
var sTxtLocale    = "字母顺序(&A)";
var sTxtNum       = "数字顺序(&N)";
var sTxtSortAnd   = "排序，同时也";
var sTxtRemoDuKey = "删除重复行(&M)";
var sTxtExtrDuKey = "提取重复行(&X)";
var sTxtExtrUnKey = "提取唯一行(&C)";
var sTxtNoSort    = "不排序，仅";
var sTxtReverse   = "反向排列行(&O)";
var sTxtCopyToCB  = "复制已排序的内容到剪贴板，而不是替换文本(&T)";
var sTxtUsePlug   = "使用 &Format 插件";
var sTxtSort      = "排序";
var sTxtUndo      = "撤消(&U)";
var sTxtRedo      = "恢复(&R)";
var sTxtNoRangeL  = "未指定行范围，无法排序。";
var sTxtNoKey2    = "第二个排序关键字中未指定列范围。";

var DT_DWORD    = 3;
var BM_SETCHECK = 241;

var oSys       = AkelPad.SystemFunction();
var hMainWnd   = AkelPad.GetMainWnd();
var hEditWnd   = AkelPad.GetEditWnd();
var hGuiFont   = oSys.Call("Gdi32::GetStockObject", 17 /*DEFAULT_GUI_FONT*/);
var hInstDLL   = AkelPad.GetInstanceDll();
var sClassName = "AkelPad::Scripts::" + WScript.ScriptName + "::" + hInstDLL;
var bKey1      = 0;
var bDescLen   = 0;
var bKey2      = 1;
var bRTL       = 0;
var bEntLine   = 0;
var bDesc      = 0;
var bIgCase    = 1;
var nMethod    = 1;
var nSortAnd   = 0;
var nNoSort    = 0;
var bCopyToCB  = 0;
var bUsePlug   = 0;
var nUndoLimit = 0;
var nRedoLimit = 0;
var nWordWrap;
var nLastCol;
var hWndDlg;
var lpBuffer;
var bGetSel;
var bIsReturn;
var hFocus;
var nWndPosX;
var nWndPosY;

ReadWriteIni(0);

var aWnd        = [];
var IDRANGE     = 1000;
var IDFROM      = 1001;
var IDTO        = 1002;
var IDLINES     = 1003;
var IDBEGLINE   = 1004;
var IDENDLINE   = 1005;
var IDSORT1     = 1006;
var IDKEY1      = 1007;
var IDDESCLEN   = 1008;
var IDSORT2     = 1009;
var IDKEY2      = 1010;
var IDRTL       = 1011;
var IDENTLINE   = 1012;
var IDFROM2     = 1013;
var IDTO2       = 1014;
var IDCOLUMNS   = 1015;
var IDBEGCOL    = 1016;
var IDENDCOL    = 1017;
var IDDESC      = 1018;
var IDIGCASE    = 1019;
var IDSORT3     = 1020;
var IDSTRING    = 1021;
var IDLOCALE    = 1022;
var IDNUM       = 1023;
var IDSORT4     = 1024;
var IDREMODUKEY = 1025;
var IDEXTRDUKEY = 1026;
var IDEXTRUNKEY = 1027;
var IDNOSORT    = 1028;
var IDREVERSE   = 1029;
var IDREMODULIN = 1030;
var IDEXTRDULIN = 1031;
var IDEXTRUNLIN = 1032;
var IDCOPYTOCB  = 1033;
var IDUSEPLUG   = 1034;
var IDSORT      = 1035;
var IDUNDO      = 1036;
var IDREDO      = 1037;

var CLASS   = 0;
var WND     = 1;
var EXSTYLE = 2;
var STYLE   = 3;
var X       = 4;
var Y       = 5;
var W       = 6;
var H       = 7;
var TXT     = 8;

//0x50000000 - WS_VISIBLE|WS_CHILD
//0x50000007 - WS_VISIBLE|WS_CHILD|BS_GROUPBOX
//0x50000009 - WS_VISIBLE|WS_CHILD|BS_AUTORADIOBUTTON
//0x50010000 - WS_VISIBLE|WS_CHILD|WS_TABSTOP
//0x50010003 - WS_VISIBLE|WS_CHILD|WS_TABSTOP|BS_AUTOCHECKBOX
//0x50012080 - WS_VISIBLE|WS_CHILD|WS_TABSTOP|ES_AUTOHSCROLL|ES_NUMBER
//Windows               CLASS, WND, EXSTYLE,      STYLE,   X,   Y,   W,   H, TXT
aWnd[IDRANGE    ] = ["BUTTON",   0,       0, 0x50000007,  10,  10, 185,  70, sTxtRange];
aWnd[IDFROM     ] = ["STATIC",   0,       0, 0x50000000,  70,  30,  55,  13, sTxtFrom];
aWnd[IDTO       ] = ["STATIC",   0,       0, 0x50000000, 130,  30,  55,  13, sTxtTo];
aWnd[IDLINES    ] = ["STATIC",   0,       0, 0x50000000,  20,  45,  50,  13, sTxtLines];
aWnd[IDBEGLINE  ] = ["STATIC",   0,   0x200, 0x50000000,  70,  45,  55,  20, ""];
aWnd[IDENDLINE  ] = ["STATIC",   0,   0x200, 0x50000000, 130,  45,  55,  20, ""];
aWnd[IDSORT1    ] = ["BUTTON",   0,       0, 0x50000007,  10,  90, 290,  52, sTxtKeys];
aWnd[IDKEY1     ] = ["BUTTON",   0,       0, 0x50010003,  20, 115, 150,  16, sTxtKey1];
aWnd[IDDESCLEN  ] = ["BUTTON",   0,       0, 0x50010003, 205, 115,  90,  16, sTxtDesc];
aWnd[IDSORT2    ] = ["BUTTON",   0,       0, 0x50000007,  10, 135, 290, 130, ""];
aWnd[IDKEY2     ] = ["BUTTON",   0,       0, 0x50010003,  20, 150, 150,  16, sTxtKey2];
aWnd[IDRTL      ] = ["BUTTON",   0,       0, 0x50010003,  40, 170, 140,  16, sTxtRTL];
aWnd[IDENTLINE  ] = ["BUTTON",   0,       0, 0x50010003,  40, 190, 140,  16, sTxtEntLine];
aWnd[IDFROM2    ] = ["STATIC",   0,       0, 0x50000000,  70, 215,  55,  13, sTxtFrom];
aWnd[IDTO2      ] = ["STATIC",   0,       0, 0x50000000, 130, 215,  55,  13, sTxtTo];
aWnd[IDCOLUMNS  ] = ["STATIC",   0,       0, 0x50000000,  20, 230,  50,  13, sTxtColumns];
aWnd[IDBEGCOL   ] = ["EDIT",     0,   0x200, 0x50012080,  70, 230,  55,  20, ""];
aWnd[IDENDCOL   ] = ["EDIT",     0,   0x200, 0x50012080, 130, 230,  55,  20, ""];
aWnd[IDDESC     ] = ["BUTTON",   0,       0, 0x50010003, 205, 150,  90,  16, sTxtDesc];
aWnd[IDIGCASE   ] = ["BUTTON",   0,       0, 0x50010003, 205, 170,  90,  16, sTxtIgCase];
aWnd[IDSORT3    ] = ["BUTTON",   0,       0, 0x50000007, 195, 185, 105,  80, ""];
aWnd[IDSTRING   ] = ["BUTTON",   0,       0, 0x50000009, 205, 200,  90,  16, sTxtString];
aWnd[IDLOCALE   ] = ["BUTTON",   0,       0, 0x50000009, 205, 220,  90,  16, sTxtLocale];
aWnd[IDNUM      ] = ["BUTTON",   0,       0, 0x50000009, 205, 240,  90,  16, sTxtNum];
aWnd[IDSORT4    ] = ["BUTTON",   0,       0, 0x50000007,  10, 275, 290,  85, sTxtSortAnd];
aWnd[IDREMODUKEY] = ["BUTTON",   0,       0, 0x50010003,  60, 295, 230,  16, sTxtRemoDuKey];
aWnd[IDEXTRDUKEY] = ["BUTTON",   0,       0, 0x50010003,  60, 315, 230,  16, sTxtExtrDuKey];
aWnd[IDEXTRUNKEY] = ["BUTTON",   0,       0, 0x50010003,  60, 335, 230,  16, sTxtExtrUnKey];
aWnd[IDNOSORT   ] = ["BUTTON",   0,       0, 0x50000007,  10, 370, 290, 105, sTxtNoSort];
aWnd[IDREVERSE  ] = ["BUTTON",   0,       0, 0x50010003,  60, 390, 230,  16, sTxtReverse];
aWnd[IDREMODULIN] = ["BUTTON",   0,       0, 0x50010003,  60, 410, 230,  16, sTxtRemoDuKey];
aWnd[IDEXTRDULIN] = ["BUTTON",   0,       0, 0x50010003,  60, 430, 230,  16, sTxtExtrDuKey];
aWnd[IDEXTRUNLIN] = ["BUTTON",   0,       0, 0x50010003,  60, 450, 230,  16, sTxtExtrUnKey];
aWnd[IDCOPYTOCB ] = ["BUTTON",   0,       0, 0x50010003,  20, 485, 270,  16, sTxtCopyToCB];
aWnd[IDUSEPLUG  ] = ["BUTTON",   0,       0, 0x50010003,  20, 505, 270,  16, sTxtUsePlug];
aWnd[IDSORT     ] = ["BUTTON",   0,       0, 0x50010000, 220,  10,  80,  23, sTxtSort];
aWnd[IDUNDO     ] = ["BUTTON",   0,       0, 0x50010000, 220,  35,  80,  23, sTxtUndo];
aWnd[IDREDO     ] = ["BUTTON",   0,       0, 0x50010000, 220,  60,  80,  23, sTxtRedo];

if (hEditWnd)
{
  nWordWrap = AkelPad.SendMessage(hEditWnd, 3241 /*AEM_GETWORDWRAP*/, 0, 0);
  if (nWordWrap > 0) AkelPad.Command(4209 /*IDM_VIEW_WORDWRAP*/);

  if (AkelPad.WindowRegisterClass(sClassName))
  {
    if (lpBuffer = AkelPad.MemAlloc(256 * _TSIZE))
    {
      hWndDlg = oSys.Call("User32::CreateWindowEx" + _TCHAR,
                  0,               //dwExStyle
                  sClassName,      //lpClassName
                  sTxtCaption,     //lpWindowName
                  0x90CA0000,      //WS_VISIBLE|WS_POPUP|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX
                  0,               //x
                  0,               //y
                  315,             //nWidth
                  560,             //nHeight
                  hMainWnd,        //hWndParent
                  0,               //ID
                  hInstDLL,        //hInstance
                  DialogCallback); //Script function callback. To use it class must be registered by WindowRegisterClass.
      if (hWndDlg)
      {
        //Disable main window, to make dialog modal
        oSys.Call("User32::EnableWindow", hMainWnd, false);

        //Message loop
        AkelPad.WindowGetMessage();
      }
      AkelPad.MemFree(lpBuffer);
    }
    AkelPad.WindowUnregisterClass(sClassName);
  }

  if (nWordWrap > 0) AkelPad.Command(4209 /*IDM_VIEW_WORDWRAP*/);
}

function DialogCallback(hWnd, uMsg, wParam, lParam)
{
  var i, nLowParam;

  if (uMsg == 1) //WM_CREATE
  {
    for (i = 1000; i < aWnd.length; ++i)
    {
      aWnd[i][WND] = oSys.Call("User32::CreateWindowEx" + _TCHAR,
                       aWnd[i][EXSTYLE], //dwExStyle
                       aWnd[i][CLASS],   //lpClassName
                       0,                //lpWindowName
                       aWnd[i][STYLE],   //dwStyle
                       aWnd[i][X],       //x
                       aWnd[i][Y],       //y
                       aWnd[i][W],       //nWidth
                       aWnd[i][H],       //nHeight
                       hWnd,             //hWndParent
                       i,                //ID
                       hInstDLL,         //hInstance
                       0);               //lpParam
      SetWindowFontAndText(aWnd[i][WND], hGuiFont, aWnd[i][TXT]);
    }

    GetSelection();
    SetSelection();
    CheckButtons();
    CheckUndoRedo();

    //Set window position
    if ((nWndPosX == undefined) || (nWndPosY == undefined))
      MoveWindow(hMainWnd, hWnd, "RT");
    else
      MoveWindow(hMainWnd, hWnd, [nWndPosX, nWndPosY]);

    hFocus = aWnd[bUsePlug ? IDKEY2 : IDKEY1][WND];
  }

  else if ((uMsg == 6 /*WM_ACTIVATE*/) && (! wParam))
    hFocus = oSys.Call("User32::GetFocus");

  else if (uMsg == 7) //WM_SETFOCUS
    oSys.Call("User32::SetFocus", hFocus);

  else if (uMsg == 256) //WM_KEYDOWN
  {
    if (wParam == 27) //VK_ESCAPE
      oSys.Call("User32::PostMessage" + _TCHAR, hWndDlg, 16 /*WM_CLOSE*/, 0, 0);
    else if (wParam == 13) //VK_RETURN
    {
      if (! bIsReturn)
      {
        hFocus = oSys.Call("User32::GetFocus");
        if ((hFocus != aWnd[IDSORT][WND]) && (hFocus != aWnd[IDUNDO][WND]) && (hFocus != aWnd[IDREDO][WND]))
          oSys.Call("User32::PostMessage" + _TCHAR, hWnd, 273 /*WM_COMMAND*/, IDSORT, 0);
      }
    }
  }

  else if (uMsg == 257) //WM_KEYUP
    bIsReturn = 0;

  else if ((uMsg == 260) /*WM_SYSKEYDOWN*/ &&
           (oSys.Call("User32::GetAsyncKeyState", 0xA0 /*VK_LSHIFT*/)))
  {
    if (wParam == 0x27) //VK_RIGHT
      MoveWindow(hMainWnd, hWnd, "R");
    else if (wParam == 0x25) //VK_LEFT
      MoveWindow(hMainWnd, hWnd, "L");
    else if (wParam == 0x28) //VK_DOWN
      MoveWindow(hMainWnd, hWnd, "D");
    else if (wParam == 0x26) //VK_UP
      MoveWindow(hMainWnd, hWnd, "U");
    else if (wParam == 0x23) //VK_END
      MoveWindow(hMainWnd, hWnd, "E");
    else if (wParam == 0x24) //VK_HOME
      MoveWindow(hMainWnd, hWnd, "H");
    else if (wParam == 0x22) //VK_NEXT
      MoveWindow(hMainWnd, hWnd, "B");
    else if (wParam == 0x21) //VK_PRIOR
      MoveWindow(hMainWnd, hWnd, "T");
  }

  else if (uMsg == 273) //WM_COMMAND
  {
    nLowParam = LoWord(wParam);

    if ((nLowParam == IDBEGCOL) || (nLowParam == IDENDCOL))
    {
      CheckEditValue(nLowParam);
      if (bGetSel)
        SetSelection();
    }

    else if (((nLowParam >= IDKEY1) && (nLowParam <= IDENTLINE)) || ((nLowParam >= IDDESC) && (nLowParam <= IDUSEPLUG)))
      CheckButtons(nLowParam);

    else if (nLowParam == IDSORT)
    {
      if (SortLines())
      {
        CheckUndoRedo(nLowParam);
        GetSelection();
      }
    }

    else if (nLowParam == IDUNDO)
    {
      bIsReturn = 1;
      CheckUndoRedo(nLowParam);
      GetSelection();
    }

    else if (nLowParam == IDREDO)
    {
      bIsReturn = 1;
      CheckUndoRedo(nLowParam);
      GetSelection();
    }
  }

  else if (uMsg == 16) //WM_CLOSE
  {
    ReadWriteIni(1);

    //Enable main window
    oSys.Call("User32::EnableWindow", hMainWnd, true);
    //Destroy dialog
    oSys.Call("User32::DestroyWindow", hWnd);
  }

  else if (uMsg == 2) //WM_DESTROY
    oSys.Call("User32::PostQuitMessage", 0);

  else
    SetDefID(hWnd);

  return 0;
}

function SetWindowFontAndText(hWnd, hFont, sText)
{
  AkelPad.SendMessage(hWnd, 48 /*WM_SETFONT*/, hFont, true);
  oSys.Call("User32::SetWindowText" + _TCHAR, hWnd, sText);
}

function MoveWindow(hWndParent, hWnd, vAction)
{
  var aRectParent;
  var aRect;
  var nX;
  var nY;

  if (! hWndParent)
    hWndParent=oSys.Call("User32::GetDesktopWindow");

  aRectParent = GetWindowPos(hWndParent);
  aRect       = GetWindowPos(hWnd);

  nX = aRect.left;
  nY = aRect.top;

  if (vAction == "R") //Move right
    nX = aRect.left + ((aRect.left < aRectParent.right - 50) ? 20 : 0);
  else if (vAction == "L") //Move left
    nX = aRect.left - ((aRect.right > aRectParent.left + 50) ? 20 : 0);
  else if (vAction == "D") //Move down
    nY = aRect.top + ((aRect.top < aRectParent.bottom - 50) ? 20 : 0);
  else if (vAction == "U") //Move up
    nY = aRect.top - ((aRect.bottom > aRectParent.top + 50) ? 20 : 0);
  else if (vAction == "E") //Move end (right)
    nX = aRect.left + (aRectParent.right - aRect.right);
  else if (vAction == "H") //Move home (left)
    nX = aRect.left + (aRectParent.left - aRect.left);
  else if (vAction == "B") //Move bottom
    nY = aRect.top + (aRectParent.bottom - aRect.bottom);
  else if (vAction == "T") //Move top
    nY = aRect.top + (aRectParent.top - aRect.top);
  else if (vAction == "C") //Center window
  {
    nX = aRectParent.left + ((aRectParent.right  - aRectParent.left) / 2 - (aRect.right  - aRect.left) / 2);
    nY = aRectParent.top  + ((aRectParent.bottom - aRectParent.top)  / 2 - (aRect.bottom - aRect.top)  / 2);
  }
  else if (vAction == "LT") //Move left top
  {
    nX = aRectParent.left;
    nY = aRectParent.top;
  }
  else if (vAction == "RT") //Move right top
  {
    nX = aRect.left + (aRectParent.right - aRect.right);
    nY = aRectParent.top;
  }
  else if (vAction == "LB") //Move left bottom
  {
    nX = aRectParent.left;
    nY = aRect.top + (aRectParent.bottom - aRect.bottom);
  }
  else if (vAction == "RB") //Move right bottom
  {
    nX = aRect.left + (aRectParent.right - aRect.right);
    nY = aRect.top + (aRectParent.bottom - aRect.bottom);
  }
  else
  {
    nX = vAction[0];
    nY = vAction[1];
  }

  oSys.Call("User32::SetWindowPos", hWnd, 0, nX, nY, 0, 0, 0x15 /*SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOSIZE*/);
}

function GetWindowPos(hWnd)
{
  var lpRect = AkelPad.MemAlloc(16) //sizeof(RECT);
  var aRect  = [];

  oSys.Call("User32::GetWindowRect", hWnd, lpRect);

  aRect.left   = AkelPad.MemRead(_PtrAdd(lpRect,  0), DT_DWORD);
  aRect.top    = AkelPad.MemRead(_PtrAdd(lpRect,  4), DT_DWORD);
  aRect.right  = AkelPad.MemRead(_PtrAdd(lpRect,  8), DT_DWORD);
  aRect.bottom = AkelPad.MemRead(_PtrAdd(lpRect, 12), DT_DWORD);
  AkelPad.MemFree(lpRect);

  return aRect;
}

function LoWord(nParam)
{
  return (nParam & 0xFFFF);
}

function SetDefID(hWnd)
{
  var nID    = 1000;
  var hFocus = oSys.Call("User32::GetFocus");

  while ((nID < aWnd.length) && (hFocus != aWnd[nID][WND]))
    ++nID;

  if ((nID != IDUNDO) && (nID != IDREDO))
    nID = IDSORT;

  oSys.Call("User32::DefDlgProc" + _TCHAR, hWnd, 1025 /*DM_SETDEFID*/, nID, 0);
}

function ReadWriteIni(bWrite)
{
  var oFSO     = new ActiveXObject("Scripting.FileSystemObject");
  var sIniName = WScript.ScriptFullName.substring(0, WScript.ScriptFullName.lastIndexOf(".")) + ".ini";
  var aRect;
  var oFile;
  var sIniTxt;

  if (bWrite)
  {
    aRect = GetWindowPos(hWndDlg);
    sIniTxt =
      'nWndPosX='  + aRect.left + ';\r\n' +
      'nWndPosY='  + aRect.top  + ';\r\n' +
      'bKey1='     + bKey1 + ';\r\n' +
      'bDescLen='  + bDescLen + ';\r\n' +
      'bKey2='     + bKey2 + ';\r\n' +
      'bRTL='      + bRTL + ';\r\n' +
      'bEntLine='  + bEntLine + ';\r\n' +
      'bDesc='     + bDesc + ';\r\n' +
      'bIgCase='   + bIgCase + ';\r\n' +
      'nMethod='   + nMethod + ';\r\n' +
      'nSortAnd='  + nSortAnd + ';\r\n' +
      'nNoSort='   + nNoSort + ';\r\n' +
      'bCopyToCB=' + bCopyToCB + ';\r\n' +
      'bUsePlug='  + bUsePlug + ';'

    oFile = oFSO.OpenTextFile(sIniName, 2, true, 0);
    oFile.Write(sIniTxt);
    oFile.Close();
  }

  else if (oFSO.FileExists(sIniName))
  {
    try
    {
      eval(AkelPad.ReadFile(sIniName));
    }
    catch (oError)
    {}
  }
}

function CheckButtons(nButton)
{
  if ((nButton == IDKEY1) && ((! bKey1) || bKey2))
    bKey1 = ! bKey1;
  else if (nButton == IDDESCLEN)
    bDescLen = ! bDescLen;
  else if ((nButton == IDKEY2) && ((! bKey2) || bKey1))
    bKey2 = ! bKey2;
  else if (nButton == IDRTL)
    bRTL = ! bRTL;
  else if (nButton == IDENTLINE)
    bEntLine = ! bEntLine;
  else if (nButton == IDDESC)
    bDesc = ! bDesc;
  else if (nButton == IDIGCASE)
    bIgCase = ! bIgCase;
  else if ((nButton == IDSTRING) || (nButton == IDLOCALE) || (nButton == IDNUM))
    nMethod = nButton - IDSTRING + 1;
  else if ((nButton == IDREMODUKEY) || (nButton == IDEXTRDUKEY) || (nButton == IDEXTRUNKEY))
  {
    if (nSortAnd == nButton - IDREMODUKEY + 1)
      nSortAnd = 0;
    else
      nSortAnd = nButton - IDREMODUKEY + 1;
  }
  else if ((nButton == IDREVERSE) || (nButton == IDREMODULIN) || (nButton == IDEXTRDULIN) || (nButton == IDEXTRUNLIN))
  {
    if (nNoSort == nButton - IDREVERSE + 1)
      nNoSort = 0;
    else
      nNoSort = nButton - IDREVERSE + 1;
  }
  else if (nButton == IDCOPYTOCB)
    bCopyToCB = ! bCopyToCB;
  else if (nButton == IDUSEPLUG)
    bUsePlug = ! bUsePlug;

  if ((bUsePlug) && ((nNoSort > 1) || (nMethod == 2)))
    nMethod = 1;

  AkelPad.SendMessage(aWnd[IDKEY1][WND],      BM_SETCHECK, bKey1,           0);
  AkelPad.SendMessage(aWnd[IDDESCLEN][WND],   BM_SETCHECK, bDescLen,        0);
  AkelPad.SendMessage(aWnd[IDKEY2][WND],      BM_SETCHECK, bKey2,           0);
  AkelPad.SendMessage(aWnd[IDRTL][WND],       BM_SETCHECK, bRTL,            0);
  AkelPad.SendMessage(aWnd[IDENTLINE][WND],   BM_SETCHECK, bEntLine,        0);
  AkelPad.SendMessage(aWnd[IDDESC][WND],      BM_SETCHECK, bDesc,           0);
  AkelPad.SendMessage(aWnd[IDIGCASE][WND],    BM_SETCHECK, bIgCase,         0);
  AkelPad.SendMessage(aWnd[IDSTRING][WND],    BM_SETCHECK, (nMethod == 1),  0);
  AkelPad.SendMessage(aWnd[IDLOCALE][WND],    BM_SETCHECK, (nMethod == 2),  0);
  AkelPad.SendMessage(aWnd[IDNUM][WND],       BM_SETCHECK, (nMethod == 3),  0);
  AkelPad.SendMessage(aWnd[IDREMODUKEY][WND], BM_SETCHECK, (nSortAnd == 1), 0);
  AkelPad.SendMessage(aWnd[IDEXTRDUKEY][WND], BM_SETCHECK, (nSortAnd == 2), 0);
  AkelPad.SendMessage(aWnd[IDEXTRUNKEY][WND], BM_SETCHECK, (nSortAnd == 3), 0);
  AkelPad.SendMessage(aWnd[IDREVERSE][WND],   BM_SETCHECK, (nNoSort == 1),  0);
  AkelPad.SendMessage(aWnd[IDREMODULIN][WND], BM_SETCHECK, (nNoSort == 2),  0);
  AkelPad.SendMessage(aWnd[IDEXTRDULIN][WND], BM_SETCHECK, (nNoSort == 3),  0);
  AkelPad.SendMessage(aWnd[IDEXTRUNLIN][WND], BM_SETCHECK, (nNoSort == 4),  0);
  AkelPad.SendMessage(aWnd[IDCOPYTOCB][WND],  BM_SETCHECK, bCopyToCB,       0);
  AkelPad.SendMessage(aWnd[IDUSEPLUG][WND],   BM_SETCHECK, bUsePlug,        0);

  oSys.Call("User32::EnableWindow", aWnd[IDKEY1][WND],      (nNoSort != 1) && (! bUsePlug));
  oSys.Call("User32::EnableWindow", aWnd[IDDESCLEN][WND],   (nNoSort == 0) && bKey1 && (! bUsePlug));
  oSys.Call("User32::EnableWindow", aWnd[IDKEY2][WND],      (nNoSort != 1));
  oSys.Call("User32::EnableWindow", aWnd[IDRTL][WND],       (nNoSort != 1) && bKey2 && (! bUsePlug));
  oSys.Call("User32::EnableWindow", aWnd[IDENTLINE][WND],   (nNoSort != 1) && bKey2);
  oSys.Call("User32::EnableWindow", aWnd[IDFROM2][WND],     (nNoSort != 1) && bKey2 && (! bEntLine));
  oSys.Call("User32::EnableWindow", aWnd[IDTO2][WND],       (nNoSort != 1) && bKey2 && (! bEntLine));
  oSys.Call("User32::EnableWindow", aWnd[IDCOLUMNS][WND],   (nNoSort != 1) && bKey2 && (! bEntLine));
  oSys.Call("User32::EnableWindow", aWnd[IDBEGCOL][WND],    (nNoSort != 1) && bKey2 && (! bEntLine));
  oSys.Call("User32::EnableWindow", aWnd[IDENDCOL][WND],    (nNoSort != 1) && bKey2 && (! bEntLine));
  oSys.Call("User32::EnableWindow", aWnd[IDDESC][WND],      (nNoSort == 0) && bKey2);
  oSys.Call("User32::EnableWindow", aWnd[IDIGCASE][WND],    (nNoSort != 1) && bKey2 && ((nMethod != 3) || bUsePlug));
  oSys.Call("User32::EnableWindow", aWnd[IDSTRING][WND],    (nNoSort != 1) && bKey2);
  oSys.Call("User32::EnableWindow", aWnd[IDLOCALE][WND],    (nNoSort != 1) && bKey2 && (! bUsePlug));
  oSys.Call("User32::EnableWindow", aWnd[IDNUM][WND],       (nNoSort != 1) && bKey2 && (! (bUsePlug && (nNoSort > 1))));
  oSys.Call("User32::EnableWindow", aWnd[IDREMODUKEY][WND], (nNoSort == 0) && (! bUsePlug));
  oSys.Call("User32::EnableWindow", aWnd[IDEXTRDUKEY][WND], (nNoSort == 0) && (! bUsePlug));
  oSys.Call("User32::EnableWindow", aWnd[IDEXTRUNKEY][WND], (nNoSort == 0) && (! bUsePlug));
  oSys.Call("User32::EnableWindow", aWnd[IDCOPYTOCB][WND],  (! bUsePlug));
}

function CheckUndoRedo(nButton)
{
  var nBegSel  = AkelPad.GetSelStart();
  var sTxtUndo = aWnd[IDUNDO][TXT];
  var sTxtRedo = aWnd[IDREDO][TXT];

  if (nButton == IDSORT)
  {
    if (nUndoLimit < AkelPad.SendMessage(hEditWnd, 3084 /*AEM_GETUNDOLIMIT*/, 0, 0))
      ++nUndoLimit;
    nRedoLimit = 0;
  }
  else if ((nButton == IDUNDO) && (nUndoLimit))
  {
    --nUndoLimit;
    ++nRedoLimit;
    AkelPad.SendMessage(hEditWnd, 3077 /*AEM_UNDO*/, 0, 0);
  }
  else if ((nButton == IDREDO) && (nRedoLimit))
  {
    ++nUndoLimit;
    --nRedoLimit;
    AkelPad.SendMessage(hEditWnd, 3078 /*AEM_REDO*/, 0, 0);
    AkelPad.SetSel(nBegSel, AkelPad.GetSelEnd());
  }

  if (nUndoLimit)
    sTxtUndo += " (" + nUndoLimit + ")";
  if (nRedoLimit)
    sTxtRedo += " (" + nRedoLimit + ")";

  if (((oSys.Call("User32::GetFocus") == aWnd[IDUNDO][WND]) && (nUndoLimit == 0)) ||
      ((oSys.Call("User32::GetFocus") == aWnd[IDREDO][WND]) && (nRedoLimit == 0)))
    oSys.Call("User32::SetFocus", aWnd[IDKEY1][WND]);

  SetWindowFontAndText(aWnd[IDUNDO][WND], hGuiFont, sTxtUndo);
  SetWindowFontAndText(aWnd[IDREDO][WND], hGuiFont, sTxtRedo);

  oSys.Call("User32::EnableWindow", aWnd[IDUNDO][WND], nUndoLimit);
  oSys.Call("User32::EnableWindow", aWnd[IDREDO][WND], nRedoLimit);
}

function CheckEditValue(nID)
{
  var sEditValue;

  oSys.Call("User32::GetWindowText" + _TCHAR, aWnd[nID][WND], lpBuffer, 256);
  sEditValue = AkelPad.MemRead(lpBuffer, _TSTR);

  if (sEditValue == 0)
  {
    SetWindowFontAndText(aWnd[nID][WND], hGuiFont, "1");
    AkelPad.SendMessage(aWnd[nID][WND], 177 /*EM_SETSEL*/, 0, -1);
  }
  else if (sEditValue > nLastCol)
  {
    SetWindowFontAndText(aWnd[nID][WND], hGuiFont, String(nLastCol));
    AkelPad.SendMessage(aWnd[nID][WND], 177 /*EM_SETSEL*/, 0, -1);
  }
}

function GetSelection()
{
  var lpBegSel = AkelPad.MemAlloc(_X64 ? 24 : 12 /*sizeof(AECHARINDEX)*/);
  var lpEndSel = AkelPad.MemAlloc(_X64 ? 24 : 12 /*sizeof(AECHARINDEX)*/);
  var nLine1, nLine2, nCol1, nCol2;

  AkelPad.SendMessage(hEditWnd, 3123 /*AEM_EXGETSEL*/, lpBegSel, lpEndSel);

  nLine1 = AkelPad.MemRead(lpBegSel, DT_DWORD) + 1;
  nLine2 = AkelPad.MemRead(lpEndSel, DT_DWORD) + 1;

  if ((nLine1 == nLine2) && (! bGetSel))
  {
    nLine1 = 1;
    nLine2 = AkelPad.SendMessage(hEditWnd, 3129 /*AEM_GETLINENUMBER*/, 0 /*AEGL_LINECOUNT*/, 0);
  }

  nLastCol = LineMaxLength(nLine1, nLine2) + 1;
  SetWindowFontAndText(aWnd[IDBEGLINE][WND], hGuiFont, String(nLine1));
  SetWindowFontAndText(aWnd[IDENDLINE][WND], hGuiFont, String(nLine2));

  if (! bGetSel)
  {
    if (AkelPad.MemRead(_PtrAdd(lpBegSel, _X64 ? 16 : 8), DT_DWORD) < AkelPad.MemRead(_PtrAdd(lpEndSel, _X64 ? 16 : 8), DT_DWORD))
    {
      nCol1 = AkelPad.MemRead(_PtrAdd(lpBegSel, _X64 ? 16 : 8), DT_DWORD) + 1;
      nCol2 = AkelPad.MemRead(_PtrAdd(lpEndSel, _X64 ? 16 : 8), DT_DWORD) + 1;
    }
    else
    {
      nCol1 = AkelPad.MemRead(_PtrAdd(lpEndSel, _X64 ? 16 : 8), DT_DWORD) + 1;
      nCol2 = AkelPad.MemRead(_PtrAdd(lpBegSel, _X64 ? 16 : 8), DT_DWORD) + 1;
    }
  
    if (nCol1 > nLastCol)
      nCol1 = nLastCol;
    if (nCol2 > nLastCol)
      nCol2 = nLastCol;

    SetWindowFontAndText(aWnd[IDBEGCOL][WND],  hGuiFont, String(nCol1));
    SetWindowFontAndText(aWnd[IDENDCOL][WND],  hGuiFont, String(nCol2));

    //removed (2018-02-01)
    //bEntLine = ! AkelPad.SendMessage(hEditWnd, 3127 /*AEM_GETCOLUMNSEL*/, 0, 0);
  }

  AkelPad.MemFree(lpBegSel);
  AkelPad.MemFree(lpEndSel);
  bGetSel = 1;
}

function LineMaxLength(nLine1, nLine2)
{
  var nMaxLenL = 0;
  var nBegLine;
  var nLenLine;
  var i;

  for (i = nLine1 - 1; i < nLine2; ++i)
  {
    nBegLine = AkelPad.SendMessage(hEditWnd, 187 /*EM_LINEINDEX*/, i, 0);
    nLenLine = AkelPad.SendMessage(hEditWnd, 193 /*EM_LINELENGTH*/, nBegLine, 0);
    if (nLenLine > nMaxLenL)
      nMaxLenL = nLenLine;
  }

  return nMaxLenL;
}

function SetSelection()
{
  var hFocus    = oSys.Call("User32::GetFocus");
  var lpSelect  = AkelPad.MemAlloc(_X64 ? 56 : 32 /*sizeof(AESELECTION)*/);
  var lpBegSel  = lpSelect;
  var lpEndSel  = _PtrAdd(lpSelect, _X64 ? 24 : 12);
  var lpSelFlag = _PtrAdd(lpSelect, _X64 ? 48 : 24);
  var nColSel   = 1;
  var nLine1, nLine2, nCol1, nCol2;

  oSys.Call("User32::GetWindowText" + _TCHAR, aWnd[IDBEGLINE][WND], lpBuffer, 256);
  nLine1 = Number(AkelPad.MemRead(lpBuffer, _TSTR)) - 1;
  oSys.Call("User32::GetWindowText" + _TCHAR, aWnd[IDENDLINE][WND], lpBuffer, 256);
  nLine2 = Number(AkelPad.MemRead(lpBuffer, _TSTR)) - 1;

  if ((hFocus == aWnd[IDBEGCOL][WND]) || (hFocus == aWnd[IDENDCOL][WND]))
  {
    oSys.Call("User32::GetWindowText" + _TCHAR, aWnd[IDBEGCOL][WND], lpBuffer, 256);
    nCol1 = Number(AkelPad.MemRead(lpBuffer, _TSTR)) - 1;
    oSys.Call("User32::GetWindowText" + _TCHAR, aWnd[IDENDCOL][WND], lpBuffer, 256);
    nCol2 = Number(AkelPad.MemRead(lpBuffer, _TSTR)) - 1;
  }
  else
  {
    nCol1   = 0;
    nCol2   = AkelPad.SendMessage(hEditWnd, 193 /*EM_LINELENGTH*/, AkelPad.SendMessage(hEditWnd, 187 /*EM_LINEINDEX*/, nLine2, 0), 0);
    nColSel = 0;
  }

  AkelPad.MemCopy(lpBegSel, nLine1,  DT_DWORD);
  AkelPad.MemCopy(_PtrAdd(lpBegSel, _X64 ? 16 : 8), nCol1, DT_DWORD);
  AkelPad.MemCopy(lpEndSel, nLine2,  DT_DWORD);
  AkelPad.MemCopy(_PtrAdd(lpEndSel, _X64 ? 16 : 8), nCol2, DT_DWORD);
  AkelPad.MemCopy(lpSelFlag, nColSel, DT_DWORD);

  AkelPad.SendMessage(hEditWnd, 3132 /*AEM_INDEXUPDATE*/, 0, lpBegSel);
  AkelPad.SendMessage(hEditWnd, 3132 /*AEM_INDEXUPDATE*/, 0, lpEndSel);

  AkelPad.SendMessage(hEditWnd, 3126 /*AEM_SETSEL*/, 0, lpSelect);

  AkelPad.MemFree(lpSelect);
}

function SortLines()
{
  var aTxt  = [];
  var sText = "";
  var nLine1;
  var nLine2;
  var nCol1;
  var nCol2;
  var i;

  oSys.Call("User32::GetWindowText" + _TCHAR, aWnd[IDBEGLINE][WND], lpBuffer, 256);
  nLine1 = Number(AkelPad.MemRead(lpBuffer, _TSTR)) - 1;
  oSys.Call("User32::GetWindowText" + _TCHAR, aWnd[IDENDLINE][WND], lpBuffer, 256);
  nLine2 = Number(AkelPad.MemRead(lpBuffer, _TSTR)) - 1;
  oSys.Call("User32::GetWindowText" + _TCHAR, aWnd[IDBEGCOL][WND], lpBuffer, 256);
  nCol1 = Number(AkelPad.MemRead(lpBuffer, _TSTR)) - 1;
  oSys.Call("User32::GetWindowText" + _TCHAR, aWnd[IDENDCOL][WND], lpBuffer, 256);
  nCol2 = Number(AkelPad.MemRead(lpBuffer, _TSTR)) - 1;

  if (nLine1 == nLine2)
  {
    AkelPad.MessageBox(hWndDlg, sTxtNoRangeL, sTxtCaption, 48 /*MB_ICONEXCLAMATION*/);
    return 0;
  }

  if ((nCol1 == nCol2) && bKey2 && (! bEntLine) && (nNoSort != 1))
  {
    AkelPad.MessageBox(hWndDlg, sTxtNoKey2, sTxtCaption, 48 /*MB_ICONEXCLAMATION*/);
    oSys.Call("User32::SetFocus", aWnd[IDENDCOL][WND]);
    AkelPad.SendMessage(aWnd[IDENDCOL][WND], 177 /*EM_SETSEL*/, 0, -1);
    return 0;
  }

  hFocus = oSys.Call("User32::GetFocus");

  if (bUsePlug)
  {
    if (! bEntLine)
    {
      oSys.Call("User32::SetFocus", aWnd[IDENDCOL][WND]);
      SetSelection();
    }
    SortByPlugin();
    oSys.Call("User32::SetFocus", hFocus);
    return 1;
  }
  else
  {
    oSys.Call("User32::SetFocus", aWnd[IDSORT][WND]);
    SetSelection();

    if (nNoSort == 1)
    {
      aTxt  = AkelPad.GetSelText(1 /*\r*/).split("\r");
      sText = aTxt.reverse().join("\r");
    }
    else
    {
      aTxt[0] = AkelPad.GetSelText(1 /*\r*/).split("\r");

      if (bKey2)
      {
        if (bEntLine)
          aTxt[1] = AkelPad.GetSelText(1 /*\r*/).split("\r");
        else
          aTxt[1] = GetTextRangeColumn([nLine1, nCol1], [nLine2, nCol2]);

        if (bRTL)
        {
          for (i = 0; i < aTxt[1].length; ++i)
            aTxt[1][i] = aTxt[1][i].split("").reverse().join("");
        }
      }

      aTxt = ArrayTransp(aTxt);

      ArraySort(aTxt);

      for (i = 0; i < aTxt.length; ++i)
        sText += aTxt[i][0] + "\r";

      sText = sText.slice(0, -1);
    }

    if (bCopyToCB)
    {
      AkelPad.SetClipboardText(sText);
      oSys.Call("User32::SetFocus", hFocus);
      return 0;
    }
    else
    {
      AkelPad.ReplaceSel(sText, -1);
      oSys.Call("User32::SetFocus", hFocus);
      return 1;
    }
  }
}

function GetTextRangeColumn(aBeg, aEnd, bReturnText, nNewLine, bFillSpaces)
{
  var nBegLine1;
  var nBegLine2;
  var nLenLine2;
  var nLine1;
  var nLine2;
  var nCol1;
  var nCol2;
  var aLines;
  var nWidth;
  var i;

  if (aBeg[0] < aEnd[0])
  {
    nLine1 = aBeg[0];
    nLine2 = aEnd[0];
  }
  else
  {
    nLine1 = aEnd[0];
    nLine2 = aBeg[0];
  }

  if (aBeg[1] < aEnd[1])
  {
    nCol1 = aBeg[1];
    nCol2 = aEnd[1];
  }
  else
  {
    nCol1 = aEnd[1];
    nCol2 = aBeg[1];
  }

	nBegLine1 = AkelPad.SendMessage(hEditWnd, 187 /*EM_LINEINDEX*/, nLine1, 0);
	nBegLine2 = AkelPad.SendMessage(hEditWnd, 187 /*EM_LINEINDEX*/, nLine2, 0);
	nLenLine2 = AkelPad.SendMessage(hEditWnd, 193 /*EM_LINELENGTH*/, nBegLine2, 0);
  aLines    = AkelPad.GetTextRange(nBegLine1, nBegLine2 + nLenLine2, 1 /*"\r"*/).split("\r");
  nWidth    = nCol2 - nCol1;

  for (i = 0; i < aLines.length; ++i)
  {
    aLines[i] = aLines[i].substring(nCol1, nCol2);
    if (bFillSpaces)
      while (aLines[i].length < nWidth)
        aLines[i] += " ";
  }

  if (bReturnText)
  {
    if ((! nNewLine) || (nNewLine == 1))
      pNewLine = "\r";
    else if (nNewLine == 2)
      pNewLine = "\n";
    else
      pNewLine = "\r\n";

    return aLines.join(pNewLine);
  }
  else
    return aLines;
}

function ArrayTransp(aA)
{
  var nLen1 = aA.length;
  var nLen2 = aA[0].length;
  var aB    = new Array(nLen2);
  var i, n;

  for (n = 0; n < nLen2; ++n)
  {
    aB[n] = [];

    for (i = 0; i < nLen1; ++i)
      aB[n].push(aA[i][n]);
  }

  return aB;
}

function ArraySort(aTxt)
{
  if (nNoSort == 0)
  {
    aTxt.sort(CompareValue);
    RemoveFromArray(aTxt);
  }
  else
    NoSort(aTxt);
}

function CompareValue(aA, aB)
{
  var nResult = 0;

  if (bKey1)
  {
    if (aA[0].length > aB[0].length)
      nResult = bDescLen ? -1 : 1;
    else if (aA[0].length < aB[0].length)
      nResult = bDescLen ? 1 : -1;
  }

  if (bKey2 && (nResult == 0))
  {
    if (nMethod == 1)
    {
      if (bIgCase)
      {
        aA[1] = aA[1].toUpperCase();
        aB[1] = aB[1].toUpperCase();
      }
      if (aA[1] > aB[1])
        nResult = bDesc ? -1 : 1;
      else if (aA[1] < aB[1])
        nResult = bDesc ? 1 : -1;
    }
    else if (nMethod == 2)
    {
      nResult = oSys.Call("Kernel32::lstrcmp" + (bIgCase ? "i" : "") + _TCHAR, aA[1], aB[1]);
      if (bDesc)
        nResult = -nResult;
    }
    else
    {
      var oRegExp = /(-?\d+\.?\d*)|(-?\.\d+)/;
  
      aA[1] = oRegExp.exec(aA[1]);
      aB[1] = oRegExp.exec(aB[1]);
      aA[1] = aA[1] ? parseFloat(aA[1][0]) : 0;
      aB[1] = aB[1] ? parseFloat(aB[1][0]) : 0;
      nResult = bDesc ? (aB[1] - aA[1]) : (aA[1] - aB[1]);
    }
  }

  return nResult;
}

function RemoveFromArray(aTxt)
{
  var i;

  if (nSortAnd == 1)
  {
    for (i = aTxt.length - 1; i > 0; --i)
    {
      if (CompareValue(aTxt[i], aTxt[i - 1]) == 0)
        aTxt.splice(i, 1);
    }
  }
  else if (nSortAnd == 2)
  {
    for (i = aTxt.length - 2; i > 0; --i)
    {
      if ((CompareValue(aTxt[i], aTxt[i + 1]) != 0) && (CompareValue(aTxt[i], aTxt[i - 1]) != 0))
        aTxt.splice(i, 1);
    }
    if ((aTxt.length > 1) && (CompareValue(aTxt[aTxt.length - 1], aTxt[aTxt.length - 2]) != 0))
      aTxt.splice(aTxt.length - 1, 1);
    if ((aTxt.length > 1) && (CompareValue(aTxt[0], aTxt[1]) != 0))
      aTxt.splice(0, 1);
    if (aTxt.length == 1)
      aTxt.splice(0, 1);
  }
  else if (nSortAnd == 3)
  {
    i = aTxt.length - 1;
    while (i > 0)
    {
      if (CompareValue(aTxt[i], aTxt[i - 1]) == 0)
      {
        aTxt.splice(i - 1, 2);
        i -= 2;
      }
      else
        --i;
    }
  }
}

function NoSort(aTxt)
{
  var i, n, bIsDup;

  if (nNoSort == 2)
  {
    for (n = 0; n < aTxt.length; ++n)
    {
      i = n + 1;
      while (i < aTxt.length)
      {
        if (CompareValue(aTxt[i], aTxt[n]) == 0)
          aTxt.splice(i, 1);
        else
          ++i;
      }
    }
  }

  else if (nNoSort == 3)
  {
    n = 0;
    while (n < aTxt.length)
    {
      bIsDup = false;
      for (i = 0; i < aTxt.length; ++i)
      {
        if ((CompareValue(aTxt[i], aTxt[n]) == 0) && (i != n))
        {
          bIsDup = true;
          break;
        }
      }
      if (bIsDup)
        ++n;
      else
        aTxt.splice(n, 1);
    }
  }

  else if (nNoSort == 4)
  {
    n = 0;
    while (n < aTxt.length)
    {
      bIsDup = false;
      i = n + 1;
      while (i < aTxt.length)
      {
        if (CompareValue(aTxt[i], aTxt[n]) == 0)
        {
          bIsDup = true;
          aTxt.splice(i, 1);
        }
        else
          ++i;
      }
      if (bIsDup)
        aTxt.splice(n, 1);
      else
        ++n;
    }
  }
}

function SortByPlugin()
{
  var sAscDesc = bDesc ? "Desc" : "Asc";

  if (nNoSort == 1)
    AkelPad.Call("Format::LineReverse");
  else if (nNoSort == 2)
    AkelPad.Call("Format::LineRemoveDuplicates", ! bIgCase);
  else if (nNoSort == 3)
    AkelPad.Call("Format::LineGetDuplicates", ! bIgCase);
  else if (nNoSort == 4)
    AkelPad.Call("Format::LineGetUnique", ! bIgCase);
  else if (nMethod == 1)
    AkelPad.Call("Format::LineSortStr" + sAscDesc, ! bIgCase);
  else if (nMethod == 3)
    AkelPad.Call("Format::LineSortInt" + sAscDesc, ! bIgCase);
}
