#include <windows.h>
#include <shlobj.h>
#include "util.h"
#include "state.h"
#include "config.h"

#include "fileform.h"
#include "ui.h"

#ifdef NSIS_CONFIG_LOG
char g_log_file[1024];
#endif

char g_usrvars[24][NSIS_MAX_STRLEN];

HANDLE g_hInstance;

HANDLE myCreateProcess(char *cmd, char *dir)
{
  PROCESS_INFORMATION ProcInfo={0,};
  STARTUPINFO StartUp={0,};
  StartUp.cb=sizeof(StartUp);
  if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, dir, &StartUp, &ProcInfo))
    return NULL;
  if (NULL != ProcInfo.hThread)  CloseHandle( ProcInfo.hThread );
  return ProcInfo.hProcess;
}


void addtrailingslash(char *str)
{
  if (lastchar(str)!='\\') lstrcat(str,"\\");
}

char lastchar(const char *str)
{
  return *CharPrev(str,str+lstrlen(str));
}

void trimslashtoend(char *buf)
{
  char *p=scanendslash(buf);
  if (p<buf) p=buf;
  *p=0;
}


char *scanendslash(const char *str)
{
	char *s=CharPrev(str,str+lstrlen(str));
	if (!*str) return (char*)str-1;
	for (;;)
	{
    char *t;
  	if ('\\' == *s) return s;
		t=CharPrev(str,s);
		if (t==s) return (char*)str-1;
		s=t;
	}
}

int validpathspec(char *ubuf)
{
  return ((ubuf[0]=='\\' && ubuf[1]=='\\') || (ubuf[0] && *CharNext(ubuf)==':')); 
}

int is_valid_instpath(char *s)
{
  int ivp=0;
  // if 8 is set, req is 0, which means rootdirs are not allowed.
  int req=!(g_inst_cmnheader->misc_flags&8);
  if (s[0] == '\\' && s[1] == '\\') // \\ path
  {
    if (lastchar(s)!='\\') ivp++;
    while (*s) 
    {
      if (*s == '\\') ivp++;
      s=CharNext(s);
    }
    ivp/=5-req;
  }
  else
  {
    if (*s)
    {
      s=CharNext(s);
      if (*s == ':')
      {
        s=CharNext(s);
        if (*s == '\\')
        {
          s=CharNext(s);
          if (req || (*s && *s != '\\')) ivp++;
        }
      }
    }
  }
  return ivp;
}

static char *findinmem(char *a, char *b, int len_of_a)
{
  if (len_of_a<0) len_of_a=lstrlen(a);
  len_of_a -= lstrlen(b);
  while (*a && len_of_a >= 0)
  {
    char *t=a,*u=b;
    while (*t && *t == *u)
    {
      t++;
      u++;
    }
    if (!*u) return a;
    a++;
    len_of_a--;
  }
  return NULL;
}


void *mini_memcpy(void *out, const void *in, int len)
{
  char *c_out=(char*)out;
  char *c_in=(char *)in;
  while (len-- > 0)
  {
    *c_out++=*c_in++;
  }
  return out;
}


HANDLE myOpenFile(const char *fn, DWORD da, DWORD cd)
{
  return CreateFile(fn,da,FILE_SHARE_READ,NULL,cd,0,NULL);
}

#ifdef NSIS_SUPPORT_MOVEONREBOOT
BOOL MoveFileOnReboot(LPCTSTR pszExisting, LPCTSTR pszNew)
{
  BOOL fOk = 0;
  HMODULE hLib=LoadLibrary("kernel32.dll");
  if (hLib)
  {
    typedef BOOL (WINAPI *mfea_t)(LPCSTR lpExistingFileName,LPCSTR lpNewFileName,DWORD dwFlags);
    mfea_t mfea;
    mfea=(mfea_t) GetProcAddress(hLib,"MoveFileExA");
    if (mfea)
    {
      fOk=mfea(pszExisting, pszNew, MOVEFILE_DELAY_UNTIL_REBOOT|MOVEFILE_REPLACE_EXISTING);
    }
    FreeLibrary(hLib);
  }

  if (!fOk)
  {
    static char szRenameLine[1024];
    static char wininit[1024];
    static char tmpbuf[1024];
    int cchRenameLine;
    char *szRenameSec = "[Rename]\r\n";
    HANDLE hfile, hfilemap;
    DWORD dwFileSize, dwRenameLinePos;
    static const char nulint[4]="NUL";

    if (pszNew) GetShortPathName(pszNew,tmpbuf,1024);
    else *((int *)tmpbuf) = *((int *)nulint);
    // wininit is used as a temporary here
    GetShortPathName(pszExisting,wininit,1024);
    cchRenameLine = wsprintf(szRenameLine,"%s=%s\r\n",tmpbuf,wininit);
  
    GetWindowsDirectory(wininit, 1024-16);
    lstrcat(wininit, "\\wininit.ini");
    hfile = CreateFile(wininit,      
        GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);

    if (hfile != INVALID_HANDLE_VALUE) 
    {
      dwFileSize = GetFileSize(hfile, NULL);
      hfilemap = CreateFileMapping(hfile, NULL, PAGE_READWRITE, 0, dwFileSize + cchRenameLine + 10, NULL);

      if (hfilemap != NULL) 
      {
        LPSTR pszWinInit = (LPSTR) MapViewOfFile(hfilemap, FILE_MAP_WRITE, 0, 0, 0);

        if (pszWinInit != NULL) 
        {
          int do_write=0;
          LPSTR pszRenameSecInFile = findinmem(pszWinInit, szRenameSec,-1);
          if (pszRenameSecInFile == NULL) 
          {
            lstrcpy(pszWinInit+dwFileSize, szRenameSec);
            dwFileSize += 10;
            dwRenameLinePos = dwFileSize;
            do_write++;
          } 
          else 
          {
            char *pszFirstRenameLine = findinmem(pszRenameSecInFile, "\n",-1)+1;
            int l=pszWinInit + dwFileSize-pszFirstRenameLine;
            if (!findinmem(pszFirstRenameLine,szRenameLine,l))
            {
              void* data=(void*)GlobalAlloc(GMEM_FIXED,l);
              mini_memcpy(data, pszFirstRenameLine, l);
              mini_memcpy(pszFirstRenameLine + cchRenameLine, data, l);
              GlobalFree((HGLOBAL)data);

              dwRenameLinePos = pszFirstRenameLine - pszWinInit;
              do_write++;
            }
          }

          if (do_write) 
          {
            mini_memcpy(&pszWinInit[dwRenameLinePos], szRenameLine,cchRenameLine);
            dwFileSize += cchRenameLine;
          }

          UnmapViewOfFile(pszWinInit);

          fOk++;
        }
        CloseHandle(hfilemap);
      }
      SetFilePointer(hfile, dwFileSize, NULL, FILE_BEGIN);
      SetEndOfFile(hfile);
      CloseHandle(hfile);
    }
  }
  return fOk;
}
#endif

void recursive_create_directory(char *directory)
{
  char *tp;
	char *p;
  p=directory;
  while (*p == ' ') p=CharNext(p);
  if (!*p) return;
  tp=CharNext(p);
  if (*tp == ':' && tp[1] == '\\') p=tp+2;
  else if (p[0] == '\\' && p[1] == '\\')
  {
    int x;
    for (x = 0; x < 2; x ++)
    {
      while (*p != '\\' && *p) p=CharNext(p); // skip host then share
      if (*p) p=CharNext(p);
    }

  }
  else return;
  while (*p)
  {
    while (*p != '\\' && *p) p=CharNext(p);
    if (!*p) CreateDirectory(directory,NULL);
    else
    {
      *p=0;
  	  CreateDirectory(directory,NULL);
      *p++ = '\\';
    }
  }
}


void myRegGetStr(HKEY root, const char *sub, const char *name, char *out)
{
	HKEY hKey;
  *out=0;
  if (RegOpenKeyEx(root,sub,0,KEY_READ,&hKey) == ERROR_SUCCESS)
  {
		DWORD l = NSIS_MAX_STRLEN;
		DWORD t;
    if (RegQueryValueEx(hKey,name,NULL,&t,out,&l ) != ERROR_SUCCESS || t != REG_SZ) *out=0;
    out[NSIS_MAX_STRLEN-1]=0;
    RegCloseKey(hKey);
  }
}


char g_all_user_var_flag;

static void queryShellFolders(const char *name, char *out)
{
  int f=g_all_user_var_flag;
again:

  myRegGetStr(g_all_user_var_flag?HKEY_LOCAL_MACHINE:HKEY_CURRENT_USER,
    "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
    name+(f?0:7),out);
  if (!out[0])
  {
    if (f) 
    {
      f=0; goto again;
    }
    GetTempPath(NSIS_MAX_STRLEN,out);
  }
}

char ps_tmpbuf[NSIS_MAX_STRLEN*2];


void process_string_fromtab(char *out, int offs)
{
  process_string(ps_tmpbuf,GetStringFromStringTab(offs));
  lstrcpyn(out,ps_tmpbuf,NSIS_MAX_STRLEN);
}

void myitoa(char *s, int d) { wsprintf(s,"%d",d); }
int myatoi(char *s)
{
  unsigned int v=0;
  if (*s == '0' && (s[1] == 'x' || s[1] == 'X'))
  {
    s+=2;
    for (;;)
    {
      int c=*s++;
      if (c >= '0' && c <= '9') c-='0';
      else if (c >= 'a' && c <= 'f') c-='a'-10;
      else if (c >= 'A' && c <= 'F') c-='A'-10;
      else break;
      v<<=4;
      v+=c;
    }
  }
  else if (*s == '0' && s[1] <= '7' && s[1] >= '0')
  {
    s++;
    for (;;)
    {
      int c=*s++;
      if (c >= '0' && c <= '7') c-='0';
      else break;
      v<<=3;
      v+=c;
    }
  }
  else
  {
    int sign=0;
    if (*s == '-') { s++; sign++; }
    for (;;)
    {
      int c=*s++ - '0';
      if (c < 0 || c > 9) break;
      v*=10;
      v+=c;
    }
    if (sign) return -(int) v;
  }
  return (int)v;
}


int process_string_fromtab_toint(int offs)
{
  process_string(ps_tmpbuf,GetStringFromStringTab(offs));
  return myatoi(ps_tmpbuf);
}

// Dave Laundon's simplified process_string
void process_string(char *out, const char *in)
{
  char *outsave = out;
  while (*in && out - outsave < NSIS_MAX_STRLEN)
  {
    int nVarIdx = (unsigned char)*in++;
    if (nVarIdx < VAR_CODES_START)
    {
      *out++ = nVarIdx;
    }
    else if (nVarIdx == 255)
    {
      *out++ = *in++;
    }
    else
    {
      DWORD f;
      switch (nVarIdx) // The order of this list must match that in ..\strlist.cpp (err, build.cpp -J)
      {
        case VAR_CODES_START + 0: // HWNDPARENT
          wsprintf(out, "%u", (unsigned int)g_hwnd);
        break;
        case VAR_CODES_START + 1:  // 0
        case VAR_CODES_START + 2:  // 1
        case VAR_CODES_START + 3:  // 2
        case VAR_CODES_START + 4:  // 3
        case VAR_CODES_START + 5:  // 4
        case VAR_CODES_START + 6:  // 5
        case VAR_CODES_START + 7:  // 6
        case VAR_CODES_START + 8:  // 7
        case VAR_CODES_START + 9:  // 8
        case VAR_CODES_START + 10: // 9
        case VAR_CODES_START + 11: // R0
        case VAR_CODES_START + 12: // R1
        case VAR_CODES_START + 13: // R2
        case VAR_CODES_START + 14: // R3
        case VAR_CODES_START + 15: // R4
        case VAR_CODES_START + 16: // R5
        case VAR_CODES_START + 17: // R6
        case VAR_CODES_START + 18: // R7
        case VAR_CODES_START + 19: // R8
        case VAR_CODES_START + 20: // R9
        case VAR_CODES_START + 21: // CMDLINE
        case VAR_CODES_START + 22: // INSTDIR
        case VAR_CODES_START + 23: // OUTDIR
        case VAR_CODES_START + 24: // EXEDIR
          lstrcpy(out, g_usrvars[nVarIdx - (VAR_CODES_START + 1)]);
          break;

        case VAR_CODES_START + 25: // PROGRAMFILES
          myRegGetStr(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion", "ProgramFilesDir", out);
          if (!*out)
            lstrcpy(out, "C:\\Program Files");
          break;

        case VAR_CODES_START + 26: // SMPROGRAMS
        case VAR_CODES_START + 27: // SMSTARTUP
        case VAR_CODES_START + 28: // DESKTOP
        case VAR_CODES_START + 29: // STARTMENU
          {
            static const char *tab[]={
              "Common Programs",
              "Common Startup",
              "Common Desktop",
              "Common Start Menu"
            };
            queryShellFolders(tab[nVarIdx-(VAR_CODES_START+26)], out);
          }
        break;

        case VAR_CODES_START + 30: // QUICKLAUNCH
          queryShellFolders("Common AppData", out);
          lstrcat(out, "\\Microsoft\\Internet Explorer\\Quick Launch");
          f = GetFileAttributes(out);
          if (f != (DWORD)-1 && (f & FILE_ATTRIBUTE_DIRECTORY))
            break;
        case VAR_CODES_START + 31: // TEMP
          GetTempPath(NSIS_MAX_STRLEN, out);
          break;

        case VAR_CODES_START + 32: // WINDIR
          GetWindowsDirectory(out, NSIS_MAX_STRLEN);
          break;

        case VAR_CODES_START + 33: // SYSDIR
          GetSystemDirectory(out, NSIS_MAX_STRLEN);
          break;

        #if VAR_CODES_START + 33 >= 255
          #error "Too many variables!  Extend VAR_CODES_START!"
        #endif
      } // switch
      // remove trailing slash
      while (*out && *CharNext(out)) out++;
      if (nVarIdx > 21+VAR_CODES_START && *out == '\\') // only if not $0 to $R9, $CMDLINE, or $HWNDPARENT
        *out = 0;
      out=CharNext(out);
    } // >= VAR_CODES_START
  } // while
  *out = 0;
}
#ifdef NSIS_CONFIG_LOG

char log_text[4096];
int log_dolog;
void log_write(int close)
{ 
  extern char g_log_file[1024];
  static HANDLE fp=INVALID_HANDLE_VALUE;
  if (close)
  {
    if (fp!=INVALID_HANDLE_VALUE) 
    {
      CloseHandle(fp);
    }
    fp=INVALID_HANDLE_VALUE;
    return;
  }
  if (log_dolog)
  {
    if (g_log_file[0] && fp==INVALID_HANDLE_VALUE)
    {
      fp = CreateFile(g_log_file,GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,0,NULL);
      if (fp!=INVALID_HANDLE_VALUE) 
        SetFilePointer(fp,0,NULL,FILE_END);
    }
    if (fp!=INVALID_HANDLE_VALUE)
    {
      DWORD d;
      lstrcat(log_text,"\r\n");
      WriteFile(fp,log_text,lstrlen(log_text),&d,NULL);
    }
  }
}


#endif

#ifdef NSIS_SUPPORT_CREATESHORTCUT
int CreateShortCut(HWND hwnd, LPCSTR pszShortcutFile, LPCSTR pszIconFile, int iconindex, LPCSTR pszExe, LPCSTR pszArg, LPCSTR workingdir, int showmode, int hotkey)
{
  HRESULT hres;
  int rv=1;
  IShellLink* psl;
  hres=OleInitialize(NULL);
  if (hres != S_FALSE && hres != S_OK) return rv;

  hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
                            &IID_IShellLink, (void **) &psl);
  if (SUCCEEDED(hres))
  {
    IPersistFile* ppf;

    hres = psl->lpVtbl->QueryInterface(psl,&IID_IPersistFile, (void **) &ppf);
    if (SUCCEEDED(hres))
    {

       hres = psl->lpVtbl->SetPath(psl,pszExe);
       psl->lpVtbl->SetWorkingDirectory(psl,workingdir);
       if (showmode) psl->lpVtbl->SetShowCmd(psl,showmode);
       if (hotkey) psl->lpVtbl->SetHotkey(psl,(unsigned short)hotkey);
       if (pszIconFile) psl->lpVtbl->SetIconLocation(psl,pszIconFile,iconindex);
       if (pszArg) 
       {
         psl->lpVtbl->SetArguments(psl,pszArg);
       }

       if (SUCCEEDED(hres))
       {
          WCHAR wsz[1024];
          MultiByteToWideChar(CP_ACP, 0, pszShortcutFile, -1, wsz, 1024);
		      hres=ppf->lpVtbl->Save(ppf,(const WCHAR*)wsz,TRUE);
          if (SUCCEEDED(hres)) rv=0;
       }
      ppf->lpVtbl->Release(ppf);
    }
    psl->lpVtbl->Release(psl);
  }
  OleUninitialize();
  return rv;
}
#endif//NSIS_SUPPORT_CREATESHORTCUT