/*
 * JCalendar.java - A bean for choosing a date Copyright (C) 2004 Kai Toedter
 * kai@toedter.com www.toedter.com This program 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
 * of the License, or (at your option) any later version. This program 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 program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.freeplane.core.ui.components.calendar;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;

/**
 * JCalendar is a bean for entering a date by choosing the year, month and day.
 *
 * @author Kai Toedter
 * @version $LastChangedRevision: 95 $
 * @version $LastChangedDate: 2006-05-05 18:43:15 +0200 (Fr, 05 Mai 2006) $
 */
public class JCalendar extends JPanel implements PropertyChangeListener, MouseListener {
	private final class JCalendarPopupMenu extends JPopupMenu {
		/**
		 *
		 */
		private static final long serialVersionUID = 1L;

		@Override
		public void menuSelectionChanged(boolean isIncluded) {
			if (!isIncluded) {
				final Object source = EventQueue.getCurrentEvent().getSource();
				if (source instanceof Component) {
					final Component c = (Component) source;
					isIncluded = SwingUtilities.isDescendingFrom(c, this);
				}
			}
			super.menuSelectionChanged(isIncluded);
		}
	}

	public static final String DATE_PROPERTY = "date";
	/**
	 *
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * Creates a JFrame with a JCalendar inside and can be used for testing.
	 *
	 * @param s
	 *            The command line arguments
	 */
	public static void main(final String[] s) {
		final JFrame frame = new JFrame("JCalendar");
		final JCalendar jcalendar = new JCalendar();
		frame.getContentPane().add(jcalendar);
		frame.pack();
		frame.setVisible(true);
	}

	private Calendar calendar;
	private JPopupMenu calendarPopupMenu;
	final private JTimeChooser timeChooser;
	/** the day chooser */
	final private JDayChooser dayChooser;
	private boolean initialized = false;
	/** the locale */
	private Locale locale;
	/** the month chooser */
	final private JMonthChooser monthChooser;
	final private JPanel monthYearPanel;
	/** the year chhoser */
	final private JYearChooser yearChooser;

	/**
	 * Default JCalendar constructor.
	 */
	public JCalendar() {
		this(null, null, true, true, true);
	}

	/**
	 * JCalendar constructor specifying the month spinner type.
	 *
	 * @param monthSpinner
	 *            false, if no month spinner should be used
	 */
	public JCalendar(final boolean monthSpinner) {
		this(null, null, monthSpinner, true, false);
	}

	/**
	 * JCalendar constructor which allows the initial calendar to be set.
	 *
	 * @param calendar
	 *            the calendar
	 */
	public JCalendar(final Calendar calendar) {
		this(null, null, true, true, false);
		setCalendar(calendar);
	}

	/**
	 * JCalendar constructor which allows the initial date to be set.
	 *
	 * @param date
	 *            the date
	 */
	public JCalendar(final Date date) {
		this(date, null, true, true, false);
	}

	/**
	 * JCalendar constructor specifying both the initial date and the month
	 * spinner type.
	 *
	 * @param date
	 *            the date
	 * @param monthSpinner
	 *            false, if no month spinner should be used
	 */
	public JCalendar(final Date date, final boolean monthSpinner) {
		this(date, null, monthSpinner, true, false);
	}

	/**
	 * JCalendar constructor specifying both the initial date and locale.
	 *
	 * @param date
	 *            the date
	 * @param locale
	 *            the new locale
	 */
	public JCalendar(final Date date, final Locale locale) {
		this(date, locale, true, true, false);
	}

	/**
	 * JCalendar constructor with month spinner parameter.
	 *
	 * @param date
	 *            the date
	 * @param locale
	 *            the locale
	 * @param monthSpinner
	 *            false, if no month spinner should be used
	 * @param weekOfYearVisible
	 *            true, if weeks of year shall be visible
	 * @param timeVisible
	 *            true, if hours and minutes shall be visible
	 */
	public JCalendar(final Date date, final Locale locale, final boolean monthSpinner, final boolean weekOfYearVisible, final boolean timeVisible) {
		setName("JCalendar");
		this.locale = locale;
		if (locale == null) {
			this.locale = Locale.getDefault();
		}
		calendar = Calendar.getInstance();
		setLayout(new BorderLayout());
		monthYearPanel = new JPanel();
		monthYearPanel.setLayout(new BorderLayout());
		monthChooser = new JMonthChooser(monthSpinner);
		yearChooser = new JYearChooser();
		monthChooser.setYearChooser(yearChooser);
		monthYearPanel.add(monthChooser, BorderLayout.WEST);
		monthYearPanel.add(yearChooser, BorderLayout.CENTER);
		monthYearPanel.setBorder(BorderFactory.createEmptyBorder());
		dayChooser = new JDayChooser(weekOfYearVisible);
		dayChooser.addPropertyChangeListener(this);
		dayChooser.addMouseListener(this);
		monthChooser.setDayChooser(dayChooser);
		monthChooser.addPropertyChangeListener(this);
		yearChooser.setDayChooser(dayChooser);
		yearChooser.addPropertyChangeListener(this);
		dayChooser.setYearChooser(yearChooser);
		dayChooser.setMonthChooser(monthChooser);
		add(monthYearPanel, BorderLayout.NORTH);
		add(dayChooser, BorderLayout.CENTER);
		if(timeVisible){
			timeChooser = new JTimeChooser();
			add(timeChooser, BorderLayout.SOUTH);
		}
		else{
			timeChooser = null;
		}
		if (date != null) {
			calendar.setTime(date);
		}
		initialized = true;
		setCalendar(calendar);
	}

	/**
	 * JCalendar constructor allowing the initial locale to be set.
	 *
	 * @param locale
	 *            the new locale
	 */
	public JCalendar(final Locale locale) {
		this(null, locale, true, true, false);
	}

	/**
	 * JCalendar constructor specifying both the locale and the month spinner.
	 *
	 * @param locale
	 *            the locale
	 * @param monthSpinner
	 *            false, if no month spinner should be used
	 */
	public JCalendar(final Locale locale, final boolean monthSpinner) {
		this(null, locale, monthSpinner, true, false);
	}

	public JPopupMenu createPopupMenu() {
		if (calendarPopupMenu != null) {
			return calendarPopupMenu;
		}
		calendarPopupMenu = new JCalendarPopupMenu();
		calendarPopupMenu.add(this);
		return calendarPopupMenu;
	}

	/**
	 * Returns the calendar property.
	 *
	 * @return the value of the calendar property.
	 */
	public Calendar getCalendar() {
		return calendar;
	}

	/**
	 * Returns a Date object.
	 *
	 * @return a date object constructed from the calendar property.
	 */
	public Date getDate() {
		return new Date(calendar.getTimeInMillis());
	}

	/**
	 * Gets the dayChooser attribute of the JCalendar object
	 *
	 * @return the dayChooser value
	 */
	public JDayChooser getDayChooser() {
		return dayChooser;
	}

	/**
	 * Returns the color of the decoration (day names and weeks).
	 *
	 * @return the color of the decoration (day names and weeks).
	 */
	public Color getDecorationBackgroundColor() {
		return dayChooser.getDecorationBackgroundColor();
	}

	/**
	 * Returns the locale.
	 *
	 * @return the value of the locale property.
	 * @see #setLocale
	 */
	@Override
	public Locale getLocale() {
		return locale;
	}

	/**
	 * Gets the maximum number of characters of a day name or 0. If 0 is
	 * returned, dateFormatSymbols.getShortWeekdays() will be used.
	 *
	 * @return the maximum number of characters of a day name or 0.
	 */
	public int getMaxDayCharacters() {
		return dayChooser.getMaxDayCharacters();
	}

	/**
	 * Gets the minimum selectable date.
	 *
	 * @return the minimum selectable date
	 */
	public Date getMaxSelectableDate() {
		return dayChooser.getMaxSelectableDate();
	}

	/**
	 * Gets the maximum selectable date.
	 *
	 * @return the maximum selectable date
	 */
	public Date getMinSelectableDate() {
		return dayChooser.getMinSelectableDate();
	}

	/**
	 * Gets the monthChooser attribute of the JCalendar object
	 *
	 * @return the monthChooser value
	 */
	public JMonthChooser getMonthChooser() {
		return monthChooser;
	}

	/**
	 * Returns the Sunday foreground.
	 *
	 * @return Color the Sunday foreground.
	 */
	public Color getSundayForeground() {
		return dayChooser.getSundayForeground();
	}

	/**
	 * Returns the weekday foreground.
	 *
	 * @return Color the weekday foreground.
	 */
	public Color getWeekdayForeground() {
		return dayChooser.getWeekdayForeground();
	}

	/**
	 * Gets the yearChooser attribute of the JCalendar object
	 *
	 * @return the yearChooser value
	 */
	public JYearChooser getYearChooser() {
		return yearChooser;
	}

	/**
	 * Gets the visibility of the decoration background.
	 *
	 * @return true, if the decoration background is visible.
	 */
	public boolean isDecorationBackgroundVisible() {
		return dayChooser.isDecorationBackgroundVisible();
	}

	/**
	 * Gets the visibility of the decoration border.
	 *
	 * @return true, if the decoration border is visible.
	 */
	public boolean isDecorationBordersVisible() {
		return dayChooser.isDecorationBordersVisible();
	}

	/**
	 * Indicates if the weeks of year are visible..
	 *
	 * @return boolean true, if weeks of year are visible
	 */
	public boolean isWeekOfYearVisible() {
		return dayChooser.isWeekOfYearVisible();
	}

	public void mouseClicked(final MouseEvent e) {
		processMouseEvent(e);
	}

	public void mouseEntered(final MouseEvent e) {
		processMouseEvent(e);
	}

	public void mouseExited(final MouseEvent e) {
		processMouseEvent(e);
	}

	public void mousePressed(final MouseEvent e) {
		processMouseEvent(e);
	}

	public void mouseReleased(final MouseEvent e) {
		processMouseEvent(e);
	}

	/**
	 * JCalendar is a PropertyChangeListener, for its day, month and year
	 * chooser.
	 *
	 * @param evt
	 *            the property change event
	 */
	public void propertyChange(final PropertyChangeEvent evt) {
		if (calendar != null) {
			if (evt.getPropertyName().equals(JDayChooser.DAY_PROPERTY)) {
				calendar.set(Calendar.DAY_OF_MONTH, ((Integer) evt.getNewValue()).intValue());
			}
			else if (evt.getPropertyName().equals(JMonthChooser.MONTH_PROPERTY)) {
				calendar.set(Calendar.MONTH, ((Integer) evt.getNewValue()).intValue());
			}
			else if (evt.getPropertyName().equals(JYearChooser.YEAR_PROPERTY)) {
				calendar.set(Calendar.YEAR, ((Integer) evt.getNewValue()).intValue());
			}
			else if (evt.getPropertyName().equals(JCalendar.DATE_PROPERTY)) {
				calendar.setTime((Date) evt.getNewValue());
			}
		}
		firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
	}

	/**
	 * Sets the background color.
	 *
	 * @param bg
	 *            the new background
	 */
	@Override
	public void setBackground(final Color bg) {
		super.setBackground(bg);
		if (dayChooser != null) {
			dayChooser.setBackground(bg);
		}
	}

	/**
	 * Sets the calendar property. This is a bound property.
	 *
	 * @param c
	 *            the new calendar
	 * @throws NullPointerException
	 *             - if c is null;
	 * @see #getCalendar
	 */
	public void setCalendar(final Calendar c) {
		if (c == null) {
		setDate(null);
        }
        final Calendar oldCalendar = calendar;
        calendar = c;
        yearChooser.setYear(c.get(Calendar.YEAR));
        monthChooser.setMonth(c.get(Calendar.MONTH));
        dayChooser.setDay(c.get(Calendar.DATE));
        if(timeChooser != null)
		timeChooser.setCalendar(calendar);
        firePropertyChange("calendar", oldCalendar, calendar);
	}

	/**
	 * Sets the date. Fires the property change "date".
	 *
	 * @param date
	 *            the new date.
	 * @throws NullPointerException
	 *             - if tha date is null
	 */
	public void setDate(final Date date) {
		final Date oldDate = calendar.getTime();
		calendar.setTime(date);
		final int year = calendar.get(Calendar.YEAR);
		final int month = calendar.get(Calendar.MONTH);
		final int day = calendar.get(Calendar.DAY_OF_MONTH);
		yearChooser.setYear(year);
		monthChooser.setMonth(month);
		dayChooser.setCalendar(calendar);
		dayChooser.setDay(day);
		firePropertyChange(JCalendar.DATE_PROPERTY, oldDate, date);
	}

	/**
	 * Sets the background of days and weeks of year buttons.
	 *
	 * @param decorationBackgroundColor
	 *            the background color
	 */
	public void setDecorationBackgroundColor(final Color decorationBackgroundColor) {
		dayChooser.setDecorationBackgroundColor(decorationBackgroundColor);
	}

	/**
	 * Sets the decoration background visible.
	 *
	 * @param decorationBackgroundVisible
	 *            true, if the decoration background should be visible.
	 */
	public void setDecorationBackgroundVisible(final boolean decorationBackgroundVisible) {
		dayChooser.setDecorationBackgroundVisible(decorationBackgroundVisible);
		setLocale(locale);
	};

	/**
	 * Sets the decoration borders visible.
	 *
	 * @param decorationBordersVisible
	 *            true, if the decoration borders should be visible.
	 */
	public void setDecorationBordersVisible(final boolean decorationBordersVisible) {
		dayChooser.setDecorationBordersVisible(decorationBordersVisible);
		setLocale(locale);
	}

	/**
	 * Enable or disable the JCalendar.
	 *
	 * @param enabled
	 *            the new enabled value
	 */
	@Override
	public void setEnabled(final boolean enabled) {
		super.setEnabled(enabled);
		if (dayChooser != null) {
			dayChooser.setEnabled(enabled);
			monthChooser.setEnabled(enabled);
			yearChooser.setEnabled(enabled);
		}
	}

	/**
	 * Sets the font property.
	 *
	 * @param font
	 *            the new font
	 */
	@Override
	public void setFont(final Font font) {
		super.setFont(font);
		if (dayChooser != null) {
			dayChooser.setFont(font);
			monthChooser.setFont(font);
			yearChooser.setFont(font);
		}
	}

	/**
	 * Sets the foreground color.
	 *
	 * @param fg
	 *            the new foreground
	 */
	@Override
	public void setForeground(final Color fg) {
		super.setForeground(fg);
		if (dayChooser != null) {
			dayChooser.setForeground(fg);
			monthChooser.setForeground(fg);
			yearChooser.setForeground(fg);
		}
	}

	/**
	 * Sets the locale property. This is a bound property.
	 *
	 * @param l
	 *            the new locale value
	 * @see #getLocale
	 */
	@Override
	public void setLocale(final Locale l) {
		if (!initialized) {
			super.setLocale(l);
		}
		else {
			final Locale oldLocale = locale;
			locale = l;
			dayChooser.setLocale(locale);
			monthChooser.setLocale(locale);
			firePropertyChange("locale", oldLocale, locale);
		}
	}

	/**
	 * Sets the maximum number of characters per day in the day bar. Valid
	 * values are 0-4. If set to 0, dateFormatSymbols.getShortWeekdays() will be
	 * used, otherwise theses strings will be reduced to the maximum number of
	 * characters.
	 *
	 * @param maxDayCharacters
	 *            the maximum number of characters of a day name.
	 */
	public void setMaxDayCharacters(final int maxDayCharacters) {
		dayChooser.setMaxDayCharacters(maxDayCharacters);
	}

	/**
	 * Sets the maximum selectable date.
	 *
	 * @param max
	 *            maximum selectable date
	 */
	public void setMaxSelectableDate(final Date max) {
		dayChooser.setMaxSelectableDate(max);
	}

	/**
	 * Sets the minimum selectable date.
	 *
	 * @param min
	 *            minimum selectable date
	 */
	public void setMinSelectableDate(final Date min) {
		dayChooser.setMinSelectableDate(min);
	}

	/**
	 * Sets a valid date range for selectable dates. If max is before min, the
	 * default range with no limitation is set.
	 *
	 * @param min
	 *            the minimum selectable date or null (then the minimum date is
	 *            set to 01\01\0001)
	 * @param max
	 *            the maximum selectable date or null (then the maximum date is
	 *            set to 01\01\9999)
	 */
	public void setSelectableDateRange(final Date min, final Date max) {
		dayChooser.setSelectableDateRange(min, max);
	}

	/**
	 * Sets the Sunday foreground.
	 *
	 * @param sundayForeground
	 *            the sundayForeground to set
	 */
	public void setSundayForeground(final Color sundayForeground) {
		dayChooser.setSundayForeground(sundayForeground);
	}

	/**
	 * Sets the weekday foreground.
	 *
	 * @param weekdayForeground
	 *            the weekdayForeground to set
	 */
	public void setWeekdayForeground(final Color weekdayForeground) {
		dayChooser.setWeekdayForeground(weekdayForeground);
	}

	/**
	 * Sets the week of year visible.
	 *
	 * @param weekOfYearVisible
	 *            true, if weeks of year shall be visible
	 */
	public void setWeekOfYearVisible(final boolean weekOfYearVisible) {
		dayChooser.setWeekOfYearVisible(weekOfYearVisible);
		setLocale(locale);
	}

	public boolean isTimeVisible(){
		return timeChooser != null;
	}
}
