/*
 * Copyright (c) 2005-2006 Tom N Harris <telliamed@whoopdedo.org>.
 *
 *     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 2 of the License, or
 *     (at your option) any later version.
 *
 *     A copy of the GNU General Public License can be found at
 *     http://www.gnu.org/
 *
 * Written by Tom N Harris.
 *
 */

#include <cstring>
#include <cstdio>
#include <cctype>
#include <cerrno>

#include "win32.h"
#include <shlwapi.h>

#include "InstallCfg.h"


using namespace Setup;
using namespace std;

static void
strlower(char* line)
{
	char* p = line;
	while (*p)
	{
		*p = tolower(*p);
		++p;
	}
}

static void 
clean_cfg(char* line)
{
	char* endp = line + (strlen(line) - 1);
	while (isspace(*endp)) --endp;
	*++endp = '\0';
	char* p = strchr(line, '+');
	while (p)
	{
		char* a = p - 1;
		++p;
		while (isspace(*a)) --a;
		while (isspace(*p)) ++p;
		++a;
		*a++ = '+';
		memmove(a, p, (endp - p) + 1);
		p = strchr(a, '+');
	}
}

static bool
has_cfg(const char* cfg, const char* dir)
{
	int l = strlen(dir);
	char const* e = cfg + strlen(cfg);
	char const* p = cfg;
	char const* q;
	while (p < e)
	{
		q = strchr(p, '+');
		if (!q)
			q = e;
		if (l == (q - p)
		 && 0 == strnicmp(dir, p, q - p))
		{
			return true;
		}
		p = q + 1;
	}
	return false;
}

String 
temp_config(String const& cfgname)
{
	TCHAR path[MAX_PATH];
	TCHAR tempfile[MAX_PATH];
	cfgname.copy(path, MAX_PATH - 1);
	path[MAX_PATH] = TEXT('\0');

	::PathRemoveFileSpec(path);
	if (! ::GetTempFileName(path, TEXT("CFG"), 0, tempfile))
	{
		return cfgname;
	}
	return String(tempfile);
}


InstallCfg::InstallCfg()
	: m_dirty(false)
{
}

InstallCfg::~InstallCfg()
{
}

void 
InstallCfg::AddDir(std::string const& cfgname, String const& dirname)
{
#ifdef UNICODE
	string dir = Win32::AnsiString(dirname.c_str(), dirname.size());
#else
	string dir = dirname;
#endif

	vector<CfgLine>::iterator line = m_lines.begin();
	for (; line != m_lines.end(); ++line)
	{
		if (line->first == cfgname)
		{
			string& cfg = line->second;
			size_t inp = cfg.find('+');
			if (! has_cfg(cfg.c_str(), dirname.c_str()))
			{
				if (inp > cfg.size())
					inp = cfg.size();
				cfg.insert(inp++, 1, '+');
				cfg.insert(inp, dir);
				m_dirty = true;
			}
			return;
		}
	}

	m_lines.push_back(make_pair(cfgname, dir));
	m_dirty = true;
}

void 
InstallCfg::AddDirFront(std::string const& cfgname, String const& dirname)
{
#ifdef UNICODE
	string dir = Win32::AnsiString(dirname.c_str(), dirname.size());
#else
	string dir = dirname;
#endif

	vector<CfgLine>::iterator line = m_lines.begin();
	for (; line != m_lines.end(); ++line)
	{
		if (line->first == cfgname)
		{
			string& cfg = line->second;
			if (! cfg.empty())
			{
				if (! has_cfg(cfg.c_str(), dirname.c_str()))
				{
					cfg.insert(0, dir);
					cfg.insert(dir.size(), 1, '+');
					m_dirty = true;
				}
			}
			else
			{
				cfg.append(dir);
				m_dirty = true;
			}
			return;
		}
	}

	m_lines.push_back(make_pair(cfgname, dir));
	m_dirty = true;
}

void 
InstallCfg::AddDirBack(std::string const& cfgname, String const& dirname)
{
#ifdef UNICODE
	string dir = Win32::AnsiString(dirname.c_str(), dirname.size());
#else
	string dir = dirname;
#endif

	vector<CfgLine>::iterator line = m_lines.begin();
	for (; line != m_lines.end(); ++line)
	{
		if (line->first == cfgname)
		{
			string& cfg = line->second;
			if (! cfg.empty())
			{
				if (! has_cfg(cfg.c_str(), dirname.c_str()))
				{
					cfg.append(1, '+');
					cfg.append(dir);
					m_dirty = true;
				}
			}
			else
			{
				cfg.append(dir);
				m_dirty = true;
			}
			return;
		}
	}

	m_lines.push_back(make_pair(cfgname, dir));
	m_dirty = true;
}

bool InstallCfg::LoadFile(String const& filename)
{
	FILE* hCfgFile;
#ifdef UNICODE
	hCfgFile = _wfopen(filename.c_str(), L"r");
#else
	hCfgFile = fopen(filename.c_str(), "r");
#endif
	if (hCfgFile == NULL)
	{
		if (ENOFILE == errno)
		{
			// Missing file is a non-error.
			return true;
		}
		return false;
	}

	try
	{
		m_lines.clear();
		m_dirty = false;

		char buffer[140];
		size_t dwBytes = 0;
		size_t dwAvail = sizeof(buffer);
		char* startp = buffer;
		while (1)
		{
			dwBytes = fread(startp, 1, dwAvail - 1, hCfgFile);

			char* line = buffer;
			char* endp = startp + dwBytes;
			*endp = '\0';
			while (line < endp)
			{
				char* endl = strchr(line, '\n');
				if (!endl)
				{
					if (dwBytes != 0)
					{
						dwBytes = endp - line;
						memmove(buffer, line, dwBytes);
						startp = buffer + dwBytes;
						dwAvail = sizeof(buffer) - dwBytes;
						++dwBytes;
						break;
					}
					endl = endp;
				}
				else
				{
					*endl++ = '\0';
					startp = buffer;
					dwAvail = sizeof(buffer);
				}
				
				if (line[0] == ';' || (line[0] == '/' && line[1] == '/'))
				{
					m_lines.push_back(make_pair(string(),string(line)));
					line = endl;
					continue;
				}

				char* cfgp = strchr(line, ' ');
				if (!cfgp)
				{
					strlower(line);
					m_lines.push_back(make_pair(string(line),string()));
					line = endl;
					continue;
				}

				*cfgp++ = '\0';
				while (isspace(*cfgp)) ++cfgp;
				clean_cfg(cfgp);
				strlower(line);
				m_lines.push_back(make_pair(string(line),string(cfgp)));
				
				line = endl;
			}

			if (dwBytes == 0)
				break;
		}

		fclose(hCfgFile);
		return true;
	}
	catch (...)
	{
		fclose(hCfgFile);
		throw;
	}
}

bool InstallCfg::SaveFile(String const& filename)
{
	if (!m_dirty || m_lines.empty())
		return true;

	String tmpfile = temp_config(filename);
	FILE* hCfgFile;
#ifdef UNICODE
	hCfgFile = _wfopen(tmpfile.c_str(), L"w");
#else
	hCfgFile = fopen(tmpfile.c_str(), "w");
#endif
	if (hCfgFile == NULL)
	{
		return false;
	}

	try
	{
		for (vector<CfgLine>::const_iterator line = m_lines.begin();
		     line != m_lines.end(); ++line)
		{
			if (! line->first.empty())
			{
				fwrite(line->first.c_str(), 1, line->first.size(), hCfgFile);
				fputc(' ', hCfgFile);
			}
			fwrite(line->second.c_str(), 1, line->second.size(), hCfgFile);
			fputc('\n', hCfgFile);
		}

		fclose(hCfgFile);
		m_dirty = false;
		::MoveFileEx(tmpfile.c_str(), filename.c_str(), MOVEFILE_REPLACE_EXISTING);
		return true;
	}
	catch (...)
	{
		fclose(hCfgFile);
		::DeleteFile(tmpfile.c_str());
		throw;
	}
}

