content: /*
  The contents of this file are subject to the Mozilla Public License
  Version 1.1 (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.mozilla.org/MPL/
  
  Software distributed under the License is distributed on an "AS IS"
  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
  License for the specific language governing rights and limitations
  under the License.
  
  The Original Code is collection of files collectively known as Open Camera.
  
  The Initial Developer of the Original Code is Almalence Inc.
  Portions created by Initial Developer are Copyright (C) 2013 
  by Almalence Inc. All Rights Reserved.
   */
  
  package com.almalence.plugins.processing.sequence;
  
  import java.util.Arrays;
  
  import android.content.Context;
  import android.graphics.Bitmap;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.graphics.Paint.Style;
  import android.graphics.Rect;
  import android.graphics.RectF;
  import android.util.AttributeSet;
  import android.view.MotionEvent;
  import android.view.View;
  
  public class OrderControl extends View
  {
  	private static int	BORDER_SIZE	= 4;
  
  	public interface SequenceListener
  	{
  		public void onSequenceChanged(int[] indexes);
  	}
  
  	private Bitmap[]			images			= null;
  	private boolean[]			enabled			= null;
  	private int[]				indexes			= null;
  	private int[]				lastIndexes		= null;
  	private final Rect			inRect			= new Rect();
  	private final RectF			outRect			= new RectF();
  
  	private int					width;
  	private int					height;
  
  	private int					touchedIndex	= -1;
  	private boolean				touchedClose;
  	private float				touchedX;
  	private float				touchedY;
  	private float				touchingX;
  
  	private final Paint			paint			= new Paint();
  
  	private SequenceListener	listener		= null;
  
  	public OrderControl(final Context context)
  	{
  		super(context);
  
  		this.init();
  	}
  
  	public OrderControl(final Context context, final AttributeSet attrs)
  	{
  		super(context, attrs);
  
  		this.init();
  	}
  
  	public OrderControl(final Context context, final AttributeSet attrs, final int defStyle)
  	{
  		super(context, attrs, defStyle);
  
  		this.init();
  	}
  
  	private void init()
  	{
  		this.paint.setStrokeWidth(BORDER_SIZE);
  		this.paint.setStyle(Style.STROKE);
  		this.paint.setAntiAlias(true);
  	}
  
  	@Override
  	protected void onDraw(final Canvas canvas)
  	{
  		if (this.indexes != null)
  		{
  			final int imageWidth = this.getImageWidth();
  			int touched = -1;
  
  			for (int i = 0; i < this.indexes.length; i++)
  			{
  				if (this.indexes[i] != this.touchedIndex)
  				{
  					final int actualIndex = this.indexes[i];
  
  					this.inRect.left = 0;
  					this.inRect.top = 0;
  					this.inRect.right = this.images[actualIndex].getWidth() - 1;
  					this.inRect.bottom = this.images[actualIndex].getHeight() - 1;
  
  					final float imageHeight = Math.min(this.height, this.images[actualIndex].getHeight() * imageWidth
  							/ (float) this.images[actualIndex].getWidth());
  					this.outRect.left = i * imageWidth;
  					this.outRect.top = this.height - imageHeight;
  					this.outRect.right = (i + 1) * imageWidth - 1;
  					this.outRect.bottom = this.height - 1;
  
  					canvas.drawBitmap(this.images[actualIndex], this.inRect, this.outRect, null);
  
  					if (!this.enabled[actualIndex])
  					{
  						this.paint.setColor(Color.rgb(255, 64, 64));
  						canvas.drawLine(this.outRect.left, this.outRect.top, this.outRect.right, this.outRect.bottom,
  								this.paint);
  						canvas.drawLine(this.outRect.left, this.outRect.bottom, this.outRect.right, this.outRect.top,
  								this.paint);
  					}
  
  					this.paint.setColor(Color.WHITE);
  					canvas.drawRect(this.outRect.left + BORDER_SIZE / 2.0f, this.outRect.top + BORDER_SIZE / 2.0f,
  							this.outRect.right - BORDER_SIZE / 2.0f, this.outRect.bottom - BORDER_SIZE / 2.0f,
  							this.paint);
  				} else
  				{
  					touched = i;
  				}
  			}
  
  			if (touched >= 0)
  			{
  				this.inRect.left = 0;
  				this.inRect.top = 0;
  				this.inRect.right = this.images[this.touchedIndex].getWidth() - 1;
  				this.inRect.bottom = this.images[this.touchedIndex].getHeight() - 1;
  
  				final float imageHeight = Math.min(this.height, this.images[this.touchedIndex].getHeight() * imageWidth
  						/ (float) this.images[this.touchedIndex].getWidth());
  				this.outRect.left = touched * imageWidth + this.touchingX - this.touchedX;
  				this.outRect.top = this.height - imageHeight;
  				this.outRect.right = this.outRect.left + imageWidth;
  				this.outRect.bottom = this.height - 1;
  
  				canvas.drawBitmap(this.images[this.touchedIndex], this.inRect, this.outRect, null);
  
  				if (!this.enabled[this.touchedIndex])
  				{
  					this.paint.setColor(Color.rgb(255, 64, 64));
  					canvas.drawLine(this.outRect.left, this.outRect.top, this.outRect.right, this.outRect.bottom,
  							this.paint);
  					canvas.drawLine(this.outRect.left, this.outRect.bottom, this.outRect.right, this.outRect.top,
  							this.paint);
  				}
  
  				this.paint.setColor(Color.rgb(0, 160, 255));
  				canvas.drawRect(this.outRect.left + BORDER_SIZE / 2.0f, this.outRect.top + BORDER_SIZE / 2.0f,
  						this.outRect.right - BORDER_SIZE / 2.0f, this.outRect.bottom - BORDER_SIZE / 2.0f, this.paint);
  			}
  		}
  	}
  
  	@Override
  	protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)
  	{
  		this.setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
  	}
  
  	@Override
  	protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh)
  	{
  		this.width = w;
  		this.height = h;
  
  		super.onSizeChanged(w, h, oldw, oldh);
  	}
  
  	@Override
  	public boolean onTouchEvent(final MotionEvent event)
  	{
  		if (!this.isEnabled())
  		{
  			return true;
  		}
  
  		switch (event.getAction())
  		{
  		case MotionEvent.ACTION_MOVE:
  			{
  				this.touchingX = event.getX();
  
  				final int image_width = this.getImageWidth();
  				final int distance = (int) (this.touchingX - this.touchedX);
  				final int shift = (distance / image_width) + ((distance % image_width) / (image_width / 2));
  
  				int pos = 0;
  				for (int i = 1; i < this.indexes.length; i++)
  				{
  					if (this.indexes[i] == this.touchedIndex)
  					{
  						pos = i;
  					}
  				}
  
  				for (int i = 0; i < Math.abs(shift); i++)
  				{
  					final int n0 = Math.min(this.indexes.length - 1, Math.max(0, pos + i * (int) Math.signum(shift)));
  					final int n1 = Math.min(this.indexes.length - 1,
  							Math.max(0, pos + (i + 1) * (int) Math.signum(shift)));
  
  					final int t = this.indexes[n0];
  					this.indexes[n0] = this.indexes[n1];
  					this.indexes[n1] = t;
  				}
  
  				if (Math.abs(shift) > 0)
  				{
  					this.touchedX += shift * image_width;
  				}
  
  				final float dx = event.getX() - this.touchedX;
  				final float dy = event.getY() - this.touchedY;
  				if (Math.sqrt(dx * dx + dy * dy) > 10.0f * this.getContext().getResources().getDisplayMetrics().density)
  				{
  					this.touchedClose = false;
  				}
  			}
  			break;
  
  		case MotionEvent.ACTION_DOWN:
  			{
  				this.touchedX = event.getX();
  				this.touchedY = event.getY();
  				this.touchingX = event.getX();
  				final int touch_pos = (int) (this.touchedX / this.getImageWidth());
  				this.touchedIndex = this.indexes[Math.min(Math.max(touch_pos, 0), this.indexes.length - 1)];
  				this.touchedClose = true;
  			}
  			break;
  
  		case MotionEvent.ACTION_UP:
  			{
  				int count = 0;
  				for (int i = 0; i < this.enabled.length; i++)
  				{
  					if (this.enabled[i])
  					{
  						count++;
  					}
  				}
  
  				if (this.touchedClose && this.touchedIndex != -1)
  				{
  					if (count > 1 || !this.enabled[this.touchedIndex])
  					{
  						this.enabled[this.touchedIndex] = !this.enabled[this.touchedIndex];
  					}
  				}
  
  				this.touchedIndex = -1;
  
  				this.notifyListener();
  			}
  			break;
  		default:
  			break;
  		}
  
  		this.invalidate();
  		this.requestLayout();
  
  		return true;
  	}
  
  	public void setContent(final Bitmap[] images, final SequenceListener listener)
  	{
  		if (images != null)
  		{
  			this.listener = listener;
  
  			this.images = new Bitmap[images.length];
  			System.arraycopy(images, 0, this.images, 0, images.length);
  
  			this.initIndexes();
  		}
  	}
  
  	private void notifyListener()
  	{
  		if (this.listener != null)
  		{
  			final int[] order = this.generateOrderArray();
  			boolean changed = false;
  
  			for (int i = 0; i < order.length; i++)
  			{
  				if (order[i] != this.lastIndexes[i])
  				{
  					changed = true;
  					break;
  				}
  			}
  
  			if (changed)
  			{
  				final int[] msg = Arrays.copyOf(order, order.length);
  
  				this.lastIndexes = order;
  
  				this.listener.onSequenceChanged(msg);
  			}
  		}
  	}
  
  	private int[] generateOrderArray()
  	{
  		final int[] out = new int[this.indexes.length];
  
  		int count = 0;
  
  		for (int i = 0; i < this.images.length; i++)
  		{
  			if (this.enabled[this.indexes[i]])
  			{
  				out[count++] = this.indexes[i];
  			}
  		}
  
  		while (count < out.length)
  		{
  			out[count++] = -1;
  		}
  
  		return out;
  	}
  
  	private void initIndexes()
  	{
  		if (this.images != null)
  		{
  			this.indexes = new int[this.images.length];
  			for (int i = 0; i < this.images.length; i++)
  			{
  				this.indexes[i] = i;
  			}
  
  			this.lastIndexes = Arrays.copyOf(this.indexes, this.indexes.length);
  
  			this.enabled = new boolean[this.images.length];
  			for (int i = 0; i < this.images.length; i++)
  			{
  				this.enabled[i] = true;
  			}
  		} else
  		{
  			throw new RuntimeException();
  		}
  	}
  
  	private int getImageWidth()
  	{
  		return (this.width / this.images.length);
  	}
  }
  