/*
 Program WinCaml: Graphical User Interface
 for interactive use of Caml-Light and Ocaml.
 Copyright (C) 2005-2015 Jean Mouric 35700 Rennes France
 email: jean.mouric@orange.fr
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

// File Win/Paths.cpp

#include "Win.h"
#include <fstream>
#include <sstream>

enum{OCAML64_LOCAL, OCAML64, OCAML32_64, OCAML32_64A, OCAML32, OCAML32A, OCAML32B, CAMLLIGHT64, CAMLLIGHT64A, CAMLLIGHT32, CAMLLIGHT32A, OCAMLNOTFOUND};

#include <shlobj.h>
#include "Shlwapi.h"

bool getConfigFolderPath(wstring& path);
void deleteFile(const wstring& fileName);

extern CMDIFrame* mainFrame;

void ldconf(wstring& ocamlPath)
{
	wstring ldconfPath = ocamlPath +  L"\\lib\\ld.conf";
#if defined(_MINGW_) || defined(_MSVC98_)
	string toBytes(const wstring& wstr);
	wfstream fs(toBytes(ldconfPath).c_str(), wfstream::out);
#else
	wfstream fs(ldconfPath.c_str(), wfstream::out);
#endif
	if (!fs.fail())
	{
		wstring s = ocamlPath + L"\\lib\\stublibs";
		fs << s.c_str() << endl;
		fs.close();
	}
}

wstring setOCamlPath()
{
    WCHAR path[MAX_PATH];
	memset(path, 0, sizeof(path));
    BROWSEINFO bi = {0};
    bi.lpszTitle = (L"Emplacement d'OCaml ?");
	bi.hwndOwner = mainFrame->hWnd;
    LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
    if (pidl != 0)
    {
        SHGetPathFromIDList (pidl, path);
		wstring s;
		if (getConfigFolderPath(s))
		{
			s += L"ocamlbase.txt";
#if defined(_MINGW_) || defined(_MSVC98_)
			string toBytes(const wstring& wstr);
			wfstream wfs(toBytes(s).c_str(), wfstream::out);
#else
			wfstream wfs(s.c_str(), wfstream::out);
#endif			
			if (!wfs.fail())
			{
				wfs << path << endl;
				wfs.close();
			}
		}
        IMalloc* p = NULL;
        if ( SUCCEEDED(SHGetMalloc(&p)))
        {
            p->Free (pidl);
            p->Release();
        }
	}
	return path;
}

wstring getOCamlPath()
{
    wstring cfp;
    wstring ocamlPath;
    if (getConfigFolderPath(cfp))
    {
        cfp += L"ocamlbase.txt";
 #if defined(_MINGW_) || defined(_MSVC98_)
		string toBytes(const wstring& wstr);
		wfstream wfs(toBytes(cfp).c_str(), wfstream::in);
#else
		wfstream wfs(cfp.c_str(), wfstream::in);
#endif			
        if (!wfs.fail())
        {
            wchar_t c;
            while (wfs.get(c) && c != L'\r' && c != L'\n')
                ocamlPath += c;
            
            wfs.close();
        }
    }
    return ocamlPath;
}

void resetOCamlPath()
{
    wstring s;
    if (getConfigFolderPath(s))
    {
        s += L"ocamlbase.txt";
        if (PathFileExists(s.c_str()))
        {
            deleteFile(s);
        }
    }
}

#ifndef SCS_32BIT_BINARY
#define SCS_32BIT_BINARY 0
#endif
#ifndef SCS_64BIT_BINARY
#define SCS_64BIT_BINARY 6
#endif

wstring commandLine(int option, const wstring& dirPath)
{
	wstring cmdl;
	switch(option)
	{
	case OCAML64_LOCAL:
		{
			wstring ocamlPath = dirPath + L"\\ocaml\\windows\\ocaml64\\miniocaml";
			cmdl = L"\"" + ocamlPath + L"\\bin\\ocamlrun.exe\"" + L" \"" + ocamlPath + L"\\bin\\ocaml\" -I \"" + ocamlPath + L"\\lib\" -I \"" + ocamlPath + L"\\lib\\stublibs\"";
			::SetEnvironmentVariable(wstring(L"PATH").c_str(), (ocamlPath + L"\\bin").c_str());
			::SetEnvironmentVariable(wstring(L"OCAMLLIB").c_str(), (ocamlPath + L"\\lib").c_str());
			ldconf(ocamlPath);
			break;
		}
	case OCAML64:
		{
			wstring ocamlPath = L"C:\\Program Files\\Objective Caml";
			cmdl = L"\"" + ocamlPath + L"\\bin\\ocamlrun.exe\"" + L" \"" + ocamlPath + L"\\bin\\ocaml\" -I \"" + dirPath + L"\\ocaml\\windows\\ocaml64" + L"\" -I \"" + ocamlPath + L"\\lib\"";
			::SetEnvironmentVariable(wstring(L"OCAMLLIB").c_str(), (ocamlPath + L"\\lib").c_str());
			::SetEnvironmentVariable(wstring(L"PATH").c_str(), (ocamlPath + L"\\bin").c_str());
			break;
		}
	case OCAML32_64:
		{
			wstring ocamlPath = L"C:\\Program Files (x86)\\Objective Caml";
			cmdl = L"\"" + ocamlPath + L"\\bin\\ocamlrun.exe\"" + L" \"" + ocamlPath + L"\\bin\\ocaml\" -I \"" + dirPath + L"\\ocaml\\windows\\ocaml32" + L"\" -I \"" + ocamlPath + L"\\lib\"";
			::SetEnvironmentVariable(wstring(L"OCAMLLIB").c_str(), (ocamlPath + L"\\lib").c_str());
			::SetEnvironmentVariable(wstring(L"PATH").c_str(), (ocamlPath + L"\\bin").c_str());
			break;
		}
	case OCAML32_64A:
		{
			wstring ocamlPath = L"C:\\OCaml";
			cmdl = L"\"" + ocamlPath + L"\\bin\\ocamlrun.exe\"" + L" \"" + ocamlPath + L"\\bin\\ocaml\" -I \"" + dirPath + L"\\ocaml\\windows\\ocaml32" + L"\" -I \"" + ocamlPath + L"\\lib\"";
			::SetEnvironmentVariable(wstring(L"OCAMLLIB").c_str(), (ocamlPath + L"\\lib").c_str());
			::SetEnvironmentVariable(wstring(L"PATH").c_str(), (ocamlPath + L"\\bin").c_str());
			break;
		}
	case OCAML32:
		{
			wstring ocamlPath = L"C:\\Program Files\\Objective Caml";
			cmdl = L"\"" + ocamlPath + L"\\bin\\ocamlrun.exe\"" + L" \"" + ocamlPath + L"\\bin\\ocaml\" -I \"" + dirPath + L"\\ocaml\\windows\\ocaml32" + L"\" -I \"" + ocamlPath + L"\\lib\"";
			::SetEnvironmentVariable(wstring(L"OCAMLLIB").c_str(), (ocamlPath + L"\\lib").c_str());
			::SetEnvironmentVariable(wstring(L"PATH").c_str(), (ocamlPath + L"\\bin").c_str());
			break;
		}
	case OCAML32A:
		{
			wstring ocamlPath = L"C:\\OCaml";
			cmdl = L"\"" + ocamlPath + L"\\bin\\ocamlrun.exe\"" + L" \"" + ocamlPath + L"\\bin\\ocaml\" -I \"" + dirPath + L"\\ocaml\\windows\\ocaml32" + L"\" -I \"" + ocamlPath + L"\\lib\"";
			::SetEnvironmentVariable(wstring(L"OCAMLLIB").c_str(), (ocamlPath + L"\\lib").c_str());
			::SetEnvironmentVariable(wstring(L"PATH").c_str(), (ocamlPath + L"\\bin").c_str());
			break;
		}
	case OCAML32B:
		{
			wstring ocamlPath = getOCamlPath();
			if (ocamlPath == L"")
			{
				cmdl = L"";
			}
			else
			{
				ldconf(ocamlPath);
				DWORD binaryType;
				wstring libgraphDirPath = dirPath;
				if (GetBinaryType(LPCTSTR((ocamlPath + L"\\bin\\ocamlrun.exe").c_str()), (LPDWORD)&binaryType)) {
					if (binaryType  == SCS_32BIT_BINARY)
					{
						libgraphDirPath +=  L"\\ocaml\\windows\\ocaml32";
					}
					else if (binaryType  == SCS_64BIT_BINARY)
					{
						libgraphDirPath +=  L"\\ocaml\\windows\\ocaml64";
					}
				}
				cmdl = L"\"" + ocamlPath + L"\\bin\\ocamlrun.exe\"" + L" \"" + ocamlPath + L"\\bin\\ocaml\" -I \"" + libgraphDirPath + L"\" -I \"" + ocamlPath + L"\\lib\"" + L"\" -I \"" + ocamlPath + L"\\lib\\stublibs\"";
				wstring libPath = ocamlPath + L"\\lib";
				::SetEnvironmentVariable(wstring(L"OCAMLLIB").c_str(), libPath.c_str());
				::SetEnvironmentVariable(wstring(L"PATH").c_str(), (ocamlPath + L"\\bin").c_str());
			}
			break;
		}
	case OCAMLNOTFOUND:
		{
			wstring ocamlPath = setOCamlPath();
			if (ocamlPath == L"")
			{
				cmdl = L"";
			}
			else
			{
				ldconf(ocamlPath);
				DWORD binaryType;
				wstring libgraphDirPath = dirPath;
				if (GetBinaryType(LPCTSTR((ocamlPath + L"\\bin\\ocamlrun.exe").c_str()), (LPDWORD)&binaryType)) {
					if (binaryType  == SCS_32BIT_BINARY)
					{
						libgraphDirPath +=  L"\\ocaml\\windows\\ocaml32";
					}
					else if (binaryType  == SCS_64BIT_BINARY)
					{
						libgraphDirPath +=  L"\\ocaml\\windows\\ocaml64";
					}
				}
				cmdl = L"\"" + ocamlPath + L"\\bin\\ocamlrun.exe\"" + L" \"" + ocamlPath + L"\\bin\\ocaml\" -I \"" + libgraphDirPath + L"\" -I \"" + ocamlPath + L"\\lib\"" + L"\" -I \"" + ocamlPath + L"\\lib\\stublibs\"";
				wstring libPath = ocamlPath + L"\\lib";
				::SetEnvironmentVariable(wstring(L"OCAMLLIB").c_str(), libPath.c_str());
				::SetEnvironmentVariable(wstring(L"PATH").c_str(), (ocamlPath + L"\\bin").c_str());
			}
			break;
		}
	case CAMLLIGHT64:
		{
			wstring locale = L"fr";
			wstring dirPath1 = dirPath;
			for(size_t i = 0; i < dirPath1.length(); i++)
			{
				if (dirPath1[i] == L'\\') dirPath1.replace(i, 1, L"/");
			}
			dirPath1 = L"/cygdrive/" + dirPath1.substr(0, 1) + dirPath1.substr(2);
			cmdl = L"\"" + dirPath + L"\\caml-light\\bin\\camltoplevel64cygwin.exe\"" + L" -stdlib \"" + dirPath1 + L"/caml-light/lib\"" + L" -lang \"" + locale + L"\"";
			break;
		}
	case CAMLLIGHT64A:
		{
			wstring locale = L"fr";
			cmdl = L"\"" + dirPath + L"\\caml-light\\bin\\camltoplevel64.exe\"" + L" -stdlib \"" + dirPath + L"\\caml-light\\lib\"" + L" -lang \"" + locale + L"\"";
			break;
		}
	case CAMLLIGHT32:
		{
			wstring locale = L"fr";
			wstring dirPath1 = dirPath;
			for(size_t i = 0; i < dirPath1.length(); i++)
			{
				if (dirPath1[i] == L'\\') dirPath1.replace(i, 1, L"/");
			}
			dirPath1 = L"/cygdrive/" + dirPath1.substr(0, 1) + dirPath1.substr(2);
			cmdl = L"\"" + dirPath + L"\\caml-light\\bin\\camltoplevel32cygwin.exe\"" + L" -stdlib \"" + dirPath1 + L"/caml-light/lib\"" + L" -lang \"" + locale + L"\"";
			break;
		}
	case CAMLLIGHT32A:
		{
			wstring locale = L"fr";
			cmdl = L"\"" + dirPath + L"\\caml-light\\bin\\camltoplevel32.exe\"" + L" -stdlib \"" + dirPath + L"\\caml-light\\lib\"" + L" -lang \"" + locale + L"\"";
			break;
		}
	default:
		cmdl = L"";
		break;
	}
	return cmdl;
}

bool architecture64();
wstring applicationDirectoryPath();

CProcess* startCaml(bool ocaml)
{
	bool arch64 = architecture64();
	int option;
	if (ocaml && arch64) {option = OCAML64_LOCAL;}
	else if (ocaml && !arch64) {option = OCAML32A;}
	else if (!ocaml&& arch64) {option = CAMLLIGHT64;}
	else {option = CAMLLIGHT32;}
    
	wstring appDirPath = applicationDirectoryPath();
   
	CProcess* camlProcess = new CProcess();

start:
	wstring cmdl = commandLine(option, appDirPath);
	if (cmdl == L"")
	{
		if (ocaml && option != OCAMLNOTFOUND)
		{
			option = OCAMLNOTFOUND;
			goto start;
		}
		else
		{
			delete camlProcess;
			return NULL;
		}
	}
	if (camlProcess->start(cmdl))
	{
		return camlProcess;
	}
	switch(option)
	{
        case OCAML64_LOCAL:
		{
			option = OCAML64;
			goto start;
		}
        case OCAML64:
		{
			option = OCAML32_64A;
			goto start;
		}
        case OCAML32_64A:
		{
			option = OCAML32_64;
			goto start;
		}
        case OCAML32_64:
		{
			option = OCAML32B;
			goto start;
		}
        case OCAML32A:
		{
			option = OCAML32;
			goto start;
		}
        case OCAML32:
		{
			option = OCAML32B;
			goto start;
		}
		case OCAML32B:
		{
			option = OCAMLNOTFOUND;
			goto start;
		}
        case CAMLLIGHT64:
		{
			option = CAMLLIGHT64A;
			goto start;
		}
        case CAMLLIGHT64A:
		{
			option = CAMLLIGHT32;
			goto start;
		}
        case CAMLLIGHT32:
		{
			option = CAMLLIGHT32A;
			goto start;
		}
        case CAMLLIGHT32A:
		{
			delete camlProcess;
			return NULL;
		}
        default:
		{
			delete camlProcess;
			return NULL;
		}
	}
}

void camllightHelp()
{
	wstring appDirPath = applicationDirectoryPath();
	wstring docPath = appDirPath + L"\\caml-light\\man-caml\\index.html";
	::ShellExecute(NULL, L"open", docPath.c_str(), NULL, appDirPath.c_str(), SW_SHOW);
}

void ocamlHelp()
{
	wstring appDirPath = applicationDirectoryPath();
	wstring docPath = appDirPath;
	docPath += L"\\ocaml\\htmlman\\index.html";
	if (32 >= (size_t)::ShellExecute(NULL, L"open", docPath.c_str(), NULL, appDirPath.c_str(), SW_SHOW))
	{
		if (32 >= (size_t)::ShellExecute(NULL, L"open", L"C:\\Program Files\\Objective Caml\\htmlman\\index.html", NULL, L"C:\\Program Files\\Objective Caml\\htmlman", SW_SHOW))
			::ShellExecute(NULL, L"open", L"C:\\Program Files (x86)\\Objective Caml\\htmlman\\index.html", NULL, L"C:\\Program Files (x86)\\Objective Caml\\htmlman", SW_SHOW);
	}
}
