
// === Base64 encode/decode ===
// (c) Fr0sT 2010
// http://akelpad.sourceforge.net/forum/viewtopic.php?p=7972#7972
//
// §³onverts selection to Base64 and vice versa
// Arguments:
//   enc - encode
//   dec - decode
// Example:
//   Call("Scripts::Main", 1, "Base64.js", "enc")
//
// v0.1.0 - 18:55 09.07.2010.
//   First release
// v0.1.1 - 20:30 10.07.2010.
//   Optimized encoding/decoding, added dec16 parameter, text length checking
// v1.0   - 17:05 04.08.2010.
//   Completely rewritten decoding/endcoding. Removed dec16 parameter. Works significantly faster!
//   Selected text is converted from wide char to multi-byte
//   string (using current document code page) so that decode now works perfectly with every text code
//   page. Text remains selected after recoding to allow quickly adjust text code page by Alt-R. Remember
//   that in recode dialog you should set target code page in the field "TO" while field "FROM" must be
//   the current document codepage. Added GetLastError to error reports. Removed buffer zero-padding in
//   decode() - it's already zeroed by MemAlloc. Added workaround if current document codepage is UTF16/32
// v1.1   - 11:28 06.08.2010
//   Added HTML entity decoding (&#nnnn;), parameter "dechtml"
// v1.2   - 12:43 08.12.2010
//   Fixed wrong buffer boundary checking (random characters appeared beyond the trailing "=")


/* Base64 encode / decode (originally from http://www.webtoolkit.info) */
/* Text-only modification: stops at 0 character */

var Base64 = {
   // private property
   _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

   // public method for encoding
   // in:  pBuf - buffer with multibyte string (ZERO-TERMINATED!)
   //      bufLen - buffer size
   // out: Base64 string
   encode : function(pBuf, bufLen)
   {
      var output = "";
      var pCurr = pBuf;

      while (true)
      {
         var chr1 = (pCurr < pBuf+bufLen) ? AkelPad.MemRead(pCurr++, 5 /* DT_BYTE */) : 0;
         var chr2 = (pCurr < pBuf+bufLen) ? AkelPad.MemRead(pCurr++, 5 /* DT_BYTE */) : 0;
         var chr3 = (pCurr < pBuf+bufLen) ? AkelPad.MemRead(pCurr++, 5 /* DT_BYTE */) : 0;
         if (!chr1) break;
         var enc1 = chr1 >> 2;
         var enc2 = ((chr1 & 0x03) << 4) | ((chr2 & 0xf0) >> 4);
         var enc3 = (!chr2) ? 0x40 : ((chr2 & 0x0f) << 2) | ((chr3 & 0xc0) >> 6);
         var enc4 = (!chr3) ? 0x40 : (chr3 & 0x3f);
         output += this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
         if (!chr3) break;
      }
      return output;
   },

   // public method for decoding
   // in:  base64str - Base64 string
   //      pBuf - buffer for output
   // out: decoded multibyte string (written to pBuf),
   //      remaining space filled with zero's by MemAlloc (!)
   decode : function(base64str, pBuf)
   {
      // chr1, chr2, chr3;
      // enc1, enc2, enc3, enc4;
      var i = 0;
      base64str = base64str.replace(/[^A-Za-z0-9\+\/\=]/g, "");
      if (base64str.length = 0 || base64str.length % 4 != 0)
         throw("Not a Base64 string!");
      var pCurr = pBuf;

      while (i < base64str.length)
      {
         var enc1 = this._keyStr.indexOf(base64str.charAt(i++));
         var enc2 = this._keyStr.indexOf(base64str.charAt(i++));
         var enc3 = this._keyStr.indexOf(base64str.charAt(i++));
         var enc4 = this._keyStr.indexOf(base64str.charAt(i++));
         var chr1 = (enc1 << 2) | (enc2 >> 4);
         var chr2 = (enc3 != 0x40) ? (((enc2 & 0x0f) << 4) | (enc3 >> 2)) : 0;
         var chr3 = (enc4 != 0x40) ? (((enc3 & 0x03) << 6) | enc4) : 0;
         AkelPad.MemCopy(pCurr++, chr1, 5 /* DT_BYTE */);
         AkelPad.MemCopy(pCurr++, chr2, 5 /* DT_BYTE */);
         AkelPad.MemCopy(pCurr++, chr3, 5 /* DT_BYTE */);
      }
      
      return;
   }
}

var hWndEdit;
var Usage = "Usage: select text and execute Call(\"Scripts::Main\", 1, \""+WScript.ScriptName+"\", \"enc|dec\")";

function ReportError(msg, printUsage)
{
   AkelPad.MessageBox(hWndEdit, msg+(printUsage?("\n"+Usage):""), WScript.ScriptName, 48 /*MB_ICONEXCLAMATION*/);
   WScript.Quit();
}

if (hWndEdit = AkelPad.GetMainWnd())
{
   // check arguments
   if (WScript.Arguments.length == 0)
      ReportError("Expect parameter", true);

   // Get & check selected text
   if ((selText = AkelPad.GetSelText()) == "")
      ReportError("No text selected!", true);

   // Warn about long text
   if (selText.length > 60000)
   {
      if (AkelPad.MessageBox(hWndEdit,
         "Selected text is "+selText.length+" characters long.\n"+
         "Process may take a long time. Continue?",
         WScript.ScriptName, 0x4 | 0x30 /*MB_YESNO | MB_ICONWARNING*/) != 6)
            WScript.Quit();
   }

   try
   {
      var pWCBuf = 0; // buffer to store wide char string
      var pMBBuf = 0; // buffer to store multi-byte string
      var sysFn = AkelPad.SystemFunction();
      var codePage = AkelPad.GetEditCodePage(0);
      // current code page is UTF16* or UTF32* - set ansi current code page
      // (WideChar <-> MultiByte functions don't work with this code pages)
      if (codePage == 1 || codePage == 1200 || codePage == 1201 || codePage == 12000 || codePage == 12001)
         codePage = 0;

      switch(WScript.Arguments(0))
      {
         // Encode selected text to Base64.
         // Before encoding convert it from UTF16 to multibyte string using current codepage (!)
         case "enc" :
            selText += "\u0000"; // so we don't worry about string length in conversions

            if (!(pWCBuf = AkelPad.MemAlloc(selText.length * _TSIZE)))
               throw("MemAlloc fail");
            AkelPad.MemCopy(pWCBuf, selText, _TSTR);

            // get required buffer size
            var bufLen = sysFn.Call(
                  "Kernel32::WideCharToMultiByte",
                  codePage, //   __in   UINT CodePage,         
                  0,        //   __in   DWORD dwFlags,         
                  pWCBuf,   //   __in   LPCWSTR lpWideCharStr, 
                  -1,       //   __in   int cchWideChar,       
                  0,        //   __out  LPSTR lpMultiByteStr,   
                  0,        //   __in   int cbMultiByte,       
                  0,        //   __in   LPCSTR lpDefaultChar,   
                  0);       //   __out  LPBOOL lpUsedDefaultChar
            if (bufLen == 0)
               throw("WCtoMB fail "+sysFn.GetLastError());
            if (!(pMBBuf = AkelPad.MemAlloc(bufLen)))
               throw("MemAlloc fail");

            // convert buffer
            bufLen = sysFn.Call(
                  "Kernel32::WideCharToMultiByte",
                  codePage, //   __in   UINT CodePage,         
                  0,        //   __in   DWORD dwFlags,         
                  pWCBuf,   //   __in   LPCWSTR lpWideCharStr, 
                  -1,       //   __in   int cchWideChar,       
                  pMBBuf,   //   __out  LPSTR lpMultiByteStr,   
                  bufLen,   //   __in   int cbMultiByte,       
                  0,        //   __in   LPCSTR lpDefaultChar,   
                  0);       //   __out  LPBOOL lpUsedDefaultChar
            if (bufLen == 0)
               throw("WCtoMB fail "+sysFn.GetLastError());
 
            // encode buffer
            selText = Base64.encode(pMBBuf, bufLen);

            break;

         case "dec"  :
            selText += "\u0000"; // so we don't worry about string length in conversions

            // allocate buffer with enough space for trailing zero's
            var bufLen = Math.ceil(selText.length * 3/4) + 1;
            if (!(pMBBuf = AkelPad.MemAlloc(bufLen)))
               throw("MemAlloc fail");

            // decode string to buffer
            Base64.decode(selText, pMBBuf);
            
            // get required buffer size
            var code = sysFn.Call(
                  "Kernel32::MultiByteToWideChar",
                  codePage, //   __in   UINT CodePage,         
                  0,        //   __in   DWORD dwFlags,         
                  pMBBuf,   //   __in   LPCSTR lpMultiByteStr, 
                  -1,       //   __in   int cbMultiByte,       
                  0,        //   __out  LPWSTR lpWideCharStr,   
                  0);       //   __in   int cchWideChar      
            if (code == 0)
               throw("MBtoWC fail "+sysFn.GetLastError());
            if (!(pWCBuf = AkelPad.MemAlloc(code * _TSIZE)))
               throw("MemAlloc fail");
            
            // convert buffer to wide char
            code = sysFn.Call(
                  "Kernel32::MultiByteToWideChar",
                  codePage,   //   __in   UINT CodePage,         
                  0,          //   __in   DWORD dwFlags,         
                  pMBBuf,     //   __in   LPCSTR lpMultiByteStr, 
                  -1,         //   __in   int cbMultiByte,       
                  pWCBuf,     //   __out  LPWSTR lpWideCharStr,   
                  code);      //   __in   int cchWideChar      
            if (code == 0)
               throw("MBtoWC fail "+sysFn.GetLastError());

            selText = AkelPad.MemRead(pWCBuf, 1 /* DT_UNICODE */);

            break;

         case "dechtml"  :
            var regex = new RegExp("&#([0-9][0-9][0-9][0-9]);", "gm");
            selText = selText.replace(regex,
                function (str, p1, offs, s) { return String.fromCharCode(Number(p1));}
                );

            break;
         default :
            throw("Wrong parameter: "+WScript.Arguments(0));
      } // switch
   } // try
   catch(err)
   {
      ReportError(err);
   }
   finally
   {
      if (pWCBuf) AkelPad.MemFree(pWCBuf);
      if (pMBBuf) AkelPad.MemFree(pMBBuf);
   }

   AkelPad.ReplaceSel(selText, -1);
}
