/*******************************************************************************
 * Copyright (c) 2000, 2008 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.animation;

import java.lang.reflect.*;

import org.eclipse.swt.*;
import org.eclipse.swt.effects.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.wpf.*;
import org.eclipse.swt.widgets.*;

public class PropertyAnimation extends Animation {
	Object from, to;
	long duration;
	String name, property;
	Object target;
	Method method;
	Class paramType;
	int animatorHandle, customAnimation;

	Object nextValue;
	IInterpolator interpolator;
		
	void create() {
		if (target == null || property == null) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		if (handle == 0) {
			super.create();
			setParamType();
			createAnimation();
			register(widget);
		}
		updateFromToValues();
	}

	void createCustomAnimation() {
		customAnimation = OS.gcnew_SWTAnimation(jniRef);
		int children = OS.TimelineGroup_Children(handle);
		OS.IList_Add(children, customAnimation);
		OS.GCHandle_Free(children);
		setTargetProperty (customAnimation, property);
	}
	
	void createDoubleAnimation() {
	}

	void createIntegerAnimation() {
	}
	
	void createRectangleAnimation() {
	}
	
	void createPointAnimation() {
	}
	
	void OnPropertyChanged(int object, int args) {
		try {
			if (nextValue != null) {
				method.invoke(target, new Object[] {nextValue});
				return;
			}
			if (paramType == Double.TYPE
					|| paramType == Color.class
					|| paramType == Transform.class) {
				double newValue = OS.DependencyPropertyChangedEventArgs_NewValueDouble(args);
				Object methodArg;
				if (paramType == Transform.class) {
					Transform transform = getTransform(newValue);
					methodArg = transform;
				} else if (paramType == Color.class) {
					Color color = getColor(newValue);
					methodArg = color;
				} else {
					methodArg = new Double(newValue);
				}
				method.invoke(target, new Object[] {methodArg});
				if (paramType == Transform.class) {
					((Transform)methodArg).dispose();
				}
				return;
			} 
			if (paramType == Integer.TYPE) {
				int newValue = OS.DependencyPropertyChangedEventArgs_NewValueInt(args);
				method.invoke(target, new Object[] {new Integer(newValue)});
				return;
			} 
			throw new RuntimeException(paramType + " not supported yet");
		} catch (IllegalArgumentException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		}
	}
	
	Color getColor(double newValue) {
		if (!(from instanceof Color && to instanceof Color)) return null;
		Color colorFrom = (Color)from;
		Color colorTo = (Color)to;
		RGB start = colorFrom.getRGB();
		RGB end = colorTo.getRGB();
		int red = (int) (((end.red-start.red)*newValue) + start.red);
		int green = (int) (((end.green-start.green)*newValue) + start.green);
		int blue = (int) (((end.blue-start.blue)*newValue) + start.blue);
		RGB current = new RGB(red, green, blue);
		return new Color(colorFrom.getDevice(), current);
	}

	void GetCurrentValueCore(double currentTime) {
		if (interpolator != null) {
			try {
				nextValue = interpolator.getCurrentValue(from, to, (long)currentTime, duration);
				return;
			} catch (RuntimeException e) {
				e.printStackTrace();
			}
		}
		nextValue = null;
	}
	
	public long getDuration() {
		return duration;
	}
	
	Transform getTransform(double newValue) {
		if (!(from instanceof Transform && to instanceof Transform)) return null;
		float[] transformFrom = new float[6];
		float[] transformTo = new float[6];
		((Transform)from).getElements(transformFrom);
		((Transform)to).getElements(transformTo);
		Control control = (Control)target;
		Transform transform = control.getTransform();
		float[] f = new float[6];
		for (int i = 0; i < 6; i++) {
			f[i] = (float) (((transformTo[i]-transformFrom[i])*newValue) + transformFrom[i]);
		}
		transform.setElements(f[0], f[1], f[2], f[3], f[4], f[5]);
		control.setTransform(transform); 
		return transform;
	}

	void register(Widget widget) {
		if (name == null) {
			int nameScope = OS.NameScope_GetNameScope(widget.handle);
			count ++;
			name = "SWTAnimRef" + count;
			int str = createDotNetString(name);
			int targetHandle = targetHandle();
			OS.NameScope_RegisterName(nameScope, str, targetHandle);
			OS.Storyboard_SetTargetName(handle, str);
			OS.GCHandle_Free(str);
			OS.GCHandle_Free(nameScope);
		}
	}
	
	void release() {
		super.release();
		if (customAnimation != 0) OS.GCHandle_Free(customAnimation);
		customAnimation = 0;
		if (animatorHandle != 0) OS.GCHandle_Free(animatorHandle);
		animatorHandle = 0;
	}
	
	public void setDuration(long duration) {
		checkAnimation();
		this.duration = duration;
	}

	public void setFrom(Object from) {
		checkAnimation();
		this.from = from;
	}
	
//	public void setInterpolator(IInterpolator interpolator) {
//		checkAnimation();
//		this.interpolator = interpolator;
//	}
	
	void setParamType() {
		if ("alpha".equals(property) || "x".equals(property) || "y".equals(property) || "width".equals(property) || "height".equals(property)) {
			paramType = Double.TYPE;
			return;
		}
		String mName = "set" + property.substring(0, 1).toUpperCase() + property.substring(1);
		Class clazz = target.getClass();
		Method[] methods = clazz.getMethods();
		for (int i = 0; i < methods.length; i++) {
			Method m = methods[i];
			if (m.getName().equals(mName)) {
				Class[] parameterTypes = m.getParameterTypes();
				if (parameterTypes.length == 1) {
					method = m;
					break;
				}
			}
		}
//		if (method == null) error?
		paramType = method.getParameterTypes()[0];
	}
	
	public void setProperty(String property) {
		checkAnimation();
		this.property = property;
	}
	
	public void setTarget(Object target) {
		checkAnimation();
		this.target = target;
	}

	void setTargetProperty(int animation, String prop) {
		int dp;
		if (interpolator != null) {
			dp = OS.SWTAnimator_DoubleValueProperty();
		} else {
			dp = Properties.getDependencyProperty(target, prop);
		}
		if (dp == 0) {
			animatorHandle = OS.gcnew_SWTAnimator(jniRef);
			if (paramType == null
					|| paramType == Double.TYPE
					|| paramType == Color.class
					|| paramType == Transform.class) {
				dp = OS.SWTAnimator_DoubleValueProperty();
			} else if (paramType == Integer.TYPE) {
				dp = OS.SWTAnimator_IntValueProperty();
			} else {
				throw new RuntimeException(paramType + " is not supported yet.");
			}
		}
		int propertyPath = OS.gcnew_PropertyPath(dp);
		OS.Storyboard_SetTargetProperty(animation, propertyPath);
		OS.GCHandle_Free(propertyPath);
		OS.GCHandle_Free(dp);	
	}

	void createAnimation() {
		if (interpolator != null) {
			createCustomAnimation();
			return;
		}
		if (paramType == Double.TYPE
				|| paramType == Color.class
				|| paramType == Transform.class) {
			createDoubleAnimation();
			return;
		} 
		if (paramType == Integer.TYPE) {
			createIntegerAnimation();
			return;
		}
		if (paramType == Rectangle.class) {
			createRectangleAnimation();
			return;
		}
		if (paramType == Point.class) {
			createPointAnimation();
			return;
		}
		throw new RuntimeException(paramType + " is not supported yet.");
	}

	public void setTo(Object to) {
		checkAnimation();
		this.to = to;
	}
		
	int targetHandle() {
		if (animatorHandle != 0) return animatorHandle;
		if (target instanceof Widget) {
			return ((Widget)target).handle;
		}
		if (target instanceof Effect) {
			return ((Effect)target).handle;
		}
		return 0;
	}
	
	long updateDuration(long delay) {
		if (interpolator == null) return delay;
		int timeSpan = OS.TimeSpan_FromMilliseconds(duration);
		int dur = OS.gcnew_Duration(timeSpan);
		OS.Timeline_Duration(customAnimation, dur);
		OS.GCHandle_Free(dur);
		OS.GCHandle_Free(timeSpan);
		//set begin time
		timeSpan = OS.TimeSpan_FromMilliseconds(delay + beginTime);
		OS.Timeline_BeginTime(customAnimation, timeSpan);
		OS.GCHandle_Free(timeSpan);		
		return delay+beginTime+duration;
	}
	
	void updateFromToValues() {
		OS.DoubleAnimation_From(customAnimation, 0);
		OS.DoubleAnimation_To(customAnimation, 1);
	}
}
