/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */
package com.queplix.core.client.controls.datefield;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MouseListener;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.queplix.core.client.app.vo.uisettings.DialogUISettings;
import com.queplix.core.client.common.OrderedHashMap;
import com.queplix.core.client.common.StringUtil;
import com.queplix.core.client.common.event.Event;
import com.queplix.core.client.common.event.EventListener;
import com.queplix.core.client.common.ui.ButtonData;
import com.queplix.core.client.common.ui.DialogHelper;
import com.queplix.core.client.common.ui.OkayCancelPopup;

import java.util.ArrayList;
import java.util.Date;

/**
 * Class represents internal view realisatio~n for the date field widget.
 *
 * @author Sergey Kozmin, Aliaksandr Melnik
 * @since 14 Sep 2006
 */
class QDateFieldViewImpl extends QDateFieldView
        implements ChangeListener, ClickListener, MouseRightClickListener {
    private static final String BUTTON_IMAGE_PATH = "datefield/button.gif";
    private static final String BUTTON_CSS_STYLE = "simple_button";

    public final static String DATEFIELD_STYLE_NAME_PREFIX = "dateField_";

    private static final String CALENDAR = " : Calendar";

    private static final String INCORRECT_DATE_FORMAT_ERROR_MESSAGE
            = "Incorrect date format";

    private QDateFieldModelImpl model;

    private ButtonWithRightClick pickButton;
    private TextBox dateTimeTextBox;

    private boolean userUpdate;
    private OkayCancelPopup datePickerDialog;
    private DateTimeSelector calendar;

    private boolean showDatePickerDialog;

    private OkayCancelPopup hotDatesDialog;
    private VerticalPanel hotDatesPanel;
    private OrderedHashMap hotDates;

    private static final String SEPARATOR_ITEM_TEXT
            = "__________________________";

    private static final int NOT_ACTIVE_ELEMENT = -1,
            CURRENT_MONTH = 0,
            CURRENT_YEAR = 1,
            MONTH_TO_DATE = 2,
            YEAR_TO_DATE = 3,
            PREVIOUS_MONTH = 4,
            PREVIOUS_YEAR = 5,
            LAST_30_DAYS = 6,
            LAST_3_MONTH = 7,
            LAST_6_MONTH = 8,
            LAST_12_MONTH = 9,
            NEXT_30_DAYS = 10,
            NEXT_3_MONTH = 11,
            NEXT_6_MONTH = 12,
            NEXT_12_MONTH = 13,
            CUSTOM_DATES = 14,
            NULL_DATE = 15,
            TODAY = 16;

    // =========== User actions notification for controller implementation
    public static interface UserActionsListener {
        public void onUserActions(String stringToParse);

        public void onUserActions(Date startDate, Date endDate);
    }

    private UserActionsListener userActionsListener; // the only listener will be the controller implementation

    void addUserActionsListener(UserActionsListener listener) {
        userActionsListener = listener;
    }

    private void fireUserActions(String stringToParse) {
        if(userActionsListener != null) {
            userActionsListener.onUserActions(stringToParse);
        }
    }

    private void fireUserActions(Date startDate, Date endDate) {
        if(userActionsListener != null) {
            userActionsListener.onUserActions(startDate, endDate);
        }
    }
    // ========== End of user actions event

    /**
     * Constuct datefield view with given parameters.
     *
     * @param model  model to render
     * @param layout element layout
     */
    QDateFieldViewImpl(QDateFieldModelImpl model, int layout) {
        super(model, layout);
        this.model = model;
        initializeGUI(model.getMeta().getCaption(), StringUtil.imgSrc(
                BUTTON_IMAGE_PATH));
    }

    public void onModelMetaChanged() {
        dataChanged();
    }

    public void onModelDataChanged() {
        super.onModelDataChanged();
        dataChanged();
    }

    private void initializeGUI(String fieldCaption, String buttonValue) {
        setCaption(fieldCaption);
        dateTimeTextBox = new TextBox();
        dateTimeTextBox.setMaxLength(24);
        dateTimeTextBox.addChangeListener(this);
        dateTimeTextBox.addKeyboardListener(new KeyboardListener() {
            public void onKeyDown(Widget sender, char keyCode, int modifiers) {
                if(keyCode == KeyboardListener.KEY_ENTER) {
                    onChange(dateTimeTextBox);
                }
                if(model.getData().getStartDate() != null) {
                    switch(keyCode) {
                        case KeyboardListener.KEY_UP:
                            model.getData().setStartDate(new Date(model
                                    .getData().getStartDate().getTime()
                                    + DateHelper.DAY));
                            if(isSearchMode()) {
                                model.getData().setEndDate((Date) model
                                        .getData().getStartDate().clone());
                            }
                            fireUserActions(model.getData().getStartDate(),
                                    isSearchMode() ? model.getData()
                                            .getEndDate():null);
                            break;
                        case KeyboardListener.KEY_DOWN:
                            model.getData().setStartDate(new Date(model
                                    .getData().getStartDate().getTime()
                                    - DateHelper.DAY));
                            if(isSearchMode()) {
                                model.getData().setEndDate((Date) model
                                        .getData().getStartDate().clone());
                            }
                            fireUserActions(model.getData().getStartDate(),
                                    isSearchMode() ? model.getData()
                                            .getEndDate():null);
                            break;
                    }
                }
            }

            public void onKeyPress(Widget sender, char keyCode, int modifiers) {
            }

            public void onKeyUp(Widget sender, char keyCode, int modifiers) {
            }
        });
        dateTimeTextBox.addStyleName("styled_input");
        Panel internalPanel = new HorizontalPanel();
        internalPanel.add(dateTimeTextBox);

        pickButton = new ButtonWithRightClick(buttonValue);
        pickButton.setStyleName(BUTTON_CSS_STYLE);
        pickButton.addClickListener(this);
        pickButton.addRightClickListener(this);
        internalPanel.add(pickButton);

        addToPanel(internalPanel);

        initPanel();
    }

    public void formated() {
        dateTimeTextBox.setText(model.getData().getFormatedDate());
    }

    /**
     * Implementation of the view string.
     * Expression "model.getData().getFormatedDate() == null" - indicates if clear was performed.
     * Used for clear DataField with out error message. (enter invalida date, click clear => data field cleared with out error msg)
     */
    public void dataChanged() {
        if(!userUpdate) {
            if(model.getData().getFormatedDate() == null) {
                model.getData().setFormatedDate("");
            }
            dateTimeTextBox.setText(model.getData().getFormatedDate());
        }
    }

    protected void onModeChanged(int newMode) {
        if(calendar != null) {
            switch(newMode) {
                case MODE_EDIT: {
                    calendar.setMode(MODE_EDIT);
                }
                case MODE_DISABLED: {
                    calendar.setMode(MODE_DISABLED);
                }
            }
        }
    }

    protected void setEnabled(boolean isEnabled) {
        String readOnly = isEnabled ? null:"readonly";
        DOM.setAttribute(dateTimeTextBox.getElement(), "readOnly", readOnly);
        if(!isEnabled && datePickerDialog != null && datePickerDialog
                .isVisible()) {
            datePickerDialog.setVisible(false);
        }
    }

    /**
     * Updates model, when user updates it in dateTimeTextBox
     *
     * @param sender dateTimeTextBox component
     */
    public void onChange(Widget sender) {
        String stringToParse = dateTimeTextBox.getText().trim();
        if(stringToParse.equalsIgnoreCase("null")) {
            getModel().getBaseData().clear();
            model.getData().setFormatedDate("null");
        } else if(stringToParse.equals("")) {
            dateTimeTextBox.setText("");
            getModel().getBaseData().clear();
            userUpdate = false;
        } else {
            fireUserActions(stringToParse);
            userUpdate = true;
        }
    }

    /**
     * Expression "model.getData().getFormatedDate() == null" - indicates if clear was performed.
     * Used for clear DataField without error message. (enter invalida date, click clear => data field cleared with out error msg)
     */
    void parseFailed() {
        if(model.getData().getFormatedDate() != null) {
            DialogHelper.showModalMessageDialog(
                    INCORRECT_DATE_FORMAT_ERROR_MESSAGE);
        } else {
            model.getData().setFormatedDate("");
        }
        dateTimeTextBox.setText(model.getData().getFormatedDate());
        userUpdate = false;
    }

    void parseSucceed() {
        model.getData().setFormatedDate(dateTimeTextBox.getText());
        if(showDatePickerDialog) {
            showDatePickerDialog();
        }
        userUpdate = false;
    }

    public void onRightClick(Widget sender) {
        showDatePickerDialog = true;
        if(!isReportDesignMode() && !isDisabled()) {
            showHotDatesDialog();
        }
    }

    private void showHotDatesDialog() {
        initDropDown();
        hotDatesDialog.show(pickButton);
        hotDatesPanel.setWidth(
                hotDatesDialog.getDialogOffetWidth() - 10 + "px");
    }

    private void initDropDown() {
        if(hotDatesDialog != null) {
            return;
        }
        hotDatesDialog = new OkayCancelPopup(
                model.getMeta().getCaption() + " : Select Date...");
        hotDatesPanel = new VerticalPanel();
        ArrayList labels = getLabels();
        for(int i = 0; i < labels.size(); i++) {
            Label label = (Label) labels.get(i);
            if(((Integer) hotDates.get(label)).intValue() >= 0) {
                label.setStyleName("iconButton");
                label.addClickListener(labelClickListener);
                label.addMouseListener(labelMouseListener);
            }
            hotDatesPanel.add(label);
        }

        hotDatesDialog.setWidget(hotDatesPanel, ButtonData.CANCEL);
    }

    private ArrayList getLabels() {
        if(hotDates != null) {
            return hotDates.getKeys();
        }
        hotDates = new OrderedHashMap();
        hotDates.put(new Label("Today"), new Integer(TODAY));
        hotDates.put(new Label("Current month"), new Integer(CURRENT_MONTH));
        hotDates.put(new Label("Current year"), new Integer(CURRENT_YEAR));
        hotDates.put(new Label("Month to date"), new Integer(MONTH_TO_DATE));
        hotDates.put(new Label("Year to date"), new Integer(YEAR_TO_DATE));
        hotDates.put(new Label("Previous month"), new Integer(PREVIOUS_MONTH));
        hotDates.put(new Label("Previous year"), new Integer(PREVIOUS_YEAR));
        hotDates.put(new Label("Last 30 days"), new Integer(LAST_30_DAYS));
        hotDates.put(new Label("Last 3 months"), new Integer(LAST_3_MONTH));
        hotDates.put(new Label("Last 6 months"), new Integer(LAST_6_MONTH));
        hotDates.put(new Label("Last 12 months"), new Integer(LAST_12_MONTH));
        hotDates.put(new Label("Next 30 days"), new Integer(NEXT_30_DAYS));
        hotDates.put(new Label("Next 3 months"), new Integer(NEXT_3_MONTH));
        hotDates.put(new Label("Next 6 months"), new Integer(NEXT_6_MONTH));
        hotDates.put(new Label("Next 12 months"), new Integer(NEXT_12_MONTH));
        hotDates.put(new Label(SEPARATOR_ITEM_TEXT), new Integer(
                NOT_ACTIVE_ELEMENT));
        hotDates.put(new Label("Custom dates"), new Integer(CUSTOM_DATES));
        hotDates.put(new Label("NULL"), new Integer(NULL_DATE));
        return hotDates.getKeys();
    }

    private static final MouseListener labelMouseListener
            = new MouseListener() {
        public void onMouseDown(Widget sender, int x, int y) {
        }

        public void onMouseEnter(Widget sender) {
            sender.setStyleName("calendar_hotDates_highlighted");
        }

        public void onMouseLeave(Widget sender) {
            sender.setStyleName("calendar_hotDates");
        }

        public void onMouseMove(Widget sender, int x, int y) {
        }

        public void onMouseUp(Widget sender, int x, int y) {
        }
    };

    private ClickListener labelClickListener = new ClickListener() {
        public void onClick(Widget sender) {
            int index = ((Integer) hotDates.get(sender)).intValue();
            sender.setStyleName("calendar_hotDates");
            hotDatesDialog.hide();

            Date startDate = null;
            Date endDate = null;
            switch(index) {
                case TODAY:
                    startDate = model.getData().getNowDate();
                    endDate = startDate;
                    break;
                case CURRENT_MONTH:
                    startDate = DateHelper.getFirstMonthDate(
                            model.getData().getNowDate());
                    endDate = DateHelper.getLastMonthDate(
                            model.getData().getNowDate());
                    break;
                case CURRENT_YEAR:
                    startDate = DateHelper.getFirstYearDate(
                            model.getData().getNowDate());
                    endDate = DateHelper.getLastYearDate(
                            model.getData().getNowDate());
                    break;
                case MONTH_TO_DATE:
                    startDate = DateHelper.getFirstMonthDate(
                            model.getData().getNowDate());
                    endDate = model.getData().getNowDate();
                    break;
                case YEAR_TO_DATE:
                    startDate = DateHelper.getFirstYearDate(
                            model.getData().getNowDate());
                    endDate = model.getData().getNowDate();
                    break;
                case PREVIOUS_MONTH:
                    startDate = (Date) model.getData().getNowDate().clone();
                    startDate.setMonth(startDate.getMonth() - 1);
                    endDate = DateHelper.getLastMonthDate(startDate);
                    startDate = DateHelper.getFirstMonthDate(startDate);
                    break;
                case PREVIOUS_YEAR:
                    startDate = (Date) model.getData().getNowDate().clone();
                    startDate.setYear(startDate.getYear() - 1);
                    endDate = DateHelper.getLastYearDate(startDate);
                    startDate = DateHelper.getFirstYearDate(startDate);
                    break;
                case LAST_30_DAYS:
                    startDate = (Date) model.getData().getNowDate().clone();
                    startDate.setDate(startDate.getDate() - 29);
                    endDate = model.getData().getNowDate();
                    break;
                case LAST_3_MONTH:
                    startDate = (Date) model.getData().getNowDate().clone();
                    startDate.setMonth(startDate.getMonth() - 3);
                    endDate = model.getData().getNowDate();
                    break;
                case LAST_6_MONTH:
                    startDate = (Date) model.getData().getNowDate().clone();
                    startDate.setMonth(startDate.getMonth() - 6);
                    endDate = model.getData().getNowDate();
                    break;
                case LAST_12_MONTH:
                    startDate = (Date) model.getData().getNowDate().clone();
                    startDate.setMonth(startDate.getMonth() - 12);
                    endDate = model.getData().getNowDate();
                    break;
                case NEXT_30_DAYS:
                    startDate = model.getData().getNowDate();
                    endDate = (Date) model.getData().getNowDate().clone();
                    endDate.setDate(endDate.getDate() + 29);
                    break;
                case NEXT_3_MONTH:
                    startDate = model.getData().getNowDate();
                    endDate = (Date) model.getData().getNowDate().clone();
                    endDate.setMonth(endDate.getMonth() + 3);
                    break;
                case NEXT_6_MONTH:
                    startDate = model.getData().getNowDate();
                    endDate = (Date) model.getData().getNowDate().clone();
                    endDate.setMonth(endDate.getMonth() + 6);
                    break;
                case NEXT_12_MONTH:
                    startDate = model.getData().getNowDate();
                    endDate = (Date) model.getData().getNowDate().clone();
                    endDate.setMonth(endDate.getMonth() + 12);
                    break;
                case CUSTOM_DATES:
                    QDateFieldViewImpl.this.onClick(pickButton);
                    return;
                case NULL_DATE:
                    model.getData().clear();
                    model.getData().setFormatedDate("null");
                    dateTimeTextBox.setText("null");
                    return;
            }
            model.getData().setStartDate(startDate);
            model.getData().setEndDate(endDate);
            onOkButtonClick(false);
        }
    };

    /**
     * Shows date dialog.
     *
     * @param sender
     */
    public void onClick(Widget sender) {
        showDatePickerDialog = true;
        if(!userUpdate && !isReportDesignMode()) {
            if(isDisabled()) {
                if(!getModel().getBaseData().isEmpty()) {
                    showDatePickerDialog();
                }
            } else {
                showDatePickerDialog();
            }
        }
    }

    private void showDatePickerDialog() {
        showDatePickerDialog = false;
        if(datePickerDialog == null) {
            initializeDatePickerDialog(model.getData().getNowDate());
        }
        calendar.setMode(getViewMode());
        if(!super.isSearchMode()) {
            Date selectedDate = model.getData().getStartDate();
            Date dateToSet = null;
            if(selectedDate != null) {
                dateToSet = (Date) selectedDate.clone();
            }
            if(model.getData().getNowDate() != null) {
                calendar.setCalendarState(dateToSet, null,
                        (Date) model.getData().getNowDate().clone());
            }
        } else {
            Date startDate = model.getData().getStartDate();
            Date endDate = model.getData().getEndDate();

            Date startDateToSet = null;
            if(startDate != null) {
                startDateToSet = (Date) startDate.clone();
            }
            Date endDateToSet = null;
            if(endDate != null) {
                endDateToSet = (Date) endDate.clone();
            }
            if(model.getData().getNowDate() != null) {
                calendar.setCalendarState(startDateToSet, endDateToSet,
                        (Date) model.getData().getNowDate().clone());
            }
        }
        calendar.setPreviouslySelectedElement(null);
        calendar.updateTime();
        datePickerDialog.show(dateTimeTextBox);
    }

    private boolean handleClick() {
        if(model.getMeta().isReadOnly()) {
            return isSearchMode();
        }
        return true;
    }

    private void initializeDatePickerDialog(Date nowDate) {
        DockPanel mainPanel = new DockPanel();
        DockPanel btnsPanel = new DockPanel();

        calendar = new DateTimeSelector(nowDate, model.getMeta().isReadOnly());
        calendar.setMode(getViewMode());
        calendar.addDateTimeSelectorListener(
                new DateTimeSelector.DateTimeSelectorListener() {
                    public void onDoubleClick() {
                        onOkButtonClick(true);
                    }
                });

        mainPanel.add(calendar, DockPanel.CENTER);
        mainPanel.add(btnsPanel, DockPanel.SOUTH);

        datePickerDialog = new OkayCancelPopup(
                getModel().getBaseMeta().getCaption() + CALENDAR, true);
        datePickerDialog.setWidget(mainPanel);
        datePickerDialog.setUISettings(model.getBaseMeta().getUISettings());

        datePickerDialog.getEventSource().addEventListener(new EventListener() {
            public void onEvent(Event event, Widget sender) {
                if(OkayCancelPopup.Events.OK == event) {
                    if(!isDisabled()) {
                        onOkButtonClick(true);
                    }
                } else if(OkayCancelPopup.Events.CANCEL == event) {
                    datePickerDialog.hide();
                }
            }
        });
    }

    private void onOkButtonClick(boolean wasDialogShown) {
        if(isDisabled() || !handleClick()) {
            return;
        }

        if(wasDialogShown) {
            datePickerDialog.hide();
        }

        Date startDate = wasDialogShown ? calendar.getStartDate()
                :model.getData().getStartDate();
        if(startDate != null) {
            if(wasDialogShown) {
                model.getData().setStartDate(new Date(startDate.getTime()));
            }
            if(!super.isSearchMode()) {
                model.getData().setEndDate(null);
                fireUserActions(startDate, null);
            } else {
                if(wasDialogShown) {
                    Date endDate = calendar.getEndDate();
                    model.getData().setEndDate(
                            endDate == null ? startDate:new Date(
                                    endDate.getTime()));
                }
                fireUserActions(
                        model.getData().getStartDate(),
                        model.getData().getEndDate()
                );
            }
        } else {
            dateTimeTextBox.setText("");
        }
    }

    protected void setClientWidth(String clientWidth) {
        dateTimeTextBox.setWidth(clientWidth);
    }

    public int getClientWidth() {
        return dateTimeTextBox.getOffsetWidth();
    }

    public int getFilledWidth() {
        return super.getFilledWidth() + pickButton.getOffsetWidth();
    }

    protected DialogUISettings getUISettings() {
        return datePickerDialog != null ? datePickerDialog.getUISettings():null;
    }

}