/**
 * Copyright (C) 2019 Patrik Dufresne Service Logiciel <info@patrikdufresne.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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package com.planimod.ui.views;

import static com.planimod.ui.Localized._;

import java.util.Calendar;
import java.util.Date;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuCreator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;

import com.patrikdufresne.managers.ManagerException;
import com.patrikdufresne.util.DateFormatRegistry;
import com.planimod.core.PlanimodManagers;
import com.planimod.core.TimeRanges;
import com.planimod.ui.PlanimodPolicy;
import com.planimod.ui.theme.Resources;

/**
 * Action to change the planner period.
 * 
 * @author Patrik Dufresne
 * 
 */
public class ChangeDateAction extends Action {

    public static final String DATE = "date";
    /**
     * Next style action.
     */
    private static final int DIRECTION_NEXT = 1;
    /**
     * Previous style action.
     */
    private static final int DIRECTION_PREVIOUS = -1;

    /**
     * Today style action.
     */
    private static final int DIRECTION_TODAY = 0;

    /**
     * Create a new action to go foward in time.
     * 
     * @return
     */
    public static ChangeDateAction nextAction(PlanimodManagers managers) {
        return new ChangeDateAction(managers, DIRECTION_NEXT);
    }

    /**
     * Create a new action to go back in time.
     * 
     * @return
     */
    public static ChangeDateAction previousAction(PlanimodManagers managers) {
        return new ChangeDateAction(managers, DIRECTION_PREVIOUS);
    }

    /**
     * Query the first day of week using the given managers.
     * 
     * @param managers
     * @return
     */
    private static int queryFirstDayOfWeek(PlanimodManagers managers) {
        try {
            return managers.getApplicationSettingManager().getFirstDayOfWeek();
        } catch (ManagerException e) {
            PlanimodPolicy.showException(e);
            return Calendar.SUNDAY;
        }
    }

    /**
     * Create an action to go at the current date.
     * 
     * @return
     */
    public static ChangeDateAction todayAction(PlanimodManagers managers) {
        return new ChangeDateAction(managers, DIRECTION_TODAY);
    }

    /**
     * Define the date state to update.
     */
    private Date date;

    /**
     * The style of this action. One of the STYLE_* constants.
     */
    private final int direction;

    /**
     * A menu creator to display steps.
     */
    private IMenuCreator menuCreator = new IMenuCreator() {

        private Menu menu;

        @Override
        public void dispose() {
            if (this.menu != null) {
                this.menu.dispose();
                this.menu = null;
            }
        }

        @Override
        public Menu getMenu(Control parent) {
            if (this.menu != null) this.menu.dispose();
            this.menu = new Menu(parent);
            return createMenu(this.menu);
        }

        @Override
        public Menu getMenu(Menu parent) {
            if (this.menu != null) this.menu.dispose();
            this.menu = new Menu(parent);
            return createMenu(this.menu);
        }
    };

    /**
     * Listener on menu item.
     */
    private Listener privateListener = new Listener() {

        @Override
        public void handleEvent(Event event) {
            MenuItem menuitem = (MenuItem) event.widget;
            Date step = (Date) menuitem.getData();
            setDate(step);
        }
    };

    private final int firstDayOfWeek;

    /**
     * Create a new action.
     * 
     * @param planner
     *            the planner to look for.
     */
    public ChangeDateAction(int direction, int firstDayOfWeek) {
        super(null, direction == DIRECTION_TODAY ? IAction.AS_PUSH_BUTTON : IAction.AS_DROP_DOWN_MENU);
        this.direction = direction;
        this.firstDayOfWeek = firstDayOfWeek;
        switch (this.direction) {
        case DIRECTION_NEXT:
            setText(_("Suivante"));
            setToolTipText(_("Affiche la période suivante"));
            setImageDescriptor(Resources.getImageDescriptor(Resources.ICON_GO_NEXT_16));
            setMenuCreator(this.menuCreator);
            break;
        case DIRECTION_PREVIOUS:
            setText(_("Précédente"));
            setToolTipText(_("Affiche la période précédente"));
            setImageDescriptor(Resources.getImageDescriptor(Resources.ICON_GO_PREVIOUS_16));
            setMenuCreator(this.menuCreator);
            break;
        case DIRECTION_TODAY:
            setText(_("Suivante"));
            setToolTipText(_("Affiche la période suivante"));
            setImageDescriptor(Resources.getImageDescriptor(Resources.ICON_GO_HOME_16));
            break;
        }
        setEnabled(canRun());
    }

    public ChangeDateAction(PlanimodManagers managers, int direction) {
        this(direction, queryFirstDayOfWeek(managers));
    }

    /**
     * Returns True if the action can be run.
     * 
     * @return
     */
    protected boolean canRun() {
        return this.date != null;
    }

    /**
     * Called every time the menu need to be displayed.
     * 
     * @param menu
     *            the menu to be populate
     * @return the menu
     */
    protected Menu createMenu(Menu menu) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(this.date);
        // Create week steps
        for (int count = 1; count <= 4; count++) {
            cal.setTime(this.date);
            cal.add(Calendar.WEEK_OF_YEAR, this.direction * count);
            Date step = cal.getTime();
            createMenuItem(menu, step);
        }
        // Create month steps
        for (int count = 2; count <= 11; count++) {
            cal.setTime(this.date);
            cal.add(Calendar.MONTH, this.direction * count);
            Date step = cal.getTime();
            createMenuItem(menu, step);
        }
        return menu;
    }

    /**
     * Create a new menu item to represent the element.
     * 
     * @param menu
     * 
     * @param step
     *            the date step.
     */
    protected void createMenuItem(Menu menu, Date step) {
        MenuItem item = new MenuItem(menu, SWT.PUSH);
        item.setText(getDateLabel(step));
        item.setData(step);
        item.addListener(SWT.Selection, this.privateListener);
    }

    /**
     * Returns the current date state of null if not define.
     * 
     * @return the date state or null.
     */
    public Date getDate() {
        if (this.date == null) {
            return null;
        }
        return new Date(this.date.getTime());
    }

    /**
     * Format the date for the menu item.
     * 
     * @param step
     *            the date step.
     * @return
     */
    protected String getDateLabel(Date step) {
        return DateFormatRegistry.format(TimeRanges.getWeekStart(step, this.firstDayOfWeek), DateFormatRegistry.DATE | DateFormatRegistry.SHORT, true);
    }

    /**
     * This implementation change the planner period.
     */
    @Override
    public void run() {
        // Get the date value from the state
        if (!canRun()) {
            return;
        }
        // Update the date value.
        Calendar cal = Calendar.getInstance();
        cal.setTime(this.date);
        if (this.direction == 0) {
            setDate(new Date());
        } else {
            cal.add(Calendar.WEEK_OF_YEAR, this.direction);
            setDate(cal.getTime());
        }
    }

    /**
     * Sets the date state to be updated.
     * 
     * @param date
     *            the date.
     */
    public void setDate(Date date) {
        firePropertyChange(DATE, this.date, this.date = new Date(date.getTime()));
        setEnabled(canRun());
    }
}
