package de.robingrether.idisguise.disguise;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.block.data.BlockData;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;

import de.robingrether.idisguise.iDisguise;
import de.robingrether.idisguise.management.VersionHelper;

/**
 * Represents a disguise as an area effect cloud.
 * 
 * @since 5.7.1
 * @author RobinGrether
 */
@SuppressWarnings("deprecation")
public class AreaEffectCloudDisguise extends ObjectDisguise {
	
	public static final Particle DEFAULT_PARTICLE = Particle.SPELL_MOB;
	public static final Map<Particle, Object> DEFAULT_PARAMETERS;
	
	private float radius;
	private Particle particle;
	private Object parameter;
	
	/**
	 * Creates an instance.<br>
	 * The defaults are: radius <code>0.5</code>, {@linkplain Particle#SPELL_MOB}, {@linkplain Color#GRAY}.
	 * 
	 * @since 5.7.1
	 */
	public AreaEffectCloudDisguise() {
		this(0.5f, DEFAULT_PARTICLE);
	}
	
	/**
	 * @since 5.7.1
	 * 
	 * @deprecated Replaced by {@linkplain #AreaEffectCloudDisguise(float, Particle, Object)}.
	 */
	@Deprecated
	public AreaEffectCloudDisguise(float radius, Color color, Particle particle) {
		this(radius, particle, color);
	}
	
	/**
	 * @since 5.8.1
	 */
	public AreaEffectCloudDisguise(float radius, Particle particle) {
		this(radius, particle, null);
	}
	
	/**
	 * @since 5.8.1
	 * @throws IllegalArgumentException if the given parameter is not valid
	 */
	public AreaEffectCloudDisguise(float radius, Particle particle, Object parameter) {
		super(DisguiseType.AREA_EFFECT_CLOUD);
		setRadius(radius);
		setParticle(particle, parameter);
	}
	
	/**
	 * @since 5.7.1
	 */
	public float getRadius() {
		return radius;
	}
	
	/**
	 * @param radius Radius must be positive and may not be larger than 20!
	 * @since 5.7.1
	 */
	public void setRadius(float radius) {
		if(radius <= 0) throw new IllegalArgumentException("Radius must be positive!");
		if(radius > 20) throw new IllegalArgumentException("Radius may not be larger than 20!");
		this.radius = radius;
	}
	
	/**
	 * @since 5.7.1
	 * 
	 * @deprecated Replaced by {@linkplain AreaEffectCloudDisguise#getParameter(Object)}.
	 */
	@Deprecated
	public Color getColor() {
		return parameter instanceof Color ? (Color)parameter : null;
	}
	
	/**
	 * @since 5.7.1
	 * 
	 * @deprecated Replaced by {@linkplain AreaEffectCloudDisguise#setParameter(Object)}.
	 */
	@Deprecated
	public void setColor(Color color) {
		setParameter(color);
	}
	
	/**
	 * @since 5.7.1
	 * 
	 * @deprecated Replaced by {@linkplain AreaEffectCloudDisguise#setParameter(Object)}.
	 */
	@Deprecated
	public void setColor(int rgb) {
		setParameter(Color.fromRGB(rgb));
	}
	
	/**
	 * @since 5.7.1
	 */
	public Particle getParticle() {
		return particle;
	}
	
	/**
	 * @since 5.7.1
	 */
	public void setParticle(Particle particle) {
		setParticle(particle, null);
	}
	
	/**
	 * @since 5.8.1
	 * @throws IllegalArgumentException if the given parameter is not valid
	 */
	public void setParticle(Particle particle, Object parameter) {
		Particle oldParticle = this.particle;
		this.particle = particle;
		try {
			setParameter(parameter);
		} catch(IllegalArgumentException|NullPointerException e) {
			this.particle = oldParticle;
			throw e;
		}
	}
	
	/**
	 * @since 5.8.1
	 */
	public Object getParameter() {
		return parameter;
	}
	
	/**
	 * @since 5.8.1
	 */
	public String getParameterStringRepresentation() {
		Class<?> parameterType = getParameterType(particle);
		if(parameterType.equals(Color.class)) {
			return Integer.toString(((Color)parameter).asRGB(), 16);
		} else if(parameterType.equals(ItemStack.class)) {
			return ((ItemStack)parameter).getType().name().toLowerCase(Locale.ENGLISH).replace('_', '-');
		} else if(parameterType.equals(MaterialData.class)) {
			return ((MaterialData)parameter).getItemType().name().toLowerCase(Locale.ENGLISH).replace('_', '-');
		} else if(VersionHelper.require1_13() && parameterType.equals(BlockData.class)) {
			return ((BlockData)parameter).getAsString();
		}
		
		return "";
	}
	
	/**
	 * @since 5.8.1
	 */
	public Class<?> getParameterType() {
		return getParameterType(particle);
	}
	
	/**
	 * @since 5.8.1
	 * @throws IllegalArgumentException if the given parameter is not valid
	 */
	public void setParameter(Object parameter) {
		if(parameter == null) parameter = DEFAULT_PARAMETERS.get(particle);
		Class<?> parameterType = getParameterType(particle);
		if(parameterType.equals(Void.class)) {
			if(parameter != null) {
				throw new IllegalArgumentException(String.format("Illegal argument: Particle %s does not support parameters", particle.name()));
			}
		} else {
			if(!parameterType.isInstance(parameter)) {
				throw new IllegalArgumentException(String.format("Illegal argument type: Particle %s requires %s", particle.name(), getParameterType(particle).getSimpleName()));
			}
		}
		this.parameter = parameter;
	}
	
	
	/**
	 * Match the particle parameter from its string representation.<br>
	 * This function is used to parse the command argument <em>parameter=...</em>
	 * 
	 * @since 5.8.1
	 * 
	 * @throws IllegalArgumentException If the parsed parameter is invalid or no value could be parsed.
	 */
	public void setParameterFromString(String parameter) {
		parameter = parameter.toLowerCase(Locale.ENGLISH).replace('-', '_');
		Class<?> parameterType = getParameterType(particle);
		if(parameterType.equals(Void.class)) {
			setParameter(null);
			return;
		} else if(parameterType.equals(Color.class)) {
			if(parameter.isEmpty()) {
				setParameter(null);
				return;
			} else if(DEFAULT_COLORS.containsKey(parameter)) {
				setParameter(DEFAULT_COLORS.get(parameter));
				return;
			} else {
				try {
					setParameter(Color.fromRGB(Integer.parseInt(parameter, 16)));
					return;
				} catch(NumberFormatException e) {
					throw new IllegalArgumentException("Invalid color: expected hexadecimal RGB format");
				}
			}
		} else if(parameterType.equals(ItemStack.class)) {
			if(Material.matchMaterial(parameter) != null) {
				setParameter(new ItemStack(Material.matchMaterial(parameter)));
				return;
			}
		} else if(parameterType.equals(MaterialData.class)) {
			if(Material.matchMaterial(parameter) != null) {
				setParameter(new MaterialData(Material.matchMaterial(parameter)));
				return;
			}
		} else if(VersionHelper.require1_13() && parameterType.equals(BlockData.class)) {
			if(Material.matchMaterial(parameter) != null) {
				setParameter(Material.matchMaterial(parameter).createBlockData());
				return;
			} else {
				try {
					setParameter(Bukkit.createBlockData(parameter));
					return;
				} catch(IllegalArgumentException e) { // fail silently
				}
			}
		}
		
		throw new IllegalArgumentException("Unknown parameter!");
	}
	
	/**
	 * This map holds the default values of {@linkplain org.bukkit.Color} mapped to their field names (lower case).
	 * 
	 * @since 5.8.1
	 */
	public static final Map<String, Color> DEFAULT_COLORS;
	
	/**
	 * {@inheritDoc}
	 */
	public String toString() {
		return String.format("%s; radius=%s; particle=%s; parameter=%s", super.toString(), radius, particle.name().toLowerCase(Locale.ENGLISH).replace('_', '-'), getParameterStringRepresentation());
	}
	
	static {
		Map<String, Color> defaultColors2 = new HashMap<String, Color>();
		try {
			for(Field field : Color.class.getFields()) {
				if(Modifier.isStatic(field.getModifiers()) && field.getType().equals(Color.class)) {
					defaultColors2.put(field.getName().toLowerCase(Locale.ENGLISH), (Color)field.get(null));
				}
			}
		} catch(IllegalAccessException e) {
			iDisguise.getInstance().getLogger().log(Level.SEVERE, "An unexpected exception occured.", e);
		}
		DEFAULT_COLORS = Collections.unmodifiableMap(defaultColors2);
		
		Subtypes.registerParameterizedSubtype(AreaEffectCloudDisguise.class, (disguise, parameter) -> disguise.setRadius(Float.parseFloat(parameter)), "radius",
				new HashSet<String>(Arrays.asList("0.1", "0.5", "1.0", "2.0", "5.0", "10.0", "20.0")));
		
		Set<String> parameterSuggestions = new HashSet<String>();
		Map<Particle, Object> localMap = new HashMap<Particle, Object>(Particle.values().length);
		for(Particle particle : Particle.values()) {
			parameterSuggestions.add(particle.name().toLowerCase(Locale.ENGLISH).replace('_', '-'));
			Class<?> parameterType = getParameterType(particle);
			if(parameterType.equals(Void.class)) {
				localMap.put(particle, null);
			} else if(parameterType.equals(Color.class)) {
				localMap.put(particle, Color.GRAY);
			} else if(parameterType.equals(ItemStack.class)) {
				localMap.put(particle, new ItemStack(Material.STONE));
			} else if(parameterType.equals(MaterialData.class)) {
				localMap.put(particle, new MaterialData(Material.STONE));
			} else if(VersionHelper.require1_13() && parameterType.equals(BlockData.class)) {
				localMap.put(particle, Material.STONE.createBlockData());
			}
		}
		Subtypes.registerParameterizedSubtype(AreaEffectCloudDisguise.class, (disguise, parameter) -> disguise.setParticle(Particle.valueOf(parameter.toUpperCase(Locale.ENGLISH).replace('-', '_'))), "particle", parameterSuggestions);
		DEFAULT_PARAMETERS = Collections.unmodifiableMap(localMap);
		
//		Set<String> colors = new HashSet<String>(defaultColors.keySet());
//		colors.addAll(Arrays.asList("16711680", "65280", "255"));
		Subtypes.registerParameterizedSubtype(AreaEffectCloudDisguise.class, (disguise, parameter) -> disguise.setParameterFromString(parameter), "parameter");
		
	}
	
	/**
	 * @since 5.8.1
	 */
	public static Class<?> getParameterType(Particle particle) {
		if(particle.equals(Particle.SPELL_MOB)) return Color.class;
		else if(VersionHelper.require1_13() && particle.equals(Particle.REDSTONE)) return Color.class;
		else return particle.getDataType();
	}
	
}