/*
 *   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-2014 Peter Güttinger
 * 
 */

package ch.njol.skript.classes.data;

import java.io.StreamCorruptedException;

import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.SkriptConfig;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.classes.NumberArithmetic;
import ch.njol.skript.classes.Parser;
import ch.njol.skript.classes.Serializer;
import ch.njol.skript.lang.ParseContext;
import ch.njol.skript.lang.VariableString;
import ch.njol.skript.lang.util.SimpleLiteral;
import ch.njol.skript.localization.Message;
import ch.njol.skript.localization.RegexMessage;
import ch.njol.skript.registrations.Classes;
import ch.njol.skript.util.Utils;
import ch.njol.util.StringUtils;
import ch.njol.yggdrasil.Fields;

/**
 * @author Peter Güttinger
 */
public class JavaClasses {
	public JavaClasses() {}
	
	public final static int VARIABLENAME_NUMBERACCURACY = 8;
	
	static {
		Classes.registerClass(new ClassInfo<Object>(Object.class, "object")
				.user("objects?")
				.name("Object")
				.description("The supertype of all types, meaning that if %object% is used in e.g. a condition it will accept all kinds of expressions.")
				.usage("")
				.examples("")
				.since("1.0"));
		
		Classes.registerClass(new ClassInfo<Number>(Number.class, "number")
				.user("num(ber)?s?")
				.name("Number")
				.description("A number, e.g. 2.5, 3, or -9812454.",
						"Please note that many expressions only need integers, i.e. will discard any frational parts of any numbers without producing an error.")
				.usage("<code>[-]###[.###]</code> (any amount of digits; very large numbers will be truncated though)")
				.examples("set the player's health to 5.5",
						"set {_temp} to 2*{_temp} - 2.5")
				.since("1.0")
				// is registered after all other number classes
				.defaultExpression(new SimpleLiteral<Number>(1, true))
				.parser(new Parser<Number>() {
					@Override
					@Nullable
					public Number parse(final String s, final ParseContext context) {
						try {
							return Long.valueOf(s);
						} catch (final NumberFormatException e) {}
						try {
							return s.endsWith("%") ? Double.parseDouble(s.substring(0, s.length() - 1)) / 100 : Double.parseDouble(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public String toString(final Number n, final int flags) {
						return StringUtils.toString(n.doubleValue(), SkriptConfig.numberAccuracy.value());
					}
					
					@Override
					public String toVariableNameString(final Number n) {
						return StringUtils.toString(n.doubleValue(), VARIABLENAME_NUMBERACCURACY);
					}
					
					@Override
					public String getVariableNamePattern() {
						return "-?\\d+(\\.\\d+)?";
					}
				}).serializer(new Serializer<Number>() {
					@Override
					public Fields serialize(final Number n) {
						throw new IllegalStateException(); // serialised natively by Yggdrasil
					}
					
					@Override
					public boolean canBeInstantiated() {
						return true;
					}
					
					@Override
					public void deserialize(final Number o, final Fields f) throws StreamCorruptedException {
						assert false;
					}
					
//						return "" + n;
					@Override
					@Nullable
					public Number deserialize(final String s) {
						try {
							return Integer.valueOf(s);
						} catch (final NumberFormatException e) {}
						try {
							return Double.valueOf(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public boolean mustSyncDeserialization() {
						return false;
					}
				}).math(Number.class, new NumberArithmetic()));
		
		Classes.registerClass(new ClassInfo<Long>(Long.class, "long")
				.user("int(eger)?s?")
				.name(ClassInfo.NO_DOC)
				.before("integer", "short", "byte")
				.defaultExpression(new SimpleLiteral<Long>((long) 1, true))
				.parser(new Parser<Long>() {
					@Override
					@Nullable
					public Long parse(final String s, final ParseContext context) {
						try {
							return Long.valueOf(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public String toString(final Long l, final int flags) {
						return "" + l;
					}
					
					@Override
					public String toVariableNameString(final Long l) {
						return "" + l;
					}
					
					@Override
					public String getVariableNamePattern() {
						return "-?\\d+";
					}
				}).serializer(new Serializer<Long>() {
					@Override
					public Fields serialize(final Long n) {
						throw new IllegalStateException(); // serialised natively by Yggdrasil
					}
					
					@Override
					public boolean canBeInstantiated() {
						return true;
					}
					
					@Override
					public void deserialize(final Long o, final Fields f) throws StreamCorruptedException {
						assert false;
					}
					
//						return "" + l;
					@Override
					@Nullable
					public Long deserialize(final String s) {
						try {
							return Long.parseLong(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public boolean mustSyncDeserialization() {
						return false;
					}
				}).math(Number.class, new NumberArithmetic()));
		
		Classes.registerClass(new ClassInfo<Integer>(Integer.class, "integer")
				.name(ClassInfo.NO_DOC)
				.defaultExpression(new SimpleLiteral<Integer>(1, true))
				.parser(new Parser<Integer>() {
					@Override
					@Nullable
					public Integer parse(final String s, final ParseContext context) {
						try {
							return Integer.valueOf(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public String toString(final Integer i, final int flags) {
						return "" + i;
					}
					
					@Override
					public String toVariableNameString(final Integer i) {
						return "" + i;
					}
					
					@Override
					public String getVariableNamePattern() {
						return "-?\\d+";
					}
				}).serializer(new Serializer<Integer>() {
					@Override
					public Fields serialize(final Integer n) {
						throw new IllegalStateException(); // serialised natively by Yggdrasil
					}
					
					@Override
					public boolean canBeInstantiated() {
						return true;
					}
					
					@Override
					public void deserialize(final Integer o, final Fields f) throws StreamCorruptedException {
						assert false;
					}
					
//						return "" + i;
					@Override
					@Nullable
					public Integer deserialize(final String s) {
						try {
							return Integer.parseInt(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public boolean mustSyncDeserialization() {
						return false;
					}
				}).math(Number.class, new NumberArithmetic()));
		
		Classes.registerClass(new ClassInfo<Double>(Double.class, "double")
				.name(ClassInfo.NO_DOC)
				.defaultExpression(new SimpleLiteral<Double>(1., true))
				.after("long")
				.before("float", "integer", "short", "byte")
				.parser(new Parser<Double>() {
					@Override
					@Nullable
					public Double parse(final String s, final ParseContext context) {
						try {
							return s.endsWith("%") ? Double.parseDouble(s.substring(0, s.length() - 1)) / 100 : Double.parseDouble(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public String toString(final Double d, final int flags) {
						return StringUtils.toString(d, SkriptConfig.numberAccuracy.value());
					}
					
					@Override
					public String toVariableNameString(final Double d) {
						return StringUtils.toString(d.doubleValue(), VARIABLENAME_NUMBERACCURACY);
					}
					
					@Override
					public String getVariableNamePattern() {
						return "-?\\d+(\\.\\d+)?";
					}
				}).serializer(new Serializer<Double>() {
					@Override
					public Fields serialize(final Double n) {
						throw new IllegalStateException(); // serialised natively by Yggdrasil
					}
					
					@Override
					public boolean canBeInstantiated() {
						return true;
					}
					
					@Override
					public void deserialize(final Double o, final Fields f) throws StreamCorruptedException {
						assert false;
					}
					
//						return "" + d;
					@Override
					@Nullable
					public Double deserialize(final String s) {
						try {
							return Double.parseDouble(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public boolean mustSyncDeserialization() {
						return false;
					}
				}).math(Number.class, new NumberArithmetic()));
		
		Classes.registerClass(new ClassInfo<Float>(Float.class, "float")
				.name(ClassInfo.NO_DOC)
				.defaultExpression(new SimpleLiteral<Float>(1f, true))
				.parser(new Parser<Float>() {
					@Override
					@Nullable
					public Float parse(final String s, final ParseContext context) {
						try {
							return s.endsWith("%") ? Float.parseFloat(s.substring(0, s.length() - 1)) / 100 : Float.parseFloat(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public String toString(final Float f, final int flags) {
						return StringUtils.toString(f, SkriptConfig.numberAccuracy.value());
					}
					
					@Override
					public String toVariableNameString(final Float f) {
						return StringUtils.toString(f.doubleValue(), VARIABLENAME_NUMBERACCURACY);
					}
					
					@Override
					public String getVariableNamePattern() {
						return "-?\\d+(\\.\\d+)?";
					}
				}).serializer(new Serializer<Float>() {
					@Override
					public Fields serialize(final Float n) {
						throw new IllegalStateException(); // serialised natively by Yggdrasil
					}
					
					@Override
					public boolean canBeInstantiated() {
						return true;
					}
					
					@Override
					public void deserialize(final Float o, final Fields f) throws StreamCorruptedException {
						assert false;
					}
					
//						return "" + f;
					@Override
					@Nullable
					public Float deserialize(final String s) {
						try {
							return Float.parseFloat(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public boolean mustSyncDeserialization() {
						return false;
					}
				}).math(Number.class, new NumberArithmetic()));
		
		Classes.registerClass(new ClassInfo<Boolean>(Boolean.class, "boolean")
				.user("booleans?")
				.name("Boolean")
				.description("A boolean is a value that is either true or false. Other accepted names are 'on' and 'yes' for true, and 'off' and 'no' for false.")
				.usage("true/yes/on or false/no/off")
				.examples("set {config.%player%.use mod} to false")
				.since("1.0")
				.parser(new Parser<Boolean>() {
					private final RegexMessage truePattern = new RegexMessage("boolean.true.pattern");
					private final RegexMessage falsePattern = new RegexMessage("boolean.false.pattern");
					
					@Override
					@Nullable
					public Boolean parse(final String s, final ParseContext context) {
						if (truePattern.matcher(s).matches())
							return Boolean.TRUE;
						if (falsePattern.matcher(s).matches())
							return Boolean.FALSE;
						return null;
					}
					
					private final Message trueName = new Message("boolean.true.name");
					private final Message falseName = new Message("boolean.false.name");
					
					@Override
					public String toString(final Boolean b, final int flags) {
						return b ? trueName.toString() : falseName.toString();
					}
					
					@Override
					public String toVariableNameString(final Boolean b) {
						return "" + b;
					}
					
					@Override
					public String getVariableNamePattern() {
						return "(true|false)";
					}
				}).serializer(new Serializer<Boolean>() {
					@Override
					public Fields serialize(final Boolean n) {
						throw new IllegalStateException(); // serialised natively by Yggdrasil
					}
					
					@Override
					public boolean canBeInstantiated() {
						return true;
					}
					
					@Override
					public void deserialize(final Boolean o, final Fields f) throws StreamCorruptedException {
						assert false;
					}
					
//						return "" + b;
					@Override
					@Nullable
					public Boolean deserialize(final String s) {
						if (s.equals("true"))
							return Boolean.TRUE;
						if (s.equals("false"))
							return Boolean.FALSE;
						return null;
					}
					
					@Override
					public boolean mustSyncDeserialization() {
						return false;
					}
				}));
		
		Classes.registerClass(new ClassInfo<Short>(Short.class, "short")
				.name(ClassInfo.NO_DOC)
				.defaultExpression(new SimpleLiteral<Short>((short) 1, true))
				.parser(new Parser<Short>() {
					@Override
					@Nullable
					public Short parse(final String s, final ParseContext context) {
						try {
							return Short.valueOf(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public String toString(final Short s, final int flags) {
						return "" + s;
					}
					
					@Override
					public String toVariableNameString(final Short s) {
						return "" + s;
					}
					
					@Override
					public String getVariableNamePattern() {
						return "-?\\d+";
					}
				}).serializer(new Serializer<Short>() {
					@Override
					public Fields serialize(final Short n) {
						throw new IllegalStateException(); // serialised natively by Yggdrasil
					}
					
					@Override
					public boolean canBeInstantiated() {
						return true;
					}
					
					@Override
					public void deserialize(final Short o, final Fields f) throws StreamCorruptedException {
						assert false;
					}
					
//						return "" + s;
					@Override
					@Nullable
					public Short deserialize(final String s) {
						try {
							return Short.parseShort(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public boolean mustSyncDeserialization() {
						return false;
					}
				}).math(Number.class, new NumberArithmetic()));
		
		Classes.registerClass(new ClassInfo<Byte>(Byte.class, "byte")
				.name(ClassInfo.NO_DOC)
				.defaultExpression(new SimpleLiteral<Byte>((byte) 1, true))
				.parser(new Parser<Byte>() {
					@Override
					@Nullable
					public Byte parse(final String s, final ParseContext context) {
						try {
							return Byte.valueOf(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public String toString(final Byte b, final int flags) {
						return "" + b;
					}
					
					@Override
					public String toVariableNameString(final Byte b) {
						return "" + b;
					}
					
					@Override
					public String getVariableNamePattern() {
						return "-?\\d+";
					}
				}).serializer(new Serializer<Byte>() {
					@Override
					public Fields serialize(final Byte n) {
						throw new IllegalStateException(); // serialised natively by Yggdrasil
					}
					
					@Override
					public boolean canBeInstantiated() {
						return true;
					}
					
					@Override
					public void deserialize(final Byte o, final Fields f) throws StreamCorruptedException {
						assert false;
					}
					
//						return "" + b;
					@Override
					@Nullable
					public Byte deserialize(final String s) {
						try {
							return Byte.parseByte(s);
						} catch (final NumberFormatException e) {
							return null;
						}
					}
					
					@Override
					public boolean mustSyncDeserialization() {
						return false;
					}
				}).math(Number.class, new NumberArithmetic()));
		
		Classes.registerClass(new ClassInfo<String>(String.class, "string")
				.user("(text|string)s?")
				.name("Text")
				.description("Text is simply text, i.e. a sequence of characters, which can optionally contain expressions which will be replaced with a meaningful representation " +
						"(e.g. %player% will be replaced with the player's name).",
						"Because scripts are also text, you have to put text into double quotes to tell Skript which part of the line is an effect/expression and which part is the text.",
						"Please read the article on <a href='../strings/'>Texts and Variable Names</a> to learn more.")
				.usage("simple: <code>\"...\"</code>",
						"quotes: <code>\"...\"\"...\"</code>",
						"expressions: <code>\"...%expression%...\"</code>",
						"percent signs: <code>\"...%%...\"</code>")
				.examples("broadcast \"Hello World!\"",
						"message \"Hello %player%\"",
						"message \"The id of \"\"%type of tool%\"\" is %id of tool%.\"")
				.since("1.0")
				.parser(new Parser<String>() {
					@Override
					@Nullable
					public String parse(final String s, final ParseContext context) {
						switch (context) {
							case DEFAULT: // in DEFAULT, parsing is handled by VariableString
								assert false;
								return null;
							case CONFIG: // duh
								return s;
							case SCRIPT:
							case EVENT:
								if (VariableString.isQuotedCorrectly(s, true))
									return Utils.replaceChatStyles("" + s.substring(1, s.length() - 1).replace("\"\"", "\""));
								return null;
							case COMMAND:
								return s;
						}
						assert false;
						return null;
					}
					
					@Override
					public boolean canParse(final ParseContext context) {
						if (context == ParseContext.DEFAULT)
							return false;
						return true;
					}
					
					@Override
					public String toString(final String s, final int flags) {
						return s;
					}
					
					@Override
					public String getDebugMessage(final String s) {
						return '"' + s + '"';
					}
					
					@Override
					public String toVariableNameString(final String s) {
						return s;
					}
					
					@Override
					public String getVariableNamePattern() {
						return ".*";
					}
				}).serializer(new Serializer<String>() {
					@Override
					public Fields serialize(final String n) {
						throw new IllegalStateException(); // serialised natively by Yggdrasil
					}
					
					@Override
					public boolean canBeInstantiated() {
						return true;
					}
					
					@Override
					public void deserialize(final String o, final Fields f) throws StreamCorruptedException {
						assert false;
					}
					
//						return s;
					@Override
					public String deserialize(final String s) {
						return s;
					}
					
					@Override
					public boolean mustSyncDeserialization() {
						return false;
					}
				}));
	}
}
