package controlP5;

/**
 * controlP5 is a processing gui library.
 *
 *  2006-2012 by Andreas Schlegel
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA
 *
 * @author 		Andreas Schlegel (http://www.sojamo.de)
 * @modified	08/27/2012
 * @version		0.7.6
 *
 */

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import processing.core.PImage;

/**
 * A radioButton is a list of toggles that can be turned on or off. radioButton is of type
 * ControllerGroup, therefore a controllerPlug can't be set. this means that an event from a
 * radioButton can't be forwarded to a method other than controlEvent in a sketch.
 * 
 * a radioButton has 2 sets of values. radioButton.getValue() returns the value of the active
 * radioButton item. radioButton.getArrayValue() returns a float array that represents the active
 * (1) and inactive (0) items of a radioButton.
 * 
 * ControlP5 CheckBox Toggle
 * 
 * @example controllers/ControlP5radioButton
 * 
 * @nosuperclasses Controller Controller
 */
public class RadioButton extends ControlGroup<RadioButton> {

	protected List<Toggle> _myRadioToggles;

	protected int spacingRow = 1;

	protected int spacingColumn = 1;

	protected int itemsPerRow = -1;

	protected boolean isMultipleChoice;

	protected int itemHeight = 9;

	protected int itemWidth = 9;

	protected boolean[] availableImages = new boolean[3];

	protected PImage[] images = new PImage[3];

	protected boolean noneSelectedAllowed = true;

	private Object _myPlug;

	private String _myPlugName;

	protected int alignX = RIGHT_OUTSIDE;

	protected int alignY = CENTER;
	
	protected int _myPaddingX = Label.paddingX;

	protected int _myPaddingY = 0;

	/**
	 * Convenience constructor to extend RadioButton.
	 * 
	 * @example use/ControlP5extendController
	 * @param theControlP5
	 * @param theName
	 */
	public RadioButton(ControlP5 theControlP5, String theName) {
		this(theControlP5, theControlP5.getDefaultTab(), theName, 0, 0);
		theControlP5.register(theControlP5.papplet, theName, this);
	}

	/**
	 * @exclude
	 * @param theControlP5
	 * @param theParent
	 * @param theName
	 * @param theX
	 * @param theY
	 */
	public RadioButton(final ControlP5 theControlP5, final ControllerGroup<?> theParent, final String theName, final int theX, final int theY) {
		super(theControlP5, theParent, theName, theX, theY, 99, 9);
		isBarVisible = false;
		isCollapse = false;
		_myRadioToggles = new ArrayList<Toggle>();
		setItemsPerRow(1);
		_myPlug = cp5.papplet;
		_myPlugName = getName();
		if (!ControllerPlug.checkPlug(_myPlug, _myPlugName, new Class[] { int.class })) {
			_myPlug = null;
		}
	}

	/**
	 * @param theName
	 * @param theValue
	 * @return
	 */
	public RadioButton addItem(final String theName, final float theValue) {
		Toggle t = cp5.addToggle(theName, 0, 0, itemWidth, itemHeight);
		t.getCaptionLabel().align(alignX, alignY).setPadding(_myPaddingX, _myPaddingY);
		t.setMode(ControlP5.DEFAULT);
		t.setImages(images[0], images[1], images[2]);
		t.setSize(images[0]);
		addItem(t, theValue);
		return this;
	}

	/**
	 * @param theToggle
	 * @param theValue
	 * @return
	 */
	public RadioButton addItem(final Toggle theToggle, final float theValue) {
		theToggle.setGroup(this);
		theToggle.isMoveable = false;
		theToggle.setInternalValue(theValue);
		theToggle.isBroadcast = false;
		_myRadioToggles.add(theToggle);
		updateLayout();
		getColor().copyTo(theToggle);
		theToggle.addListener(this);
		updateValues(false);
		cp5.removeProperty(theToggle);
		return this;
	}

	/**
	 * @param theName
	 */
	public RadioButton removeItem(final String theName) {
		int n = _myRadioToggles.size();
		for (int i = 0; i < n; i++) {
			if ((_myRadioToggles.get(i)).getName().equals(theName)) {
				(_myRadioToggles.get(i)).removeListener(this);
				_myRadioToggles.remove(i);
			}
		}
		updateValues(false);
		return this;
	}

	private void updateAlign() {
		for (Toggle t : _myRadioToggles) {
			t.getCaptionLabel().align(alignX, alignY);
		}
	}

	public RadioButton align(int[] a) {
		return align(a[0], a[1]);
	}

	public RadioButton align(int theX, int theY) {
		alignX = theX;
		alignY = theY;
		updateAlign();
		return this;
	}

	public RadioButton alignX(int theX) {
		return align(theX, alignY);
	}

	public RadioButton alignY(int theY) {
		return align(alignX, theY);
	}

	public int[] getAlign() {
		return new int[] { alignX, alignY };
	}

	public RadioButton setLabelPadding(int thePaddingX, int thePaddingY) {
		_myPaddingX = thePaddingX;
		_myPaddingY = thePaddingY;
		for (Toggle t : _myRadioToggles) {
			t.getCaptionLabel().setPadding(thePaddingX, thePaddingY);
		}
		return this;
	}

	/**
	 * 
	 * @param theDefaultImage
	 * @param theOverImage
	 * @param theActiveImage
	 * @return RadioButton
	 */
	public RadioButton setImages(PImage theDefaultImage, PImage theOverImage, PImage theActiveImage) {
		setImage(theDefaultImage, DEFAULT);
		setImage(theOverImage, OVER);
		setImage(theActiveImage, ACTIVE);
		return this;
	}

	/**
	 * @param theImage
	 */
	public RadioButton setImage(PImage theImage) {
		return setImage(theImage, DEFAULT);
	}

	/**
	 * @param theImage
	 * @param theState use Controller.DEFAULT (background), or Controller.OVER (foreground), or
	 *            Controller.ACTIVE (active)
	 * @return
	 */
	public RadioButton setImage(PImage theImage, int theState) {
		if (theImage != null) {
			images[theState] = theImage;
			availableImages[theState] = true;
			for (int i = 0; i < _myRadioToggles.size(); i++) {
				_myRadioToggles.get(i).setImage(theImage, theState);
			}
		}
		return this;
	}

	public RadioButton setSize(PImage theImage) {
		return setSize(theImage.width, theImage.height);
	}

	public RadioButton setSize(int theWidth, int theHeight) {
		setItemWidth(theWidth);
		setItemHeight(theHeight);
		return this;
	}

	/**
	 * set the height of a radioButton/checkBox item. by default the height is 11px. in order to
	 * recognize a custom height, the itemHeight has to be set before adding items to a
	 * radioButton/checkBox.
	 * 
	 * @param theItemHeight
	 */
	public RadioButton setItemHeight(int theItemHeight) {
		itemHeight = theItemHeight;
		for (Toggle t : _myRadioToggles) {
			t.setHeight(theItemHeight);
		}
		updateLayout();
		return this;
	}

	/**
	 * set the width of a radioButton/checkBox item. by default the width is 11px. in order to
	 * recognize a custom width, the itemWidth has to be set before adding items to a
	 * radioButton/checkBox.
	 * 
	 * @param theItemWidth
	 */
	public RadioButton setItemWidth(int theItemWidth) {
		itemWidth = theItemWidth;
		for (Toggle t : _myRadioToggles) {
			t.setWidth(theItemWidth);
		}
		updateLayout();
		return this;
	}

	/**
	 * Gets a radio button item by index.
	 * 
	 * @param theIndex
	 * @return Toggle
	 */
	public Toggle getItem(int theIndex) {
		return _myRadioToggles.get(theIndex);
	}

	public Toggle getItem(String theName) {
		for (Toggle t : _myRadioToggles) {
			if (theName.equals(t.getName())) {
				return t;
			}
		}
		return null;
	}

	public List<Toggle> getItems() {
		return _myRadioToggles;
	}

	/**
	 * Gets the state of an item - this can be true (for on) or false (for off) - by index.
	 * 
	 * @param theIndex
	 * @return boolean
	 */
	public boolean getState(int theIndex) {
		if (theIndex < _myRadioToggles.size() && theIndex >= 0) {
			return ((Toggle) _myRadioToggles.get(theIndex)).getState();
		}
		return false;
	}

	/**
	 * Gets the state of an item - this can be true (for on) or false (for off) - by name.
	 * 
	 * @param theName
	 * @return
	 */
	public boolean getState(String theName) {
		int n = _myRadioToggles.size();
		for (int i = 0; i < n; i++) {
			Toggle t = _myRadioToggles.get(i);
			if (theName.equals(t.getName())) {
				return t.getState();
			}
		}
		return false;
	}

	/**
	 * @exclude
	 */
	public void updateLayout() {
		int nn = 0;
		int xx = 0;
		int yy = 0;
		int n = _myRadioToggles.size();
		for (int i = 0; i < n; i++) {
			Toggle t = _myRadioToggles.get(i);
			t.position.y = yy;
			t.position.x = xx;

			xx += t.width + spacingColumn;
			nn++;
			if (nn == itemsPerRow) {
				nn = 0;
				_myWidth = xx;
				yy += t.height + spacingRow;
				xx = 0;
			} else {
				_myWidth = xx;
			}
		}
	}

	/**
	 * Items of a radioButton or a checkBox are organized in columns and rows. SetItemsPerRow sets
	 * the limit of items per row. items exceeding the limit will be pushed to the next row.
	 * 
	 * @param theValue
	 */
	public RadioButton setItemsPerRow(final int theValue) {
		itemsPerRow = theValue;
		updateLayout();
		return this;
	}

	/**
	 * Sets the spacing in pixels between columns.
	 * 
	 * @param theSpacing
	 */
	public RadioButton setSpacingColumn(final int theSpacing) {
		spacingColumn = theSpacing;
		updateLayout();
		return this;
	}

	/**
	 * Sets the spacing in pixels between rows.
	 * 
	 * @param theSpacing
	 */
	public RadioButton setSpacingRow(final int theSpacing) {
		spacingRow = theSpacing;
		updateLayout();
		return this;
	}

	public RadioButton deactivateAll() {
		if (!isMultipleChoice && !noneSelectedAllowed) {
			return this;
		}
		int n = _myRadioToggles.size();
		for (int i = 0; i < n; i++) {
			((Toggle) _myRadioToggles.get(i)).deactivate();
		}
		_myValue = -1;
		updateValues(true);
		return this;
	}

	/**
	 * Deactivates all active RadioButton items and only activates the item corresponding to
	 * theIndex.
	 * 
	 * @param theIndex
	 */
	public RadioButton activate(int theIndex) {
		int n = _myRadioToggles.size();
		if (theIndex < n) {
			for (int i = 0; i < n; i++) {
				_myRadioToggles.get(i).deactivate();
			}
			((Toggle) _myRadioToggles.get(theIndex)).activate();
			_myValue = _myRadioToggles.get(theIndex).internalValue();
			updateValues(true);
		}
		return this;
	}

	/**
	 * @param theIndex
	 */
	public RadioButton deactivate(int theIndex) {
		if (!isMultipleChoice && !noneSelectedAllowed) {
			return this;
		}
		if (theIndex < _myRadioToggles.size()) {
			Toggle t = _myRadioToggles.get(theIndex);
			if (t.isActive) {
				t.deactivate();
				_myValue = -1;
				updateValues(true);
			}
		}
		return this;
	}

	/**
	 * Actives an item of the Radio button by name.
	 * 
	 * @param theName
	 */
	public RadioButton activate(String theName) {
		int n = _myRadioToggles.size();
		for (int i = 0; i < n; i++) {
			Toggle t = _myRadioToggles.get(i);
			if (theName.equals(t.getName())) {
				activate(i);
				return this;
			}
		}
		return this;
	}

	/**
	 * Deactivates a RadioButton by name and sets the value of the RadioButton to the default value
	 * -1.
	 * 
	 * @param theName
	 */
	public RadioButton deactivate(String theName) {
		int n = _myRadioToggles.size();
		for (int i = 0; i < n; i++) {
			Toggle t = _myRadioToggles.get(i);
			if (theName.equals(t.getName())) {
				t.deactivate();
				_myValue = -1;
				updateValues(true);
				return this;
			}
		}
		return this;
	}

	/**
	 * @exclude
	 * @param theIndex
	 */
	public RadioButton toggle(int theIndex) {
		// TODO
		// boolean itemState = ((Toggle)
		// _myRadioToggles.get(theIndex)).getState();
		// if (theIndex < _myRadioToggles.size()) {
		// Toggle t = ((Toggle) _myRadioToggles.get(theIndex));
		// if (t.isActive) {
		// t.deactivate();
		// _myValue = -1;
		// updateValues(true);
		// }
		// }
		ControlP5.logger().info("toggle() not yet implemented, working on it.");
		return this;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @exclude
	 */
	@ControlP5.Invisible @Override public void controlEvent(ControlEvent theEvent) {
		if (!isMultipleChoice) {
			if (noneSelectedAllowed == false && theEvent.getController().getValue() < 1) {
				if (theEvent.getController() instanceof Toggle) {
					Toggle t = ((Toggle) theEvent.getController());
					boolean b = t.isBroadcast();
					t.setBroadcast(false);
					t.setState(true);
					t.setBroadcast(b);
					return;
				}
			}
			_myValue = -1;
			int n = _myRadioToggles.size();
			for (int i = 0; i < n; i++) {
				Toggle t = _myRadioToggles.get(i);
				if (!t.equals(theEvent.getController())) {
					t.deactivate();
				} else {
					if (t.isOn) {
						_myValue = t.internalValue();
					}
				}
			}
		}
		if (_myPlug != null) {
			try {
				Method method = _myPlug.getClass().getMethod(_myPlugName, int.class);
				method.invoke(_myPlug, (int) _myValue);
			} catch (SecurityException ex) {
				ex.printStackTrace();
			} catch (NoSuchMethodException ex) {
				ex.printStackTrace();
			} catch (IllegalArgumentException ex) {
				ex.printStackTrace();
			} catch (IllegalAccessException ex) {
				ex.printStackTrace();
			} catch (InvocationTargetException ex) {
				ex.printStackTrace();
			}
		}
		updateValues(true);
	}

	public RadioButton plugTo(Object theObject) {
		_myPlug = theObject;
		if (!ControllerPlug.checkPlug(_myPlug, _myPlugName, new Class[] { int.class })) {
			_myPlug = null;
		}
		return this;
	}

	public RadioButton plugTo(Object theObject, String thePlugName) {
		_myPlug = theObject;
		_myPlugName = thePlugName;
		if (!ControllerPlug.checkPlug(_myPlug, _myPlugName, new Class[] { int.class })) {
			_myPlug = null;
		}
		return this;
	}

	protected void updateValues(boolean theBroadcastFlag) {
		int n = _myRadioToggles.size();
		_myArrayValue = new float[n];
		for (int i = 0; i < n; i++) {
			Toggle t = ((Toggle) _myRadioToggles.get(i));
			_myArrayValue[i] = t.getValue();
		}
		if (theBroadcastFlag) {
			ControlEvent myEvent = new ControlEvent(this);
			cp5.getControlBroadcaster().broadcast(myEvent, ControlP5Constants.FLOAT);
		}

	}

	/**
	 * In order to always have 1 item selected, use setNoneSelectedAllowed(false), by default this
	 * is true. setNoneSelectedAllowed does not apply when in multipleChoice mode.
	 * 
	 * @param theValue
	 */
	public RadioButton setNoneSelectedAllowed(boolean theValue) {
		noneSelectedAllowed = theValue;
		return this;
	}

	/**
	 * Sets the value for all RadioButton items according to the values of the array passed on. 0
	 * will turn off an item, any other value will turn it on.
	 */
	@Override public RadioButton setArrayValue(float[] theArray) {
		for (int i = 0; i < theArray.length; i++) {
			if (_myArrayValue[i] != theArray[i]) {
				if (theArray[i] == 0) {
					deactivate(i);
				} else {
					activate(i);
				}
			}
		}
		super.setArrayValue(theArray);
		return this;
	}

	public RadioButton setColorLabels(int theColor) {
		for (Toggle t : _myRadioToggles) {
			t.getCaptionLabel().setColor(theColor);
		}
		return this;
	}

	public RadioButton hideLabels() {
		for (Toggle t : _myRadioToggles) {
			t.getCaptionLabel().setVisible(false);
		}
		return this;
	}

	public RadioButton showLabels() {
		for (Toggle t : _myRadioToggles) {
			t.getCaptionLabel().setVisible(true);
		}
		return this;
	}

	public RadioButton toUpperCase(boolean theValue) {
		for (Toggle t : _myRadioToggles) {
			t.getCaptionLabel().toUpperCase(theValue);
		}
		return this;
	}

	/**
	 * @exclude {@inheritDoc}
	 */
	@Override public String getInfo() {
		return "type:\tRadioButton\n" + super.getInfo();
	}

	/**
	 * @deprecated
	 * @exclude
	 */
	@Deprecated public RadioButton add(final String theName, final float theValue) {
		return addItem(theName, theValue);
	}
}
