package net.returnvoid.io;

import java.util.ArrayList;

import net.returnvoid.color.ColorPalette;
import net.returnvoid.tools.RMath;
import net.returnvoid.tools.StringTools;
import processing.core.PApplet;

/*
 * This code is copyright (c) Diana Lange 2017
 *
 * The library is published under the Creative Commons license NonCommercial 4.0.
 * Please check https://creativecommons.org/licenses/by-nc/4.0/ for more information.
 * 
 * This program is distributed in the hope that it will be useful, but without any warranty.
 */

/**
 * ColorPaletteLoader will handle to load files from type ".color.json".
 * Currently doesn't support the automatic loading of files within sub-folders.
 * The files are loaded "on demand" as they are tried to be read with the
 * getCurrent() or any other get method. The class will handle to run through
 * all files of the given type via methods (e.g. next()) or on a KeyEvent
 * (keyPressed with a pre-defined / defined key / keycode). The ".color.json"
 * files should be constructed as follows:
 * 
 * <pre>
 * {
 *   "name" : "String",
 *   "tags" : ["String1","String2",...,"StringN"],
 *   "colors" : [
 *      {
 *        "hexString" : {
 *           "r" : "float",
 *           "g" : "float",
 *           "b" : "float"}
 *           "a" : "float",
 *           "importance" : "int"
 *        }
 *      }, ...]
 * }
 * </pre>
 * 
 * On default the keyPressed event with following keys are associated with
 * following methods: <br>
 * Arrow left : previous() - previous ColorPalette <br>
 * Arrow right : next() - next ColorPalette<br>
 * Arrow up : first() - first ColorPalette in directory<br>
 * Arrow down: last() - last ColorPalette in directory
 * <br>
 * Tab : nextRandom() - random ColorPalette in directory <br>
 * <br>
 * Use disableKeys() to disable these automatic set key events.<br>
 * <br>
 * You can add ColorPaletteUpdateObservers. These observers will be informed
 * when there is a change of the current color palette. (e.g. after every next()
 * method call).<br>
 * 
 * 
 * @author Diana Lange
 *
 */
public class ColorPaletteLoader extends DataLoader {

	/**
	 * The ColorPalette array with all the loaded palettes. The entries will be
	 * null, if the ColorPalette wasn't loaded yet. Each ColorPalette will be
	 * loaded on demand as they are tried to get with any of the get methods,
	 * e.g. getCurrent(), get(int).
	 */
	private ColorPalette[] palettes;

	/**
	 * All objects that are interested in a change of the current ColorPalette.
	 */
	private ArrayList<ColorPaletteUpdateObserver> observers;

	/**
	 * Builds the ColorPaletteLoader and registers the KeyEvent to the parent
	 * (PApplet). The files have to be located in a sub-folder named "data" in
	 * the current sketch folder. The "data" folder should at least contain one
	 * ".color.json" file.
	 * 
	 * @param parent
	 *            A PApplet object.
	 */
	public ColorPaletteLoader(PApplet parent) {
		this(parent, null, "data");
	}

	/**
	 * Builds the ColorPaletteLoader and registers the KeyEvent to the parent
	 * (PApplet).
	 * 
	 * @param parent
	 *            A PApplet object.
	 * @param folderName
	 *            Folder containing files (not null). This folder should at
	 *            least contain one .color.json file. The given folder should be
	 *            a sub-folder of the current sketch directory or a relative
	 *            path to the sketch path. e.g. "..\\data" is a folder which is
	 *            located in the parent directory of the sketch folder. Make
	 *            sure to escape the backslashes in the path in the shown way.
	 */
	public ColorPaletteLoader(PApplet parent, String folderName) {
		this(parent, null, folderName);
	}

	/**
	 * Builds the ColorPaletteLoader and registers the KeyEvent to the parent
	 * (PApplet).
	 * 
	 * @param parent
	 *            A PApplet object.
	 * @param folderPath
	 *            A absolute path to the folder. Example:
	 *            "C:\\" is the folderPath and "data
	 *            " is the foldername then the files of the folder located at "
	 *            C:\\data\\" will be loaded. Make sure to escape the
	 *            backslashes in the path in the shown way.If folderPath is
	 *            null, the sketch location will be used to set this path.
	 * @param folderName
	 *            Folder containing files (not null). This folder should at
	 *            least contain one .color.json file. The given folder should be
	 *            a sub-folder of folderpath or a relative path to folderPath.
	 *            e.g. "C:\\data" is the folderpath and "..\\temp" is the
	 *            foldername then "C:\\temp\\" will be the location where the
	 *            files will be loaded. Make sure to escape the backslashes in
	 *            the path in the shown way.
	 */
	public ColorPaletteLoader(PApplet parent, String folderPath, String folderName) {
		super(parent, folderPath, folderName);
		observers = new ArrayList<ColorPaletteUpdateObserver>();
		this.type = DataLoader.COLOR_PALETTE;
		this.filePaths = loadPaths(this.type);
		this.palettes = new ColorPalette[filePaths.length];
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#update()
	 */
	public ColorPaletteLoader update() {
		super.update();
		this.fileIndex = 0;
		this.filePaths = loadPaths(this.type);
		this.palettes = new ColorPalette[filePaths.length];
		return this;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#resetKeySettings()
	 */
	public ColorPaletteLoader resetKeySettings() {
		return (ColorPaletteLoader) super.resetKeySettings();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#disableKeys()
	 */
	public ColorPaletteLoader disableKeys() {
		return (ColorPaletteLoader) super.disableKeys();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#enableKeys()
	 */
	public ColorPaletteLoader enableKeys() {
		return (ColorPaletteLoader) super.enableKeys();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#setKey(java.lang.String, int)
	 */
	public DataLoader setKey(String which, int keyCode) {
		return (ColorPaletteLoader) super.setKey(which, keyCode);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#setKey(java.lang.String, char)
	 */
	public ColorPaletteLoader setKey(String which, char key) {
		return (ColorPaletteLoader) super.setKey(which, key);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#loadFile(int)
	 */
	@Override
	public ColorPalette loadFile(int index) {
		index = RMath.constrain(index, 0, filePaths.length);

		String json = StringTools.join(parent.loadStrings(filePaths[index]), " ");

		ColorPalette palette = new ColorPalette(json);

		palettes[index] = palette;

		return palette;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#loadAllFiles()
	 */
	@Override
	public ColorPalette[] loadAllFiles() {

		for (int i = 0; i < palettes.length; i++) {

			if (palettes[i] == null) {
				loadFile(i);
			}
		}

		return palettes;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#nextRandom()
	 */
	@Override
	public ColorPalette nextRandom() {

		if (filePaths.length == 0) {
			return null;
		} else {
			fileIndex = RMath.random(0, filePaths.length);
			if (palettes[fileIndex] == null) {
				loadFile(fileIndex);
			}

			informObservers();

			return palettes[fileIndex];
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#previous()
	 */
	@Override
	public ColorPalette previous() {
		if (filePaths.length == 0) {
			return null;
		} else {
			fileIndex = --fileIndex >= 0 ? fileIndex : filePaths.length - 1;
			if (palettes[fileIndex] == null) {
				loadFile(fileIndex);
			}

			informObservers();

			return palettes[fileIndex];
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#next()
	 */
	@Override
	public ColorPalette next() {
		if (filePaths.length == 0) {
			return null;
		} else {
			fileIndex = ++fileIndex < filePaths.length ? fileIndex : 0;
			if (palettes[fileIndex] == null) {
				loadFile(fileIndex);
			}

			informObservers();

			return palettes[fileIndex];
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#first()
	 */
	@Override
	public ColorPalette first() {
		if (filePaths.length == 0) {
			return null;
		} else {
			fileIndex = 0;
			if (palettes[fileIndex] == null) {
				loadFile(fileIndex);
			}

			informObservers();

			return palettes[fileIndex];
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#last()
	 */
	@Override
	public ColorPalette last() {
		if (filePaths.length == 0) {
			return null;
		} else {
			fileIndex = filePaths.length - 1;
			if (palettes[fileIndex] == null) {
				loadFile(fileIndex);
			}

			informObservers();

			return palettes[fileIndex];
		}
	}
	
	/**
	 * Sets the current element.
	 * 
	 * @param index
	 *            The index of the element which should be the current element.
	 *            Index is in range of [0, size()).
	 * @return The current ImageLoader object.
	 */
	public ColorPaletteLoader setIndex(int index) {
		super.setIndex(index);
		this.informObservers();
		return this;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#last()
	 */
	@Override
	public ColorPaletteLoader setIndex(String filename) {
		super.setIndex(filename);
		this.informObservers();
		return this;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#getCurrent()
	 */
	@Override
	public ColorPalette getCurrent() {
		if (palettes[fileIndex] == null) {
			loadFile(fileIndex);
		}
		return palettes[fileIndex];
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.io.DataLoader#get(int)
	 */
	@Override
	public ColorPalette get(int index) {
		index = RMath.constrain(index, 0, filePaths.length);

		if (palettes[index] == null) {
			loadFile(index);
		}
		return palettes[index];
	}

	/**
	 * Add an object that wants to be informed when there is a change of the
	 * current color palette.
	 * 
	 * @param observer
	 *            An object that will be informed at a change of the current
	 *            color palette.
	 * @return The ColorPaletteLoader object.
	 */
	public ColorPaletteLoader addObserver(ColorPaletteUpdateObserver observer) {
		observers.add(observer);
		return this;
	}

	/**
	 * Remove an observer from this loader.
	 * 
	 * @param observer
	 *            An object that was formerly an observer and which should be
	 *            removed.
	 * @return The ColorPaletteLoader object.
	 */
	public ColorPaletteLoader removeObserver(ColorPaletteUpdateObserver observer) {
		observers.remove(observer);
		return this;
	}

	/**
	 * Remove all observers from this loader.
	 * 
	 * @return The ColorPaletteLoader object.
	 */
	public ColorPaletteLoader clearObservers() {
		observers.clear();
		return this;
	}

	/**
	 * Inform all observers when there is a change of the current color palette.
	 * This method should be called after fileIndex has changed and the file has
	 * been loaded successfully.
	 */
	private void informObservers() {
		for (ColorPaletteUpdateObserver o : observers) {
			o.setColorPalette(getCurrent());
		}
	}
}