package eu.dnetlib.data.utils;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jbibtex.BibTeXDatabase;
import org.jbibtex.BibTeXEntry;
import org.jbibtex.BibTeXFormatter;
import org.jbibtex.Key;
import org.jbibtex.StringValue;
import org.jbibtex.StringValue.Style;

import eu.dnetlib.data.mdstore.plugins.objects.Author;
import eu.dnetlib.data.mdstore.plugins.objects.MdRecord;

public class BibTexConverter {

	private static final Log log = LogFactory.getLog(BibTexConverter.class);

	public static String asBibTex(final MdRecord record) {
		try {

			final BibTeXEntry dbEntry = new BibTeXEntry(findBibTexType(record.getType()), new Key(record.getId()));

			addSimpleStringValue(dbEntry, BibTeXEntry.KEY_TITLE, record.getTitle());
			addMultipleAuthorValue(dbEntry, BibTeXEntry.KEY_AUTHOR, record.getCreators());
			addSimpleStringValue(dbEntry, BibTeXEntry.KEY_PUBLISHER, record.getPublisher());
			addMultipleStringValue(dbEntry, BibTeXEntry.KEY_DOI, record.getDois());

			addSourceAndDate(dbEntry, record);

			final BibTeXFormatter formatter = new BibTeXFormatter();
			final Writer res = new StringWriter();
			final BibTeXDatabase db = new BibTeXDatabase();
			db.addObject(dbEntry);
			formatter.format(db, res);
			return res.toString();
		} catch (final IOException e) {
			log.warn("Error generating bibtex", e);
			return "";
		}
	}

	private static void addSimpleStringValue(final BibTeXEntry dbEntry, final Key key, final Object value) {
		if ((value != null) && StringUtils.isNotBlank(value.toString())) {
			dbEntry.addField(key, new StringValue(escapeString(value.toString()), Style.BRACED));
		}
	}

	private static void addMultipleStringValue(final BibTeXEntry dbEntry, final Key key, final Collection<String> list) {
		final String val = list.stream()
				.map(String::trim)
				.filter(StringUtils::isNotBlank)
				.map(BibTexConverter::escapeString)
				.collect(Collectors.joining(" and "));

		if (StringUtils.isNotBlank(val)) {
			dbEntry.addField(key, new StringValue(val, Style.BRACED));
		}
	}

	private static void addMultipleAuthorValue(final BibTeXEntry dbEntry, final Key key, final Collection<Author> list) {
		final String val = list.stream()
				.map(Author::getName)
				.map(String::trim)
				.filter(StringUtils::isNotBlank)
				.map(BibTexConverter::escapeString)
				.collect(Collectors.joining(" and "));

		if (StringUtils.isNotBlank(val)) {
			dbEntry.addField(key, new StringValue(val, Style.BRACED));
		}
	}

	private static String escapeString(final String s) {
		return s.replaceAll("&", "\\\\&");
	}

	private static void addSourceAndDate(final BibTeXEntry dbEntry, final MdRecord record) {
		String year = Integer.toString(record.getDate());

		if (record.getSource() != null) {
			switch (record.getType().toLowerCase()) {
			case "conference article":
			case "contribution to conference":
				addSimpleStringValue(dbEntry, BibTeXEntry.KEY_BOOKTITLE, record.getSource());
				break;
			case "contribution to book":
				addSimpleStringValue(dbEntry, BibTeXEntry.KEY_BOOKTITLE, record.getSource());
				break;
			case "report":
				addSimpleStringValue(dbEntry, BibTeXEntry.KEY_INSTITUTION, record.getSource());
				break;
			case "journal article":
				final String regex = "^(.+) (\\d*) \\((\\d{4})\\)(: (\\d+\\–?\\d*))?";
				final Pattern pattern = Pattern.compile(regex);
				final Matcher matcher = pattern.matcher(record.getSource());

				if (matcher.find()) {
					if (matcher.groupCount() >= 1) {
						addSimpleStringValue(dbEntry, BibTeXEntry.KEY_JOURNAL, matcher.group(1));
					}
					if (matcher.groupCount() >= 2) {
						addSimpleStringValue(dbEntry, BibTeXEntry.KEY_VOLUME, matcher.group(2));
					}
					if (matcher.groupCount() >= 3) {
						year = matcher.group(3);
					}
					if (matcher.groupCount() >= 5) {
						addSimpleStringValue(dbEntry, BibTeXEntry.KEY_PAGES, matcher.group(5));
					}
				}
				break;
			case "patent":
				addSimpleStringValue(dbEntry, BibTeXEntry.KEY_HOWPUBLISHED, record.getSource());
				break;
			}
		}

		addSimpleStringValue(dbEntry, BibTeXEntry.KEY_YEAR, year);
	}

	private static Key findBibTexType(final String resourceType) {
		switch (resourceType.toLowerCase()) {
		case "conference article":
		case "contribution to conference":
			return BibTeXEntry.TYPE_INPROCEEDINGS;
		case "report":
			return BibTeXEntry.TYPE_TECHREPORT;
		case "journal article":
			return BibTeXEntry.TYPE_ARTICLE;
		case "other":
			return BibTeXEntry.TYPE_MISC;
		case "contribution to book":
			return BibTeXEntry.TYPE_INBOOK;
		case "book":
			return BibTeXEntry.TYPE_BOOK;
		case "bachelor thesis":
			return BibTeXEntry.TYPE_MASTERSTHESIS;
		case "doctoral thesis":
			return BibTeXEntry.TYPE_PHDTHESIS;
		case "patent":
			return BibTeXEntry.TYPE_MISC;
		case "master thesis":
			return BibTeXEntry.TYPE_MASTERSTHESIS;
		default:
			return BibTeXEntry.TYPE_MISC;
		}
	}
}
