/*
*

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package railo.runtime.img.filter;import java.awt.image.BufferedImage;

import railo.runtime.engine.ThreadLocalPageContext;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.FunctionException;
import railo.runtime.exp.PageException;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.Struct;
import railo.runtime.type.util.CollectionUtil;

/**
 * A filter which distorts and image by performing coordinate conversions between rectangular and polar coordinates.
 */
public class PolarFilter extends TransformFilter  implements DynFiltering {
	
	/**
     * Convert from rectangular to polar coordinates.
     */
    public final static int RECT_TO_POLAR = 0;

	/**
     * Convert from polar to rectangular coordinates.
     */
	public final static int POLAR_TO_RECT = 1;

	/**
     * Invert the image in a circle.
     */
	public final static int INVERT_IN_CIRCLE = 2;

	private int type;
	private float width, height;
	private float centreX, centreY;
	private float radius;

	/**
     * Construct a PolarFilter.
     */
    public PolarFilter() {
		this(RECT_TO_POLAR);
	}

	/**
     * Construct a PolarFilter.
     * @param type the distortion type
     */
	public PolarFilter(int type) {
		super(ConvolveFilter.CLAMP_EDGES);
		this.type=type;
	}

    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
		this.width = src.getWidth();
		this.height = src.getHeight();
		centreX = width/2;
		centreY = height/2;
		radius = Math.max(centreY, centreX);
		return super.filter( src, dst );
	}
	
	/**
     * Set the distortion type, valid values are
     * - RECT_TO_POLAR = Convert from rectangular to polar coordinates
     * - POLAR_TO_RECT = Convert from polar to rectangular coordinates
     * - INVERT_IN_CIRCLE = Invert the image in a circle
     */
	public void setType(String type) throws ExpressionException {
		type=type.trim().toUpperCase();
		if("RECT_TO_POLAR".equals(type)) this.type = RECT_TO_POLAR;
		else if("POLAR_TO_RECT".equals(type)) this.type = POLAR_TO_RECT;
		else if("INVERT_IN_CIRCLE".equals(type)) this.type = INVERT_IN_CIRCLE;
		else
			throw new ExpressionException("inavlid type defintion ["+type+"], valid types are [RECT_TO_POLAR,POLAR_TO_RECT,INVERT_IN_CIRCLE]");
	}

	/**
     * Get the distortion type.
     * @return the distortion type
     * @see #setType
     */
	public int getType() {
		return type;
	}

	private float sqr(float x) {
		return x*x;
	}

	protected void transformInverse(int x, int y, float[] out) {
		float theta, t;
		float m, xmax, ymax;
		float r = 0;
		
		switch (type) {
		case RECT_TO_POLAR:
			theta = 0;
			if (x >= centreX) {
				if (y > centreY) {
					theta = ImageMath.PI - (float)Math.atan(((x - centreX))/((y - centreY)));
					r = (float)Math.sqrt(sqr (x - centreX) + sqr (y - centreY));
				} else if (y < centreY) {
					theta = (float)Math.atan (((x - centreX))/((centreY - y)));
					r = (float)Math.sqrt (sqr (x - centreX) + sqr (centreY - y));
				} else {
					theta = ImageMath.HALF_PI;
					r = x - centreX;
				}
			} else if (x < centreX) {
				if (y < centreY) {
					theta = ImageMath.TWO_PI - (float)Math.atan (((centreX -x))/((centreY - y)));
					r = (float)Math.sqrt (sqr (centreX - x) + sqr (centreY - y));
				} else if (y > centreY) {
					theta = ImageMath.PI + (float)Math.atan (((centreX - x))/((y - centreY)));
					r = (float)Math.sqrt (sqr (centreX - x) + sqr (y - centreY));
				} else {
					theta = 1.5f * ImageMath.PI;
					r = centreX - x;
				}
			}
			if (x != centreX)
				m = Math.abs (((y - centreY)) / ((x - centreX)));
			else
				m = 0;
			
			if (m <= (height / width)) {
				if (x == centreX) {
					xmax = 0;
					ymax = centreY;
				} else {
					xmax = centreX;
					ymax = m * xmax;
				}
			} else {
				ymax = centreY;
				xmax = ymax / m;
			}
			
			out[0] = (width-1) - (width - 1)/ImageMath.TWO_PI * theta;
			out[1] = height * r / radius;
			break;
		case POLAR_TO_RECT:
			theta = x / width * ImageMath.TWO_PI;
			float theta2;

			if (theta >= 1.5f * ImageMath.PI)
				theta2 = ImageMath.TWO_PI - theta;
			else if (theta >= ImageMath.PI)
				theta2 = theta - ImageMath.PI;
			else if (theta >= 0.5f * ImageMath.PI)
				theta2 = ImageMath.PI - theta;
			else
				theta2 = theta;
	
			t = (float)Math.tan(theta2);
			if (t != 0)
				m = 1.0f / t;
			else
				m = 0;
	
			if (m <= ((height) / (width))) {
				if (theta2 == 0) {
					xmax = 0;
					ymax = centreY;
				} else {
					xmax = centreX;
					ymax = m * xmax;
				}
			} else {
				ymax = centreY;
				xmax = ymax / m;
			}
	
			r = radius * (y / (height));

			float nx = -r * (float)Math.sin(theta2);
			float ny = r * (float)Math.cos(theta2);
			
			if (theta >= 1.5f * ImageMath.PI) {
				out[0] = centreX - nx;
				out[1] = centreY - ny;
			} else if (theta >= Math.PI) {
				out[0] = centreX - nx;
				out[1] = centreY + ny;
			} else if (theta >= 0.5 * Math.PI) {
				out[0] = centreX + nx;
				out[1] = centreY + ny;
			} else {
				out[0] = centreX + nx;
				out[1] = centreY - ny;
			}
			break;
		case INVERT_IN_CIRCLE:
			float dx = x-centreX;
			float dy = y-centreY;
			float distance2 = dx*dx+dy*dy;
			out[0] = centreX + centreX*centreX * dx/distance2;
			out[1] = centreY + centreY*centreY * dy/distance2;
			break;
		}
	}

	public String toString() {
		return "Distort/Polar Coordinates...";
	}

	public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=null;//ImageUtil.createBufferedImage(src);
		Object o;
		if((o=parameters.removeEL(KeyImpl.init("Type")))!=null)setType(ImageFilterUtil.toString(o,"Type"));
		if((o=parameters.removeEL(KeyImpl.init("EdgeAction")))!=null)setEdgeAction(ImageFilterUtil.toString(o,"EdgeAction"));
		if((o=parameters.removeEL(KeyImpl.init("Interpolation")))!=null)setInterpolation(ImageFilterUtil.toString(o,"Interpolation"));

		// check for arguments not supported
		if(parameters.size()>0) {
			throw new FunctionException(ThreadLocalPageContext.get(), "ImageFilter", 3, "parameters", "the parameter"+(parameters.size()>1?"s":"")+" ["+CollectionUtil.getKeyList(parameters,", ")+"] "+(parameters.size()>1?"are":"is")+" not allowed, only the following parameters are supported [Type, EdgeAction, Interpolation]");
		}

		return filter(src, dst);
	}
}
