/*
------------------------------------------------------------------------------
MetaCam - Extract EXIF information from digital camera files, with
support for Vendor specific blocks.
Copyright (C) 2000 Daniel Stephens (daniel@cheeseplant.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.

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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
------------------------------------------------------------------------------
*/

#include <iostream>
#include <fstream>
#include <cstdio>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <math.h>

#include <map>
#include <vector>
#include <string>
#include <sstream>

#include <unistd.h>
#include <getopt.h>

#include "metacam.h"
#include "filetiff.h"
#include "odrivers.h"

#include "dpyfuncs.h"

static const char *rcsid __attribute__((unused))="$Id: metacam.cc,v 1.12 2004/08/21 17:11:17 daniel Exp $";

static void
processIFD(const IFD &ifd, tagmap &tag_map)
{
    for (int i=0; i<ifd.entries(); ++i) {
	IFDEntry e = ifd[i];
	idpair id(e.getTag(), e.getType());
	tag_map[id] = e;
    }
}

option opts[] = {
    {"all", 0, NULL, 'a'},
    {"help", 0, NULL, 'h'},
    {"verbose", 0, NULL, 'v'},
    {"xml", 0, NULL, 'x'},
    {0,0,0,0}
};

const char *resolution_unit = "Inch";

void
displayTags(OutputDriver *driver, const char *header, tagmap &tag_map, knowntag *known, int verbose)
{
    OutputContext *ctx = driver->startGroup(header);
    ctx->setContextValue("resolutionUnit", "Inch");

    tagmap::iterator iter;
    for (int i=0; known[i].tag; ++i) {
	idpair id(known[i].tag, known[i].type);
	iter = tag_map.find(id);
	if (iter == tag_map.end()) {continue;}
	IFDEntry e = (*iter).second;
	if (!e) continue;
	tag_map[id] = IFDEntry();

	if (known[i].verbose > verbose) continue;

	if (known[i].func) {
	    known[i].func(*ctx, known[i].name, e, known[i].funcdata);
	} else {
	    ctx->startBlock(known[i].name);
	    e.outputValue(ctx->os());
	}
	ctx->endBlock();
    }

    if (verbose > 1) {
	for (iter=tag_map.begin(); iter != tag_map.end(); ++iter) {
	    if (!(*iter).second) continue;
	    IFDEntry e = (*iter).second ;
	    ostringstream taghdr;
	    taghdr << "[0x" << hex << e.getTag() << dec << "-" << e.getType() << "]" << ends;
	    ctx->startBlock(taghdr.str());

	    e.outputValue(ctx->os());
	    ctx->endBlock();
	} 
    }

    driver->endGroup();
}

void
clearTagMap(tagmap &tag_map)
{
    tagmap::iterator iter;
    for (iter = tag_map.begin(); iter != tag_map.end(); ++iter) {
	if ((*iter).second)  (*iter).second = IFDEntry();
    }
    tag_map.clear();
}


static int verbose_option=0;
static int help_option=0;
static int all_option=0;
static int xml_option=0;
static int verbose_level = 0;


static void
processFile(OutputDriver *driver, const char *fname, istream &is)
{
    tagmap tag_map;
    unsigned char header[2];
    is.read((char*)header, 2);
    if (is.gcount() != 2) {
	int err = errno;
	cerr << "Read failed: " << strerror(err) << endl;
	return;
    }

    driver->startFile(fname);

    int ofs=0;

    if ((header[0] == 0xFF) && (header[1] == 0xD8)) {
	// JPEG File
	// Search through file for Exif header....
	    
	int curofs = 2;
	unsigned char scanbuf[1024];
	int startofs = 0; // Where do we start reading into the buffer
	bool failed_scan = false;
	bool found_exif = true;
	    
	while (!is.eof()) {
	    if (!is.good()) {
		int err = errno;
		cerr << "Failure while scanning: " 
		     << strerror(err) << endl;
		failed_scan = true;
		break;
	    }
	    is.read((char*)&scanbuf[startofs], 1024 - startofs);
	    int got = is.gcount();
	    if (got == 0) continue;
	    if (got < 0) {
		int err = errno;
		cerr << "Failure while scanning: " 
		     << strerror(err) << endl;
		failed_scan = true;
		break;
	    }

	    // Pretend we read the WHOLE buffer
	    got += startofs;

	    // If we didn't get enough, try to get more
	    if (got < 4) {
		startofs = got;
		continue;
	    }

	    for (int i=0; i<got-3; ++i) {
		if (scanbuf[i] == 'E') {
		    if ((scanbuf[i+1] == 'x') &&
			(scanbuf[i+2] == 'i') &&
			(scanbuf[i+3] == 'f')) {
			    
			ofs = curofs + i + 6;
			found_exif = true;
		    }
		}
	    }

	    if (found_exif) break;

	    // Determine how much of buffer to keep (in case Exif
	    // straddles buffer edges)

	    int keep = 0;
	    if (scanbuf[got-1] == 'E') {
		keep = 1;
	    } else if (scanbuf[got-2] == 'E') {
		keep = 2;
	    } else if (scanbuf[got-3] == 'E') {
		keep = 3;
	    }

	    if (keep) {
		for (int i=0; i<keep; ++i) 
		    scanbuf[i] = scanbuf[got-keep+i];
	    }

	    curofs += (got - keep);
	    startofs = keep;
	}

	if (failed_scan) return;
	is.clear(is.rdstate() & (~ios::eofbit));
    }

    TIFFDataSource tiff(new _FileTIFF(is, ofs));
    if (!tiff.isGood()) {
	cerr << "No EXIF/TIFF data found in '" << fname << "'" << endl;
	driver->endFile();
	return;
    }

    tiffUNSIGNED exif_ifd_start = 0;
    IFD maker_ifd;
    knowntag *maker_known_table = 0;

    // Get IFD0
    IFD ifd = tiff.getIFD();
    processIFD(ifd, tag_map);
    ifd = ifd.nextIFD();

    // Look for the EXIF IFD
    idpair exif_id(0x8769, 4);
    tagmap::iterator iter = tag_map.find(exif_id);
    if (iter != tag_map.end() && ((*iter).second)) {
	vector<unsigned long> uv = (*iter).second.getUVALUE();
	exif_ifd_start = uv[0];
	(*iter).second = IFDEntry();
    }

    string maker_name; // For later use

    idpair makername_id(0x010f, 2);
    iter = tag_map.find(makername_id);
    if (iter != tag_map.end() && ((*iter).second)) {
	vector<string> v = (*iter).second.getSTRING();
	maker_name = v[0];
    }

    displayTags(driver, "Standard Fields", tag_map, main_known, verbose_level);
    clearTagMap(tag_map);

    if (exif_ifd_start) {
	IFD exif = tiff.getIFD(exif_ifd_start);
	processIFD(exif, tag_map);

	// Look for the MakerNote

	idpair makernote_id(0x927C, 7);
	iter = tag_map.find(makernote_id);
	if (iter != tag_map.end() && ((*iter).second)) {
	    if (strncmp(maker_name.data(), "NIKON", 5) == 0) {
		unsigned char nikontest[7];
		nikontest[6] = 0;
		tiff.seek((*iter).second.getSourceOffset());
		tiff.getData(nikontest, 6);
		if (strcmp((const char *)nikontest,"Nikon") == 0) {
		    // Strange new D100 way of doing things
		    TIFFDataSource ntiff(new _FileTIFF(is, ofs + (*iter).second.getSourceOffset() + 10));
		    if (ntiff.isGood()) maker_ifd = ntiff.getIFD();
		} else {
		    maker_ifd = tiff.getIFD((*iter).second.getSourceOffset());
		}
		maker_known_table = nikon_known;
		(*iter).second = IFDEntry();
	    } else if (strncmp(maker_name.data(), "OLYMP", 5) == 0) {
		maker_ifd = tiff.getIFD((*iter).second.getSourceOffset()+0x08);
		maker_known_table = olympus_known;
		(*iter).second = IFDEntry();
	    } else if (strncasecmp(maker_name.data(), "CASIO", 5) == 0) {
		maker_ifd = tiff.getIFD((*iter).second.getSourceOffset());
		maker_known_table = casio_known;
		(*iter).second = IFDEntry();
	    } else if (strncasecmp(maker_name.data(), "CANON", 5) == 0) {
		maker_ifd = tiff.getIFD((*iter).second.getSourceOffset());
		maker_known_table = canon_known;
		(*iter).second = IFDEntry();
	    }
	}

	displayTags(driver, "EXIF Fields", tag_map, exif_known, verbose_level);
	clearTagMap(tag_map);
    }

    if (maker_ifd && maker_known_table) {
	processIFD(maker_ifd, tag_map);

	displayTags(driver, "Manufacturer Fields", tag_map, maker_known_table, verbose_level);
	clearTagMap(tag_map);
    }

    driver->endFile();
}

extern int
main(int argc, char *argv[])
{
    while (1) {
	int oidx;
	int opt = getopt_long(argc, argv, "havx", opts, &oidx);
	if (opt == -1) break;

	switch (opt) {
	case 'h': help_option = 1; break;
	case 'v': verbose_option = 1; break;
	case 'a': all_option = 1; break;
	case 'x': xml_option = 1; break;
	}
    }


    if ((optind >= argc) || (help_option)) {
	cerr << "Usage: " << argv[0] << " <options> [filename(s)...]" << endl;
	cerr << endl
	     << "       -h,--help           Display this help" << endl
	     << "       -v,--verbose        Display unknown tags too" << endl
	     << "       -a,--all            Display ALL tags (implies -v)" << endl
	     << "       -x,--xml            Output as XML" << endl
	     << endl;
	if (!help_option) exit(2);
	exit(0);
    }

    if (verbose_option) verbose_level = 1;
    if (all_option) verbose_level = 2;

    OutputDriver *odriver = 0;

    if (xml_option) {
	odriver = new XMLOutputDriver(cout);
    } else {
	odriver = new DisplayOutputDriver(cout);
    }

    for (int o=optind; o<argc; ++ o) {
	ifstream is;
	is.open((const char*)(argv[o]), ios::binary | ios::in);
	if (!is.good()) {
	    int err = errno;
	    cerr << "Failed to open file '" << argv[o] << "': " << strerror(err) << endl;
	    continue;
	}

	processFile(odriver, argv[o], is);
    }

    odriver->close();
    delete odriver;
}



// Table of tags I know about
// 1000000+ are tags in the odd Nikon data structure
// Tags at the bottom are 'real' tags I know about and want to ignore

