/*
 * 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;

import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.MouseListenerAdapter;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Command;
import com.queplix.core.client.app.vo.uisettings.DialogUISettings;
import com.queplix.core.client.common.event.Event;
import com.queplix.core.client.common.event.EventSource;
import com.queplix.core.client.common.ui.ButtonData;
import com.queplix.core.client.common.ui.IconButton;

/**
 * General interface for the QueWeb widget view.
 *
 * @author Sergey Kozmin
 * @since 21 Sep 2006
 */
public abstract class QFormElementView extends QFormLayoutElement
        implements QFormElementModelImpl.ModelListener {
    public final static int UNKNOWN_STATE = -1;
    public final static int MODE_SEARCH = 0;
    public final static int MODE_EDIT = 1;
    public final static int MODE_REPORT_DESIGN = 2;
    public final static int MODE_DISABLED = 3;
    public final static int MODE_NEW = 4;

    private static final ButtonData EMPTY = new ButtonData("", "",
            "control/info_invisible.gif");
    private static final ButtonData INFO = new ButtonData("", "",
            "control/info.gif");
    private static final ButtonData REPORT_DESIGN_MODE_ADD = new ButtonData(
            null, null, "control/report_design_add.gif", null,
            "control/report_design_add_bright.gif");
    private static final ButtonData REPORT_DESIGN_MODE_REMOVE = new ButtonData(
            null, null, "control/report_design_remove.gif", null,
            "control/report_design_remove_bright.gif");
    private static final ButtonData REPORT_DESIGN_MODE_DISABLED
            = new ButtonData(null, null, "control/report_design_disabled.gif");

    private int mode = UNKNOWN_STATE;

    protected final IconButton info = new IconButton(EMPTY);
    protected final IconButton emptyButton = new IconButton(EMPTY);

    private boolean isLinkedFormStyle;

    private QFormElementModelImpl model;

    // -------------------- public events ------------------------
    public static interface Events {
        Event/*]<FieldMeta>[*/ FORM_ELEMENT_REPORT_DESIGN_EVENT
                = new Event/*]<FieldMeta>[*/();
        Event DATE_PARSE_EVENT = new Event();
        Event ENTITY_REFERANCE_RESOLVE_EVENT = new Event();
        Event TEXTAREA_FOCUSED = new Event();
        Event TEXTAREA_FOCUS_LOST = new Event();
        Event/*]<String>[*/ LINKED_FORM_SELECTION_EVENT
                = new Event/*]<String>[*/();
    }

    private EventSource eventSource = new EventSource(this);

    public EventSource getEventSource() {
        return eventSource;
    }
    // ----------------- end of public events --------------------

    protected QFormElementView(QFormElementModelImpl model, int layout) {
        this.model = model;
        setCaption(model.getBaseMeta().getCaption());
        model.addModelListener(this);
        initializeBaseUI(layout);
        if(model.getBaseMeta().getLinkedForm() != null) {
            addCaptionClickListener(new ClickListener() {
                public void onClick(Widget sender) {
                    fireLinkedFormSelectionEvent();
                }
            });
        }
    }

    /**
     * Control turn it's state to enabled for search
     * By calling this action QControl view shouldn't generate any of its events. If the component had been written
     * correctly controller and model should only listen notification from view.
     */
    protected void initializeBaseUI(int layout) {
        super.initializeBaseUI(layout);
        addToLabelPanel(info);
        info.addClickListener(new ClickListener() {
            public void onClick(Widget sender) {
                if(mode == MODE_REPORT_DESIGN) {
                    fireReportDesignEvent();
                }
            }
        });
    }

    private void fireReportDesignEvent() {
        Events.FORM_ELEMENT_REPORT_DESIGN_EVENT.setData(model.getBaseMeta());
        eventSource.fireEvent(Events.FORM_ELEMENT_REPORT_DESIGN_EVENT);
    }

    protected void fireLinkedFormSelectionEvent() {
        if(mode != MODE_REPORT_DESIGN && isLinkedFormStyle) {
            DeferredCommand.add(new Command() {
                // Have to fire it this way: in other cases it gets fired
                // before onClick on in QFormViewImpl#subscribeEvents
                // which causes incorrect behavior
                public void execute() {
                    Events.LINKED_FORM_SELECTION_EVENT.setData(
                    getModel().getBaseMeta().getFieldID());
                    eventSource.fireEvent(Events.LINKED_FORM_SELECTION_EVENT);
                }
            });
        }
    }

    public int getViewMode() {
        return mode;
    }

    public boolean isSearchMode() {
        return (mode == MODE_SEARCH);
    }

    public boolean isEditMode() {
        return (mode == MODE_EDIT);
    }

    public boolean isNewMode() {
        return (mode == MODE_NEW);
    }

    public boolean isReportDesignMode() {
        return (mode == MODE_REPORT_DESIGN);
    }

    public boolean isDisabled() {
        return (mode == MODE_DISABLED);
    }

    protected void setupIndicatorState() {
        updateIndicator();
    }

    private void setupState(int newMode) {
        if(newMode != mode) {
            mode = newMode;
            updateIndicator();
            onModeChanged(mode);
        }
    }

    private void updateIndicator() {
        switch(mode) {
            case MODE_SEARCH: {
                setCommonState(isValid(), model.getBaseMeta().isSearchable());
                break;
            }
            case MODE_NEW:
            case MODE_EDIT: {
                setCommonState(isValid(), !model.getBaseMeta().isReadOnly());
                break;
            }
            case MODE_DISABLED: {
                setCommonState(true, false);
                break;
            }
            case MODE_REPORT_DESIGN: {
                setReportDesignState(model.getReportDesignState());
                break;
            }
        }
        representLinkedStyle();
    }

    private void setCommonState(boolean isValid, boolean enabled) {
        if(isValid) {
            setOkCaptionStyle();
            info.setButtonState(QFormElementView.EMPTY);
            info.setTitle("");
        } else {
            setErrorCaptionStyle();
            info.setButtonState(QFormElementView.INFO);
            info.setTitle(model.getErrorTitle());
        }
        setEnabled(enabled);
        info.setEnabled(true);
    }

    protected void setErrorCaptionStyle() {
        captionLabel.setStyleName("form_formElementRequired");
    }

    protected void setOkCaptionStyle() {
        captionLabel.setStyleName("form_formElement");
    }

    /**
     * Apply icon according state of form element in Report Design mode
     *
     * @param state report state type
     */
    private void setReportDesignState(int state) {
        if(state == QFormElementModel.NOT_IN_REPORT) {
            info.setButtonState(QFormElementView.REPORT_DESIGN_MODE_ADD);
            info.setEnabled(true);
        } else if(state == QFormElementModel.IN_REPORT) {
            info.setButtonState(QFormElementView.REPORT_DESIGN_MODE_REMOVE);
            info.setEnabled(true);
        } else {
            info.setButtonState(QFormElementView.REPORT_DESIGN_MODE_DISABLED);
            info.setEnabled(false);
        }
        setEnabled(false);
    }

    public final void setEnabledForSearch() {
        setupState(MODE_SEARCH);
    }

    public final void setEnabledForNew() {
        setupState(MODE_NEW);
    }

    /**
     * Control turn it's state to enabled for edit.
     */
    public final void setEnabledForEdit() {
        setupState(MODE_EDIT);
    }

    /**
     * Control should be disabled
     */
    public final void disable() {
        setupState(MODE_DISABLED);
    }

    /**
     * Turn form element to report design mode
     */
    public void setModeRepordDesign() {
        setupState(MODE_REPORT_DESIGN);
    }

    protected void setCaption(String caption) {
        if(!captionLabel.getText().equalsIgnoreCase(caption)) {
            captionLabel.setText(caption);
        }
    }

    public String getCaption() {
        return captionLabel.getText();
    }

    /**
     * This method will be called when form element mode is changed.
     *
     * @param newMode new mode.
     */
    protected void onModeChanged(int newMode) {
    }

    /**
     * This method should be correctly implemented in heritors. It will be called if control should be disabled for some reason
     * (example: not searchable in search mode)
     *
     * @param isEnabled is enabled
     */
    protected abstract void setEnabled(boolean isEnabled);

    /**
     * This method can be called from
     *
     * @return is control in valid state. Returns false value if for example control should be filled, but it is in edit mode and empty.
     */
    protected boolean isValid() {
        return isSearchMode() || isDisabled() || isReportDesignMode() || model
                .isValid();
    }

    public void onModelMetaChanged() {
        setCaption(model.getBaseMeta().getCaption());
    }

    public void onModelDataChanged() {
        setupIndicatorState();
    }

    public void onDemandModelChanged() {
    }

    public void onDesignStateChanged(int reportState) {
        if(isReportDesignMode()) {
            setupIndicatorState();
        }
    }

    /**
     * Turns on flag linkedFormStyle.
     * Turned on isLinkedFormStyle flag adds linked style to element.
     * Method addLinkedStyle() must be invoked after any captionLabel.setStyleName().
     */
    public void addLinkedFormStyle() {
        isLinkedFormStyle = true;
        representLinkedStyle();
    }

    /**
     * Adds link style to element.
     */
    private void representLinkedStyle() {
        final String CSS = "form_formElementLinked";
        final String CSS_HOVER = "form_formElementLinked_hover";
        if(isLinkedFormStyle) {
            captionLabel.addStyleName(CSS);
            captionLabel.addMouseListener(new MouseListenerAdapter() {
                public void onMouseEnter(Widget sender) {
                    captionLabel.addStyleName(CSS_HOVER);
                }

                public void onMouseLeave(Widget sender) {
                    captionLabel.removeStyleName(CSS_HOVER);
                }
            });
        }
    }

    /**
     * This method sets widht of the editable field (e.g. text field width).
     * It doesn't affect for labels width etc. Furthermore it mustn't affect non-editable fields
     * (like calendars, checkboxes and other).
     *
     * @param clientWidth width of the edit field (usually in pixels). E.g. <src>setClientWidth("100px")</src>
     *                    for text edit sets size if the edit field to 100 pixels.
     */
    protected abstract void setClientWidth(String clientWidth);

    /**
     * Returns width of a unoccupied controls part.
     *
     * @return width of a unoccupied controls part.
     */
    public int getFilledWidth() {
        if(isVerticalLayout()) {
            return 0;
        } else {
            int result = getCaptionOffsetWidth();
            if(info.isVisible()) {
                return result + info.getOffsetWidth();
            } else {
                return result;
            }
        }
    }

    public int getInfoLabelOffsetWidth() {
        return info.getOffsetWidth();
    }

    protected DialogUISettings getUISettings() {
        // can be overriden by interested children
        return null;
    }

    protected void onAttach() {
        super.onAttach();
        representLinkedStyle();
    }

    protected QFormElementModelImpl getModel() {
        return model;
    }

}
