/*
 * Copyright (C) 2007 Steve Ratcliffe
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 * 
 *  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.
 * 
 * 
 * Author: Steve Ratcliffe
 * Create date: Feb 17, 2008
 */
package uk.me.parabola.mkgmap.osmstyle;

import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.Reader;
import java.net.URL;

import uk.me.parabola.log.Logger;

/**
 * Open a style which can be on the classpath (mainly for the built-in styles)
 * or in a specified file or directory.
 *
 * @author Steve Ratcliffe
 */
public abstract class StyleFileLoader implements Closeable {
	private static final Logger log = Logger.getLogger(StyleFileLoader.class);

	/**
	 * Open a style that is contained in a file.  This is expected to be a
	 * directory or zip file containing the files that make up the style.
	 *
	 * @param loc The file or directory containing the style(s). If this is null then the location "classpath:styles"
	 * is used.
	 * @param name If the name is given then we look for a directory with the
	 * given name.  If there is no name, then the style is assumed to be at
	 * the top level and/or the only file.
	 *
	 * @return A style loader.
	 */
	public static StyleFileLoader createStyleLoader(String loc, String name)
			throws FileNotFoundException
	{
		if (loc == null)
			return createStyleLoader("classpath:styles", name);
		
		StyleFileLoader loader;

		File file = new File(loc);
		if (file.isDirectory()) {
			File dir = file;
			if (name != null) {
				dir = new File(file, name);
				if (!dir.exists())
					throw new FileNotFoundException("style " + name + " not found in " + dir);
				if (!dir.isDirectory())
					dir = file;
			}

			log.debug("style directory", dir);
			loader = new DirectoryFileLoader(dir);
		} else if (file.isFile()) {
			String loclc = loc.toLowerCase();
			if (loclc.endsWith(".style")) {
				if (name != null)
					throw new FileNotFoundException("no sub styles in a simple style file");
				log.debug("a single file style");
				loader = new CombinedStyleFileLoader(loc);
			} else {
				log.debug("jar file", file);
				loader = new JarFileLoader(file.toURI().toString(), name);
			}
		} else {
			log.debug("style url location", loc);
			String s = loc.toLowerCase();
			if (s.startsWith("classpath:")) {
				log.debug("load style off classpath");
				loader = classpathLoader(loc.substring(10), name);
				return loader;
			} else if (s.startsWith("jar:")) {
				loader = new JarFileLoader(loc, name);
			} else if (s.indexOf(':') > 0) {
				loader = new JarFileLoader(s, name);
			} else {
				throw new FileNotFoundException("no such file or path: " + loc);
			}
		}

		return loader;
	}

	/**
	 * Open the specified file in the style definition.
	 * @param filename The name of the file in the style.
	 * @return An open file reader for the file.
	 * @throws FileNotFoundException When the file can't be opened.
	 */
	public abstract Reader open(String filename) throws FileNotFoundException;

	/**
	 * Close the FileLoader.  This is different from closing individual files
	 * that were opened via {@link #open}.  After this call then you shouldn't
	 * open any more files.
	 */
	public abstract void close();

	/**
	 * List the names of the styles that are contained in this loader.
	 * @return An array of style names.
	 */
	public abstract String[] list();

	/**
	 * Find a style on the class path.  First we find out if the style is in
	 * a jar or a directory and then use the appropriate Loader.
	 *
	 * @param loc The file or directory location.
	 * @param name The style name.
	 * @return A loader for the style.
	 * @throws FileNotFoundException If it can't be found.
	 */
	private static StyleFileLoader classpathLoader(String loc, String name) throws FileNotFoundException
	{
		String path = loc;
		if (name != null)
			path = loc + '/' + name + '/';

		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		if (classLoader == null)
			throw new FileNotFoundException("no classloader to find style");

		// all style files must be in the same directory or zip
		URL url = classLoader.getResource(path);
		if (url == null) {
			classLoader = StyleFileLoader.class.getClassLoader();
			url = classLoader.getResource(path);
			if (url == null)
				throw new FileNotFoundException("Could not find style " + path);
		}

		String proto = url.getProtocol().toLowerCase();
		if ("jar".equals(proto)) {
			log.debug("classpath loading from jar with url", url);
			return new JarFileLoader(url);
		} else if ("file".equals(proto)) {
			log.debug("classpath loading from directory", url.getPath());
			return new DirectoryFileLoader(new File(url.getPath()));
		}
		throw new FileNotFoundException("Could not load style from classpath");
	}
}
