package be.nikiroo.fanfix.supported;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

import be.nikiroo.fanfix.Instance;
import be.nikiroo.fanfix.bundles.Config;
import be.nikiroo.fanfix.data.Chapter;
import be.nikiroo.fanfix.data.MetaData;
import be.nikiroo.utils.IOUtils;
import be.nikiroo.utils.Image;
import be.nikiroo.utils.streams.MarkableFileInputStream;

public class InfoReader {
	static protected BasicSupportHelper bsHelper = new BasicSupportHelper();
	static protected BasicSupportImages bsImages = new BasicSupportImages();
	static protected BasicSupportPara bsPara = new BasicSupportPara(
			new BasicSupportHelper(), new BasicSupportImages());

	public static MetaData readMeta(File infoFile, boolean withCover)
			throws IOException {
		if (infoFile == null) {
			throw new IOException("File is null");
		}
		
		MetaData meta = null;

		if (infoFile.exists()) {
			InputStream in = new MarkableFileInputStream(infoFile);
			try {
				meta = createMeta(infoFile.toURI().toURL(), in,
						withCover);

				// Some old .info files were using UUID for URL...
				if (!hasIt(meta.getUrl()) && meta.getUuid() != null
						&& (meta.getUuid().startsWith("http://")
								|| meta.getUuid().startsWith("https://"))) {
					meta.setUrl(meta.getUuid());
				}

				// Some old .info files don't have those now required fields...
				// So we check if we can find the info in another way (many
				// formats have a copy of the original text file)
				if (!hasIt(meta.getTitle(), meta.getAuthor(), meta.getDate(),
						meta.getUrl())) {
					String base = infoFile.getPath();
					if (base.endsWith(".info")) {
						base = base.substring(0,
								base.length() - ".info".length());
					}
					File textFile = new File(base);
					if (!textFile.exists()) {
						textFile = new File(base + ".txt");
					}
					if (!textFile.exists()) {
						textFile = new File(base + ".text");
					}

					completeMeta(textFile, meta);
				}

				
			} finally {
				in.close();
			}
		}

		if (meta != null) {
			try {
				File summaryFile = new File(infoFile.getAbsolutePath()
						.replaceFirst("\\.info$", ".summary"));
				InputStream in = new MarkableFileInputStream(summaryFile);
				try {
					String content = IOUtils.readSmallStream(in);
					Chapter desc = bsPara.makeChapter(null, null, 0,
							"Description", content, false, null);
					meta.setResume(desc);
				} finally {
					in.close();
				}
			} catch (IOException e) {
				// ignore absent or bad summary
			}
			
			return meta;
		}

		throw new FileNotFoundException(
				"File given as argument does not exists: "
						+ infoFile.getAbsolutePath());
	}
	
	/**
	 * Complete the given {@link MetaData} with the original text file if needed
	 * and possible.
	 * 
	 * @param textFile
	 *            the original text file
	 * @param meta
	 *            the {@link MetaData} to complete if needed and possible
	 * 
	 * @throws IOException
	 *             in case of I/O errors
	 */
	static public void completeMeta(File textFile,
			MetaData meta)	throws IOException {
		// TODO: not nice, would be better to do it properly...
		if (textFile != null && textFile.exists()) {
			final URL source = textFile.toURI().toURL();
			final MetaData[] superMetaA = new MetaData[1];
			@SuppressWarnings("unused")
			Text unused = new Text() {
				private boolean loaded = loadDocument();

				@Override
				public SupportType getType() {
					return SupportType.TEXT;
				}

				protected boolean loadDocument() throws IOException {
					loadDocument(source);
					superMetaA[0] = getMeta();
					return true;
				}

				@Override
				protected Image getCover(File sourceFile) {
					return null;
				}
			};

			MetaData superMeta = superMetaA[0];
			if (!hasIt(meta.getTitle())) {
				meta.setTitle(superMeta.getTitle());
			}
			if (!hasIt(meta.getAuthor())) {
				meta.setAuthor(superMeta.getAuthor());
			}
			if (!hasIt(meta.getDate())) {
				meta.setDate(superMeta.getDate());
			}
			if (!hasIt(meta.getUrl())) {
				meta.setUrl(superMeta.getUrl());
			}
		}
	}

	/**
	 * Check if we have non-empty values for all the given {@link String}s.
	 * 
	 * @param values
	 *            the values to check
	 * 
	 * @return TRUE if none of them was NULL or empty
	 */
	static private boolean hasIt(String... values) {
		for (String value : values) {
			if (value == null || value.trim().isEmpty()) {
				return false;
			}
		}

		return true;
	}

	private static MetaData createMeta(URL sourceInfoFile, InputStream in,
			boolean withCover) throws IOException {
		MetaData meta = new MetaData();

		meta.setTitle(getInfoTag(in, "TITLE"));
		meta.setAuthor(getInfoTag(in, "AUTHOR"));
		meta.setDate(bsHelper.formatDate(getInfoTag(in, "DATE")));
		meta.setTags(getInfoTagList(in, "TAGS", ","));
		meta.setSource(getInfoTag(in, "SOURCE"));
		meta.setUrl(getInfoTag(in, "URL"));
		meta.setPublisher(getInfoTag(in, "PUBLISHER"));
		meta.setUuid(getInfoTag(in, "UUID"));
		meta.setLuid(getInfoTag(in, "LUID"));
		meta.setLang(getInfoTag(in, "LANG"));
		meta.setSubject(getInfoTag(in, "SUBJECT"));
		meta.setType(getInfoTag(in, "TYPE"));
		meta.setImageDocument(getInfoTagBoolean(in, "IMAGES_DOCUMENT", false));
		if (withCover) {
			String infoTag = getInfoTag(in, "COVER");
			if (infoTag != null && !infoTag.trim().isEmpty()) {
				meta.setCover(bsHelper.getImage(null, sourceInfoFile, infoTag));
			}
			if (meta.getCover() == null) {
				// Second chance: try to check for a cover next to the info file
				meta.setCover(getCoverByName(sourceInfoFile));
			}
		}
		try {
			meta.setWords(Long.parseLong(getInfoTag(in, "WORDCOUNT")));
		} catch (NumberFormatException e) {
			meta.setWords(0);
		}
		meta.setCreationDate(
				bsHelper.formatDate(getInfoTag(in, "CREATION_DATE")));
		meta.setFakeCover(Boolean.parseBoolean(getInfoTag(in, "FAKE_COVER")));

		if (withCover && meta.getCover() == null) {
			meta.setCover(bsHelper.getDefaultCover(meta.getSubject()));
		}

		return meta;
	}

	/**
	 * Return the cover image if it is next to the source file.
	 * 
	 * @param sourceInfoFile
	 *            the source file
	 * 
	 * @return the cover if present, NULL if not
	 */
	public static Image getCoverByName(URL sourceInfoFile) {
		Image cover = null;

		File basefile = new File(sourceInfoFile.getFile());

		String ext = "." + Instance.getInstance().getConfig()
				.getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase();

		// Without removing ext
		cover = bsHelper.getImage(null, sourceInfoFile,
				basefile.getAbsolutePath() + ext);

		// Try without ext
		String name = basefile.getName();
		int pos = name.lastIndexOf(".");
		if (cover == null && pos > 0) {
			name = name.substring(0, pos);
			basefile = new File(basefile.getParent(), name);

			cover = bsHelper.getImage(null, sourceInfoFile,
					basefile.getAbsolutePath() + ext);
		}

		return cover;
	}

	private static boolean getInfoTagBoolean(InputStream in, String key,
			boolean def) throws IOException {
		Boolean value = getInfoTagBoolean(in, key);
		return value == null ? def : value;
	}

	private static Boolean getInfoTagBoolean(InputStream in, String key)
			throws IOException {
		String value = getInfoTag(in, key);
		if (value != null && !value.trim().isEmpty()) {
			value = value.toLowerCase().trim();
			return value.equals("1") || value.equals("on")
					|| value.equals("true") || value.equals("yes");
		}

		return null;
	}

	private static List<String> getInfoTagList(InputStream in, String key,
			String separator) throws IOException {
		List<String> list = new ArrayList<String>();
		String tt = getInfoTag(in, key);
		if (tt != null) {
			for (String tag : tt.split(separator)) {
				list.add(tag.trim());
			}
		}

		return list;
	}

	/**
	 * Return the value of the given tag in the <tt>.info</tt> file if present.
	 * 
	 * @param key
	 *            the tag key
	 * 
	 * @return the value or NULL
	 * 
	 * @throws IOException
	 *             in case of I/O error
	 */
	private static String getInfoTag(InputStream in, String key)
			throws IOException {
		key = "^" + key + "=";

		if (in != null) {
			in.reset();
			String value = getLine(in, key, 0);
			if (value != null && !value.isEmpty()) {
				value = value.trim().substring(key.length() - 1).trim();
				if (value.length() > 1 && //
						(value.startsWith("'") && value.endsWith("'")
								|| value.startsWith("\"")
										&& value.endsWith("\""))) {
					value = value.substring(1, value.length() - 1).trim();
				}

				// Some old files ended up with TITLE="'xxxxx'"
				if ("^TITLE=".equals(key)) {
					if (value.startsWith("'") && value.endsWith("'")
							&& value.length() > 1) {
						value = value.substring(1, value.length() - 1).trim();
					}
				}

				return value;
			}
		}

		return null;
	}

	/**
	 * Return the first line from the given input which correspond to the given
	 * selectors.
	 * 
	 * @param in
	 *            the input
	 * @param needle
	 *            a string that must be found inside the target line (also
	 *            supports "^" at start to say "only if it starts with" the
	 *            needle)
	 * @param relativeLine
	 *            the line to return based upon the target line position (-1 =
	 *            the line before, 0 = the target line...)
	 * 
	 * @return the line
	 */
	static private String getLine(InputStream in, String needle,
			int relativeLine) {
		return getLine(in, needle, relativeLine, true);
	}

	/**
	 * Return a line from the given input which correspond to the given
	 * selectors.
	 * 
	 * @param in
	 *            the input
	 * @param needle
	 *            a string that must be found inside the target line (also
	 *            supports "^" at start to say "only if it starts with" the
	 *            needle)
	 * @param relativeLine
	 *            the line to return based upon the target line position (-1 =
	 *            the line before, 0 = the target line...)
	 * @param first
	 *            takes the first result (as opposed to the last one, which will
	 *            also always spend the input)
	 * 
	 * @return the line
	 */
	static private String getLine(InputStream in, String needle,
			int relativeLine, boolean first) {
		String rep = null;

		List<String> lines = new ArrayList<String>();
		@SuppressWarnings("resource")
		Scanner scan = new Scanner(in, "UTF-8");
		int index = -1;
		scan.useDelimiter("\\n");
		while (scan.hasNext()) {
			lines.add(scan.next());

			if (index == -1) {
				if (needle.startsWith("^")) {
					if (lines.get(lines.size() - 1)
							.startsWith(needle.substring(1))) {
						index = lines.size() - 1;
					}

				} else {
					if (lines.get(lines.size() - 1).contains(needle)) {
						index = lines.size() - 1;
					}
				}
			}

			if (index >= 0 && index + relativeLine < lines.size()) {
				rep = lines.get(index + relativeLine);
				if (first) {
					break;
				}
			}
		}

		return rep;
	}
}
