// http://akelpad.sourceforge.net/forum/viewtopic.php?p=9418#9418
// Version: 2.4
// Author: Shengalts Aleksander aka Instructor
//
//
// Description(1033): Show help for keyword.
// Description(2052): 显示与 关键字 有关的帮助。
//
// 参数:
// -File="ext1:file1.chm|ext2:file2.chm"  -根据指定的扩展名来定义打开相应的 chm 文件 (默认为预定义的 CHM 名)。
//                                         默认的 chm 文件 (对任意其它扩展名) 可以无扩展名指定: "file.chm"。
// -Path="C:\MyPath"                      -放置 CHM 文件的路径 (默认路径为 "[AkelPad]\AkelFiles\Help")。
// -Maximize=false                        -不最大化 CHM 帮助对话框 (默认值为 true)。
// -CatchEsc=false                        -在按下 ESC 键后不关闭 CHM 帮助对话框 (默认值为 true)。
// -MethodsView=false                     -不检查 AkelPadMethodsView.js 执行条件 (默认值为 true)。
//
// 用法 #1. 根据当前文件扩展名使用预定义 CHM 名:
// Call("Scripts::Main", 1, "ChmKeyword.js")
//
// 用法 #2. 使用 "help_name.chm" 来搜索关键字:
// Call("Scripts::Main", 1, "ChmKeyword.js", `-File="help_name.chm"`)

//参数
var pChmFile=AkelPad.GetArgValue("File", "js  :wsh.chm|" +
                                         "vbs :wsh.chm|" +
                                         "ahk :AutoHotkey.chm" +
                                         "au3 :AutoIt3.chm" +
                                         "c   :cpp.chm|" +
                                         "cpp :cpp.chm|" +
                                         "h   :cpp.chm|" +
                                         "nsi :nsis.chm|" +
                                         "css :css.chm|" +
                                         "php :php.chm|" +
                                         "htm :html.chm|" +
                                         "html:html.chm|" +
                                         "     html.chm");
var pChmPath=AkelPad.GetArgValue("Path", AkelPad.GetAkelDir() + "\\AkelFiles\\Help");
var bMaximize=AkelPad.GetArgValue("Maximize", true);
var bCatchEsc=AkelPad.GetArgValue("CatchEsc", true);
var bMethodsView=AkelPad.GetArgValue("MethodsView", true);

//变量
var hMainWnd=AkelPad.GetMainWnd();
var hWndEdit=AkelPad.GetEditWnd();
var oSys=AkelPad.SystemFunction();
var hWndHelpDlg=0;
var aChmFile=[];
var aChmItem=[];
var aExt2Chm=[];
var pChmFileDef="";
var pFile;
var pExt;
var pKeyword="";
var pScriptsDir=AkelPad.GetAkelDir(5 /*ADTYPE_SCRIPTS*/).toLowerCase();
var nSelStart;
var nSelEnd;
var nWordBegin=0;
var nWordEnd=0;
var pCheckLeft;
var nTimeOut;
var bClosed=false;
var i;

if (hWndEdit)
{
  //Fill aExt2Chm
  if (aChmFile=pChmFile.split("|"))
  {
    for (i=0; i < aChmFile.length; ++i)
    {
      if ((aChmItem=aChmFile[i].split(":")) && aChmItem.length >= 2)
      {
        pExt=trim(aChmItem[0]).toLowerCase();
        aExt2Chm[pExt]=trim(aChmItem[1]);
      }
      else if (!pChmFileDef)
      {
        pChmFileDef=trim(aChmFile[i]);
      }
    }
  }

  pExt=AkelPad.GetFilePath(AkelPad.GetEditFile(0), 4 /*CPF_FILEEXT*/).toLowerCase();
  if (aExt2Chm[pExt])
    pFile=aExt2Chm[pExt];
  else if (pChmFileDef)
    pFile=pChmFileDef;
  else
  {
    AkelPad.MessageBox(hMainWnd, GetLangString(0).replace(/%s/, pExt), WScript.ScriptName, 16 /*MB_ICONERROR*/);
    WScript.Quit();
  }
  pFile=pChmPath + "\\" + pFile;

  if (!IsFileExist(pFile))
  {
    AkelPad.MessageBox(hMainWnd, GetLangString(1).replace(/%s/, pFile), WScript.ScriptName, 16 /*MB_ICONERROR*/);
    WScript.Quit();
  }

  //关键字
  nSelStart=AkelPad.GetSelStart();
  nSelEnd=AkelPad.GetSelEnd();

  if (nSelStart == nSelEnd)
  {
    //选择插入点下的单词
    nWordBegin=AkelPad.SendMessage(hWndEdit, 1100 /*EM_FINDWORDBREAK*/, 0 /*WB_LEFT*/, nSelStart);
    nWordEnd=AkelPad.SendMessage(hWndEdit, 1100 /*EM_FINDWORDBREAK*/, 7 /*WB_RIGHTBREAK*/, nWordBegin);
    if (nWordEnd > nSelStart)
      pKeyword=AkelPad.GetTextRange(nWordBegin, nWordEnd);
  }
  else pKeyword=AkelPad.GetTextRange(nSelStart, nSelEnd);

  //File into scripts directory
  if (bMethodsView && AkelPad.GetEditFile(0).substr(0, pScriptsDir.length).toLowerCase() == pScriptsDir && IsFileExist(pScriptsDir + "\\AkelPadMethodsView.js"))
  {
    for (pCheckLeft=pKeyword;;)
    {
      if (pCheckLeft.substr(0, 8).toLowerCase() == "akelpad.")
      {
        AkelPad.Call("Scripts::Main", 1, "AkelPadMethodsView.js");
        WScript.Quit();
      }
      else if (pCheckLeft == pKeyword)
      {
        if (nWordEnd)
        {
          if (nWordBegin - 8 >= 0)
          {
            pCheckLeft=AkelPad.GetTextRange(nWordBegin - 8, nWordEnd);
            continue;
          }
        }
        else
        {
          if (nSelStart - 8 >= 0)
          {
            pCheckLeft=AkelPad.GetTextRange(nSelStart - 8, nSelEnd);
            continue;
          }
        }
      }
      break;
    }
  }

  //Show help
  ChmKeyword(pFile, pKeyword);
}

//Functions
function ChmKeyword(pFile, pKeyword)
{
  var hModule;
  var lpKeywordBuffer;
  var lpStructure;
  var hWndHelpList=0;
  var hWndHelpChild;
  var hWndHelpFocus;
  var dwCurThreadID;
  var dwHelpThreadID;
  var hHook;
  var hMutex;

  //Load library manually to prevent hhctrl.ocx unload after HtmlHelp call
  if (!(hModule=oSys.Call("kernel32::GetModuleHandle" + _TCHAR, "hhctrl.ocx")))
    hModule=oSys.Call("kernel32::LoadLibrary" + _TCHAR, "hhctrl.ocx");

  if (hModule)
  {
    if (lpKeywordBuffer=AkelPad.MemAlloc(256 * _TSIZE))
    {
      AkelPad.MemCopy(lpKeywordBuffer, pKeyword.substr(0, 255), _TSTR);

      if (lpStructure=AkelPad.MemAlloc(_X64?56:32 /*sizeof(HH_AKLINK)*/))
      {
        //Fill structure
        AkelPad.MemCopy(_PtrAdd(lpStructure, 0) /*offsetof(HH_AKLINK, cbStruct)*/, _X64?56:32, 3 /*DT_DWORD*/);
        AkelPad.MemCopy(_PtrAdd(lpStructure, _X64?8:8) /*offsetof(HH_AKLINK, pszKeywords)*/, lpKeywordBuffer, 2 /*DT_QWORD*/);
        AkelPad.MemCopy(_PtrAdd(lpStructure, _X64?48:28) /*offsetof(HH_AKLINK, fIndexOnFail)*/, true, 3 /*DT_DWORD*/);

        hWndHelpDlg=oSys.Call("hhctrl.ocx::HtmlHelp" + _TCHAR, oSys.Call("user32::GetDesktopWindow"), pFile, 0xD /*HH_KEYWORD_LOOKUP*/, lpStructure);

        //Send VK_RETURN
        if (hWndHelpDlg)
        {
          dwCurThreadID=oSys.Call("kernel32::GetCurrentThreadId");
          dwHelpThreadID=oSys.Call("user32::GetWindowThreadProcessId", hWndHelpDlg, 0);

          if (pKeyword)
          {
            if (oSys.Call("user32::AttachThreadInput", dwCurThreadID, dwHelpThreadID, true))
            {
              hWndHelpFocus=oSys.Call("user32::GetFocus");

              if (oSys.Call("user32::GetWindowTextLength" + _TCHAR, hWndHelpFocus))
              {
                AkelPad.SendMessage(hWndHelpFocus, 0x0100 /*WM_KEYDOWN*/, 0x0D /*VK_RETURN*/, 0x011C001);
                AkelPad.SendMessage(hWndHelpFocus, 0x0101 /*WM_KEYUP*/, 0x0D /*VK_RETURN*/, 0xC11C001);
              }
              else oSys.Call("user32::SetWindowText" + _TCHAR, hWndHelpFocus, lpKeywordBuffer);

              oSys.Call("user32::AttachThreadInput", dwCurThreadID, dwHelpThreadID, false);
            }
          }

          if (bMaximize)
          {
            //Maximize CHM window
            oSys.Call("user32::ShowWindow", hWndHelpDlg, 3 /*SW_MAXIMIZE*/);
          }
          if (bCatchEsc)
          {
            if (hMutex=oSys.Call("kernel32::CreateEvent" + _TCHAR, 0, 0, 0, WScript.ScriptName))
            {
              //Allow WindowSubClass and ThreadHook only for single script
              if (oSys.GetLastError() != 183 /*ERROR_ALREADY_EXISTS*/)
              {
                //Catch WM_CLOSE
                if (AkelPad.WindowSubClass(hWndHelpDlg, SubClassCallback, 2 /*WM_DESTROY*/))
                {
                  //Catch VK_ESCAPE
                  if (hHook=AkelPad.ThreadHook(3 /*WH_GETMESSAGE*/, HookCallback, dwHelpThreadID))
                  {
                    //Allow other scripts running
                    AkelPad.ScriptNoMutex();

                    //Message loop
                    AkelPad.WindowGetMessage();

                    if (!bClosed)
                    {
                      //Unexpected leaving loop (could be caused by program quiting)
                      AkelPad.SendMessage(hWndHelpDlg, 16 /*WM_CLOSE*/, 0, 0);
                      bClosed=true;
                    }

                    AkelPad.ThreadUnhook(hHook);
                  }
                  AkelPad.WindowUnsubClass(hWndHelpDlg);
                }
              }
              oSys.Call("kernel32::CloseHandle", hMutex);
            }
          }
        }
        AkelPad.MemFree(lpStructure);
      }
      AkelPad.MemFree(lpKeywordBuffer);
    }
  }
}

function SubClassCallback(hWnd, uMsg, wParam, lParam)
{
  if (uMsg == 2 /*WM_DESTROY*/)
  {
    //Exit message loop
    oSys.Call("user32::PostQuitMessage", 0);
    bClosed=true;
  }
}

function HookCallback(nCode, wParam, lParam)
{
  var uMsg=AkelPad.MemRead(_PtrAdd(lParam, _X64?8:4) /*offsetof(MSG, message)*/, 3 /*DT_DWORD*/);
  var wParam;

  if (uMsg == 0x100 /*WM_KEYDOWN*/)
  {
    wParam=AkelPad.MemRead(_PtrAdd(lParam, _X64?16:8) /*offsetof(MSG, wParam)*/, 2 /*DT_QWORD*/);

    if (wParam == 0x1B /*VK_ESCAPE*/)
    {
      if (oSys.Call("user32::GetForegroundWindow") == hWndHelpDlg)
        AkelPad.SendMessage(hWndHelpDlg, 16 /*WM_CLOSE*/, 0, 0);
    }
  }
}

function IsFileExist(pFile)
{
  if (oSys.Call("kernel32::GetFileAttributes" + _TCHAR, pFile) == -1)
    return false;
  return true;
}

function trim(pStr)
{
  return pStr.replace(/^\s+/, "").replace(/\s+$/, "");
}

function GetLangString(nStringID)
{
  var nLangID=AkelPad.GetLangId(1 /*LANGID_PRIMARY*/);

  if (nLangID == 0x4) //LANG_CHINESE
  {
    if (nStringID == 0)
      return "扩展名 \"%s\" 未关联任何 CHM 文件，并且未指定默认的 CHM 文件。";
    if (nStringID == 1)
      return "缺失: \"%s\"";
  }
  else
  {
    if (nStringID == 0)
      return "Extension \"%s\" is not associated with any CHM file and default CHM file not specified.";
    if (nStringID == 1)
      return "Missing: \"%s\"";
  }
  return "";
}
