/*
 *  This file is part of the KDE libraries
 *  Copyright (c) 2001 Michael Goffioul <tdeprint@swing.be>
 *  Copyright (c) 2014 Timothy Pearson <kb9vqf@pearsoncomputing.net>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License version 2 as published by the Free Software Foundation.
 *
 *  This library 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 library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 **/

#include <config.h>
#ifndef _GNU_SOURCE
#define _GNU_SOURCE   /* Needed for getline */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <ctype.h>
#include <zlib.h>

#include <tqstringlist.h>
#include <tqlocale.h>

extern "C" {
	#include "driverparse.h"
}

#define PROCESS_PPD_FILE_CONTENTS												\
		memset(value,0,256);												\
		c1 = strchr(line,':');												\
		if (c1)														\
		{														\
			c2 = strchr(c1,'"');											\
			if (c2)													\
			{													\
				c2++;												\
				c1 = strchr(c2,'"');										\
				if (c1) strlcpy(value,c2,c1-c2+1);								\
			}													\
			else													\
			{													\
				c1++;												\
				while (*c1 && isspace(*c1))									\
					c1++;											\
				if (!*c1)											\
					continue;										\
				c2 = line+strlen(line)-1;	/* point to \n */						\
				while (*c2 && isspace(*c2))									\
					c2--;											\
				strlcpy(value,c1,c2-c1+2);									\
			}													\
		}														\
		count++;													\
		if (strncmp(line,"*Manufacturer:",14) == 0) fprintf(output_file,"MANUFACTURER=%s\n",value);			\
		else if (strncmp(line,"*ShortNickName:",15) == 0) fprintf(output_file,"MODEL=%s\n",value);			\
		else if (strncmp(line,"*ModelName:",11) == 0) fprintf(output_file,"MODELNAME=%s\n",value);			\
		else if (strncmp(line,"*NickName:",10) == 0) strncat(desc,value,255-strlen(desc));				\
		else if (strncmp(line,"*pnpManufacturer:",17) == 0) fprintf(output_file,"PNPMANUFACTURER=%s\n",value);		\
		else if (strncmp(line,"*pnpModel:",10) == 0) fprintf(output_file,"PNPMODEL=%s\n",value);			\
		else if (strncmp(line,"*LanguageVersion:",17) == 0) strncat(langver,value,63-strlen(langver));			\
		else count--;													\
		/* Either we got everything we needed, or we encountered an "OpenUI" directive					\
		 * and it's reasonable to assume that there's no needed info further in the file,				\
		 * just stop here */												\
		if (count >= 7 || strncmp(line, "*OpenUI", 7) == 0)								\
		{														\
			if (strlen(langver) > 0)										\
			{													\
				strncat(desc, " [", 255-strlen(desc));								\
				strncat(desc, langver, 255-strlen(desc));							\
				strncat(desc, "]", 255-strlen(desc));								\
			}													\
			if (strlen(desc) > 0)											\
				fprintf(output_file, "DESCRIPTION=%s\n", desc);							\
			break;													\
		}

void initPpd(const char *dirname)
{
	struct stat stat_res;
	if (stat(dirname, &stat_res) == -1) {
		fprintf(stderr, "Can't open drivers directory : %s\n", dirname);
		return;
	}

	if (S_ISDIR(stat_res.st_mode)) {
		DIR	*dir = opendir(dirname);
		struct dirent	*entry;
		char		buffer[4096] = {0};
		char	drFile[256];
		int		len = strlen(dirname);
	
		if (dir == NULL)
		{
			fprintf(stderr, "Can't open drivers directory : %s\n", dirname);
			return;
		}
		while ((entry=readdir(dir)) != NULL)
		{
			if (strcmp(entry->d_name,".") == 0 || strcmp(entry->d_name,"..") == 0)
			{
				continue;
			}
			if (len+strlen(entry->d_name)+1 < 4096)
			{
				struct stat	st;
	
				strcpy(buffer,dirname);
				strcat(buffer,"/");
				strcat(buffer,entry->d_name);
				if (stat(buffer,&st) == 0)
				{
					if (S_ISDIR(st.st_mode))
					{
						initPpd(buffer);
					}
					else if (S_ISREG(st.st_mode))
					{
						char	*c = strrchr(buffer,'.');
						snprintf(drFile, 255, "ppd:%s", buffer);
						if (c && strncmp(c,".ppd",4) == 0)
						{
							addFile(drFile, "", "");
						}
						else if (c && strncmp(c, ".gz", 3) == 0)
						{ /* keep also compressed driver files */
							while (c != buffer)
							{
								if (*(--c) == '.') break;
							}
							if (*c == '.' && strncmp(c, ".ppd",4) == 0)
							{
								addFile(drFile, "", "");
							}
						}
					}
				}
			}
		}
		closedir(dir);
	}
	else if (access(dirname, X_OK) != -1) {
		char *filename;
		int n = strlen(dirname)+strlen(" list");
		filename = (char*)malloc(n*sizeof(char)+1);
		memset(filename,0,n);
		strcat(filename, dirname);
		strcat(filename, " list");

		FILE* file = popen(filename, "r");
		if (file) {
			char * line = NULL;
			size_t len = 0;
			ssize_t read;
			while ((read = getline(&line, &len, file)) != -1) {
				char * pos1 = strstr(line, "\"");
				if (pos1 != NULL) {
					char * pos2 = strstr(pos1 + 1, "\"");
					if (pos2 != NULL) {
						*pos2 = 0;
						char * pos3 = strstr(pos1 + 1, ":");
						if (pos3 != NULL) {
							char *ppduri;
							int n2 = strlen("compressed-ppd:")+strlen(pos3+1);
							ppduri = (char*)malloc(n2*sizeof(char)+1);
							memset(ppduri,0,n2);
							strcat(ppduri, "compressed-ppd:");
							strcat(ppduri, pos3+1);
							addFile(ppduri, dirname, pos2+1);
							free(ppduri);
							ppduri = NULL;
						}
					}
				}
			}
			if (line) {
				free(line);
			}

			pclose(file);
		}
		else {
			fprintf(stderr, "Can't execute compressed driver handler : %s\n", dirname);
		}

		free(filename);
		filename = NULL;
	}
	else {
		fprintf(stderr, "Can't open drivers directory : %s\n", dirname);
		return;
	}
}

void initCompressedPpd(const char *dirname)
{
	// HACK
	// The initPpd function actually handles the compressed PPDs as well, so do nothing here
	// If we were to rerun initPpd here then all drivers would be duplicated!
}

int parsePpdFile(const char *filename, const char *origin, const char *metadata, FILE *output_file)
{
	gzFile	ppd_file;
	char	line[4096], value[256], langver[64] = {0}, desc[256] = {0};
	char	*c1, *c2;
	int	count = 0;

	ppd_file = gzopen(filename,"r");
	if (ppd_file == NULL)
	{
		fprintf(stderr, "Can't open driver file : %s\n", filename);
		return 0;
	}
	fprintf(output_file,"FILE=ppd:%s\n",filename);

	while (gzgets(ppd_file,line,4095) != Z_NULL)
	{
		PROCESS_PPD_FILE_CONTENTS
	}
	fprintf(output_file,"\n");

	gzclose(ppd_file);
	return 1;
}

int parseCompressedPpdFile(const char *ppdfilename, const char *origin, const char *metadata, FILE *output_file)
{
	char	value[256], langver[64] = {0}, desc[256] = {0};
	char	*c1, *c2;
	int	count = 0;

	bool useFallbackExtractionMethod = false;

	if (strlen(metadata) > 0) {
		TQString metadataProcessed(metadata);
		metadataProcessed = metadataProcessed.stripWhiteSpace();
		TQStringList metadataList = TQStringList::split(" ", metadataProcessed, TRUE);
		TQLocale ppdLanguage(metadataList[0]);
		TQString languageVersion = TQLocale::languageToString(ppdLanguage.language());
		metadataList = TQStringList::split("\" \"", metadataProcessed, TRUE);
		TQString description = metadataList[1];

		int pos = metadataProcessed.find("MFG:");
		if (pos < 0) {
			pos = metadataProcessed.find("MANUFACTURER:");
		}
		if (pos >= 0) {
			TQString manufacturer;
			TQString model;
			TQString modelName;
			TQString pnpManufacturer;
			TQString pnpModel;
			TQString driver;
			TQStringList metadataList = TQStringList::split(";", metadataProcessed.mid(pos), TRUE);
			for (TQStringList::Iterator it = metadataList.begin(); it != metadataList.end(); ++it) {
				TQStringList kvPair = TQStringList::split(":", *it, TRUE);
				if ((kvPair[0].upper() == "MFG") || (kvPair[0].upper() == "MANUFACTURER")) {
					manufacturer = kvPair[1];
				}
				else if ((kvPair[0].upper() == "MDL") ||(kvPair[0].upper() == "MODEL")) {
					modelName = kvPair[1];
				}
// 				else if (kvPair[0].upper() == "PNPMANUFACTURER") {
// 					pnpManufacturer = kvPair[1];
// 				}
// 				else if (kvPair[0].upper() == "PNPMODEL") {
// 					pnpModel = kvPair[1];
// 				}
				else if ((kvPair[0].upper() == "DRV") || (kvPair[0].upper() == "DRIVER")) {
					driver = kvPair[1];
				}
			}

			manufacturer = manufacturer.stripWhiteSpace();
			modelName = modelName.stripWhiteSpace();
			driver = driver.stripWhiteSpace();

			TQStringList driverList = TQStringList::split(",", driver, TRUE);
			driver = driverList[0];
			if (driver.startsWith("D")) {
				driver = driver.mid(1);
				driver = driver.stripWhiteSpace();
			}
			model = manufacturer + " " + modelName + " " + driver;
			description = description + " [" + languageVersion + "]";

			fprintf(output_file,"FILE=compressed-ppd:%s:%s\n", origin, ppdfilename);

			fprintf(output_file,"MANUFACTURER=%s\n",manufacturer.ascii());
			fprintf(output_file,"MODELNAME=%s\n",modelName.ascii());
			fprintf(output_file,"MODEL=%s\n",model.ascii());
			if (pnpManufacturer.length() > 0) {
				fprintf(output_file,"PNPMANUFACTURER=%s\n",pnpManufacturer.ascii());
			}
			if (pnpModel.length() > 0) {
				fprintf(output_file,"PNPMODEL=%s\n",pnpModel.ascii());
			}
			if (description.length() > 0) {
				fprintf(output_file,"DESCRIPTION=%s\n",description.ascii());
			}
		}
		else {
			useFallbackExtractionMethod = true;
		}
	}

	if (useFallbackExtractionMethod) {
		char *filename;
		int n = strlen(origin)+strlen(" cat ")+strlen(ppdfilename);
		filename = (char*)malloc(n*sizeof(char)+1);
		memset(filename,0,n);
		strcat(filename, origin);
		strcat(filename, " cat ");
		strcat(filename, ppdfilename);
	
		FILE* file = popen(filename, "r");
		if (file) {
			char * line = NULL;
			size_t len = 0;
			ssize_t read;
	
			fprintf(output_file,"FILE=compressed-ppd:%s:%s\n", origin, ppdfilename);
	
			while ((read = getline(&line, &len, file)) != -1) {
				PROCESS_PPD_FILE_CONTENTS
			}
			if (line) {
				free(line);
			}
	
			pclose(file);
		}
		else {
			fprintf(stderr, "Can't open driver file : %s\n", ppdfilename);
			return 0;
		}
	
		free(filename);
		filename = NULL;
	}

	return 1;
}

int main(int argc, char *argv[])
{
	registerHandler("ppd:", initPpd, parsePpdFile);
	registerHandler("compressed-ppd:", initCompressedPpd, parseCompressedPpdFile);
	initFoomatic();
	return execute(argc, argv);
}
