/**
 *   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.lang.function;

import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.Skript;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.ParseContext;
import ch.njol.skript.lang.SkriptParser;
import ch.njol.skript.lang.Variable;
import ch.njol.skript.lang.VariableString;
import ch.njol.skript.lang.util.SimpleLiteral;
import ch.njol.skript.log.RetainingLogHandler;
import ch.njol.skript.log.SkriptLogger;
import ch.njol.skript.util.Utils;

public final class Parameter<T> {
	
	/**
	 * Name of this parameter. Will be used as name for the local variable
	 * that contains value of it inside function. This is always in lower case;
	 * variable names are case-insensitive.
	 */
	final String name;
	
	/**
	 * Type of the parameter.
	 */
	final ClassInfo<T> type;
	
	/**
	 * Expression that will provide default value of this parameter
	 * when the function is called.
	 */
	@Nullable
	final Expression<? extends T> def;
	
	/**
	 * Whether this parameter takes one or many values.
	 */
	final boolean single;
	
	@SuppressWarnings("null")
	public Parameter(final String name, final ClassInfo<T> type, final boolean single, final @Nullable Expression<? extends T> def) {
		this.name = name != null ? name.toLowerCase() : null;
		this.type = type;
		this.def = def;
		this.single = single;
	}
	
	public ClassInfo<T> getType() {
		return type;
	}
	
	@SuppressWarnings("unchecked")
	@Nullable
	public static <T> Parameter<T> newInstance(final String name, final ClassInfo<T> type, final boolean single, final @Nullable String def) {
		if (!Variable.isValidVariableName(name, true, false)) {
			Skript.error("An argument's name must be a valid variable name.");
			// ... because it will be made available as local variable
			return null;
		}
		Expression<? extends T> d = null;
		if (def != null) {
//			if (def.startsWith("%") && def.endsWith("%")) {
//				final RetainingLogHandler log = SkriptLogger.startRetainingLog();
//				try {
//					d = new SkriptParser("" + def.substring(1, def.length() - 1), SkriptParser.PARSE_EXPRESSIONS, ParseContext.FUNCTION_DEFAULT).parseExpression(type.getC());
//					if (d == null) {
//						log.printErrors("Can't understand this expression: " + def + "");
//						return null;
//					}
//					log.printLog();
//				} finally {
//					log.stop();
//				}
//			} else {
			final RetainingLogHandler log = SkriptLogger.startRetainingLog();
			
			// Parse the default value literal
			try {
				if (def.startsWith("\"") && def.endsWith("\"")) { // Quoted string; always parse as string
					// Don't ever parse strings as objects, it creates UnparsedLiterals (see #2353)
					d = (Expression<? extends T>) VariableString.newInstance("" + def.substring(1, def.length() - 1));
				} else if (type.getC().equals(String.class)) { // String return type requested
					/*
					 * For historical reasons, default values of string
					 * parameters needs not to be quoted. This is true even for
					 * strings with spaces, which is very confusing. We issue a
					 * warning for it now, and the behavior may be removed in a
					 * future release.
					 */
					if (def.startsWith("\"") && def.endsWith("\"")) {
						d = (Expression<? extends T>) VariableString.newInstance("" + def.substring(1, def.length() - 1));
					} else {
						// Usage of SimpleLiteral is also deprecated; not worth the risk to change it
						if (def.contains(" ")) // Warn about whitespace in unquoted string
							Skript.warning("'" + def + "' contains spaces and is unquoted, which is discouraged");
						d = (Expression<? extends T>) new SimpleLiteral<>(def, false);
					}
				} else {
					d = new SkriptParser(def, SkriptParser.PARSE_LITERALS, ParseContext.DEFAULT).parseExpression(type.getC());
				}
				if (d == null) {
					log.printErrors("'" + def + "' is not " + type.getName().withIndefiniteArticle());
					return null;
				}
				log.printLog();
			} finally {
				log.stop();
			}
//			}
		}
		return new Parameter<>(name, type, single, d);
	}
	
	public String getName() {
		return name;
	}
	
	@Override
	public String toString() {
		return name + ": " + Utils.toEnglishPlural(type.getCodeName(), !single) + (def != null ? " = " + def.toString(null, true) : "");
	}
	
}