/*
 * Copyright (C) 2011 4th Line GmbH, Switzerland
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.fourthline.konto.client.ledger.view;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.*;
import org.fourthline.konto.client.bundle.Bundle;
import org.fourthline.konto.client.currency.CurrencyPlace;
import org.fourthline.konto.client.ledger.LedgerPlace;
import org.fourthline.konto.client.ledger.account.AccountPlace;
import org.fourthline.konto.shared.LedgerCoordinates;
import org.fourthline.konto.shared.entity.Account;
import org.fourthline.konto.shared.result.LedgerLine;
import org.fourthline.konto.shared.result.LedgerLines;
import org.seamless.gwt.component.client.widget.DateRangeSelect;
import org.seamless.gwt.component.client.widget.GhostedTextBox;
import org.seamless.gwt.component.client.widget.ImageTextButton;
import org.seamless.gwt.component.client.widget.ResizableSplitLayoutPanel;
import org.seamless.gwt.theme.shared.client.ThemeBundle;
import org.seamless.gwt.theme.shared.client.ThemeStyle;
import org.seamless.util.time.DateFormat;
import org.seamless.util.time.DateRange;

import javax.inject.Inject;

/**
 * TODO: Sorting of table http://code.google.com/p/google-web-toolkit/source/detail?r=9493
 *
 * @author Christian Bauer
 */
public class LedgerViewImpl extends Composite implements LedgerView {

    public static final int SMALL_SIZE_PX = 140;
    public static final int LARGE_SIZE_PX = 365;

    interface UI extends UiBinder<DockLayoutPanel, LedgerViewImpl> {
    }

    private UI ui = GWT.create(UI.class);

    interface Style extends CssResource, LedgerLineTable.Style {
        String dateColumn();

        String descriptionColumn();

        String amountColumn();

        String accountColumn();

        String accountCell();

        String infoLabel();

        String rowInFuture();

        String filteredBalance();
    }

    @UiField(provided = true)
    final Bundle bundle;
    @UiField(provided = true)
    final ThemeBundle themeBundle;
    @UiField
    Style style;

    @UiField
    Label accountLabel;
    @UiField
    Anchor accountCurrencyAnchor;
    @UiField
    Label accountInitialBalanceLabel;

    @UiField(provided = true)
    final DateRangeSelect dateRangeSelect;

    @UiField(provided = true)
    GhostedTextBox descriptionFilterTextBox;
    @UiField
    Button descriptionFilterClearButton;

    @UiField
    ImageTextButton addEntryButton;

    @UiField
    ImageTextButton editAccountButton;

    @UiField(provided = true)
    final DockLayoutPanel ledgerEditSplitPanel;

    @UiField
    ScrollPanel ledgerPanel;

    @UiField
    ScrollPanel editPanel;

    final LedgerLineTable ledgerLineTable;

    double ledgerEditSplitPanelSize;
    Presenter presenter;
    Account account;
    Timer filterTimer;

    @Inject
    public LedgerViewImpl(Bundle bundle,
                          DateRangeSelect dateRangeSelect) {
        this.bundle = bundle;
        this.themeBundle = bundle.themeBundle().create();

        this.ledgerEditSplitPanel = new ResizableSplitLayoutPanel();

        this.dateRangeSelect = dateRangeSelect;

        dateRangeSelect.addValueChangeHandler(new ValueChangeHandler<DateRange>() {
            @Override
            public void onValueChange(ValueChangeEvent<DateRange> event) {
                presenter.filterEffectiveOn(event.getValue());
            }
        });

        this.descriptionFilterTextBox =
                new GhostedTextBox(getFilterDescriptionLabel(), ThemeStyle.GhostedTextBox()) {
                    @Override
                    public void onKeyUp(KeyUpEvent event) {
                        super.onKeyUp(event);

                        final String enteredValue = getValue();
                        if (enteredValue.length() > 0) {
                            descriptionFilterClearButton.setEnabled(true);
                            scheduleFilterReqest(new Timer() {
                                public void run() {
                                    if (presenter != null) {
                                        presenter.filterDescription(enteredValue);
                                    }
                                }
                            });
                        } else {
                            descriptionFilterClearButton.setEnabled(false);
                            if (filterTimer != null) filterTimer.cancel();
                            if (presenter != null) {
                                presenter.filterDescription(null);
                            }
                        }
                    }
                };

        initWidget(ui.createAndBindUi(this));

        CellTable.Resources cellTableResource =
                bundle.themeBundle().create().cellTableResources().create();
        ledgerLineTable = new LedgerLineTable(cellTableResource, style) {

            @Override
            protected String getDebitLabel() {
                return account != null ? account.getType().getDebitLabel() : "";
            }

            @Override
            protected String getCreditLabel() {
                return account != null ? account.getType().getCreditLabel() : "";
            }

            @Override
            protected String getCurrentDescriptionFilter() {
                return descriptionFilterTextBox.getValue();
            }

            @Override
            protected void onSelection(LedgerLine line) {
                presenter.selectLine(line);
            }

            @Override
            protected void onSelection(Account account) {
                presenter.goTo(new LedgerPlace(new LedgerCoordinates(account.getId())));
            }
        };
    }

    public ResizableSplitLayoutPanel getLedgerEditSplitPanel() {
        // The UIBinder parser of DockLayoutPanel is buggy and prevents us from
        // having this in XML. We need to instantiate and cast it.
        return (ResizableSplitLayoutPanel) ledgerEditSplitPanel;
    }

    @Override
    public void setPresenter(Presenter presenter) {
        this.presenter = presenter;
        dateRangeSelect.reset();
        descriptionFilterTextBox.clear();
    }

    @Override
    public void setDateFormat(DateFormat dateFormat) {
        ledgerLineTable.setDateFormat(dateFormat);
        dateRangeSelect.setDateFormat(dateFormat);
    }

    @Override
    public void focus() {
        addEntryButton.setFocus(true);
    }

    @Override
    public void setLedgerLines(LedgerLines lines, Long selectEntryId, Long selectSplitId) {
        this.account = lines.getAccount();

        accountLabel.setText(account.getLabel(true, true, true, false));

        accountCurrencyAnchor.setText("Currency: " + account.getMonetaryUnit().getCurrencyCode());
        accountCurrencyAnchor.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                presenter.goTo(new CurrencyPlace(account.getMonetaryUnitId()));

            }
        });

        if (account.getInitialBalance().signum() != 0) {
            accountInitialBalanceLabel.setText(
                    "Initial Balance: " + account.getInitialBalance().getReportString(true, false)
            );
        } else {
            accountInitialBalanceLabel.setText(
                    "Initial Balance: -"
            );
        }

        dateRangeSelect.setValue(lines.getEffectiveOn());

        if (lines.size() == 0) {

            String noLinesString;
            if (!dateRangeSelect.isAllSelected() && descriptionFilterTextBox.getValue().length() > 0) {
                noLinesString = "No entries in date range with description '" + descriptionFilterTextBox.getValue() + "' found.";
            } else if (!dateRangeSelect.isAllSelected()) {
                noLinesString = "No entries in selected date range.";
            } else if (descriptionFilterTextBox.getValue().length() > 0) {
                noLinesString = "No entries with description '" + descriptionFilterTextBox.getValue() + "' found.";
            } else {
                noLinesString = "Please add entries.";
            }

            Label noLines = new Label(noLinesString);
            noLines.addStyleName(style.infoLabel());
            ledgerPanel.setWidget(noLines);
        } else {
            ledgerPanel.setWidget(ledgerLineTable);
            ledgerLineTable.setRowCount(lines.size(), true);
            ledgerLineTable.setRowData(0, lines);
            ledgerLineTable.select(selectEntryId, selectSplitId);
        }
    }

    @Override
    public AcceptsOneWidget getEntryEditContainer() {
        return editPanel;
    }

    @Override
    public void showEntryEditContainer(boolean large, boolean scrollBottom) {

        // We try our best to memorize the size of the edit panel if the user changed it
        // even though we don't have a ResizeHandler API

        double desiredSize = large ? LARGE_SIZE_PX : SMALL_SIZE_PX;
        double currentSize = getLedgerEditSplitPanel().getSplitPosition(editPanel);

        // If the current size is not one of the default sizes, store it for later re-use
        ledgerEditSplitPanelSize =
                (currentSize > 0 && !(currentSize == SMALL_SIZE_PX || currentSize == LARGE_SIZE_PX))
                        ? currentSize
                        : ledgerEditSplitPanelSize;

        // If there is a non-zero stored size, use that
        if (ledgerEditSplitPanelSize > 0) {
            desiredSize = ledgerEditSplitPanelSize;
        }

        getLedgerEditSplitPanel().setSplitPosition(editPanel, desiredSize, false);
        editPanel.setVisible(true);

        if (scrollBottom) {
            editPanel.scrollToBottom();
        }
    }

    @Override
    public void hideEntryEditContainer() {
        double currentSize = getLedgerEditSplitPanel().setSplitPosition(editPanel, 0, false);

        // Store the size only if it's not one of the default sizes
        if (currentSize > 0 && !(currentSize == SMALL_SIZE_PX || currentSize == LARGE_SIZE_PX)) {
            ledgerEditSplitPanelSize = currentSize;
        }

        editPanel.setVisible(false);
        focus();
    }

    @UiHandler("addEntryButton")
    void onClickAddEntryButton(ClickEvent e) {
        if (presenter != null)
            presenter.addLine();
    }

    @UiHandler("descriptionFilterClearButton")
    void onClickDescriptionFilterClear(ClickEvent e) {
        descriptionFilterTextBox.clear();
        descriptionFilterClearButton.setEnabled(false);
        if (filterTimer != null) filterTimer.cancel();
        if (presenter != null) {
            presenter.filterDescription(null);
        }
    }

    @UiHandler("editAccountButton")
    void onClickEditAccountButton(ClickEvent e) {
        if (presenter != null)
            presenter.goTo(new AccountPlace(account.getId()));
    }

    protected String getFilterDescriptionLabel() {
        return "Filter description...";
    }

    protected void scheduleFilterReqest(Timer timer) {
        if (filterTimer != null) {
            filterTimer.cancel();
        }
        filterTimer = timer;
        filterTimer.schedule(1000);
    }


}
