/* -*- tab-width:2; c-basic-offset:2; indent-tabs-mode:nil; c-file-style:"stroustrup" -*- */
/*
 * exempi - exempi.cpp
 *
 * Copyright (C) 2011-2013 Hubert Figuiere
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1 Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2 Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 *
 * 3 Neither the name of the Authors, nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software wit hout specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#include <string>
#include <vector>

#include <exempi/xmp.h>
#include <exempi/xmp++.hpp>


enum {
	ACTION_NONE = 0,
	ACTION_SET,
	ACTION_GET
};

static void process_file(const char * filename, bool no_reconcile,
                         bool is_an_xmp,
                         bool dump_xml, bool write_in_place,
                         int action, const std::string & value_name,
                         const std::string & prop_value, 
                         const std::string & output);


/** fatal error in argument. Display and quit. */
static void fatal_error(const char * error)
{
	fprintf(stderr, "ERROR: %s\n", error);
	exit(128);
}


/** Print the usage, and quit. Exit code is 255.
 */
static void usage()
{
	fprintf(stderr, "Exempi version %s\n", VERSION);
	fprintf(stderr, "exempi { -h | [ -R ] [ -x ] [ { -w | -o <file> } ] [ { -g <prop_name> | -s <prop_name> -v <value> }  ] } <files>\n");
	fprintf(stderr, "\t-h: show this help\n");
	fprintf(stderr, "\t-R: don't reconcile\n");
	fprintf(stderr, "\t-x: dump XML packet.\n");
	fprintf(stderr, "\t-X: file(s) is XMP\n");
	fprintf(stderr, "\t-w: write in place. Only for -s. Not compatible with -o.\n");
	fprintf(stderr, "\t-o <file>: file to write the output to.\n");
	fprintf(stderr, "\t-n <uri> <prefix>: create a new namespace.\n");
	fprintf(stderr, "\t-g <prop_name>: retrieve the value with prop_name.\n");
	fprintf(stderr, "\t-s <prop_name> -v <value>: retrieve or get the value.\n");
	fprintf(stderr, "\t<files> the files to read from.\n");

	exit(255);
}

int main(int argc, char **argv)
{
	int ch;

	int action = ACTION_NONE;
	bool dont_reconcile = false;
	bool write_in_place = false;
	bool dump_xml = false;
	bool is_an_xmp = false;
	std::string output_file;
	std::string value_name;
	std::string prop_value;
	std::vector<std::pair<std::string, std::string> > namespaces;

	while((ch = getopt(argc, argv, "hRo:wxXn:g:s:v:")) != -1) {
		switch(ch) {

		case 'R':
			dont_reconcile = true;
			break;
		case 'o':
			output_file = optarg;
			break;
		case 'w':
			write_in_place = true;
			break;
		case 'x':
			dump_xml = true;
			break;
		case 'X':
			is_an_xmp = true;
			break;
		case 'g':
			if(!value_name.empty()) {
				usage();
			}
			action = ACTION_GET;
			value_name = optarg;
			break;
		case 'n':
		{
			std::string ns = optarg;
			std::string prefix = argv[optind];
			if (prefix[0] && prefix[0] != '-') {
				namespaces.push_back(make_pair(ns, prefix));
				optind++;
			}
			break;
		}
		case 's':
			if(!value_name.empty()) {
				usage();
			}
			action = ACTION_SET;
			value_name = optarg;
			break;
		case 'v':
			if(action != ACTION_SET || value_name.empty()) {
				fatal_error("-v needs to come after -s");
			}
			prop_value = optarg;
			break;
		case 'h':
		case '?':
			usage();
			break;
		}
	}

	argc -= optind;
  argv += optind;

  if(optind == 1 || argc == 0) {
    fatal_error("Missing input.");
    return 1;
  }

  if(write_in_place && (!output_file.empty() || action != ACTION_SET)) {
    fatal_error("Need to write in place and output file are mutually exclusive.");
    return 1;
  }

  if(action == ACTION_SET && (!write_in_place && argc > 1)) {
    fatal_error("Need to write in place for more than one file.");
    return 1;
  }

	xmp_init();
	if(!namespaces.empty()) {
    for (std::vector<std::pair<std::string, std::string> >::const_iterator iter
           = namespaces.begin(); iter != namespaces.end();
         ++iter) {
			xmp_register_namespace(iter->first.c_str(),
                             iter->second.c_str(), NULL);
		}
	}
	while(argc) {

		process_file(*argv, dont_reconcile, is_an_xmp, write_in_place,
                 dump_xml, action, value_name, prop_value, output_file);
		argc--;
		argv++;
	}

  xmp_terminate();
  return 0;
}

static XmpPtr get_xmp_from_sidecar(const char * filename, bool is_an_xmp)
{
	struct stat s;

	if (stat(filename, &s) == -1) {
		perror("exempi:");
		return NULL;
	}

	off_t size = s.st_size;
	FILE* file = fopen(filename, "r");
	if (!file) {
		perror("exempi:");
		return NULL;
	}

	char *buffer = (char*)malloc(size);
	size_t len = fread(buffer, 1, size, file);

	XmpPtr xmp = xmp_new_empty();
	if(!xmp_parse(xmp, buffer, len)) {
		xmp_free(xmp);
		xmp = NULL;
	}
	free(buffer);
	fclose(file);
	return xmp;
}

/** Helper to get the XMP for the file */
static XmpPtr get_xmp_from_file(const char * filename, bool no_reconcile, bool is_an_xmp)
{
	if (is_an_xmp) {
		return get_xmp_from_sidecar(filename, is_an_xmp);
	}

	xmp::ScopedPtr<XmpFilePtr> f(xmp_files_open_new(filename,
					(XmpOpenFileOptions)(XMP_OPEN_READ | (no_reconcile ? XMP_OPEN_ONLYXMP : 0))));
	if(f) {
		XmpPtr xmp = xmp_files_get_new_xmp(f);
		xmp_files_close(f, XMP_CLOSE_NOOPTION);
		return xmp;
	}
	return NULL;
}

/** dump the XMP xml to the output IO */
static void dump_xmp(const char *filename, bool no_reconcile,
		     bool is_an_xmp, FILE *outio)
{
	printf("dump_xmp for file %s\n", filename);
	xmp::ScopedPtr<XmpPtr> xmp(get_xmp_from_file(filename, no_reconcile, is_an_xmp));

	xmp::ScopedPtr<XmpStringPtr> output(xmp_string_new());

	xmp_serialize_and_format(xmp, output,
							 XMP_SERIAL_OMITPACKETWRAPPER,
							 0, "\n", " ", 0);

	fprintf(outio, "%s", xmp_string_cstr(output));
}

/** get an xmp prop and dump it to the output IO */
static void get_xmp_prop(const char * filename, const std::string & value_name,
                         bool no_reconcile, bool is_an_xmp, FILE *outio)
{
	xmp::ScopedPtr<XmpPtr> xmp(get_xmp_from_file(filename, no_reconcile, is_an_xmp));

	std::string prefix;
	size_t idx = value_name.find(':');
	if(idx != std::string::npos) {
		prefix = std::string(value_name, 0, idx);
	}

	xmp::ScopedPtr<XmpStringPtr> property(xmp_string_new());
	xmp::ScopedPtr<XmpStringPtr> ns(xmp_string_new());
	xmp_prefix_namespace_uri(prefix.c_str(), ns);
	xmp_get_property(xmp, xmp_string_cstr(ns), value_name.c_str(), property, NULL);
	fprintf(outio, "%s\n", xmp_string_cstr(property));
}


static void set_xmp_prop(const char * filename, const std::string & value_name, 
			 const std::string & prop_value,
			 bool no_reconcile, bool is_an_xmp, bool write_in_place, FILE *outio)
{
	xmp::ScopedPtr<XmpPtr> xmp(get_xmp_from_file(filename, no_reconcile, is_an_xmp));

	std::string prefix;
	size_t idx = value_name.find(':');
	if(idx != std::string::npos) {
		prefix = std::string(value_name, 0, idx);
	}

	xmp::ScopedPtr<XmpStringPtr> ns(xmp_string_new());
	xmp_prefix_namespace_uri(prefix.c_str(), ns);
	if(!xmp_set_property(xmp, xmp_string_cstr(ns), value_name.c_str() + idx + 1, prop_value.c_str(), 0)) {
		fprintf(stderr, "set error = %d\n", xmp_get_error());
	}

	if(write_in_place) {
		xmp::ScopedPtr<XmpFilePtr> f(xmp_files_open_new(filename,
						(XmpOpenFileOptions)(XMP_OPEN_FORUPDATE
							| (no_reconcile ? XMP_OPEN_ONLYXMP : 0))));

		if(!xmp_files_can_put_xmp(f, xmp)) {
			fprintf(stderr, "can put xmp error = %d\n", xmp_get_error());
		}
		if(!xmp_files_put_xmp(f, xmp)) {
			fprintf(stderr, "put xmp error = %d\n", xmp_get_error());
		}
		if(!xmp_files_close(f, XMP_CLOSE_SAFEUPDATE)) {
			fprintf(stderr, "close error = %d\n", xmp_get_error());
		}
	}
}


/** process a file with all the options */
static void process_file(const char * filename, bool no_reconcile, bool is_an_xmp,
			 bool write_in_place, bool dump_xml,
			 int action, const std::string & value_name,
			 const std::string & prop_value, const std::string & output)
{
	printf("processing file %s\n", filename);

	FILE *outio = stdout;

	bool needclose = false;
	if(!output.empty()) {
		outio = fopen(output.c_str(), "a+");
		needclose = true;
	}

	switch (action) {
	case ACTION_NONE:
		if(dump_xml) {
			dump_xmp(filename, no_reconcile, is_an_xmp, outio);
		}
		break;
	case ACTION_SET:
		set_xmp_prop(filename, value_name, prop_value, no_reconcile, is_an_xmp,
			     write_in_place, outio);
		break;
	case ACTION_GET:
		get_xmp_prop(filename, value_name, no_reconcile, is_an_xmp, outio);
		break;
	default:
		break;
	}

	if(needclose) {
		fclose(outio);
	}
}
