/*
 * RHQ Management Platform
 * Copyright (C) 2005-2015 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU 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 org.rhq.coregui.client.dashboard.portlets.resource;

import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.smartgwt.client.widgets.form.fields.FloatItem;
import com.smartgwt.client.widgets.form.fields.LinkItem;
import com.smartgwt.client.widgets.form.fields.PickerIcon;
import com.smartgwt.client.widgets.form.fields.StaticTextItem;
import com.smartgwt.client.widgets.form.fields.events.BlurEvent;
import com.smartgwt.client.widgets.form.fields.events.BlurHandler;
import com.smartgwt.client.widgets.form.fields.events.ClickEvent;
import com.smartgwt.client.widgets.form.fields.events.ClickHandler;
import com.smartgwt.client.widgets.form.fields.events.FormItemClickHandler;
import com.smartgwt.client.widgets.form.fields.events.FormItemIconClickEvent;
import com.smartgwt.client.widgets.form.fields.events.KeyUpEvent;
import com.smartgwt.client.widgets.form.fields.events.KeyUpHandler;

import org.rhq.core.domain.measurement.MeasurementBaseline;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.MeasurementUnits;
import org.rhq.core.domain.measurement.composite.MeasurementNumericValueAndUnits;
import org.rhq.core.domain.resource.Resource;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.components.form.EnhancedDynamicForm;
import org.rhq.coregui.client.dashboard.portlets.groups.GroupMetricsPortlet;
import org.rhq.coregui.client.gwt.GWTServiceLookup;
import org.rhq.coregui.client.inventory.common.detail.summary.AbstractActivityView;
import org.rhq.coregui.client.inventory.resource.detail.monitoring.D3GraphListView;
import org.rhq.coregui.client.util.Log;
import org.rhq.coregui.client.util.MeasurementConverterClient;
import org.rhq.coregui.client.util.enhanced.Enhanced;
import org.rhq.coregui.client.util.measurement.GwtMonitorUtils;
import org.rhq.coregui.client.util.message.Message;

/**
 * Metrics chart Window with Baseline editing capabilities.
 */
public class MetricsChartWindow implements Enhanced {
    public static final String CHANGE_VALUE = MSG.chart_baseline_change_value_label();

    private static final String BASELINE_SECTION_TITLE = MSG.chart_baseline_section_title();
    private static final String BASELINE_MEAN = MSG.chart_baseline_baseline_mean();
    private static final String NEW_BASELINE_MEAN = MSG.chart_baseline_baseline_mean_new();
    private static final String BASELINE_HIGH = MSG.chart_baseline_baseline_high();
    private static final String NEW_BASELINE_HIGH = MSG.chart_baseline_baseline_high_new();
    private static final String BASELINE_LOW = MSG.chart_baseline_baseline_low();
    private static final String NEW_BASELINE_LOW = MSG.chart_baseline_baseline_low_new();

    private static final String CUSTOM_USER_BASELINE_MEAN_WAS_NOT_SAVED = MSG.chart_baseline_mean_not_saved();
    private static final String CUSTOM_USER_BASELINE_MEAN_SUCCESSFULLY_SAVED = MSG.chart_baseline_mean_saved();
    private static final String CUSTOM_USER_BASELINE_HIGH_WAS_NOT_SAVED = MSG.chart_baseline_high_not_saved();
    private static final String CUSTOM_USER_BASELINE_HIGH_BOUND_SUCCESSFULLY_SAVED = MSG.chart_baseline_high_saved();
    private static final String CUSTOM_USER_BASELINE_LOW_WAS_NOT_SAVED = MSG.chart_baseline_low_not_saved();
    private static final String CUSTOM_USER_BASELINE_LOW_BOUND_SUCCESSFULLY_SAVED = MSG.chart_baseline_low_saved();

    private StaticTextItem baselineText;
    private StaticTextItem expectedRangeHighText;
    private StaticTextItem expectedRangeLowText;

    private FloatItem newBaselineMeanText;
    private FloatItem newExpectedRangeHighText;
    private FloatItem newExpectedRangeLowText;

    private MeasurementNumericValueAndUnits baselineMeanMd;
    private MeasurementNumericValueAndUnits baselineHighMd;
    private MeasurementNumericValueAndUnits baselineLowMd;

    private MeasurementDefinition measurementDefinition;

    private int resourceId = -1;

    public MetricsChartWindow() {
        baselineText = new StaticTextItem();
        expectedRangeHighText = new StaticTextItem();
        expectedRangeLowText = new StaticTextItem();

        newBaselineMeanText = new FloatItem();
        newBaselineMeanText.setKeyPressFilter("[0-9.]");
        newExpectedRangeHighText = new FloatItem();
        newExpectedRangeHighText.setKeyPressFilter("[0-9.]");
        newExpectedRangeLowText = new FloatItem();
        newExpectedRangeLowText.setKeyPressFilter("[0-9.]");

    }

    private EnhancedDynamicForm createMetricBaselineForm() {
        EnhancedDynamicForm metricsBaselineForm = new EnhancedDynamicForm();
        metricsBaselineForm.setGroupTitle(BASELINE_SECTION_TITLE);
        metricsBaselineForm.setIsGroup(true);
        metricsBaselineForm.setExtraSpace(10);
        metricsBaselineForm.setNumCols(3);
        metricsBaselineForm.setColWidths(200, 80, "*");
        metricsBaselineForm.setCellPadding(3);
        metricsBaselineForm.setWidth("95%");

        // Baseline
        baselineText.setTitle(BASELINE_MEAN);
        LinkItem baselineLink = AbstractActivityView.newLinkItem(CHANGE_VALUE, null);
        baselineLink.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent clickEvent) {
                newBaselineMeanText.show();
                newBaselineMeanText.setSelectOnFocus(true);
                newExpectedRangeHighText.hide();
                newExpectedRangeLowText.hide();

            }
        });
        newBaselineMeanText.setTitle(NEW_BASELINE_MEAN);
        newBaselineMeanText.addBlurHandler(new BlurHandler() {
            @Override
            public void onBlur(BlurEvent blurEvent) {
                hideBaselineEditingFields();
            }
        });
        PickerIcon cancelPicker = new PickerIcon(PickerIcon.CLEAR, new FormItemClickHandler() {
            public void onFormItemClick(FormItemIconClickEvent event) {
                hideBaselineEditingFields();
            }
        });
        newBaselineMeanText.setIcons(cancelPicker);
        newBaselineMeanText.addKeyUpHandler(new KeyUpHandler() {
            @Override
            public void onKeyUp(KeyUpEvent keyUpEvent) {
                if (keyUpEvent.getKeyName().equals("Enter")) {
                    hideBaselineEditingFields();
                    if (null != newBaselineMeanText.getValueAsString()) {
                        double newBaselineMean = Double.parseDouble(newBaselineMeanText.getValueAsString());
                        saveCustomBaselineMean(newBaselineMean);
                    }
                }

            }
        });

        // High Baseline
        expectedRangeHighText.setTitle(BASELINE_HIGH);
        LinkItem baselineHighLink = AbstractActivityView.newLinkItem(CHANGE_VALUE, null);
        baselineHighLink.addClickHandler(new ClickHandler() {

            @Override
            public void onClick(ClickEvent clickEvent) {
                // High cannot be below the low
                newBaselineMeanText.hide();
                newExpectedRangeHighText.show();
                newExpectedRangeHighText.setSelectOnFocus(true);
                newExpectedRangeLowText.hide();

            }
        });
        newExpectedRangeHighText.setTitle(NEW_BASELINE_HIGH);
        newExpectedRangeHighText.addBlurHandler(new BlurHandler() {
            @Override
            public void onBlur(BlurEvent blurEvent) {
                hideBaselineEditingFields();
            }
        });
        newExpectedRangeHighText.setIcons(cancelPicker);
        newExpectedRangeHighText.addKeyUpHandler(new KeyUpHandler() {
            @Override
            public void onKeyUp(KeyUpEvent keyUpEvent) {
                if (keyUpEvent.getKeyName().equals("Enter")) {
                    hideBaselineEditingFields();
                    if (null != newExpectedRangeHighText.getValueAsString()) {
                        double newBaselineHigh = Double.parseDouble(newExpectedRangeHighText.getValueAsString());
                        saveCustomBaselineHigh(newBaselineHigh);
                    }
                }

            }
        });

        // Low Baseline
        expectedRangeLowText.setTitle(BASELINE_LOW);
        LinkItem baselineLowLink = AbstractActivityView.newLinkItem(CHANGE_VALUE, null);
        baselineLowLink.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent clickEvent) {
                newBaselineMeanText.hide();
                newExpectedRangeHighText.hide();
                newExpectedRangeLowText.show();
                newExpectedRangeLowText.setSelectOnFocus(true);

            }
        });
        newExpectedRangeLowText.setTitle(NEW_BASELINE_LOW);
        newExpectedRangeLowText.addBlurHandler(new BlurHandler() {
            @Override
            public void onBlur(BlurEvent blurEvent) {
                hideBaselineEditingFields();
            }
        });
        newExpectedRangeLowText.setIcons(cancelPicker);
        newExpectedRangeLowText.addKeyUpHandler(new KeyUpHandler() {
            @Override
            public void onKeyUp(KeyUpEvent keyUpEvent) {
                if (keyUpEvent.getKeyName().equals("Enter")) {
                    hideBaselineEditingFields();
                    if (null != newExpectedRangeLowText.getValueAsString()) {
                        double newBaselineLow = Double.parseDouble(newExpectedRangeLowText.getValueAsString());
                        saveCustomBaselineLow(newBaselineLow);
                    }
                }

            }
        });

        //@hack: the only way to hide is to wait a while for them to appear created in the DOM
        new Timer() {
            @Override
            public void run() {
                hideBaselineEditingFields();
            }
        }.schedule(150);

        metricsBaselineForm.setFields(baselineText, baselineLink, newBaselineMeanText, expectedRangeHighText,
            baselineHighLink, newExpectedRangeHighText, expectedRangeLowText, baselineLowLink, newExpectedRangeLowText);

        return metricsBaselineForm;

    }

    private void saveCustomBaselineMean(double newBaselineMean) {
        Log.debug("Saving baseline mean: " + newBaselineMean);
        Double scaledValue = MeasurementConverterClient.scale(new MeasurementNumericValueAndUnits(newBaselineMean, baselineMeanMd.getUnits()), MeasurementUnits.BYTES);
        GWTServiceLookup.getMeasurementDataService().setUserBaselineMean(resourceId, measurementDefinition.getId(),
            scaledValue, new AsyncCallback<Void>() {
                @Override
                public void onFailure(Throwable throwable) {
                    unSuccessfulSave(CUSTOM_USER_BASELINE_MEAN_WAS_NOT_SAVED, throwable);
                }

                @Override
                public void onSuccess(Void aVoid) {
                    successfulSave(CUSTOM_USER_BASELINE_MEAN_SUCCESSFULLY_SAVED);
                }
            });
    }

    private void saveCustomBaselineHigh(double newBaselineHigh) {
        Log.debug("Saving baseline high: " + newBaselineHigh);
        Double scaledValue = MeasurementConverterClient.scale(new MeasurementNumericValueAndUnits(newBaselineHigh, baselineHighMd.getUnits()), MeasurementUnits.BYTES);
        GWTServiceLookup.getMeasurementDataService().setUserBaselineMax(resourceId, measurementDefinition.getId(),
            scaledValue, new AsyncCallback<Void>() {
                @Override
                public void onFailure(Throwable throwable) {
                    unSuccessfulSave(CUSTOM_USER_BASELINE_HIGH_WAS_NOT_SAVED, throwable);
                }

                @Override
                public void onSuccess(Void aVoid) {
                    successfulSave(CUSTOM_USER_BASELINE_HIGH_BOUND_SUCCESSFULLY_SAVED);
                }
            });
    }

    private void saveCustomBaselineLow(double newBaselineLow) {
        Log.debug("Saving baseline low: " + newBaselineLow);
        Double scaledValue = MeasurementConverterClient.scale(new MeasurementNumericValueAndUnits(newBaselineLow, baselineLowMd.getUnits()), MeasurementUnits.BYTES);
        GWTServiceLookup.getMeasurementDataService().setUserBaselineMin(resourceId, measurementDefinition.getId(),
            scaledValue, new AsyncCallback<Void>() {
                @Override
                public void onFailure(Throwable throwable) {
                    unSuccessfulSave(CUSTOM_USER_BASELINE_LOW_WAS_NOT_SAVED, throwable);
                }

                @Override
                public void onSuccess(Void aVoid) {
                    successfulSave(CUSTOM_USER_BASELINE_LOW_BOUND_SUCCESSFULLY_SAVED);
                }
            });
    }

    private void successfulSave(String successMessage) {
        Log.debug(successMessage);
        CoreGUI.getMessageCenter().notify(new Message(successMessage, Message.Severity.Info));
        hideBaselineEditingFields();
        queryScheduleBaseline(resourceId, measurementDefinition);
    }

    private void unSuccessfulSave(String failureMessage, Throwable throwable) {
        Log.warn(failureMessage, throwable);
        CoreGUI.getMessageCenter().notify(new Message(failureMessage, throwable, Message.Severity.Error));
    }

    public void showPopupChart(String title, Resource resource, MeasurementDefinition measurementDefinition,
        GroupMetricsPortlet refreshablePortlet) {
        this.measurementDefinition = measurementDefinition;
        resourceId = resource.getId();
        GroupMetricsPortlet.ChartViewWindow window = new GroupMetricsPortlet.ChartViewWindow(title, "",
            refreshablePortlet);
        D3GraphListView graphView = D3GraphListView.createSingleGraph(resource, measurementDefinition.getId(), true);
        window.addItem(graphView);
        window.addItem(createMetricBaselineForm());
        window.show();
        queryScheduleBaseline(resourceId, measurementDefinition);
    }

    private void hideBaselineEditingFields() {
        newBaselineMeanText.hide();
        newExpectedRangeHighText.hide();
        newExpectedRangeLowText.hide();
    }

    private void queryScheduleBaseline(final int resourceId, final MeasurementDefinition md) {

        GWTServiceLookup.getMeasurementDataService().getBaselineForResourceAndSchedule(resourceId, md.getId(),
            new AsyncCallback<MeasurementBaseline>() {

                public void onSuccess(MeasurementBaseline measurementBaseline) {
                    if (null != measurementBaseline) {
                        MeasurementUnits units = md.getUnits();

                        baselineText.setValue(GwtMonitorUtils.formatSimpleMetric(measurementBaseline.getMean(), md));
                        expectedRangeHighText.setValue(GwtMonitorUtils.formatSimpleMetric(measurementBaseline.getMax(),
                            md));
                        expectedRangeLowText.setValue(GwtMonitorUtils.formatSimpleMetric(measurementBaseline.getMin(),
                            md));

                        baselineText.setTitle(baselineText.getTitle());
                        expectedRangeHighText.setTitle(expectedRangeHighText.getTitle());
                        expectedRangeLowText.setTitle(expectedRangeLowText.getTitle());

                        baselineMeanMd =  MeasurementConverterClient.fit(measurementBaseline.getMean(), units);
                        baselineHighMd =  MeasurementConverterClient.fit(measurementBaseline.getMax(), units);
                        baselineLowMd =  MeasurementConverterClient.fit(measurementBaseline.getMin(), units);

                        newExpectedRangeHighText.setValue(baselineHighMd.getValue());
                        newBaselineMeanText.setValue(baselineMeanMd.getValue());
                        newExpectedRangeLowText.setValue(baselineLowMd.getValue());

                        newBaselineMeanText.setTitle(newBaselineMeanText.getTitle());
                        newExpectedRangeHighText.setTitle(newExpectedRangeHighText.getTitle());
                        newExpectedRangeLowText.setTitle(newExpectedRangeLowText.getTitle());
                    }
                }

                @Override
                public void onFailure(Throwable caught) {
                    Log.debug("Error retrieving out of bound metrics baseline for resource [" + resourceId + ","
                        + md.getId() + "]:" + caught.getMessage());
                }

            });
    }

}
