/*  Copyright (C) 2003-2011 JabRef contributors.
    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.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package net.sf.jabref.export;

import net.sf.jabref.BibtexDatabase;
import net.sf.jabref.BibtexEntry;
import net.sf.jabref.Globals;
import net.sf.jabref.MetaData;
import net.sf.jabref.export.layout.Layout;
import net.sf.jabref.export.layout.LayoutHelper;

import javax.swing.filechooser.FileFilter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.*;

/**
 * Base class for export formats based on templates.
 * 
 */
public class ExportFormat implements IExportFormat {

	String displayName;
	String consoleName;
	String lfFileName;
	String directory;
	String extension;
    String encoding = null; // If this value is set, it will be used to override
      // the default encoding for the basePanel.

	FileFilter fileFilter;
	boolean customExport = false;

	/**
	 * Initialize another export format based on templates stored in dir with
	 * layoutFile lfFilename.
	 * 
	 * @param displayName
	 *            Name to display to the user.
	 * @param consoleName
	 *            Name to call this format in the console.
	 * @param lfFileName
	 *            Name of the main layout file.
	 * @param directory
	 *            Directory in which to find the layout file.
	 * @param extension
	 *            Should contain the . (for instance .txt).
	 */
	public ExportFormat(String displayName, String consoleName,
		String lfFileName, String directory, String extension) {
		this.displayName = displayName;
		this.consoleName = consoleName;
		this.lfFileName = lfFileName;
		this.directory = directory;
		this.extension = extension;
	}

	/** Empty default constructor for subclasses */
	protected ExportFormat() {
		// intentionally empty
	}

	/**
	 * Indicate whether this is a custom export. A custom export looks for its
	 * layout files using a normal file path, while a built-in export looks in
	 * the classpath.
	 * 
	 * @param custom
	 *            true to indicate a custom export format.
	 */
	public void setCustomExport(boolean custom) {
		this.customExport = custom;
	}

	/**
	 * @see IExportFormat#getConsoleName()
	 */
	public String getConsoleName() {
		return consoleName;
	}

	/**
	 * @see IExportFormat#getDisplayName()
	 */
	public String getDisplayName() {
		return displayName;
	}

    /**
     * Set an encoding which will be used in preference to the default value
     * obtained from the basepanel.
     * @param encoding The name of the encoding to use.
     */
    protected void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    /**
	 * This method should return a reader from which the given layout file can
	 * be read.
	 * 
	 * This standard implementation of this method will use the
	 * {@link FileActions#getReader(String)} method.
	 * 
	 * Subclasses of ExportFormat are free to override and provide their own
	 * implementation.
	 * 
	 * @param filename
	 *            the file name
	 * @throws IOException
	 *             if the reader could not be created
	 * 
	 * @return a newly created reader
	 */
	protected Reader getReader(String filename) throws IOException {
		// If this is a custom export, just use the given file name:
		String dir;
		if (customExport) {
			dir = "";
		} else {
			dir = Globals.LAYOUT_PREFIX
				+ (directory == null ? "" : directory + "/");
		}
		return FileActions.getReader(dir + filename);
	}

	/**
	 * Perform the export of {@code database}.
	 * 
	 * @param database
	 *            The database to export from.
     * @param metaData
     *            The database's meta data.
	 * @param file
	 *            the file to write the resulting export to
	 * @param encoding
	 *            The encoding of the database
	 * @param entryIds
	 *            Contains the IDs of all entries that should be exported. If
	 *            <code>null</code>, all entries will be exported.
	 * 
	 * @throws IOException
	 *             if a problem occurred while trying to write to {@code writer}
	 *             or read from required resources.
	 * @throws Exception
	 *             if any other error occurred during export.
	 * 
	 * @see net.sf.jabref.export.IExportFormat#performExport(net.sf.jabref.BibtexDatabase,
	 *      net.sf.jabref.MetaData, java.lang.String, java.lang.String, java.util.Set)
	 */
	public void performExport(final BibtexDatabase database,
            final MetaData metaData, final String file,
		final String encoding, Set<String> entryIds) throws Exception {

        File outFile = new File(file);
        SaveSession ss = null;
        if (this.encoding != null) {
            try {
                ss = getSaveSession(this.encoding, outFile);
            } catch (IOException ex) {
                // Perhaps the overriding encoding doesn't work?
                // We will fall back on the default encoding.
                ex.printStackTrace();

            }
        }
		if (ss == null)
		    ss = getSaveSession(encoding, outFile);
        
		VerifyingWriter ps = ss.getWriter();

        Layout beginLayout = null;
		Reader reader = null;

        // Check if this export filter has bundled name formatters:
        HashMap<String,String> customNameFormatters = readFormatterFile(lfFileName);
        // Set a global field, so all layouts have access to the custom name formatters: 
        Globals.prefs.customExportNameFormatters = customNameFormatters;

        ArrayList<String> missingFormatters = new ArrayList<String>(1);

        // Print header
        try {
			reader = getReader(lfFileName + ".begin.layout");
			LayoutHelper layoutHelper = new LayoutHelper(reader);
			beginLayout = layoutHelper
				.getLayoutFromText(Globals.FORMATTER_PACKAGE);
            reader.close();
		} catch (IOException ex) {
			// If an exception was cast, export filter doesn't have a begin
			// file.
		}
		// Write the header
		if (beginLayout != null) {
			ps.write(beginLayout.doLayout(database, encoding));
            missingFormatters.addAll(beginLayout.getMissingFormatters());
		}

		/*
		 * Write database entries; entries will be sorted as they appear on the
		 * screen, or sorted by author, depending on Preferences. We also supply
		 * the Set entries - if we are to export only certain entries, it will
		 * be non-null, and be used to choose entries. Otherwise, it will be
		 * null, and be ignored.
		 */
		List<BibtexEntry> sorted = FileActions.getSortedEntries(database,
			entryIds, false);

		// Load default layout
		reader = getReader(lfFileName + ".layout");

		LayoutHelper layoutHelper = new LayoutHelper(reader);
		Layout defLayout = layoutHelper
			.getLayoutFromText(Globals.FORMATTER_PACKAGE);
		reader.close();
        if (defLayout != null) {
            missingFormatters.addAll(defLayout.getMissingFormatters());
            System.out.println(defLayout.getMissingFormatters());
        }
		HashMap<String, Layout> layouts = new HashMap<String, Layout>();
		Layout layout;

        ExportFormats.entryNumber = 0;
		for (BibtexEntry entry : sorted) {
            ExportFormats.entryNumber++; // Increment entry counter.
			// Get the layout
			String type = entry.getType().getName().toLowerCase();
			if (layouts.containsKey(type))
				layout = layouts.get(type);
			else {
				try {
					// We try to get a type-specific layout for this entry.
					reader = getReader(lfFileName + "." + type + ".layout");
					layoutHelper = new LayoutHelper(reader);
					layout = layoutHelper
						.getLayoutFromText(Globals.FORMATTER_PACKAGE);
					layouts.put(type, layout);
					reader.close();
                    if (layout != null)
                        missingFormatters.addAll(layout.getMissingFormatters());

				} catch (IOException ex) {
					// The exception indicates that no type-specific layout
					// exists, so we
					// go with the default one.
					layout = defLayout;
				}
			}

			// Write the entry
			ps.write(layout.doLayout(entry, database));
		}

		// Print footer

		// changed section - begin (arudert)
		Layout endLayout = null;
		try {
			reader = getReader(lfFileName + ".end.layout");
			layoutHelper = new LayoutHelper(reader);
			endLayout = layoutHelper
				.getLayoutFromText(Globals.FORMATTER_PACKAGE);
			reader.close();
		} catch (IOException ex) {
			// If an exception was thrown, export filter doesn't have an end
			// file.
		}

		// Write footer
		if (endLayout != null) {
			ps.write(endLayout.doLayout(database, encoding));
            missingFormatters.addAll(endLayout.getMissingFormatters());
		}

        // Clear custom name formatters:
        Globals.prefs.customExportNameFormatters = null;

        if (missingFormatters.size() > 0) {
            StringBuilder sb = new StringBuilder("The following formatters could not be found").
                    append(": ");
            for (Iterator<String> i = missingFormatters.iterator(); i.hasNext();) {
                sb.append(i.next());
                if (i.hasNext())
                    sb.append(", ");
            }
            System.err.println(sb.toString());
        }

        finalizeSaveSession(ss);
	}

    /**
     * See if there is a name formatter file bundled with this export format. If so, read
     * all the name formatters so they can be used by the filter layouts.
     * @param lfFileName The layout file name.
     */
    private HashMap<String, String> readFormatterFile(String lfFileName) {
        HashMap<String,String> formatters = new HashMap<String, String>();
        File formatterFile = new File(lfFileName + ".formatters");
        if (formatterFile.exists()) {
            Reader in = null;
            try {
                in = new FileReader(formatterFile);
                if (in != null) {
                    // Ok, we found and opened the file. Read all contents:
                    StringBuilder sb = new StringBuilder();
                    int c;
                    while ((c = in.read()) != -1) {
                        sb.append((char)c);
                    }
                    String[] lines = sb.toString().split("\n");
                    // Go through each line:
                    for (int i=0; i<lines.length; i++) {
                        String line = lines[i].trim();
                        // Do not deal with empty lines:
                        if (line.length() == 0)
                            continue;
                        int index = line.indexOf(":"); // TODO: any need to accept escaped colons here?
                        if ((index > 0) && (index+1 < line.length())) {
                            String formatterName = line.substring(0, index);
                            String contents = line.substring(index+1);
                            //System.out.println("Name: '"+formatterName+"'");
                            //System.out.println("Contents: '"+contents+"'");
                            formatters.put(formatterName, contents);
                        }
                    }
                }

            } catch (IOException ex) {
                // TODO: show error message here?
                ex.printStackTrace();
            } finally {
                if (in != null)
                    try {
                        in.close();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
            }
        }
        return formatters;
    }

    protected SaveSession getSaveSession(final String encoding,
		final File outFile) throws IOException {
		return new SaveSession(outFile, encoding, false);
	}

	/**
	 * @see net.sf.jabref.export.IExportFormat#getFileFilter()
	 */
	public FileFilter getFileFilter() {
		if (fileFilter == null)
			fileFilter = new ExportFileFilter(this, extension);
		return fileFilter;
	}

	public void finalizeSaveSession(final SaveSession ss) throws Exception {
		ss.getWriter().flush();
		ss.getWriter().close();

		if (!ss.getWriter().couldEncodeAll()) {
			System.err.println("Could not encode...");
		}
		ss.commit();
	}
}
