// http://akelpad.sourceforge.net/forum/viewtopic.php?p=11749#11749
// Version: 2015-09-19
// Author: KDJ
//
// *** Sort words ***
//
// Usage:
//   Call("Scripts::Main", 1, "SortWords.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 sTxtSep      = "分隔符";
var sTxtSepWord  = "任意单词分隔符";
var sTxtSepNo    = "无分隔符 - 排序单个字符";
var sTxtSepAny   = "任一:";
var sTxtSepStr   = "字串:";
var sTxtTab      = "\\t = 制表符";
var sTxtSortMet  = "排序方法";
var sTxtDesc     = "降序(&D)";
var sTxtIgCase   = "忽略大小写(&I)";
var sTxtString   = "字串(&S)";
var sTxtLocale   = "字母顺序(&A)";
var sTxtNum      = "数字顺序(&N)";
var sTxtNoSort   = "不排序，仅";
var sTxtReverse  = "反向排列单词(&O)";
var sTxtRemoDup  = "删除重复单词(&M)";
var sTxtExtrDup  = "提取重复单词(&X)";
var sTxtExtrUni  = "提取唯一单词(&C)";
var sTxtInLine   = "每一行独立排序(&L)";
var sTxtCopyToCB = "复制结果到剪贴板，而不是替换文本(&T)";
var sTxtSort     = "排序";
var sTxtUndo     = "撤消(&U)";
var sTxtRedo     = "恢复(&R)";

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 nSep       = 0;
var sSep       = ";";
var bTabSep    = 1;
var bIgCaseSep = 1;
var bDesc      = 0;
var bIgCase    = 1;
var nMethod    = 1;
var nNoSort    = 0;
var bInLine    = 1;
var bCopyToCB  = 0;
var nUndoLimit = 0;
var nRedoLimit = 0;
var lpStrA;
var lpStrB;
var hWndDlg;
var lpBuffer;
var bColSel;
var bIsReturn;
var hFocus;
var nWndPosX;
var nWndPosY;

ReadWriteIni(0);

var aWnd        = [];
var IDGROUP1    = 1000;
var IDSEPWORD   = 1001;
var IDSEPNO     = 1002;
var IDSEPANY    = 1003;
var IDSEPSTR    = 1004;
var IDGROUP2    = 1005;
var IDSEP       = 1006;
var IDTABSEP    = 1007;
var IDIGCASESEP = 1008;
var IDGROUP3    = 1009;
var IDDESC      = 1010;
var IDIGCASE    = 1011;
var IDGROUP4    = 1012;
var IDSTRING    = 1013;
var IDLOCALE    = 1014;
var IDNUM       = 1015;
var IDGROUP5    = 1016;
var IDREVERSE   = 1017;
var IDREMODUP   = 1018;
var IDEXTRDUP   = 1019;
var IDEXTRUNI   = 1020;
var IDINLINE    = 1021;
var IDCOPYTOCB  = 1022;
var IDSORT      = 1023;
var IDUNDO      = 1024;
var IDREDO      = 1025;

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;

//0x50000007 - WS_VISIBLE|WS_CHILD|BS_GROUPBOX
//0x50000009 - WS_VISIBLE|WS_CHILD|BS_AUTORADIOBUTTON
//0x50010000 - WS_VISIBLE|WS_CHILD|WS_TABSTOP
//0x50010001 - WS_VISIBLE|WS_CHILD|WS_TABSTOP|BS_DEFPUSHBUTTON
//0x50010003 - WS_VISIBLE|WS_CHILD|WS_TABSTOP|BS_AUTOCHECKBOX
//0x50010080 - WS_VISIBLE|WS_CHILD|WS_TABSTOP|ES_AUTOHSCROLL
//Windows               CLASS, WND, EXSTYLE,      STYLE,   X,   Y,   W,   H, TXT
aWnd[IDGROUP1   ] = ["BUTTON",   0,       0, 0x50000007,  10,  10, 230, 135, sTxtSep];
aWnd[IDSEPWORD  ] = ["BUTTON",   0,       0, 0x50000009,  20,  30, 200,  16, sTxtSepWord];
aWnd[IDSEPNO    ] = ["BUTTON",   0,       0, 0x50000009,  20,  50, 200,  16, sTxtSepNo];
aWnd[IDSEPANY   ] = ["BUTTON",   0,       0, 0x50000009,  30,  75,  70,  16, sTxtSepAny];
aWnd[IDSEPSTR   ] = ["BUTTON",   0,       0, 0x50000009,  30,  95,  70,  16, sTxtSepStr];
aWnd[IDGROUP2   ] = ["BUTTON",   0,       0, 0x50000007, 100,  63, 140,  82, ""];
aWnd[IDSEP      ] = ["EDIT",     0,   0x200, 0x50010080, 110,  80, 120,  20, sSep];
aWnd[IDTABSEP   ] = ["BUTTON",   0,       0, 0x50010003, 125, 105,  90,  16, sTxtTab];
aWnd[IDIGCASESEP] = ["BUTTON",   0,       0, 0x50010003, 125, 125,  90,  16, sTxtIgCase];
aWnd[IDGROUP3   ] = ["BUTTON",   0,       0, 0x50000007,  10, 155, 230,  80, sTxtSortMet];
aWnd[IDDESC     ] = ["BUTTON",   0,       0, 0x50010003,  35, 180,  90,  16, sTxtDesc];
aWnd[IDIGCASE   ] = ["BUTTON",   0,       0, 0x50010003,  35, 200,  90,  16, sTxtIgCase];
aWnd[IDGROUP4   ] = ["BUTTON",   0,       0, 0x50000007, 130, 155, 110,  80, ""];
aWnd[IDSTRING   ] = ["BUTTON",   0,       0, 0x50000009, 145, 170,  90,  16, sTxtString];
aWnd[IDLOCALE   ] = ["BUTTON",   0,       0, 0x50000009, 145, 190,  90,  16, sTxtLocale];
aWnd[IDNUM      ] = ["BUTTON",   0,       0, 0x50000009, 145, 210,  90,  16, sTxtNum];
aWnd[IDGROUP5   ] = ["BUTTON",   0,       0, 0x50000007,  10, 245, 230, 105, sTxtNoSort];
aWnd[IDREVERSE  ] = ["BUTTON",   0,       0, 0x50010003,  50, 265, 180,  16, sTxtReverse];
aWnd[IDREMODUP  ] = ["BUTTON",   0,       0, 0x50010003,  50, 285, 180,  16, sTxtRemoDup];
aWnd[IDEXTRDUP  ] = ["BUTTON",   0,       0, 0x50010003,  50, 305, 180,  16, sTxtExtrDup];
aWnd[IDEXTRUNI  ] = ["BUTTON",   0,       0, 0x50010003,  50, 325, 180,  16, sTxtExtrUni];
aWnd[IDINLINE   ] = ["BUTTON",   0,       0, 0x50010003,  10, 360, 230,  16, sTxtInLine];
aWnd[IDCOPYTOCB ] = ["BUTTON",   0,       0, 0x50010003,  10, 380, 230,  16, sTxtCopyToCB];
aWnd[IDSORT     ] = ["BUTTON",   0,       0, 0x50010001,  10, 405,  70,  23, sTxtSort];
aWnd[IDUNDO     ] = ["BUTTON",   0,       0, 0x50010000,  90, 405,  70,  23, sTxtUndo];
aWnd[IDREDO     ] = ["BUTTON",   0,       0, 0x50010000, 170, 405,  70,  23, sTxtRedo];

if (hEditWnd)
{
  if (AkelPad.GetSelStart() == AkelPad.GetSelEnd())
    AkelPad.SetSel(0, -1);

  if (AkelPad.GetSelStart() == AkelPad.GetSelEnd())
    WScript.Quit();

  if (bColSel = AkelPad.SendMessage(hEditWnd, 3127 /*AEM_GETCOLUMNSEL*/, 0, 0))
    bInLine = 1;

  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
                  255,             //nWidth
                  465,             //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);
  }
}

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]);
    }

    //Set limit edit text
    AkelPad.SendMessage(aWnd[IDSEP][WND], 197 /*EM_LIMITTEXT*/, 24, 0);

    CheckButtons();
    CheckUndoRedo();

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

  else if (uMsg == 7) //WM_SETFOCUS
    oSys.Call("User32::SetFocus", aWnd[IDSEPWORD + nSep][WND]);

  else if (uMsg == 256) //WM_KEYDOWN
  {
    if (wParam == 27) //VK_ESCAPE
      oSys.Call("User32::PostMessage" + _TCHAR, hWnd, 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 >= IDSEPWORD) && (nLowParam <= IDCOPYTOCB) && (nLowParam != IDSEP))
      CheckButtons(nLowParam);
    else if (nLowParam == IDSORT)
    {
      if (SortWords())
        CheckUndoRedo(nLowParam);
    }
    else if (nLowParam == IDUNDO)
    {
      bIsReturn = 1;
      CheckUndoRedo(nLowParam);
    }
    else if (nLowParam == IDREDO)
    {
      bIsReturn = 1;
      CheckUndoRedo(nLowParam);
    }
  }

  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
  {
    //Exit message loop
    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);

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

    sIniTxt = 'nWndPosX='   + aRect.left + ';\r\n' +
              'nWndPosY='   + aRect.top  + ';\r\n' +
              'nSep='       + nSep + ';\r\n' + 
              'sSep="'      + sSep.replace(/[\\"]/g, "\\$&") + '";\r\n' +
              'bTabSep='    + bTabSep + ';\r\n' +
              'bIgCaseSep=' + bIgCaseSep + ';\r\n' +
              'bDesc='      + bDesc + ';\r\n' +
              'bIgCase='    + bIgCase + ';\r\n' +
              'nMethod='    + nMethod + ';\r\n' +
              'nNoSort='    + nNoSort + ';\r\n' +
              'bInLine='    + bInLine + ';\r\n' +
              'bCopyToCB='  + bCopyToCB + ';'

    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 == IDSEPWORD) || (nButton == IDSEPNO) || (nButton == IDSEPANY) || (nButton == IDSEPSTR))
    nSep = nButton - IDSEPWORD;
  else if (nButton == IDTABSEP)
    bTabSep = ! bTabSep;
  else if (nButton == IDIGCASESEP)
    bIgCaseSep = ! bIgCaseSep;
  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 == IDREVERSE) || (nButton == IDREMODUP) ||
           (nButton == IDEXTRDUP) || (nButton == IDEXTRUNI))
  {
    if (nNoSort == nButton - IDREVERSE + 1)
      nNoSort = 0;
    else
      nNoSort = nButton - IDREVERSE + 1;
  }
  else if (nButton == IDINLINE)
    bInLine = ! bInLine;
  else if (nButton == IDCOPYTOCB)
    bCopyToCB = ! bCopyToCB;

  AkelPad.SendMessage(aWnd[IDSEPWORD][WND],   BM_SETCHECK, (nSep == 0),    0);
  AkelPad.SendMessage(aWnd[IDSEPNO][WND],     BM_SETCHECK, (nSep == 1),    0);
  AkelPad.SendMessage(aWnd[IDSEPANY][WND],    BM_SETCHECK, (nSep == 2),    0);
  AkelPad.SendMessage(aWnd[IDSEPSTR][WND],    BM_SETCHECK, (nSep == 3),    0);
  AkelPad.SendMessage(aWnd[IDTABSEP][WND],    BM_SETCHECK, bTabSep,        0);
  AkelPad.SendMessage(aWnd[IDIGCASESEP][WND], BM_SETCHECK, bIgCaseSep,     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[IDREVERSE][WND],   BM_SETCHECK, (nNoSort == 1), 0);
  AkelPad.SendMessage(aWnd[IDREMODUP][WND],   BM_SETCHECK, (nNoSort == 2), 0);
  AkelPad.SendMessage(aWnd[IDEXTRDUP][WND],   BM_SETCHECK, (nNoSort == 3), 0);
  AkelPad.SendMessage(aWnd[IDEXTRUNI][WND],   BM_SETCHECK, (nNoSort == 4), 0);
  AkelPad.SendMessage(aWnd[IDINLINE][WND],    BM_SETCHECK, bInLine,        0);
  AkelPad.SendMessage(aWnd[IDCOPYTOCB][WND],  BM_SETCHECK, bCopyToCB,      0);

  oSys.Call("User32::EnableWindow", aWnd[IDSEP][WND],       (nSep > 1));
  oSys.Call("User32::EnableWindow", aWnd[IDTABSEP][WND],    (nSep > 1));
  oSys.Call("User32::EnableWindow", aWnd[IDIGCASESEP][WND], (nSep > 1));
  oSys.Call("User32::EnableWindow", aWnd[IDDESC][WND],      (nNoSort == 0));
  oSys.Call("User32::EnableWindow", aWnd[IDIGCASE][WND],    (nNoSort != 1) && (nMethod != 3));
  oSys.Call("User32::EnableWindow", aWnd[IDSTRING][WND],    (nNoSort != 1));
  oSys.Call("User32::EnableWindow", aWnd[IDLOCALE][WND],    (nNoSort != 1));
  oSys.Call("User32::EnableWindow", aWnd[IDNUM][WND],       (nNoSort != 1));
  oSys.Call("User32::EnableWindow", aWnd[IDINLINE][WND],    ! bColSel);
}

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);
    if ((! nUndoLimit) && (oSys.Call("User32::GetFocus") == aWnd[IDUNDO][WND]))
      oSys.Call("User32::SetFocus", aWnd[IDSORT][WND]);
  }
  else if ((nButton == IDREDO) && (nRedoLimit))
  {
    ++nUndoLimit;
    --nRedoLimit;
    AkelPad.SendMessage(hEditWnd, 3078 /*AEM_REDO*/, 0, 0);
    AkelPad.SetSel(nBegSel, AkelPad.GetSelEnd());
    AkelPad.SendMessage(hEditWnd, 3128 /*AEM_UPDATESEL*/, bColSel, 0);
    if ((! nRedoLimit) && (oSys.Call("User32::GetFocus") == aWnd[IDREDO][WND]))
      oSys.Call("User32::SetFocus", aWnd[IDSORT][WND]);
  }

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

  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 SortWords()
{
  var sText = AkelPad.GetSelText();
  var aTxt  = [];
  var sSep  = "";
  var sMod  = "g";
  var rSep;
  var aWord;
  var aSep;
  var i, n;

  lpStrA = AkelPad.MemAlloc(sText.length * _TSIZE);
  lpStrB = AkelPad.MemAlloc(sText.length * _TSIZE);

  if (bInLine)
    aTxt = sText.split("\r");
  else
    aTxt[0] = sText;

  if (nSep == 0)
    sSep = " \t'`\"\\|[](){}<>,.;:+-=~!@#$%^&*/?";
  else if (nSep > 1)
  {
    oSys.Call("User32::GetWindowText" + _TCHAR, aWnd[IDSEP][WND], lpBuffer, 256);
    sSep = AkelPad.MemRead(lpBuffer, _TSTR);
    if (bTabSep)
      sSep = sSep.replace(/\\t/g, "\t");
  }

  if (sSep)
  {
    sSep = sSep.replace(/[\\\/.^$+*?|()\[\]{}-]/g, "\\$&");

    if ((nSep == 0) || (nSep == 2))
      sSep = "[" + sSep + "\r]+";
    else if (nSep == 3)
      sSep = "((" + sSep + ")|\r)+";

    if ((nSep > 1) && bIgCaseSep)
      sMod += "i";
  }

  rSep = new RegExp(sSep, sMod);

  for (i = 0; i < aTxt.length; ++i)
  {
    aWord = aTxt[i].split(rSep);
    aSep  = aTxt[i].match(rSep);

    if ((! sSep) && (! bInLine))
    {
      n = 0;
      while (n < aWord.length)
      {
        if (aWord[n] == "\r")
        {
          aWord.splice(n, 1);
          aSep.splice(n, 2, "\r");
        }
        else
          ++n;
      }
    }

    if (aWord.length > 1)
    {
      if (! RegExp("^" + sSep, sMod).test(aTxt[i]))
        aSep.unshift("");
      if (! RegExp(sSep + "$", sMod).test(aTxt[i]))
        aSep.push("");

      if (nNoSort == 1)
        aWord.reverse();
      else if (nNoSort > 1)
        NoSort(aWord);
      else
        aWord.sort(CompareValue);

      aTxt[i] = aSep[0];

      for (n = 0; n < aWord.length; ++n)
        aTxt[i] += aWord[n] + aSep[n + 1];
    }
  }

  sText = aTxt.join("\r");

  if (bCopyToCB)
    AkelPad.SetClipboardText(sText);
  else
    AkelPad.ReplaceSel(sText, -1);

  AkelPad.MemFree(lpStrA);
  AkelPad.MemFree(lpStrB);

  return (! bCopyToCB);
}

function CompareValue(ValA, ValB)
{
  var nResult = 0;

  if (nMethod == 1)
  {
    if (bIgCase)
    {
      if (ValA.toUpperCase() > ValB.toUpperCase())
        nResult = bDesc ? -1 : 1;
      else if (ValA.toUpperCase() < ValB.toUpperCase())
        nResult = bDesc ? 1 : -1;
    }
    else
    {
      if (ValA > ValB)
        nResult = bDesc ? -1 : 1;
      else if (ValA < ValB)
        nResult = bDesc ? 1 : -1;
    }
  }
  else if (nMethod == 2)
  {
    AkelPad.MemCopy(lpStrA, ValA, _TSTR);
    AkelPad.MemCopy(lpStrB, ValB, _TSTR);
    nResult = oSys.Call("Kernel32::lstrcmp" + (bIgCase ? "i" : "") + _TCHAR, lpStrA, lpStrB);
    if (bDesc)
      nResult = -nResult;
  }
  else
  {
    var rNumber = /(-?\d+\.?\d*)|(-?\.\d+)/;
    ValA    = rNumber.exec(ValA);
    ValB    = rNumber.exec(ValB);
    ValA    = ValA ? parseFloat(ValA[0]) : 0;
    ValB    = ValB ? parseFloat(ValB[0]) : 0;
    nResult = bDesc ? (ValB - ValA) : (ValA - ValB);
  }

  return nResult;
}

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

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