package net.sf.jabref.logic.citationstyle;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import net.sf.jabref.logic.l10n.Localization;
import net.sf.jabref.model.entry.BibEntry;

import de.undercouch.citeproc.CSL;
import de.undercouch.citeproc.bibtex.BibTeXConverter;
import de.undercouch.citeproc.csl.CSLItemData;
import de.undercouch.citeproc.output.Bibliography;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jbibtex.BibTeXEntry;
import org.jbibtex.DigitStringValue;
import org.jbibtex.Key;
import org.jbibtex.TokenMgrException;


/**
 * WARNING: the citation is generated using JavaScript which may take some time, better call it from outside the main Thread
 */
public class CitationStyleGenerator {

    private static final Log LOGGER = LogFactory.getLog(CitationStyleGenerator.class);
    private static final BibTeXConverter BIBTEX_CONVERTER = new BibTeXConverter();

    /**
     * WARNING: the citation is generated using JavaScript which may take some time, better call it from outside the main Thread
     * Generates a Citation based on the given entry and style
     */
    protected static String generateCitation(BibEntry entry, CitationStyle style) {
        return generateCitation(entry, style.getSource(), CitationStyleOutputFormat.HTML);
    }

    /**
     * WARNING: the citation is generated using JavaScript which may take some time, better call it from outside the main Thread
     * Generates a Citation based on the given entry and style
     */
    protected static String generateCitation(BibEntry entry, String style) {
        return generateCitation(entry, style, CitationStyleOutputFormat.HTML);
    }

    /**
     * WARNING: the citation is generated using JavaScript which may take some time, better call it from outside the main Thread
     * Generates a Citation based on the given entry, style, and output format
     */
    protected static String generateCitation(BibEntry entry, String style, CitationStyleOutputFormat outputFormat) {
        return generateCitations(Collections.singletonList(entry), style, outputFormat).get(0);
    }

    /**
     * WARNING: the citation is generated using JavaScript which may take some time, better call it from outside the main Thread
     * Generates the citation for multiple entries at once. This is useful when the Citation Style has an increasing number
     */
    public static List<String> generateCitations(List<BibEntry> bibEntries, String style, CitationStyleOutputFormat outputFormat) {
        try {
            CSLItemData[] cslItemData = new CSLItemData[bibEntries.size()];
            for (int i = 0; i < bibEntries.size(); i++) {
                cslItemData[i] = bibEntryToCSLItemData(bibEntries.get(i));
            }
            Bibliography bibliography = CSL.makeAdhocBibliography(style, outputFormat.getFormat(), cslItemData);
            return Arrays.asList(bibliography.getEntries());

        } catch (IOException | ArrayIndexOutOfBoundsException e) {
            LOGGER.error("Could not generate BibEntry citation", e);
            return Collections.singletonList(Localization.lang("Cannot generate preview based on selected citation style."));
        } catch (TokenMgrException e) {
            LOGGER.error("Bad character inside BibEntry", e);
            // sadly one cannot easily retrieve the bad char from the TokenMgrError
            return Collections.singletonList(new StringBuilder()
                    .append(Localization.lang("Cannot generate preview based on selected citation style."))
                    .append(outputFormat.getLineSeparator())
                    .append(Localization.lang("Bad character inside entry"))
                    .append(outputFormat.getLineSeparator())
                    .append(e.getLocalizedMessage())
                    .toString());
        }
    }

    /**
     * Converts the {@link BibEntry} into {@link CSLItemData}.
     */
    private static CSLItemData bibEntryToCSLItemData(BibEntry bibEntry) {
        String citeKey = bibEntry.getCiteKeyOptional().orElse("");
        BibTeXEntry bibTeXEntry = new BibTeXEntry(new Key(bibEntry.getType()), new Key(citeKey));

        // Not every field is already generated into latex free fields
        for (String key : bibEntry.getFieldMap().keySet()) {
            Optional<String> latexFreeField = bibEntry.getLatexFreeField(key);
            latexFreeField.ifPresent(value -> bibTeXEntry.addField(new Key(key), new DigitStringValue(value)));
        }
        return BIBTEX_CONVERTER.toItemData(bibTeXEntry);
    }

}
