/* $Id: cfgfile.cpp,v 1.23 2004/01/02 03:54:01 fesnel Exp $ */
/*******************************************************************************
 *   This program is part of a library used by the Archimedes email client     * 
 *                                                                             *
 *   Copyright : (C) 1995-1998 Gennady B. Sorokopud (gena@NetVision.net.il)    *
 *               (C) 1995 Ugen. J. S. Antsilevich (ugen@latte.worldbank.org)   *
 *               (C) 1998-2004 by the Archimedes Project                       *
 *                   http://sourceforge.net/projects/archimedes                *
 *                                                                             *
 *             --------------------------------------------                    *
 *                                                                             *
 *   This program is free software; you can redistribute it and/or modify      *
 *   it under the terms of the GNU Library General Public License as published *
 *   by the Free Software Foundation; either version 2 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 Library General Public License for more details.                      *
 *                                                                             *
 *   You should have received a copy of the GNU Library General Public License *
 *   along with this program; if not, write to the Free Software               *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307, USA.  *
 *                                                                             *
 ******************************************************************************/

#include "config.h"

#include "fmail.h"
#include "cfgfile.h"

//#define CFGFILE_DEBUG
#define CFG_STATUS 1
#define CFG_DEBUG 2

extern struct _xfdef xfdefs;

#if STDC_HEADERS || defined(HAVE_STDARG_H)
inline void cfg_debug(int status, char *fmt, ...)
#else
inline void cfg_debug(int status, char *fmt, va_dcl va_list)
#endif
{
	va_list ap;

#if STDC_HEADERS || defined(HAVE_STDARG_H)
	va_start(ap, fmt);
#else
	va_start(ap);
#endif

	char buf[1024];

	vsnprintf(buf, 1024, fmt, ap);

	switch(status) {
		case CFG_STATUS:
			cerr << buf;
			break;

		case CFG_DEBUG:
#ifdef CFGFILE_DEBUG
			cerr << buf;
#endif
			break;
	}

	return;
}

int cfgfile::size() {
		return Map.size();
}

int cfgfile::sizeSaved() {
		return saved;
}

void cfgfile::print() {

#ifdef CFGFILE_DEBUG
	mapType::iterator iter;

	cfg_debug(CFG_STATUS,"--Config Map (%i)----------Start\n",Map.size());

	for ( iter = Map.begin() ; iter != Map.end() ; ++iter ) {
		cfg_debug(CFG_DEBUG, "Key = \"%s\" Value = \"%s\"\n",iter->first.c_str(),iter->second.c_str());
	}

	cfg_debug(CFG_STATUS,"--Config Map (%i)----------End\n",Map.size());
#endif

	return;
}

cfgfile::cfgfile() {
	cfile = NULL;
	strcpy(fname,"");
	changedKeys = false;
	changedFlags = false;
    clear();
}

cfgfile::~cfgfile() {
}

bool cfgfile::set(string key, string val) 
{
	return add(key,val);
}

bool cfgfile::set(string key, int val) 
{
	char s[32];
	sprintf(s,"%i",val);

	return add(key, s);
}

string cfgfile::get(string key, string def)
{
	string val = find(key);

	if(val != "") {
		return val;
	} else {
		return def;
	}
}

string cfgfile::get(string ln, string def, int return_default)
{
	if(return_default)
        return def;
    else
        return get(ln,def);
}

bool cfgfile::add(string key, string val)
{

    if((key.size() != 0) && (val.size() != 0)) {
	    cfg_debug(CFG_DEBUG, "\nadd(%s) -> %s ... ",key.c_str(), val.c_str());
        //cerr << "Adding (" << sKey << ") (" << sValue << ")\n";
	    Map[key] = val;
	    return true;
    } else {
        return false;
    }
}

string cfgfile::find(string key)
{	
	cfg_debug(CFG_DEBUG, "find(%s) -> ... ", key.c_str());

	mapType::iterator it = Map.find(key);
	if (it == Map.end()) {
		cfg_debug(CFG_DEBUG, "failed. (NOT FOUND)\n");
		return string(""); 
	}
	else {
		cfg_debug(CFG_DEBUG, "success. (FOUND)\n");
		return (*it).second;
	}
}

bool cfgfile::remove(string key)
{
	cfg_debug(CFG_DEBUG, "\nremove(%s) ... ", key.c_str());

	if(find(key).c_str() == "") {
		cfg_debug(CFG_DEBUG, " failed. (NOT FOUND)\n");
		return false;
	}
	Map.erase(key);
	cfg_debug(CFG_DEBUG, " success. (REMOVED)\n");
	return true;
}

void cfgfile::clear()
{
	Map.clear();
	return;
}

void cfgfile::destroy()
{
	clear();
	return;
}

int cfgfile::lock(char * file, const char * mode)
{
	assert(file != NULL);
	assert(cfile == NULL);

	cfg_debug(CFG_DEBUG, "Locking File\n");

	if((cfile=fopen(file,mode)) == NULL) {
   		display_msg(MSG_WARN, "Can not open", "configuration file %s", file);
   		return -1;
   	}
	cfg_debug(CFG_DEBUG, "File Open\n");

	assert(cfile != NULL);

#ifdef HAVE_FLOCK
	if(flock(fileno(cfile), LOCK_EX | LOCK_NB) != 0)
#else
	#ifdef HAVE_LOCKF
	if(lockf(fileno(cfile), F_TLOCK, 0) != 0)
	#endif
#endif
	{
		fprintf(stderr, "Can not lock %s\nProbably XFMail is already running\n", file);

		if(readonly)
			fprintf(stderr, "Proceeding in readonly mode\n");
		else
			exit(1);
	}

	assert(cfile != NULL);

	return 0;
}

int cfgfile::unlock(char * file)
{
	assert(file != NULL);
	assert(cfile != NULL);

	cfg_debug(CFG_DEBUG, "Unlocking File\n");

#ifdef HAVE_FLOCK
		flock(fileno(cfile), LOCK_UN);
#else
	#ifdef HAVE_LOCKF
		lockf(fileno(cfile), F_ULOCK, 0);
	#endif
#endif
	fclose(cfile);
	cfg_debug(CFG_DEBUG, "File Closed\n");

	cfile = NULL;

	return 0;
}
 
int cfgfile::save(int ask) {
	return save_file(fname,ask);
}

int cfgfile::save_file(char * file, int ask)
{
	cfg_debug(CFG_STATUS, "\nSaving config file...");
	if (readonly) { // Return if there's no changes to be written out
		changedKeys = 0;
		changedFlags = 0;
		return 0;
	}

	if(ask && changedKeys) {
		if(!display_msg(MSG_QUEST, "Configuration has been changed", "Do you want to save it?")) {
			return 0;
		}
	}

	if(lock(file, "w")) {
		display_msg(MSG_WARN, "save config", "Can not open %s", file);
		fclose(cfile);
		cfile = NULL;
		return -1;
	}

	print();
	mapType::iterator iter;
	for (iter = Map.begin(),saved=0 ; iter != Map.end() ; ++iter,saved++) {

		cfg_debug(CFG_DEBUG, "Saving %s=%s\n", iter->first.c_str(), iter->second.c_str());

		fprintf(cfile,"%s=%s\n", iter->first.c_str(), iter->second.c_str());

	}
	print();

	/*if(fflush(cfile) != 0) {
		if(errno == ENOSPC)
			display_msg(MSG_WARN, "save config", "DISK FULL!");
		else
			display_msg(MSG_WARN, "save config", "Failed to write %s, bad stream", file);
		unlock(file);
		return -1;
	}*/

	unlock(file);

	chmod(file,SECFILEMODE);
	changedKeys = false;

	cfg_debug(CFG_STATUS, " completed. [%i/%i]\n", saved, Map.size());

	return 1;
}

/*
 * This function reads a single config file line and
 * does The Right Thing(tm) with it by parsing it and adding it to
 * the config map. Assumes buf has no newline character at end.
 */
void cfgfile::addLine(char *buf)
{
	string configString(buf);
	string::size_type iSize;
	string::size_type iSpot;
    string sKey;
    string sValue;

	iSize = configString.size();

    if(iSize >= 3) { // k=v at min
	    iSpot = configString.find("=");

        if(iSpot != string::npos) {
            sKey = configString.substr(0,iSpot);
            sValue = configString.substr(iSpot + 1, iSize - iSpot);
            //sValue = configString.substr(iSpot + 1, iSize - 1);

	        add(sKey, sValue);
        }
    }

    return;
}


/*
 * This function preloads configuration file to be
 * changed in memory
 */
int cfgfile::load(char * fn)
{
	FILE *f;
	char buf[SLEN + 1];

	destroy();

	strcpy(fname,fn);

	lock(fn,"a+");
	rewind(cfile);

	print();

	cfg_debug(CFG_STATUS, "\nLoading Personal Settings...");

	assert(cfile != NULL);
	while(fgets(buf,SLEN,cfile) != NULL) {
		if(buf[0] != '#') { //Skip Comments
		    strip_newline(buf);
		    addLine(buf);
        }
	}
	unlock(fn);
	cfg_debug(CFG_STATUS, " completed. [%i]\n",Map.size());

	cfg_debug(CFG_STATUS, "\nLoading System Override Settings...");
	if((f = fopen(GLOBAL_MAILRC, "r")) != NULL) {
		while(fgets(buf, SLEN, f) != NULL) {
		    if(buf[0] != '#') { //Skip Comments
			    strip_newline(buf);
			    addLine(buf);
            }
		}
		fclose(f);
	}
	cfg_debug(CFG_STATUS, " completed. [%i]\n",Map.size());

	print();

	changedKeys = false;

	return 0;
}

int cfgfile::check_version()
{
	char buf[512];

	if(getString("xfversion", "").compare(VERSION)) {
#ifndef _PATH_RM
		display_msg(MSG_WARN,"upgrade","Error While Trying to Remove Cache Directory, you will have to do this manually.");
		exit(0);
#else
		sprintf(buf,"%s -rf %s/.cache",_PATH_RM, configdir);
		system(buf);
#endif

		if(!display_msg(MSG_QUEST|MSG_DEFNO, "Configuration file belongs", "to different version of XFMail, use it anyway?")) {
			clear();
		}

		set("xfversion", VERSION);
		return 1;
	}

	return 0;
}

void cfgfile::setFlags(char * ln,int flags)
{

	if(!(flags & DONT_STORE) && !(flags & CF_NOTCHANGED)) {
	   //set(ln,flags);
	   changedFlags = true;
	}

	return;
}

/*
 * Check if specific config entry exists
 */
bool cfgfile::exist(string ln)
{
	string value = find(ln);

	if (value != "")
		return true;
	else
		return false;
}

/*
 * This function gets configuration text string from
 * named file.
 *
 */
string cfgfile::getString(string ln,string def)
{
	string val = find(ln);
	if (val != "") {
		return val;
	}
	else {
		return def;
	}
}

string cfgfile::getStringDefault(string ln, string def, int return_default)
{
	if(return_default)
		return def;
	else
		return getString(ln,def);
}

int cfgfile::getInt(string key,int def) {

	string val = find(key);
	
	if(val == "") {
		return def;
	}
	else {
		return atoi(val.c_str());
	}
	
}

int cfgfile::getIntDefault(string ln, int def, int return_default)
{
	if(return_default)
		return def;
	else
		return getInt(ln,def);
}

void cfgfile::read_xfdefaults()
{
#ifdef XFMAIL
	FL_IOPT cntl;
	u_long mask;
	char xfdef[255], *p;
	FILE *xf;
	int cind, r, g, b, checkglobal = 1;

	mask = FL_PDBorderWidth;
	cntl.borderWidth = 2;

	startxfdef:
	if((checkglobal &&
		((xf = fopen(GLOBAL_DEFAULTS, "r")) != NULL)))
		checkglobal = 2;
	else {
		snprintf(xfdef, sizeof(xfdef), "%s/defaults", configdir);
		if((xf = fopen(xfdef, "r")) == NULL)
			goto xfdef_int;
		checkglobal = 0;
	}

	while(fgets(xfdef, 255, xf)) {
		if((xfdef[0] == '#') ||
		   (xfdef[0] == '!'))
			continue;

		strip_newline(xfdef);
		if((p = strchr(xfdef, '=')) == NULL)
			continue;

		*p = '\0';
		p++;
		if(!strcmp(xfdef, "Depth")) {
			mask |= FL_PDDepth;
			sscanf(p, "%d", &cntl.depth);
		} else
			if(!strcmp(xfdef, "VisualClass")) {
			mask |= FL_PDClass;
			sscanf(p, "%d", &cntl.vclass);
		} else
			if(!strcmp(xfdef, "DoubleBuffer")) {
			mask |= FL_PDDouble;
			sscanf(p, "%d", &cntl.doubleBuffer);
		} else
			if(!strcmp(xfdef, "ButtonFontSize")) {
			mask |= FL_PDButtonFontSize;
			sscanf(p, "%d", &cntl.buttonFontSize);
		} else
			if(!strcmp(xfdef, "SliderFontSize")) {
			mask |= FL_PDSliderFontSize;
			sscanf(p, "%d", &cntl.sliderFontSize);
		} else
			if(!strcmp(xfdef, "MenuFontSize")) {
			mask |= FL_PDMenuFontSize;
			sscanf(p, "%d", &cntl.menuFontSize);
		} else
			if(!strcmp(xfdef, "ChoiceFontSize")) {
			mask |= FL_PDChoiceFontSize;
			sscanf(p, "%d", &cntl.choiceFontSize);
		} else
			if(!strcmp(xfdef, "BrowserFontSize")) {
			mask |= FL_PDBrowserFontSize;
			sscanf(p, "%d", &cntl.browserFontSize);
		} else
			if(!strcmp(xfdef, "InputFontSize")) {
			mask |= FL_PDInputFontSize;
			sscanf(p, "%d", &cntl.inputFontSize);
		} else
			if(!strcmp(xfdef, "LabelFontSize")) {
			mask |= FL_PDLabelFontSize;
			sscanf(p, "%d", &cntl.labelFontSize);
		} else
			if(!strcmp(xfdef, "PopUpFontSize")) {
			sscanf(p, "%d", &cntl.pupFontSize);
			xfdefs.popfsize = cntl.pupFontSize;
		} else
			if(!strcmp(xfdef, "PopUpFontStyle")) {
			sscanf(p, "%d", &cntl.pupFontStyle);
			xfdefs.popfstyle = cntl.pupFontStyle;
		} else
			if(!strcmp(xfdef, "PopUpCursor"))
			sscanf(p, "%d", &xfdefs.popcursor);
		else
			if(!strcmp(xfdef, "PopUpBgCol"))
			sscanf(p, "%d", &xfdefs.popbgcol);
		else
			if(!strcmp(xfdef, "PopUpFgCol"))
			sscanf(p, "%d", &xfdefs.popfgcol);
		else
			if(!strcmp(xfdef, "PrivateMap")) {
			mask |= FL_PDPrivateMap;
			sscanf(p, "%d", &cntl.privateColormap);
		} else
			if(!strcmp(xfdef, "SharedMap")) {
			mask |= FL_PDSharedMap;
			sscanf(p, "%d", &cntl.sharedColormap);
		} else
			if(!strcmp(xfdef, "StandardMap")) {
			mask |= FL_PDStandardMap;
			sscanf(p, "%d", &cntl.standardColormap);
		} else
			if(!strcmp(xfdef, "BorderWidth")) {
			mask |= FL_PDBorderWidth;
			sscanf(p, "%d", &cntl.borderWidth);
		} else
			if(!strcmp(xfdef, "MapColor")) {
			if(sscanf(p, "%d %d %d %d", &cind, &r, &g, &b) == 4)
				fl_set_icm_color(cind, r, g, b);
		}

	}
	fclose(xf);

	if(checkglobal) {
		checkglobal = 0;
		goto startxfdef;
	}

	xfdef_int:
	if(exist("InputFontFSize")) {
		mask |= FL_PDInputFontSize;
		cntl.inputFontSize = getInt("InputFontFSize", 0);
	}

	if(exist("LabelFontFSize")) {
		mask |= FL_PDLabelFontSize;
		cntl.labelFontSize =getInt( "LabelFontFSize", 0);
	}

	if(exist("ChoiceFontFSize")) {
		mask |= FL_PDChoiceFontSize;
		cntl.choiceFontSize =getInt( "ChoiceFontFSize", 0);
	}

	if(exist("MenuFontFSize")) {
		mask |= FL_PDMenuFontSize;
		cntl.menuFontSize =getInt( "MenuFontFSize", 0);
	}

	if(exist("BrowserFontFSize")) {
		mask |= FL_PDBrowserFontSize;
		cntl.browserFontSize =getInt( "BrowserFontFSize", 0);
	}

	if(exist("ButtonFontFSize")) {
		mask |= FL_PDButtonFontSize;
		cntl.buttonFontSize =getInt( "ButtonFontFSize", 0);
	}

	if(exist("PopUpFontFSize")) {
		xfdefs.popfsize =getInt( "PopUpFontFSize", 0);
	}

	if(exist("SliderFontFSize")) {
		mask |= FL_PDSliderFontSize;
		cntl.sliderFontSize =getInt( "SliderFontFSize", 0);
	}

	if(mask != 0L)
		fl_set_defaults(mask, &cntl);

	return;
#endif
}

cfgfile & cfgfile::Singleton() {
    static cfgfile ConfigFile;

    return ConfigFile;
}
