/**
 *   This file is part of Skript.
 *
 *  Skript 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  Skript 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 Skript.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Copyright 2011-2017 Peter Güttinger and contributors
 */
package ch.njol.skript.config;

import java.util.Locale;

import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.Skript;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.classes.Converter;
import ch.njol.skript.classes.Parser;
import ch.njol.skript.lang.ParseContext;
import ch.njol.skript.registrations.Classes;
import ch.njol.util.Setter;

/**
 * @author Peter Güttinger
 */
public class Option<T> {
	
	public final String key;
	private boolean optional = false;
	
	@Nullable
	private String value = null;
	private final Converter<String, ? extends T> parser;
	private final T defaultValue;
	private T parsedValue;
	
	@Nullable
	private Setter<? super T> setter;
	
	public Option(final String key, final T defaultValue) {
		this.key = "" + key.toLowerCase(Locale.ENGLISH);
		this.defaultValue = defaultValue;
		parsedValue = defaultValue;
		@SuppressWarnings("unchecked")
		final Class<T> c = (Class<T>) defaultValue.getClass();
		if (c == String.class) {
			parser = new Converter<String, T>() {
				@SuppressWarnings("unchecked")
				@Override
				public T convert(final String s) {
					return (T) s;
				}
			};
		} else {
			final ClassInfo<T> ci = Classes.getExactClassInfo(c);
			final Parser<? extends T> p;
			if (ci == null || (p = ci.getParser()) == null)
				throw new IllegalArgumentException(c.getName());
			this.parser = new Converter<String, T>() {
				@Override
				@Nullable
				public T convert(final String s) {
					final T t = p.parse(s, ParseContext.CONFIG);
					if (t != null)
						return t;
					Skript.error("'" + s + "' is not " + ci.getName().withIndefiniteArticle());
					return null;
				}
			};
		}
	}
	
	public Option(final String key, final T defaultValue, final Converter<String, ? extends T> parser) {
		this.key = "" + key.toLowerCase(Locale.ENGLISH);
		this.defaultValue = defaultValue;
		parsedValue = defaultValue;
		this.parser = parser;
	}
	
	public final Option<T> setter(final Setter<? super T> setter) {
		this.setter = setter;
		return this;
	}
	
	public final Option<T> optional(final boolean optional) {
		this.optional = optional;
		return this;
	}
	
	public final void set(final Config config, final String path) {
		final String oldValue = value;
		value = config.getByPath(path + key);
		if (value == null && !optional)
			Skript.error("Required entry '" + path + key + "' is missing in " + config.getFileName() + ". Please make sure that you have the latest version of the config.");
		if ((value == null ^ oldValue == null) || value != null && !value.equals(oldValue)) {
			T parsedValue = value != null ? parser.convert(value) : defaultValue;
			if (parsedValue == null)
				parsedValue = defaultValue;
			this.parsedValue = parsedValue;
			onValueChange();
		}
	}
	
	protected void onValueChange() {
		if (setter != null)
			setter.set(parsedValue);
	}
	
	public final T value() {
		return parsedValue;
	}
	
	public final boolean isOptional() {
		return optional;
	}
	
}
