package com.example.licodeclient;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Point;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

/**
 * special layout class to handle the video streams
 */
public class VideoGridLayout extends ViewGroup {
	/** resolution ratio for the streams */
	private static final float VIDEO_RES_RATIO = .75f;
	/** how many video streams in one row */
	private int mColumns = 4;
	/** how many rows of video streams */
	private int mRows = 2;
	/** size of a child */
	private int mChildWidth = 320, mChildHeight = 240;
	/** current order number - for sorting the children */
	private AtomicInteger mCurrentOrder = new AtomicInteger(0x100);
	/** ordered list of elements for one layout pass */
	private ArrayList<View> mOrderedList = new ArrayList<View>();
	/** the instance of the zoomed object - may be null */
	private View mZoomedChild = null;
	/** the first column of the zoomed position */
	private int mZoomedPosition = 0;
	/** the control elements to position over a zoomed element */
	private View mControlElements = null;
	/** the video display element for all streams */
	private View mVideoDisplay = null;
	/**
	 * whether or not the grid layout actually desires a height (false), or not
	 * (true)
	 */
	private boolean mCollapsed = false;

	public VideoGridLayout(Context context) {
		super(context);
	}

	public VideoGridLayout(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public VideoGridLayout(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	/**
	 * Any layout manager that doesn't scroll will want this.
	 */
	@Override
	public boolean shouldDelayChildPressedState() {
		return false;
	}

	/**
	 * Ask all children to measure themselves and compute the measurement of
	 * this layout based on the children.
	 */
	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int maxWidth = View.MeasureSpec.getSize(widthMeasureSpec);
		mChildWidth = maxWidth / mColumns;
		mChildHeight = (int) (mChildWidth * VIDEO_RES_RATIO);
		int desiredHeight = mChildHeight * mRows;

		int childWidthSpecs = View.MeasureSpec.makeMeasureSpec(mChildWidth,
				View.MeasureSpec.EXACTLY);
		int childHeightSpecs = View.MeasureSpec.makeMeasureSpec(mChildHeight,
				View.MeasureSpec.EXACTLY);
		int zoomedWidthSpecs = View.MeasureSpec.makeMeasureSpec(
				2 * mChildWidth, View.MeasureSpec.EXACTLY);
		int zoomedHeightSpecs = View.MeasureSpec.makeMeasureSpec(
				2 * mChildHeight, View.MeasureSpec.EXACTLY);
		int count = getChildCount();
		int childState = 0;

		int slots = mColumns * mRows;
		for (int i = 0; i < count; ++i) {
			View child = getChildAt(i);
			if (child == mControlElements && child.getVisibility() != View.GONE) {
				child.measure(zoomedWidthSpecs, zoomedHeightSpecs);
				childState = combineMeasuredStates(childState,
						child.getMeasuredState());
			} else if (child == mVideoDisplay) {
				child.measure(widthMeasureSpec, heightMeasureSpec);
				childState = combineMeasuredStates(childState,
						child.getMeasuredState());
			} else if (slots > 0 && child.getVisibility() != View.GONE) {
				--slots;
				if (child == mZoomedChild) {
					child.measure(zoomedWidthSpecs, zoomedHeightSpecs);
				} else {
					child.measure(childWidthSpecs, childHeightSpecs);
				}
				childState = combineMeasuredStates(childState,
						child.getMeasuredState());
			}
		}

		if (slots >= mColumns) {
			int realRows = mRows - (slots / mColumns);
			// real rows must be at least 2, or mRows
			realRows = Math.max(Math.min(mRows, 2), realRows);
			desiredHeight = realRows * mChildHeight;
		}
		if (mCollapsed) {
			desiredHeight = 0;
		}

		int desiredHeightSpec = resolveSizeAndState(desiredHeight,
				heightMeasureSpec,
				childState << View.MEASURED_HEIGHT_STATE_SHIFT);
		setMeasuredDimension(
				resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
				desiredHeightSpec);
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		int parentLeft = 0;
		int parentTop = 0;
		int parentRight = right - left;
		int parentBottom = bottom - top;

		int count = getChildCount();
		int slots = mColumns * mRows;

		mOrderedList.clear();
		for (int i = 0; i < count; ++i) {
			View child = getChildAt(i);
			if (mOrderedList.size() == slots) {
				break;
			} else if (child == mControlElements || child == mVideoDisplay) {
				continue;
			} else if (mOrderedList.size() < slots
					&& child.getVisibility() != View.GONE) {
				int curOrder = ((LayoutParams) child.getLayoutParams()).order;
				int k = 0;
				for (int n = mOrderedList.size(); k < n; ++k) {
					if (((LayoutParams) mOrderedList.get(k).getLayoutParams()).order >= curOrder) {
						break;
					}
				}
				mOrderedList.add(k, child);
			}
		}

		for (int i = 0, n = mOrderedList.size(); i < n; ++i) {
			View child = mOrderedList.get(i);
			if (child == mZoomedChild && mRows > 1) {
				continue;
			}
			((LayoutParams) child.getLayoutParams()).gridPosition = i;
			int x = i % mColumns;
			int y = i / mColumns;

			int child_left = parentLeft + x * mChildWidth;
			int child_top = parentTop + y * mChildHeight;
			int child_right = parentLeft + (x + 1) * mChildWidth;
			int child_bottom = parentTop + (y + 1) * mChildHeight;
			child.layout(child_left, child_top, child_right, child_bottom);

			if (child.getVisibility() == View.INVISIBLE) {
				child.setVisibility(View.VISIBLE);
			}
		}

		synchronized (this) {
			// can only zoom if at least 2 rows
			if (mZoomedChild != null && mRows > 1 && mZoomedPosition >= 0) {
				int x0 = mZoomedPosition % mColumns;
				int y0 = mZoomedPosition / mColumns;
				int x1 = x0 + 2;
				int y1 = y0 + 2;

				int zoomed_left = parentLeft + x0 * mChildWidth;
				int zoomed_top = parentTop + y0 * mChildHeight;
				int zoomed_right = parentLeft + x1 * mChildWidth;
				int zoomed_bottom = parentTop + y1 * mChildHeight;
				mZoomedChild.layout(zoomed_left, zoomed_top, zoomed_right,
						zoomed_bottom);
				if (mControlElements != null) {
					mControlElements.layout(zoomed_left, zoomed_top,
							zoomed_right, zoomed_bottom);
				}

				for (int y = y0; y < y1; ++y) {
					for (int x = x0; x < x1; ++x) {
						int index = x + y * mColumns;
						if (index < mOrderedList.size()) {
							View view = mOrderedList.get(index);
							if (view != mZoomedChild) {
								view.setVisibility(View.INVISIBLE);
							}
						}
					}
				}
			}
		}

		mVideoDisplay.layout(parentLeft, parentTop, parentRight, parentBottom);
	}

	/** retrieve currently measured child width and height */
	public Point getChildSize() {
		return new Point(mChildWidth, mChildHeight);
	}

	/**
	 * set the grid layout, number of columns and rows - implicitly requests a
	 * layout pass!
	 */
	public void setGridDimensions(int columns, int rows) {
		if (mColumns != columns || mRows != rows) {
			mColumns = columns;
			mRows = rows;
			requestLayout();
		}
	}

	public static class LayoutParams extends ViewGroup.LayoutParams {
		/** the information on where to sort this element */
		public int order = 0;
		/** actual position in the grid this time */
		int gridPosition = 0;

		public LayoutParams(Context c, AttributeSet attrs) {
			super(c, attrs);
		}

		public LayoutParams(int width, int height) {
			super(width, height);
		}

		public LayoutParams(ViewGroup.LayoutParams source) {
			super(source);
		}

		/** create layout params with given order */
		public LayoutParams(int order) {
			super(MATCH_PARENT, MATCH_PARENT);
			this.order = order;
		}
	}

	@Override
	public LayoutParams generateLayoutParams(AttributeSet attrs) {
		LayoutParams result = new LayoutParams(getContext(), attrs);
		result.order = mCurrentOrder.incrementAndGet();
		return result;
	}

	@Override
	protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {
		LayoutParams result = new LayoutParams(LayoutParams.MATCH_PARENT,
				LayoutParams.MATCH_PARENT);
		if (mChildHeight > 0) {
			result.width = mChildWidth;
			result.height = mChildHeight;
		}
		result.order = mCurrentOrder.incrementAndGet();
		return result;
	}

	@Override
	protected android.view.ViewGroup.LayoutParams generateLayoutParams(
			ViewGroup.LayoutParams p) {
		LayoutParams result = new LayoutParams(p);
		result.order = mCurrentOrder.incrementAndGet();
		return result;
	}

	@Override
	protected boolean checkLayoutParams(android.view.ViewGroup.LayoutParams p) {
		return p instanceof LayoutParams;
	}

	/**
	 * signals that the given view was just tapped. This indicates the view
	 * should switch from normal to enlarged display, or back to normal?
	 * Implicitly calls requestLayout!
	 * 
	 * @param v
	 *            The tapped view.
	 * @return true if the element is now zoomed, false if it was returned to
	 *         normal size
	 */
	public boolean onTapped(View v) {
		if (v == null || v.getParent() != this) {
			return false;
		}

		if (mZoomedChild != v) {
			setZoomedChild(v);
		} else {
			setZoomedChild(null);
		}
		requestLayout();
		return v == mZoomedChild;
	}

	/**
	 * selects a child to be displayed in a zoomed way, unsets if null is passed
	 * as parameter
	 */
	public void setZoomedChild(View v) {
		synchronized (this) {
			mZoomedChild = v;

			if (v != null) {
				v.bringToFront();
				if (mControlElements != null) {
					mControlElements.bringToFront();
				}

				int gridpos = ((LayoutParams) v.getLayoutParams()).gridPosition;
				mZoomedPosition = gridpos % mColumns;
				if (mZoomedPosition >= mColumns / 2) {
					// move left if in right half
					mZoomedPosition = mZoomedPosition - 1;
				}

				// move up one row if in lower half
				mZoomedPosition += mColumns
						* (gridpos / mColumns + (gridpos / mColumns >= mRows / 2 ? -1
								: 0));
			}
		}
	}

	/** access current zoomed view */
	public View getZoomedChild() {
		return mZoomedChild;
	}

	/**
	 * set a child to be considered the control elements which will always be
	 * drawn over the zoomed child
	 */
	public void setControlElements(View v) {
		mControlElements = v;
		if (v != null) {
			bringChildToFront(v);
		}
	}

	/** retrieve currently set control view */
	public View getControlElements() {
		return mControlElements;
	}

	/**
	 * set the full background view - typically a surface view so its drawing
	 * order is implicitly last
	 */
	public void setVideoElement(View v) {
		mVideoDisplay = v;
	}

	/** flag to collapse or open the video grid */
	public void setCollapsed(boolean flag) {
		if (mCollapsed != flag) {
			mCollapsed = flag;
			requestLayout();
		}
	}
}