/* LayoutRegion.java

	Purpose:

	Description:

	History:
		Feb 10, 2009 4:17:16 PM , Created by jumperchen

Copyright (C) 2008 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zul;

import java.util.Iterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.zkoss.lang.Objects;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.OpenEvent;
import org.zkoss.zk.ui.event.SlideEvent;
import org.zkoss.zul.impl.Utils;
import org.zkoss.zul.impl.XulElement;

/**
 * A layout region in a border layout.
 * <p>
 * Events:<br/> onOpen, onSize, onSlide.<br/>
 *
 * <h3>Support Caption component</h3>
 * [ZK EE]
 * [Since 6.5.0]
 *
 * @author jumperchen
 * @since 5.0.0
 */
public abstract class LayoutRegion extends XulElement {

	private static final Logger log = LoggerFactory.getLogger(LayoutRegion.class);

	private String _border = "normal";
	private int[] _margins = new int[] { 0, 0, 0, 0 };
	private boolean _flex;
	private boolean _autoscroll;

	private String _title = null;
	private int _maxsize = 2000;
	private int _minsize = 0;
	private int[] _cmargins;
	private boolean _splittable;
	private boolean _collapsible;
	private boolean _closable = true;
	private boolean _slidable = true;
	private boolean _open = true;
	private boolean _slide = false;
	private transient Caption _caption;

	static {
		addClientEvent(LayoutRegion.class, Events.ON_OPEN, CE_IMPORTANT);
		addClientEvent(LayoutRegion.class, Events.ON_SLIDE, CE_IMPORTANT);
		addClientEvent(LayoutRegion.class, Events.ON_SIZE, CE_IMPORTANT | CE_DUPLICATE_IGNORE);
	}

	public LayoutRegion() {
		_cmargins = getDefaultCmargins();
	}

	/** Returns the caption of this region.
	 * @since 6.5.0
	 */
	public Caption getCaption() {
		return _caption;
	}

	/**
	 * Returns the border.
	 * <p>
	 * The border actually controls what CSS class to use: If border is null, it
	 * implies "none".
	 *
	 * <p>
	 * If you also specify the CSS class ({@link #setClass}), it overwrites
	 * whatever border you specify here.
	 *
	 * <p>
	 * Default: "normal".
	 */
	public String getBorder() {
		return _border;
	}

	/**
	 * Sets the border (either none or normal).
	 *
	 * @param border
	 *            the border. If null or "0", "none" is assumed.
	 */
	public void setBorder(String border) {
		if (border == null || "0".equals(border))
			border = "none";
		if (!_border.equals(border)) {
			_border = border;
			smartUpdate("border", _border);
		}
	}

	/**
	 * Returns the margins, which is a list of numbers separated by comma.
	 *
	 * <p>
	 * Default: "0,0,0,0".
	 */
	public String getMargins() {
		return Utils.intsToString(_margins);
	}

	/**
	 * Sets margins for the element "0,1,2,3" that direction is
	 * "top,left,right,bottom"
	 */
	public void setMargins(String margins) {
		final int[] imargins = Utils.stringToInts(margins, 0);
		if (!Objects.equals(imargins, _margins)) {
			_margins = imargins;
			smartUpdate("margins", getMargins());
		}
	}

	/**
	 * Returns whether enable overflow scrolling.
	 * <p>
	 * Default: false.
	 */
	public boolean isAutoscroll() {
		return _autoscroll;
	}

	/**
	 * Sets whether enable overflow scrolling.
	 */
	public void setAutoscroll(boolean autoscroll) {
		if (_autoscroll != autoscroll) {
			_autoscroll = autoscroll;
			smartUpdate("autoscroll", _autoscroll);
		}
	}

	/**
	 * Returns this regions position (north/south/east/west/center).
	 *
	 * @see Borderlayout#NORTH
	 * @see Borderlayout#SOUTH
	 * @see Borderlayout#EAST
	 * @see Borderlayout#WEST
	 * @see Borderlayout#CENTER
	 */
	public abstract String getPosition();

	/**
	 * Sets the size of this region. This method is shortcut for
	 * {@link #setHeight(String)} and {@link #setWidth(String)}. If this region
	 * is {@link North} or {@link South}, this method will invoke
	 * {@link #setHeight(String)}. If this region is {@link West} or
	 * {@link East}, this method will invoke {@link #setWidth(String)}.
	 * Otherwise it will throw a {@link UnsupportedOperationException}.
	 */
	public abstract void setSize(String size);

	/**
	 * Returns the size of this region. This method is shortcut for
	 * {@link #getHeight()} and {@link #getWidth()}. If this region is
	 * {@link North} or {@link South}, this method will invoke
	 * {@link #getHeight()}. If this region is {@link West} or {@link East},
	 * this method will invoke {@link #getWidth()}. Otherwise it will throw a
	 * {@link UnsupportedOperationException}.
	 */
	public abstract String getSize();

	/**
	 * Returns the title.
	 * <p>Default: null.
	 */
	public String getTitle() {
		return _title;
	}

	/**
	 * Sets the title.
	 */
	public void setTitle(String title) {
		if (!Objects.equals(_title, title)) {
			_title = title;
			smartUpdate("title", _title);
		}
	}

	/**
	 * Returns whether enable the split functionality.
	 * <p>
	 * Default: false.
	 */
	public boolean isSplittable() {
		return _splittable;
	}

	/**
	 * Sets whether enable the split functionality.
	 */
	public void setSplittable(boolean splittable) {
		if (_splittable != splittable) {
			_splittable = splittable;
			smartUpdate("splittable", _splittable);
		}
	}

	/**
	 * Sets the maximum size of the resizing element.
	 */
	public void setMaxsize(int maxsize) {
		if (_maxsize != maxsize) {
			_maxsize = maxsize;
			smartUpdate("maxsize", _maxsize);
		}
	}

	/**
	 * Returns the maximum size of the resizing element.
	 * <p>
	 * Default: 2000.
	 */
	public int getMaxsize() {
		return _maxsize;
	}

	/**
	 * Sets the minimum size of the resizing element.
	 */
	public void setMinsize(int minsize) {
		if (_minsize != minsize) {
			_minsize = minsize;
			smartUpdate("minsize", minsize);
		}
	}

	/**
	 * Returns the minimum size of the resizing element.
	 * <p>
	 * Default: 0.
	 */
	public int getMinsize() {
		return _minsize;
	}

	/**
	 * Returns the collapsed margins, which is a list of numbers separated by comma.
	 * @see #setCmargins
	 * @see #getDefaultCmargins
	 */
	public String getCmargins() {
		return Utils.intsToString(_cmargins);
	}

	/**
	 * Sets the collapsed margins for the element "0,1,2,3" that direction is
	 * "top,left,right,bottom"
	 *
	 * <p>
	 * Default: "3,3,3,3" for center, "0,3,3,0" for east and west,
	 * and "3,0,0,3" for north and south.
	 * @see #getCmargins
	 * @see #getDefaultCmargins
	 */
	public void setCmargins(String cmargins) {
		final int[] imargins = Utils.stringToInts(cmargins, 0);
		if (!Objects.equals(imargins, _cmargins)) {
			_cmargins = imargins;
			smartUpdate("cmargins", getCmargins());
		}
	}

	/** Returns the default collapsed margin.
	 * @since 5.0.5
	 */
	protected abstract int[] getDefaultCmargins();

	/**
	 * Returns whether set the initial display to collapse.
	 * <p>It only applied when {@link #getTitle()} is not null. (since 3.5.0)
	 * <p>
	 * Default: false.
	 */
	public boolean isCollapsible() {
		return _collapsible;
	}

	/**
	 * Sets whether set the initial display to collapse.
	 *
	 * <p>It only applied when {@link #getTitle()} is not null. (since 3.5.0)
	 */
	public void setCollapsible(boolean collapsible) {
		if (collapsible != _collapsible) {
			_collapsible = collapsible;
			smartUpdate("collapsible", _collapsible);
		}
	}

	/**
	 * Returns whether it is open (i.e., not collapsed. Meaningful only if
	 * {@link #isCollapsible} is not false).
	 * <p>
	 * Default: true.
	 */
	public boolean isOpen() {
		return _open;
	}

	/**
	 * Opens or collapses the splitter. Meaningful only if
	 * {@link #isCollapsible} is not false.
	 */
	public void setOpen(boolean open) {
		if (_open != open) {
			_open = open;
			smartUpdate("open", open);
		}
	}

	/**
	 * Returns whether it is slide down.
	 * <p>
	 * Default: false.
	 * @since 8.0.2
	 */
	public boolean isSlide() {
		return _slide;
	}

	/**
	 * Slides down or up the region. Meaningful only if
	 * {@link #isCollapsible} is not false and {@link #isOpen} is false.
	 * @param slide
	 * @since 8.0.2
	 */
	public void setSlide(boolean slide) {
		if (_slide != slide) {
			_slide = slide;
			smartUpdate("slide", slide);
		}
	}

	/**
	 * Returns whether users can slide (preview) the region when clicked on a collapsed region.
	 * In other words, if false, clicking on a collapsed region will open it instead of sliding.
	 * <p>Default: true.
	 *
	 * @return whether users can slide (preview) the region.
	 * @since 8.5.2
	 */
	public boolean isSlidable() {
		return _slidable;
	}

	/**
	 * Sets whether users can slide (preview) the region when clicked on a collapsed region.
	 * Meaningful only if {@link #isCollapsible()} is true and {@link #isOpen()} is false.
	 *
	 * @param slidable whether users can slide (preview) the region.
	 * @since 8.5.2
	 */
	public void setSlidable(boolean slidable) {
		if (_slidable != slidable) {
			_slidable = slidable;
			smartUpdate("slidable", slidable);
		}
	}

	/**
	 * Returns whether users can open or close the region.
	 * In other words, if false, users are no longer allowed to
	 * change the open status (by clicking the button on the bar).
	 * <p>Default: true.
	 *
	 * @return whether users can open or close the region.
	 * @since 8.5.2
	 */
	public boolean isClosable() {
		return _closable;
	}

	/**
	 * Sets whether users can open or close the region.
	 * Meaningful only if {@link #isCollapsible()} is true.
	 *
	 * @param closable whether users can open or close the region.
	 * @since 8.5.2
	 */
	public void setClosable(boolean closable) {
		if (_closable != closable) {
			_closable = closable;
			smartUpdate("closable", closable);
		}
	}

	/*package*/ boolean isNativeScrollbar() {
		return Utils.testAttribute(this, "org.zkoss.zul.nativebar", true, true);
	}

	public String getZclass() {
		return _zclass == null ? "z-" + getPosition() : _zclass;
	}

	public void beforeChildAdded(Component child, Component refChild) {
		if (child instanceof Caption) {
			if (_caption != null && _caption != child)
				throw new UiException("Only one caption is allowed: " + this);
		} else {
			int size = getChildren().size();
			if ((size > 0 && _caption == null) || size > 1)
				throw new UiException("Only one child and one caption is allowed: " + this);
		}
		super.beforeChildAdded(child, refChild);
	}

	public void beforeParentChanged(Component parent) {
		if (parent != null && !(parent instanceof Borderlayout))
			throw new UiException("Wrong parent: " + parent);
		super.beforeParentChanged(parent);
	}

	public boolean insertBefore(Component child, Component refChild) {
		if (child instanceof Caption) {
			refChild = getFirstChild();
			//always makes caption as the first child
			if (super.insertBefore(child, refChild)) {
				_caption = (Caption) child;
				invalidate();
				return true;
			}
			return false;
		}
		return super.insertBefore(child, refChild);
	}

	public void onChildRemoved(Component child) {
		if (child instanceof Caption) {
			_caption = null;
			invalidate();
		}
		super.onChildRemoved(child);
	}

	protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer) throws java.io.IOException {
		super.renderProperties(renderer);

		if (!"normal".equals(_border))
			render(renderer, "border", _border);
		render(renderer, "flex", _flex);
		render(renderer, "autoscroll", _autoscroll);
		if (_margins[0] != 0 || _margins[1] != 0 || _margins[2] != 0 || _margins[3] != 0)
			render(renderer, "margins", getMargins());

		render(renderer, "title", _title);

		if (_maxsize != 2000)
			renderer.render("maxsize", _maxsize);
		if (_minsize != 0)
			renderer.render("minsize", _minsize);

		render(renderer, "splittable", _splittable);
		render(renderer, "collapsible", _collapsible);

		int[] cms = getDefaultCmargins();
		for (int j = cms.length; --j >= 0;)
			if (cms[j] != _cmargins[j]) {
				render(renderer, "cmargins", getCmargins());
				break;
			}

		//always generate since different region might have different default
		if (!_open)
			renderer.render("open", _open);

		if (!isNativeScrollbar())
			renderer.render("_nativebar", false);

		if (!_slidable)
			renderer.render("slidable", false);
		if (!_closable)
			renderer.render("closable", false);
	}

	/** Processes an AU request.
	 *
	 * <p>Default: in addition to what are handled by {@link org.zkoss.zul.LayoutRegion#service},
	 * it also handles onOpen.
	 */
	public void service(org.zkoss.zk.au.AuRequest request, boolean everError) {
		final String cmd = request.getCommand();
		if (cmd.equals(Events.ON_OPEN)) {
			OpenEvent evt = OpenEvent.getOpenEvent(request);
			_open = evt.isOpen();
			Events.postEvent(evt);
		} else if (cmd.equals(Events.ON_SLIDE)) {
			SlideEvent evt = SlideEvent.getSlideEvent(request);
			_slide = evt.isSlide();
			Events.postEvent(evt);
		} else
			super.service(request, everError);
	}

	//Cloneable//
	public Object clone() {
		final LayoutRegion clone = (LayoutRegion) super.clone();
		if (clone._caption != null)
			clone.afterUnmarshal();
		return clone;
	}

	private void afterUnmarshal() {
		for (Iterator it = getChildren().iterator(); it.hasNext();) {
			final Object child = it.next();
			if (child instanceof Caption) {
				_caption = (Caption) child;
				break;
			}
		}
	}

	//Serializable//
	private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
		s.defaultReadObject();
		afterUnmarshal();
	}
}
