package org.pdfclown.samples.cli;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import org.pdfclown.documents.Document;
import org.pdfclown.documents.Page;
import org.pdfclown.documents.interaction.viewer.ViewerPreferences;
import org.pdfclown.documents.interchange.metadata.Information;
import org.pdfclown.files.File;
import org.pdfclown.files.SerializationModeEnum;

/**
 * Abstract sample.
 * 
 * @author Stefano Chizzolini (http://www.stefanochizzolini.it)
 * @since 0.0.8
 * @version 0.1.2, 11/30/12
 */
public abstract class Sample {
	// <class>
	// <dynamic>
	// <fields>
	private String inputPath;
	private String outputPath;

	private boolean quit;

	// </fields>

	// <interface>
	// <public>
	/**
	 * Gets whether the sample was exited before its completion.
	 */
	public boolean isQuit() {
		return quit;
	}

	/**
	 * Executes the sample.
	 * 
	 * @return Whether the sample has been completed.
	 */
	public abstract void run();

	// </public>

	// <protected>
	protected String getIndentation(int level) {
		StringBuilder indentationBuilder = new StringBuilder();
		for (int i = 0; i < level; i++) {
			indentationBuilder.append(' ');
		}
		return indentationBuilder.toString();
	}

	/**
	 * Gets the path used to serialize output files.
	 */
	protected String getOutputPath() {
		return getOutputPath(null);
	}

	/**
	 * Gets the path used to serialize output files.
	 * 
	 * @param fileName
	 *            Relative output file path.
	 */
	protected String getOutputPath(String fileName) {
		return outputPath
				+ (fileName != null ? java.io.File.separator + fileName : "");
	}

	/**
	 * Gets the path to a sample resource.
	 * 
	 * @param resourceName
	 *            Relative resource path.
	 */
	protected String getResourcePath(String resourceName) {
		return inputPath + java.io.File.separator + resourceName;
	}

	/**
	 * Prompts a message to the user.
	 * 
	 * @param message
	 *            Text to show.
	 */
	protected void prompt(String message) {
		Utils.prompt(message);
	}

	/**
	 * Gets the user's choice from the given request.
	 * 
	 * @param message
	 *            Description of the request to show to the user.
	 * @return User choice.
	 */
	protected String promptChoice(String message) {
		System.out.print(message);
		Scanner in = new Scanner(System.in);
		try {
			return in.nextLine();
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Gets the user's choice from the given options.
	 * 
	 * @param options
	 *            Available options to show to the user.
	 * @return Chosen option key.
	 */
	protected String promptChoice(Map<String, String> options) {
		System.out.println();
		List<Map.Entry<String, String>> optionEntries = new ArrayList<Map.Entry<String, String>>(
				options.entrySet());
		Collections.sort(optionEntries,
				new Comparator<Map.Entry<String, String>>() {
					@Override
					public int compare(Map.Entry<String, String> o1,
							Map.Entry<String, String> o2) {
						return o1.getKey().compareTo(o2.getKey());
					};
				});
		for (Map.Entry<String, String> option : optionEntries) {
			System.out.println((option.getKey().equals("") ? "ENTER" : "["
					+ option.getKey() + "]")
					+ " " + option.getValue());
		}
		System.out.print("Please select: ");
		Scanner in = new Scanner(System.in);
		try {
			return in.nextLine();
		} catch (Exception e) {
			return null;
		}
	}

	protected String promptFileChoice(String inputDescription) {
		Scanner in = new Scanner(System.in);

		java.io.File resourceFolder = new java.io.File(inputPath
				+ java.io.File.separator + "pdf");
		try {
			System.out.println("\nAvailable PDF files ("
					+ resourceFolder.getCanonicalPath() + "):");
		} catch (IOException e1) {/* NOOP */
		}

		// Get the list of available PDF files!
		SampleResources resources = new SampleResources(resourceFolder);
		List<String> fileNames = Arrays.asList(resources.filter("pdf"));
		Collections.sort(fileNames);

		// Display files!
		resources.printList((String[]) fileNames.toArray());

		while (true) {
			// Get the user's choice!
			System.out.print(inputDescription + ": ");
			try {
				return resourceFolder.getPath() + java.io.File.separator
						+ fileNames.get(Integer.parseInt(in.nextLine()));
			} catch (Exception e) {/* NOOP */
			}
		}
	}

	/**
	 * Prompts the user for advancing to the next page.
	 * 
	 * @param page
	 *            Next page.
	 * @param skip
	 *            Whether the prompt has to be skipped.
	 * @return Whether to advance.
	 */
	protected boolean promptNextPage(Page page, boolean skip) {
		int pageIndex = page.getIndex();
		if (pageIndex > 0 && !skip) {
			Map<String, String> options = new HashMap<String, String>();
			options.put("", "Scan next page");
			options.put("Q", "End scanning");
			if (!promptChoice(options).equals(""))
				return false;
		}

		System.out.println("\nScanning page " + (pageIndex + 1) + "...\n");
		return true;
	}

	/**
	 * Prompts the user for a page index to select.
	 * 
	 * @param inputDescription
	 *            Message prompted to the user.
	 * @param pageCount
	 *            Page count.
	 * @return Selected page index.
	 */
	protected int promptPageChoice(String inputDescription, int pageCount) {
		return promptPageChoice(inputDescription, 0, pageCount);
	}

	/**
	 * Prompts the user for a page index to select.
	 * 
	 * @param inputDescription
	 *            Message prompted to the user.
	 * @param startIndex
	 *            First page index, inclusive.
	 * @param endIndex
	 *            Last page index, exclusive.
	 * @return Selected page index.
	 */
	protected int promptPageChoice(String inputDescription, int startIndex,
			int endIndex) {
		int pageIndex;
		try {
			pageIndex = Integer.parseInt(promptChoice(inputDescription + " ["
					+ (startIndex + 1) + "-" + endIndex + "]: ")) - 1;
		} catch (Exception e) {
			pageIndex = startIndex;
		}
		if (pageIndex < startIndex) {
			pageIndex = startIndex;
		} else if (pageIndex >= endIndex) {
			pageIndex = endIndex - 1;
		}

		return pageIndex;
	}

	/**
	 * Indicates that the sample was exited before its completion.
	 */
	protected void quit() {
		quit = true;
	}

	/**
	 * Serializes the given PDF Clown file object.
	 * 
	 * @param file
	 *            PDF file to serialize.
	 * @return Serialization path.
	 */
	protected String serialize(File file) {
		return serialize(file, null, null, null);
	}

	/**
	 * Serializes the given PDF Clown file object.
	 * 
	 * @param file
	 *            PDF file to serialize.
	 * @param serializationMode
	 *            Serialization mode.
	 * @return Serialization path.
	 */
	protected String serialize(File file,
			SerializationModeEnum serializationMode) {
		return serialize(file, serializationMode, null, null, null);
	}

	/**
	 * Serializes the given PDF Clown file object.
	 * 
	 * @param file
	 *            PDF file to serialize.
	 * @param fileName
	 *            Output file name.
	 * @return Serialization path.
	 */
	protected String serialize(File file, String fileName) {
		return serialize(file, fileName, null, null);
	}

	/**
	 * Serializes the given PDF Clown file object.
	 * 
	 * @param file
	 *            PDF file to serialize.
	 * @param fileName
	 *            Output file name.
	 * @param serializationMode
	 *            Serialization mode.
	 * @return Serialization path.
	 */
	protected String serialize(File file, String fileName,
			SerializationModeEnum serializationMode) {
		return serialize(file, fileName, serializationMode, null, null, null);
	}

	/**
	 * Serializes the given PDF Clown file object.
	 * 
	 * @param file
	 *            PDF file to serialize.
	 * @param title
	 *            Document title.
	 * @param subject
	 *            Document subject.
	 * @param keywords
	 *            Document keywords.
	 * @return Serialization path.
	 */
	protected String serialize(File file, String title, String subject,
			String keywords) {
		return serialize(file, null, title, subject, keywords);
	}

	/**
	 * Serializes the given PDF Clown file object.
	 * 
	 * @param file
	 *            PDF file to serialize.
	 * @param serializationMode
	 *            Serialization mode.
	 * @param title
	 *            Document title.
	 * @param subject
	 *            Document subject.
	 * @param keywords
	 *            Document keywords.
	 * @return Serialization path.
	 */
	protected String serialize(File file,
			SerializationModeEnum serializationMode, String title,
			String subject, String keywords) {
		return serialize(file, getClass().getSimpleName(), serializationMode,
				title, subject, keywords);
	}

	/**
	 * Serializes the given PDF Clown file object.
	 * 
	 * @param file
	 *            PDF file to serialize.
	 * @param fileName
	 *            Output file name.
	 * @param serializationMode
	 *            Serialization mode.
	 * @param title
	 *            Document title.
	 * @param subject
	 *            Document subject.
	 * @param keywords
	 *            Document keywords.
	 * @return Serialization path.
	 */
	protected String serialize(File file, String fileName,
			SerializationModeEnum serializationMode, String title,
			String subject, String keywords) {
		applyDocumentSettings(file.getDocument(), title, subject, keywords);

		System.out.println();

		if (serializationMode == null) {
			if (file.getReader() == null) // New file.
			{
				serializationMode = SerializationModeEnum.Standard;
			} else // Existing file.
			{
				Scanner in = new Scanner(System.in);
				System.out.println("[0] Standard serialization");
				System.out.println("[1] Incremental update");
				System.out.print("Please select a serialization mode: ");
				try {
					serializationMode = SerializationModeEnum.values()[Integer
							.parseInt(in.nextLine())];
				} catch (Exception e) {
					serializationMode = SerializationModeEnum.Standard;
				}
			}
		}

		java.io.File outputFile = new java.io.File(outputPath
				+ java.io.File.separator + fileName + "." + serializationMode
				+ ".pdf");

		// Save the file!
		/*
		 * NOTE: You can also save to a generic target stream (see save() method
		 * overloads).
		 */
		try {
			file.save(outputFile, serializationMode);
		} catch (Exception e) {
			System.out.println("File writing failed: " + e.getMessage());
			e.printStackTrace();
		}
		System.out.println("\nOutput: " + outputFile.getPath());

		return outputFile.getPath();
	}

	// </protected>

	// <internal>
	final void initialize(String inputPath, String outputPath) {
		this.inputPath = inputPath;
		this.outputPath = outputPath;
	}

	// </internal>

	// <private>
	private void applyDocumentSettings(Document document, String title,
			String subject, String keywords) {
		if (title == null)
			return;

		// Viewer preferences.
		ViewerPreferences view = new ViewerPreferences(document); // Instantiates
																	// viewer
																	// preferences
																	// inside
																	// the
																	// document
																	// context.
		document.setViewerPreferences(view); // Assigns the viewer preferences
												// object to the viewer
												// preferences function.
		view.setDisplayDocTitle(true);

		// Document metadata.
		Information info = document.getInformation();
		info.clear();
		info.setAuthor("Stefano Chizzolini");
		info.setCreationDate(new Date());
		info.setCreator(getClass().getName());
		info.setTitle("PDF Clown - " + title + " sample");
		info.setSubject("Sample about " + subject + " using PDF Clown");
		info.setKeywords(keywords);
	}
	// </private>
	// </interface>
	// </dynamic>
	// </class>
}
