package leaf.ui;

import info.clearthought.layout.TableLayout;
import info.clearthought.layout.TableLayoutConstraints;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

import leaf.data.OrderedMap;


public class LeafInputField<ObjClass extends Object> extends JPanel implements FocusListener, MouseListener, PropertyChangeListener
{

	public static final String PROPERTY_CHANGED_CONSTANT = "LEAF_INPUT_PROPERTY_CHANGED";
	public static final String PROPERTY_CHANGED_CLICK = "LEAF_PROPERTY_CHANGED_CLICK";
	private static final long serialVersionUID = 1L;

	private static final DateFormat sdf = DateFormat.getDateInstance( DateFormat.SHORT, new Locale( "pt", "PT" ) );

	private static final int defaultColorDec = 20;

	private int colorDec = defaultColorDec;
	
	public Dimension size = null;

	private Color hot = null;
	private Color cold = null;
	private Color background = null;
	private Color endColor = null;
	private Color highLightColor = new Color( 180, 255, 180 );

	private Object selectedOption = null;

	private boolean isEditable = false;

	private boolean isClickable = false;

	private boolean highLighted = false;

	private boolean collapseOptions = true;

	private ObjClass object = null;

	private GradientPaint outerContour = null;
	private GradientPaint outerReversedContour = null;
	private GradientPaint innerContour = null;
	private GradientPaint innerReversedContour = null;
	private GradientPaint gradient = null;
	private TableLayout layout = null;

	private JComponent thiz = null;

	private List<JComponent> theeze = null;

	public LeafInputField()
	{
		super();
		setOpaque( false );
		setBorder( BorderFactory.createEmptyBorder( 3, 10, 3, 10 ) );
		setBackground( Color.WHITE );
		hot = getBackground();
		cold = new Color( hot.getRed() > colorDec ? hot.getRed() - colorDec : 0, hot.getGreen() > colorDec ? hot.getGreen() - colorDec : 0, hot.getBlue() > colorDec ? hot.getBlue() - colorDec : 0, hot.getAlpha() );
		background = cold;
		setObject( null );
	}

	public boolean isCollapseOptions()
	{
		return collapseOptions;
	}

	public void setCollapseOptions( boolean collapse )
	{
		collapseOptions = collapse;
	}

	private String getStringFromUser()
	{
		LeafTextDialog textDialog = new LeafTextDialog( getParentWindow(), this, (String) object, true );
		return textDialog.getText();
	}

	private Object getOptionFromUser()
	{
		if( object instanceof Map )
		{
			if(((Map) object).size() > 0)
			{
				LeafOptionDialog<Object> optionDialog = new LeafOptionDialog<Object>( getParentWindow(), (Map) object, null );
				return optionDialog.getOption();
			}
		}
		else if( object instanceof OrderedMap )
		{
			if(((OrderedMap) object).rows() > 0)
			{
				LeafOptionDialog<Object> optionDialog = new LeafOptionDialog<Object>( getParentWindow(), (OrderedMap<Object>) object, null, null, null, null );
				return optionDialog.getOption();
			}
		}
		return null;
	}

	private Date getDateFromUser()
	{
		LeafCalendarDialog calendarDialog = new LeafCalendarDialog( getParentWindow(), this );
		return calendarDialog.getDate();
	}

	private LeafWindow getParentWindow()
	{
		LeafWindow result = null;
		for( Container parent = this.getParent(); parent != null; parent = parent.getParent() )
		{
			if( parent instanceof LeafWindow )
			{
				result = (LeafWindow) parent;
			}
		}
		return result;
	}

	protected void paintComponent( Graphics g )
	{
		Graphics2D g2d = (Graphics2D) g;
		int width = getWidth(), height = getHeight();
		setColors( width, height );
		g2d.setPaint( outerReversedContour );
		g2d.fillRect( 15, 0, width / 4, height );
		g2d.setPaint( outerContour );
		g2d.fillRect( width / 4, 0, width, height );
		g2d.setPaint( innerReversedContour );
		g2d.fillRect( 15, 0, width / 4, height - 1 );
		g2d.setPaint( innerContour );
		g2d.fillRect( width / 4, 0, width, height - 1 );
		g2d.setPaint( gradient );
		g2d.fillRoundRect( 0, 2, width, height - 5, 15, 15 );
		super.paintComponent( g );
	}

	private void setColors( int width, int height )
	{
		endColor = getGradientEndColor( background );
		outerContour = new GradientPaint( width / 4, 0, Color.GRAY, width, 0, this.getParent().getBackground() );
		outerReversedContour = new GradientPaint( 15, 0, this.getParent().getBackground(), width / 4, 0, Color.GRAY );
		innerContour = new GradientPaint( width / 4, 0, Color.LIGHT_GRAY, width, 0, this.getParent().getBackground() );
		innerReversedContour = new GradientPaint( 15, 0, this.getParent().getBackground(), width / 4, 0, Color.LIGHT_GRAY );
		gradient = new GradientPaint( 0, 0, background, width, height, endColor );
	}

	private Color getGradientEndColor( Color startColor )
	{
		return getParent() != null ? getParent().getBackground() : startColor;
	}

	public boolean getSelected()
	{
		return highLighted;
	}

	public void setSelected( boolean selected )
	{
		highLighted = selected;
		background = selected ? highLightColor : cold;
		repaint();
	}

	public void setEditable( boolean editable )
	{
		isEditable = editable;
		if( !editable )
		{
			setMouseOver( false );
		}
		setEnabled( editable );
		setObject( object, selectedOption );
		repaint();
	}

	public boolean isEditable()
	{
		return isEditable;
	}

	public void setClickable( boolean clickable )
	{
		isClickable = clickable;
		if( !clickable )
		{
			setMouseOver( false );
		}
		repaint();
	}

	public boolean isClickable()
	{
		return isClickable;
	}

	private void setupCollapsedPanel()
	{
		List<Object> values = ((OrderedMap) object).getValues( selectedOption );
		theeze = new ArrayList<JComponent>();
		if( values != null )
		{
			double[] rows = new double[] {
				TableLayout.MINIMUM
			};
			double[] cols = new double[values.size() > 0 ? (values.size() * 2 - 1) : 0];
			for( int i = 0; i < cols.length; ++i )
			{
				cols[i] = (i % 2 == 0) ? TableLayout.FILL : TableLayout.MINIMUM;
			}
			TableLayout layout = new TableLayout( cols, rows );
			layout.setHGap( 5 );
			thiz.setLayout( layout );
			for( int i = 0, a = 0; i < cols.length; ++i )
			{
				JComponent comp = null;
				if( i % 2 == 0 )
				{
					Object value = values.get( a++ );
					comp = new JLabel( value == null ? " " : value.toString() );
					theeze.add( comp );
				}
				else
				{
					comp = new JSeparator( JSeparator.VERTICAL );
				}
				thiz.add( comp, new TableLayoutConstraints( i, 0 ) );
			}
		}
	}

	private void setupUncollapsedPanel()
	{
		theeze = new ArrayList<JComponent>();
		OrderedMap<Object> map = (OrderedMap<Object>) object;
		double[] rows = new double[map.rows() > 0 ? (map.rows() * 2 - 1) : 0];
		double[] cols = new double[map.columns() > 0 ? (map.columns() * 2 - 1) : 0];
		for( int i = 0; i < rows.length; ++i )
		{
			rows[i] = (i % 2 == 0) ? TableLayout.MINIMUM : TableLayout.MINIMUM;
		}
		for( int i = 0; i < cols.length; ++i )
		{
			cols[i] = (i % 2 == 0) ? TableLayout.FILL : TableLayout.MINIMUM;
		}
		TableLayout layout = new TableLayout( cols, rows );
		layout.setHGap( 5 );
		thiz.setLayout( layout );

		for( int i = 0, a = 0; i < rows.length; ++i )
		{
			if( i % 2 == 0 )
			{
				List<Object> values = map.getRow( a++ );
				for( int j = 0, b = 0; j < cols.length; ++j )
				{
					JComponent comp = null;
					if( j % 2 == 0 )
					{
						Object value = values.get( b++ );

						LeafInputField<Object> leaf;
						if( value instanceof LeafInputField )
						{
							leaf = (LeafInputField<Object>) value;
						}
						else
						{
							leaf = new LeafInputField<Object>();
							leaf.setObject( value );
							if( j > 1 )
							{
								leaf.setEditable( isEditable );
							}
						}

						comp = leaf;
						theeze.add( comp );
					}
					else
					{
						JSeparator sep = new JSeparator( JSeparator.VERTICAL );
						sep.setForeground( cold );
						comp = sep;
					}
					thiz.add( comp, new TableLayoutConstraints( j, i ) );
				}
			}
			else
			{
				JSeparator sep = new JSeparator();
				sep.setForeground( cold );
				thiz.add( sep, new TableLayoutConstraints( 0, i, cols.length - 1, i ) );
			}
		}
	}

	private void setSelectedObject( Object key )
	{
		selectedOption = key;
		if( object != null && thiz != null )
		{
			if( object instanceof Map )
			{
				Object value = null;
				value = ((Map) object).get( selectedOption );
				String text = value == null ? " " : value.toString();
				if( thiz instanceof JTextArea )
				{
					((JTextArea) thiz).setText( text );
				}
				else if( thiz instanceof JTextField )
				{
					((JTextField) thiz).setText( text );
				}
				else if( thiz instanceof JLabel )
				{
					((JLabel) thiz).setText( text );
				}
			}
			else if( object instanceof OrderedMap )
			{
				if( thiz instanceof JPanel && collapseOptions )
				{
					setupCollapsedPanel();
				}
				else if( thiz instanceof JPanel && !collapseOptions )
				{
					setupUncollapsedPanel();
				}
			}
		}
	}

	public Object getSelectedObject()
	{
		return selectedOption;
	}

	public void setObject( ObjClass object, Object selected )
	{
		this.selectedOption = selected;
		setObject( object );
	}

	public void setObject( ObjClass object )
	{
		this.object = object;
		if( object != null )
		{
			if( object instanceof Date )
			{
				Date date = (Date) object;
				String text = date.getTime() == 0 ? "" : sdf.format( object );
				thiz = isEditable ? new JLabel( text ) : new JTextArea( text );
				thiz.setEnabled( false );
			}
			else if( object instanceof Map )
			{
				thiz = isEditable ? new JLabel( " " ) : new JTextArea( " " );
				setSelectedObject( selectedOption );
				thiz.setEnabled( false );
			}
			else if( object instanceof OrderedMap && collapseOptions )
			{
				thiz = new JPanel();
				setSelectedObject( selectedOption );
			}
			else if( object instanceof OrderedMap && !collapseOptions )
			{
				thiz = new JPanel();
				setSelectedObject( selectedOption );
			}
			else if( object instanceof LeafInputField )
			{
				setObject( (ObjClass) ((LeafInputField) object).getObject(), ((LeafInputField) object).getSelectedObject() );
			}
			else if( object instanceof String )
			{
				JTextArea textArea =  new JTextArea();
				textArea.setEditable( false );
				textArea.setText( object == null ? "" : object.toString() );
				thiz = textArea;
			}
			else
			{
				String toString = object.toString();
				if( "".equals( toString ) )
				{
					toString = " ";
				}
				thiz = new JTextArea( toString );
			}
		}
		else
		{
			thiz = new JLabel( " " );
		}
		reListen();
		reLayout();
	}

	public ObjClass getObject()
	{
		return this.object;
	}

	private void reListen()
	{
		removeListeners();
		if( theeze != null && !collapseOptions && !theeze.isEmpty() )
		{
			for( JComponent current : theeze )
			{
				if( current instanceof LeafInputField )
				{
					current.addFocusListener( (LeafInputField) current );
					current.addMouseListener( (LeafInputField) current );
					current.addPropertyChangeListener( PROPERTY_CHANGED_CONSTANT, this );
				}
			}
		}
		else
		{
			thiz.addFocusListener( this );
			thiz.addMouseListener( this );
		}
	}

	private void removeListeners()
	{
		removeListerensFrom( thiz );
		if( theeze != null )
		{
			for( JComponent comp : theeze )
			{
				removeListerensFrom( comp );
			}
		}
	}

	private void removeListerensFrom( JComponent comp )
	{
		if( comp != null )
		{
			FocusListener[] allFocus = thiz.getFocusListeners();
			if( allFocus != null )
			{
				for( FocusListener focusListener : allFocus )
				{
					comp.removeFocusListener( focusListener );
				}
			}
			MouseListener[] allMouse = thiz.getMouseListeners();
			if( allMouse != null )
			{
				for( MouseListener mouseListener : allMouse )
				{
					comp.removeMouseListener( mouseListener );
				}
			}
		}
	}

	private void reLayout()
	{
		if( thiz != null )
		{
			SwingUtilities.invokeLater( new Runnable()
			{
				@Override
				public void run()
				{
					if( layout == null )
					{
						layout = new TableLayout( new double[] {
							TableLayout.FILL
						}, new double[] {
							TableLayout.FILL
						} );
						LeafInputField.this.setLayout( layout );
					}
					else
					{
						removeAll();
					}
					LeafInputField.this.add( thiz, new TableLayoutConstraints( 0, 0 ) );
					revalidate();
					repaint();
				}
			} );
			size = thiz.getPreferredSize();
			thiz.setOpaque( false );
		}
	}

	@Override
	public void focusLost( FocusEvent e )
	{
		repaint();
	}

	@Override
	public void focusGained( FocusEvent e )
	{
		repaint();
	}

	@Override
	public void mouseReleased( MouseEvent e )
	{
	}

	@Override
	public void mousePressed( MouseEvent e )
	{
		Object old = null;
		if( object != null && isEditable )
		{
			if( object instanceof Date )
			{
				old = object;
				ObjClass newDate = (ObjClass) getDateFromUser();
				if( newDate != null )
				{
					setObject( newDate );
					firePropertyChange( PROPERTY_CHANGED_CONSTANT, old, object );
				}
			}
			else if( object instanceof Map && collapseOptions )
			{
				old = selectedOption;
				ObjClass out = (ObjClass) getOptionFromUser();
				setObject( object, out );
				firePropertyChange( PROPERTY_CHANGED_CONSTANT, old, selectedOption );
			}
			else if( object instanceof OrderedMap && collapseOptions )
			{
				old = selectedOption;
				ObjClass out = (ObjClass) getOptionFromUser();
				setObject( object, out );
				firePropertyChange( PROPERTY_CHANGED_CONSTANT, old, object );
			}
			else if( object instanceof OrderedMap && !collapseOptions )
			{
				// old = selectedOption;
				// ObjClass out = (ObjClass) getOptionFromUser();
				// setObject( object, out );
				// firePropertyChange( PROPERTY_CHANGED_CONSTANT, old, object );
			}
			else if( object instanceof String )
			{
				old = object;
				setObject( (ObjClass) getStringFromUser() );
				firePropertyChange( PROPERTY_CHANGED_CONSTANT, old, object );
			}
		}
		if( isClickable )
		{
			firePropertyChange( PROPERTY_CHANGED_CLICK, false, true );
		}
		// setObject( object, selectedOption );
	}

	@Override
	public void mouseExited( MouseEvent e )
	{
		setMouseOver( false );
		repaint();
	}

	@Override
	public void mouseEntered( MouseEvent e )
	{
		setMouseOver( true );
		repaint();
	}

	@Override
	public void mouseClicked( MouseEvent e )
	{
	}

	private void setMouseOver( boolean mouseOver )
	{
		if( mouseOver )
		{
			background = (!highLighted && (isEditable || isClickable)) ? hot : background;
		}
		else
		{
			background = (!highLighted && (isEditable || isClickable)) ? cold : background;
		}
	}

	@Override
	public void propertyChange( PropertyChangeEvent evt )
	{
		firePropertyChange( PROPERTY_CHANGED_CONSTANT, evt.getOldValue(), evt.getNewValue() );
	}
}