/*
 * 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.ui.ChangeListener;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.queplix.core.client.common.StringUtil;
import com.queplix.core.client.common.ui.DialogHelper;
import com.queplix.core.client.controls.QFormElementView;

import java.util.Date;

/**
 * Date and time selector based on GWT
 *
 * @author Sergey Kozmin, Alexander Melnik
 * @since 18 Sep 2006
 */
class DateTimeSelector extends DockPanel
        implements ChangeListener, DateElement.DataElementListener {
    private static final int BEGIN_YEAR = 1900;
    private static final String TIME_SEPARATOR = ":";
    private static final int DECEMBER_MONTH = 11;
    private static final int JANUARY_MONTH = 0;

    private static final String COMMON_CELL_STYLE = QDateFieldViewImpl
            .DATEFIELD_STYLE_NAME_PREFIX + "commonCell";
    private static final String SELECTED_CELL_STYLE = QDateFieldViewImpl
            .DATEFIELD_STYLE_NAME_PREFIX + "selectedCell";
    private static final String SELECTABLE_CELL_STYLE = QDateFieldViewImpl
            .DATEFIELD_STYLE_NAME_PREFIX + "selectableCell";
    private static final String UNSELECTABLE_CELL_STYLE = QDateFieldViewImpl
            .DATEFIELD_STYLE_NAME_PREFIX + "unSelectableCell";
    private static final String HEADER_STYLE = QDateFieldViewImpl
            .DATEFIELD_STYLE_NAME_PREFIX + "header";

    private static final String YEAR_NEGATIVE_ERROR_MESSAGE
            = "Year value should be positive integer";
    private static final String YEAR_IS_NOT_INTEGER_ERROR_MESSAGE
            = "Year value is not correct integer";
    private static final String HOURS_IS_NOT_CORRECT_INTEGER_ERROR_MESSAGE
            = "Hours and minutes should be positive integers";
    private static final String HOURS_IS_IN_INCORRECT_FORMAT_ERROR_MESSAGE
            = "Time value is not correct, should use \"HH:MM\" pattern";

    public final static String[] MONTHS = {
            "January",
            "February",
            "March",
            "April",
            "May",
            "June",
            "July",
            "August",
            "September",
            "October",
            "November",
            "December"
    };

    private final static String[] DAYS = {
            "Monday",
            "Tuesday",
            "Wednesday",
            "Thursday",
            "Friday",
            "Saturday",
            "Sunday"
    };

    private DockPanel datePanel = new DockPanel();
    private DockPanel monthAndYearPanel = new DockPanel();
    private Grid daysTableGrid = new Grid(DateHelper.WEEK_LENGHT,
            DateHelper.WEEK_PER_SCREEN + 1);
    private ListBox monthComboBox = new ListBox();
    private TextBox yearTextBox = new TextBox();
    private TextBox timeTextBox = new TextBox();

    private Date shownDate;
    private Date startDate;
    private Date endDate;

    private boolean chooseFirstDate;

    private int mode;

    private DateTimeSelectorListener dateTimeSelectorListener;

    private Date previouslySelectedElement;

    private boolean isReadOnly;

    public interface DateTimeSelectorListener {
        public void onDoubleClick();
    }

    public void addDateTimeSelectorListener(DateTimeSelectorListener listener) {
        dateTimeSelectorListener = listener;
    }

    private void fireDateTimeSelectorEvent() {
        if(dateTimeSelectorListener != null) {
            dateTimeSelectorListener.onDoubleClick();
        }
    }

    public DateTimeSelector(Date shownDateIn, boolean isReadOnly) {
        this.isReadOnly = isReadOnly;
        setCalendarState(null, null, shownDateIn);
        initializeUI();
    }

    private void initializeUI() {
        for(int i = 0; i < MONTHS.length; i++) {
            monthComboBox.addItem(MONTHS[i]);
        }
        monthComboBox.setVisibleItemCount(1);
        monthComboBox.addChangeListener(this);
        monthComboBox.addStyleName("styled_input");

        yearTextBox.setMaxLength(4);
        yearTextBox.setVisibleLength(4);
        yearTextBox.addChangeListener(this);
        yearTextBox.addStyleName("styled_input");

        monthAndYearPanel.add(monthComboBox, DockPanel.WEST);
        monthAndYearPanel.add(yearTextBox, DockPanel.EAST);

        daysTableGrid.getRowFormatter().setStyleName(0, HEADER_STYLE);
        daysTableGrid.setCellPadding(1);
        daysTableGrid.setCellSpacing(0);
        for(int i = 0; i < DAYS.length; i++) {
            String dayName = DAYS[i];
            daysTableGrid.setHTML(0, i, dayName.substring(0, 3));
        }

        datePanel.add(monthAndYearPanel, DockPanel.NORTH);
        datePanel.add(daysTableGrid, DockPanel.SOUTH);

        HorizontalPanel timePanel = new HorizontalPanel();
        timePanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
        timePanel.setWidth("100%");

        Label timeLabel = new Label("Time:  ");
        timePanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
        timePanel.add(timeLabel);
        timePanel.setCellWidth(timeLabel, "100%");

        timePanel.add(timeTextBox);
        timeTextBox.setMaxLength(5);
        timeTextBox.setVisibleLength(5);
        timeTextBox.addChangeListener(this);

        timeTextBox.addStyleName("styled_input");

        add(datePanel, DockPanel.NORTH);
        add(timePanel, DockPanel.CENTER);
    }

    public Date getStartDate() {
        return startDate;
    }

    public void setCalendarState(Date startDate, Date endDate,
                                 Date shownDateIn) {
        chooseFirstDate = true;

        if(startDate != null && endDate != null
                && startDate.getTime() == endDate.getTime()) {
            chooseFirstDate = false;
        }

        if(startDate != null) {
            this.startDate = (Date) startDate.clone();
        } else {
            this.startDate = DateHelper.getUserDate();
            chooseFirstDate = false;
        }

        if(endDate != null) {
            this.endDate = new Date(endDate.getTime());
        } else {
            this.endDate = null;
        }

        shownDate = DateHelper.getUserDate();

        updateTime();
        updateDays();
        updateMonth();
        updateYear();
    }

    public void onChange(Widget sender) {
        if(sender == monthComboBox) {
            monthChanged();
        } else if(sender == yearTextBox) {
            yearChanged();
        } else if(sender == timeTextBox) {
            timeChanged();
        }
    }

    private void timeChanged() {
        String timeText = timeTextBox.getText();
        String values[] = timeText.split(TIME_SEPARATOR, 3);

        try {
            int hours = Integer.parseInt(values[0]);
            if(startDate != null) {
                startDate.setHours(hours);
            }
            shownDate.setHours(hours);

            int minutes = Integer.parseInt(values[1]);
            if(startDate != null) {
                startDate.setMinutes(minutes);
            }
            shownDate.setMinutes(minutes);
        } catch (NumberFormatException e) {
            DialogHelper.showModalErrorDialog(
                    HOURS_IS_NOT_CORRECT_INTEGER_ERROR_MESSAGE, e);
            updateTime();
        } catch (Exception e) {
            DialogHelper.showModalErrorDialog(
                    HOURS_IS_IN_INCORRECT_FORMAT_ERROR_MESSAGE, e);
            updateTime();
        }
    }

    private void yearChanged() {
        int year = shownDate.getYear();
        String newYear = yearTextBox.getText();
        try {
            year = Integer.parseInt(newYear) - BEGIN_YEAR;
            if(year > 0) {
                shownDate.setYear(year);
                updateDays();
            } else {
                DialogHelper.showModalMessageDialog(
                        YEAR_NEGATIVE_ERROR_MESSAGE);
                yearTextBox.setText(String.valueOf(year));
            }
        } catch (NumberFormatException e) {
            DialogHelper.showModalErrorDialog(YEAR_IS_NOT_INTEGER_ERROR_MESSAGE,
                    e);
            yearTextBox.setText(String.valueOf(year));
        }
    }

    private void monthChanged() {
        shownDate.setMonth(monthComboBox.getSelectedIndex());
        updateDays();
    }

    private void updateDays() {
        Date tt = (Date) shownDate.clone();
        tt.setMonth(tt.getMonth() - 1);
        Date[][] dates = DateHelper.getCalendarDates(tt);

        if(mode == QDateFieldView.MODE_SEARCH && startDate != null
                && endDate != null) {
            if(startDate.getTime() > endDate.getTime()) {
                Date tmp = (Date) startDate.clone();
                startDate = (Date) endDate.clone();
                endDate = (Date) tmp.clone();
            }
        }

        boolean tryToSetunselected;
        for(int i = 0; i < dates.length; i++) {
            for(int j = 0; j < dates[0].length; j++) {
                tryToSetunselected = true;
                DateElement element = new DateElement(dates[i][j].getDate());
                element.addStyleName(COMMON_CELL_STYLE);
                if(shownDate.getMonth() == dates[i][j].getMonth()) {
                    element.addStyleName(SELECTABLE_CELL_STYLE);
                    element.addDataElementLisner(this);
                }

                if(mode != QDateFieldView.MODE_SEARCH) {
                    if(isEqual(startDate, dates[i][j])) {
                        element.addStyleName(SELECTED_CELL_STYLE);
                        tryToSetunselected = false;
                    }
                } else {
                    if(startDate != null && endDate != null) {
                        if(!chooseFirstDate) {
                            if(isEqual(startDate, dates[i][j])) {
                                element.addStyleName(SELECTED_CELL_STYLE);
                                tryToSetunselected = false;
                            }
                        } else {
                            Date tmp = dates[i][j];
                            int startYear = startDate.getYear();
                            int endYear = endDate.getYear();
                            int tmpYear = tmp.getYear();
                            int startMonth = startDate.getMonth();
                            int endMonth = endDate.getMonth();
                            int tmpMonth = tmp.getMonth();
                            int startdate = startDate.getDate();
                            int enddate = endDate.getDate();
                            int tmpdate = tmp.getDate();

                            if(startYear == tmpYear && endYear == tmpYear) {
                                if(startMonth == tmpMonth
                                        && endMonth == tmpMonth) {
                                    if(startdate <= tmpdate
                                            && enddate >= tmpdate) {
                                        element.addStyleName(
                                                SELECTED_CELL_STYLE);
                                        tryToSetunselected = false;
                                    }
                                } else if(startMonth < tmpMonth
                                        && endMonth == tmpMonth) {
                                    if(enddate >= tmpdate) {
                                        element.addStyleName(
                                                SELECTED_CELL_STYLE);
                                        tryToSetunselected = false;
                                    }
                                } else if(startMonth < tmpMonth
                                        && endMonth > tmpMonth) {
                                    element.addStyleName(SELECTED_CELL_STYLE);
                                    tryToSetunselected = false;
                                } else if(startMonth == tmpMonth
                                        && endMonth > tmpMonth) {
                                    if(startdate <= tmpdate) {
                                        element.addStyleName(
                                                SELECTED_CELL_STYLE);
                                        tryToSetunselected = false;
                                    }
                                }
                            } else
                            if(startYear < tmpYear && endYear == tmpYear) {
                                if(endMonth == tmpMonth) {
                                    if(enddate >= tmpdate) {
                                        element.addStyleName(
                                                SELECTED_CELL_STYLE);
                                        tryToSetunselected = false;
                                    }
                                } else if(endMonth > tmpMonth) {
                                    element.addStyleName(SELECTED_CELL_STYLE);
                                    tryToSetunselected = false;
                                }
                            } else
                            if(startYear < tmpYear && endYear > tmpYear) {
                                element.addStyleName(SELECTED_CELL_STYLE);
                                tryToSetunselected = false;
                            } else
                            if(startYear == tmpYear && endYear > tmpYear) {
                                if(startMonth == tmpMonth) {
                                    if(startdate <= tmpdate) {
                                        element.addStyleName(
                                                SELECTED_CELL_STYLE);
                                        tryToSetunselected = false;
                                    }
                                } else if(startMonth < tmpMonth) {
                                    element.addStyleName(SELECTED_CELL_STYLE);
                                    tryToSetunselected = false;
                                }
                            }
                        }
                    } else {
                        if(isEqual(startDate, dates[i][j]) || isEqual(endDate,
                                dates[i][j])) {
                            element.addStyleName(SELECTED_CELL_STYLE);
                            tryToSetunselected = false;
                        }
                    }
                }
                if(tryToSetunselected && dates[i][j].getMonth() != shownDate
                        .getMonth()) {
                    element.addStyleName(UNSELECTABLE_CELL_STYLE);
                }
                daysTableGrid.setWidget(i, j, element);
            }
        }
    }

    private boolean isEqual(Date one, Date two) {
        if(one != null &&
                one.getYear() == two.getYear() &&
                one.getMonth() == two.getMonth() &&
                one.getDate() == two.getDate()
                ) {
            return true;
        } else {
            return false;
        }
    }

    private void updateMonth() {
        monthComboBox.setSelectedIndex(shownDate.getMonth());
    }

    private void updateYear() {
        yearTextBox.setText(String.valueOf(shownDate.getYear() + BEGIN_YEAR));
    }

    public void setEnabled(boolean enbled) {
        yearTextBox.setEnabled(enbled);
        timeTextBox.setEnabled(enbled);
    }

    public void updateTime() {
        if(mode == QDateFieldView.MODE_SEARCH) {
            timeTextBox.setText("");
            timeTextBox.setEnabled(false);
        } else {
            if(isReadOnly) {
                setEnabled(false);
            } else {
                boolean isDisabled = (mode == QDateFieldView.MODE_DISABLED);
                setEnabled(!isDisabled);
            }
            if(startDate == null) {
                timeTextBox.setText(StringUtil.getValueAsString(
                        shownDate.getHours(), 2) + TIME_SEPARATOR
                        + StringUtil.getValueAsString(shownDate.getMinutes(),
                        2));
            } else {
                timeTextBox.setText(StringUtil.getValueAsString(
                        startDate.getHours(), 2) + TIME_SEPARATOR
                        + StringUtil.getValueAsString(startDate.getMinutes(),
                        2));
            }
        }
    }

    public void setMode(int mode) {
        this.mode = mode;
        if(mode == QDateFieldView.MODE_SEARCH) {
            chooseFirstDate = true;
        }
    }

    public Date getEndDate() {
        return endDate;
    }

    public void setEndDate(Date endDate) {
        this.endDate = endDate;
    }

    private boolean handleClick() {
        if(isReadOnly) {
            return mode == QDateFieldView.MODE_SEARCH;
        }
        return true;
    }

    public void onClick(Widget sender) {
        if(handleClick()) {
            if(setSelectedDates(sender)) {
                updateDays();
            }
        }
    }

    private boolean setSelectedDates(Widget sender) {
        if(mode == QFormElementView.MODE_DISABLED) {
            return false;
        }

        DateElement el = (DateElement) sender;
        if(mode != QDateFieldView.MODE_SEARCH) {
            startDate = createDate(shownDate, el.getDate());
            if(startDate.equals(
                    previouslySelectedElement)) { //the same element was clicked
                fireDateTimeSelectorEvent();
                return false;
            }
            previouslySelectedElement = startDate;
        } else {
            if(chooseFirstDate) {
                chooseFirstDate = false;
                startDate = createDate(shownDate, el.getDate());
                endDate = (Date) startDate.clone();
                previouslySelectedElement = (Date) startDate.clone();
            } else {
                chooseFirstDate = true;
                endDate = createDate(shownDate, el.getDate());
                if(endDate.equals(
                        previouslySelectedElement)) { //the same element was clicked
                    fireDateTimeSelectorEvent();
                    return false;
                }
                previouslySelectedElement = endDate;
            }
        }
        return true;
    }

    private Date createDate(Date d, int date) {
        return new Date(d.getYear(), d.getMonth(), date, d.getHours(),
                d.getMinutes());
    }

    public void onDoubleClick(Widget sender) {
        if(handleClick()) {
            if(setSelectedDates(sender)) {
                fireDateTimeSelectorEvent();
            }
        }
    }

    public void setPreviouslySelectedElement(Date previouslySelectedElement) {
        this.previouslySelectedElement = previouslySelectedElement;
    }

}
