/*	Copyright  (c)	Gnter Woigk  1999
  					mailto:kio@little-bat.de

 	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.  

 	Permission to use, copy, modify, distribute, and sell this software and
 	its documentation for any purpose is hereby granted without fee, provided
 	that the above copyright notice appear in all copies and that both that
 	copyright notice and this permission notice appear in supporting
 	documentation, and that the name of the copyright holder not be used
 	in advertising or publicity pertaining to distribution of the software
 	without specific, written prior permission.  The copyright holder makes no
 	representations about the suitability of this software for any purpose.
 	It is provided "as is" without express or implied warranty.

 	THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 	EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 	DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 	TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 	PERFORMANCE OF THIS SOFTWARE.
 	
 
	1999-04-04 kio	started work on this file
	1999-12-30 kio	started port to MacOS
	2000-01-23 kio	not longer opened and closed by an instance's constructor/destructor
	2000-05-28 kio	multi-byte prefs
	2000-05-30 kio	secure prefs re-implemented
	2003-09-02 kio	resumed word on Linux
*/


#include <ctype.h>

#define	 LOG	0
#define	 SAFE	3

#include "prefs.h"
#include "unix/file_utilities.h"


/* ----	swap 0 <-> nl ----
*/
#define nl2null()	if (data) for (char*p=data+used-1;p>=data;p--) if (*p==10||*p==13) *p=0; else continue;
#define null2nl()	if (data) for (char*p=data+used-1;p>=data;p--) if (*p==0) *p=nl; else continue;


/* ---- stuff 4 binary bytes in 5 ascii bytes ----
*/
#define bas		40
#define	div		85

static void Stuff4 ( const void* q, char* z )
{
	ulong n = *(long*)q;
	for (int i=5; i--;) { z[i] = bas + n%div; n/=div; }
}

static void Unstuff4 ( cstr q, void* z )
{
	ulong n = 0;
	for (int i=5; i; i--) { n*=div; n += *(uchar*)q++ - bas; }
	*(long*)z = n;
}


/* ----	generate checksum ---------------------------------
*/
static long CRC ( long mask, cstr p, int n, long crc )
{
	while ( --n >= 0 )
	{
		crc ^= (long)*p++ << 24;
		for ( int i = 8; i; i-- )
		{
			if(crc<0) crc ^= mask;
			crc <<= 1;
		}
	}

	return crc;
}

static char* CRC ( long mask, cstr idf, cstr text )
{
	long crc = CRC(mask,idf,StrLen(idf),0);
	crc = CRC(mask,text,StrLen(text),crc);
	static char crcstr[] = "12345 ";
	Stuff4 ( &crc, crcstr );
	return crcstr;
}


/* ----	beautify identifier ---------------
		all separators are replaced by "_"
*/
static cstr Beautified ( cstr s )
{
	uchar* q = (uchar*)s;
	while (*q>' ') q++;

	if (*q!=0)
	{
		s = DupStr(s);
		q = (uchar*)s;
		while (*q!=0)
		{
			while (*q>' ') q++;
			if (*q!=0) *q='_';
		}
	}
	return s;
}




/* =========================================================
				application preferences
========================================================= */


/* ---- constructor ------------------------
		prefs file is not loaded when the instance is created
		prefs file is loaded on first access to a pref
*/
Prefs::Prefs ( cstr applname, cstr path )
{
//	path = "/..."   -> hard path
//	path = "~/..."  -> path starting in home dir

	XXTRAP(!applname);
	XXTRAP(!path);

	data	= NULL;		// <- this also indicates: not loaded!
	size	= 0;
	used	= 0;
	dirty	= no;

// appl_id:
	char z[6] = "\tKio!"; for( int i=0; applname[i]; i++ ) z[i%5] ^= applname[i];
	Unstuff4( z, &appl_id );

// home:
	home = getenv("HOME");
	if (!home) home="";

// filename:
	filename = NewCopy ( FullPath(path) );
}


/* ----	destructor -----------------------
*/
Prefs::~Prefs ( )
{
	if (IsOpen())
	{
		XLogIn("Prefs::~Prefs()");

		Write();
		delete[] data;
		delete[] filename;
		//delete[] home;	// 3.jun.99 kio: NOT allocated with new[] !!

		XLogOut("");
	}
}


/* ----	write preferences ---------------------------------------------
*/
void Prefs::Write()
{
	if (NotDirty()) return;

	XLogIn("Prefs::Write()");

	null2nl();
	PushError();

	FILE* fd;
	fd = fopen(filename,"w");		if (!fd) goto x;
	fwrite ( data, 1, used, fd );	if (Error()) goto x;
	dirty=false;

x:	if(Error()) XXLog(ErrorStr());
	if (fd) fclose(fd);

	PopError();
	nl2null();

	XLogOut("");
}


/* ---- read prefs ------------------------
		find the preferences file
		remember file path
		read preferences
*/
void Prefs::Read ( )
{
	if (IsOpen()) return;

	XLogIn("Prefs::Read()");

	PushError();

	data = NULL;
	FILE* fd = fopen(filename,"r"); 	if (!fd) goto x;
	fseek ( fd, 0, SEEK_END );			if (Error()) goto x;
	used = ftell(fd);					if (Error()) goto x;
	rewind ( fd );						if (Error()) goto x;

	size = used+200;
	data = new char[size]; 				if (!data)   { SetError(outofmemory); goto x; }
	fread ( data, 1, used, fd );		if (Error()) { goto x; }

	if (Error())
	{
x:		XLog(ErrorStr(Error()));
		delete[] data;
		data = NULL;
		used = 0;
		size = 0;
	}

	if (fd) fclose(fd);
	PopError();
	nl2null();
	dirty = no;

	XLogOut("(%i bytes)",(int)used);
}




/* ----------------------------------------------------------------
					GET AND STORE PREFERENCES
---------------------------------------------------------------- */



/* ----	get a preference ---------------------------
		the returned string is inside the data[] array
		and remains valid until put() or read() is called
		returns default value if not found
*/
cstr Prefs::GetStr ( cstr idf, cstr deflt, bool secure )
{
	XXLogIn ( "Prefs::GetStr(\"%s\",\"%s\")", idf, deflt );
	idf = Beautified(idf);

	if (NotOpen()) Prefs::Read();

	for ( cstr dp=data; dp<data+used; dp+=StrLen(dp)+1 )
	{
		cstr ip;
		for ( ip=idf; *ip; ip++,dp++ )
		{
			if (tolower(*ip)!=tolower(*dp)) break;
		}

		if ( *ip!=0 || *dp>' ' ) continue;

		// got it
		XXLog("found");
		while (*dp<=' '&&*dp>0) dp++;

		if (secure)
		{
			if ( StrLen(dp)>=6 && SameStr( CRC(appl_id,idf,dp+6), LeftStr(dp,6) ) ) deflt = dp+6;
			break;
		}
		else
		{
			deflt = dp;
			break;
		}
	}

	XXLogOut(" = \"%s\"",deflt);
	
	return deflt;
}


/* ----	store a preference -------------------------
		if a preference 'idf' exists, it is replaced in the current line
		if it does not exist, it is appended to the file
*/
void Prefs::PutStr ( cstr idf, cstr text, bool secure )
{	long  dl, tl;
	char *dp;
	
	XXLogIn("Prefs::PutStr(\"%s\",\"%s\")", idf, text);
	XXXTRAP(!text);
	XXXTRAP(!idf);
	idf = Beautified(idf);

	if (NotOpen()) Prefs::Read();

	while(*text<=' '&&*text>0) text++;
	
	if (secure) text = CatStr( CRC(appl_id,idf,text), text );

	dp = (char*)GetStr(idf);

	if (!dp) 	// pref new
	{ 	dp=data+used; 						// destination pointer
		tl = StrLen(text) +StrLen(idf) +1;	// new text length ((without trailing 0))
		dl=-1;								// old text length ((without trailing 0))
	}
	else 		// pref exists
	{	if (strcmp(text,dp)==0) goto x;
		dl = StrLen(dp);					// old text length ((without trailing 0))
		tl = StrLen(text);					// new text length ((without trailing 0))
	}

	if (dl!=tl)	// need to move text behind insertion point ?
	{
		if (used+tl-dl>size)	// need to grow buffer ?
		{
			char* newdata = new char[used+tl-dl+200]; 
			if (!newdata) { Log("out of memory"); goto x; }
			memcpy (newdata,data,used);
			dp += ( newdata-data );
			delete[]data; data=newdata;
		}

		memmove ( dp+tl+1,  dp+dl+1,  data+used - (dp+dl+1) );
	}

	if (dl==-1)		 			// pref new => store name & tab
	{	strcpy ( dp, idf );
		strcat ( dp, "\t" );
		strcat ( dp, text );	// store value
	}
	else
		strcpy ( dp, text );	// store value

	used += tl-dl;			// adjust used bytes count		kio 25.5.00
	dirty = true;			// atexit will sync file

x:	XXLogOut("");
}


/* ---- store / retrieve number ------------------------------
*/
void Prefs::PutNum ( cstr idf, long num, bool secure )
{
	PutStr ( idf, NumStr(num), secure );
}


long Prefs::GetNum ( cstr idf, long deflt, bool secure )
{
	cstr txt = GetStr(idf,NULL,secure); 
	char* dummy;
	return txt ? strtol(txt,&dummy,0) : deflt;
}


/* ----	store / retrieve flag --------------------------------
*/
bool Prefs::GetBool ( cstr idf, bool deflt )
{
	cstr txt = GetStr(idf); if (!txt) return deflt;
	// yes ja 1 true on ein enabled
	// no nein 0 false off aus disabled
	if (tolower(*txt)=='o') return tolower(txt[1])=='n';
	return strchr ( "yYjJ1tTeE", *txt ) != NULL;
}


void Prefs::PutBool ( cstr idf, bool flag )
{
	PutStr ( idf, flag ? "1" : "0" );
}


/* ---- store binary data into preferences ------------
		max. data length is 200 bytes
		data size should be a multiple of 4
*/
void Prefs::PutHex ( cstr idf, int n, const void* bu )
{
	XTRAP(n>200);

	n = (n+3)&~3;
	char s[251];

	ptr q = (ptr)bu;
	ptr e = q+n;
	ptr z = s;

	while (q<e) { Stuff4(q,z); q+=4; z+=5; } *z=0;

	PutStr( idf, s );
}


/* ---- retrieve binary data from preferences --------------------
		if the pref existes and seems to fit 
		then it is copied to the passed buffer
		else the passed buffer remains untouched
*/
void Prefs::GetHex ( cstr idf, int n, void* bu )
{
	XTRAP(n>200);

	ptr s = (ptr)GetStr( idf );

	if (s) { ptr p=s; while(*p>=bas) p++; *p=0; }

	if ( s && StrLen(s)/5==(n+3)/4 )
	{
		char x[200];
		ptr q = s;
		ptr z = x;
		while(*q) { Unstuff4(q,z); q+=5; z+=4; }
		memcpy ( bu, x, n );
	}
}


/* ---- save array of long numbers -----------------------------------
		up to array[20]
		saved as readable numbers
*/
void Prefs::PutNums ( cstr idf, int n, const long* array )
{
	XTRAP( n<=0 || n>20 );

	ptr s = NumStr(*array++);
	while (--n)
	{
		s = CatStr(CatStr(s,","),NumStr(*array++));
	}
	PutStr(idf,s);
}


/* ---- retrieve array of long numbers -----------------------------------
*/
void Prefs::GetNums ( cstr idf, int n, long* array )
{
	XTRAP( n<=0 || n>20 );

	ptr s = (ptr)GetStr(idf);
	if (s) 
	{
		long x[20];
		long* z = x;
		char* q = s;
		
		ClearError();
		for (int i=0;i<n;i++)
		{
			if (i&&*q++!=',') break;
			*z++ = strtol(q,&q,0);
			if (Error()) break;
		}
		if (NoError()&&*q==0) memcpy ( array, x, sizeof(*array)*n );
	}
}


/* ---- save array of short numbers -----------------------------------
		up to array[40]
		saved as readable numbers
*/
void Prefs::PutNums ( cstr idf, int n, const short* array )
{
	XTRAP( n<=0 || n>40 );

	ptr s = NumStr(*array++);
	while (--n)
	{
		s = CatStr(CatStr(s,","),NumStr(*array++));
	}
	PutStr(idf,s);
}


/* ---- retrieve array of short numbers -----------------------------------
*/
void Prefs::GetNums ( cstr idf, int n, short* array )
{
	XTRAP( n<=0 || n>40 );

	ptr s = (ptr)GetStr(idf);
	if (s) 
	{
		short  x[40];
		short* z = x;
		char*  q = s;
		
		ClearError();
		for (int i=0;i<n;i++)
		{
			if (i&&*q++!=',') break;
			*z++ = strtol(q,&q,0);
			if (Error()) break;
		}
		if (NoError()&&*q==0) memcpy ( array, x, sizeof(*array)*n );
	}
}


#if 0
/* ----	store box ---------------------------------------------------
		e.g. for windows position
*/
void Prefs::PutBox ( cstr idf, cBox& box )
{
	PutNums( idf, 4, (Cord*)&box );
}


/* ----	retrieve box ---------------------------------------------------
*/
void Prefs::GetBox ( cstr idf, Box& box )
{
	Box z = box;						// kio 26.nov.2000
	GetNums( idf, 4, (Cord*)&z );
	if (z.x1<=z.x2 && z.y1<=z.y2) box = z;
}
#endif











