/*
 * 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.frames.mainframe.impl;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.HistoryListener;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.SerializableException;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
import com.queplix.core.client.app.LengthyTaskManager;
import com.queplix.core.client.app.rpc.RPC;
import com.queplix.core.client.app.vo.AccumulatedEntityDataResponse;
import com.queplix.core.client.app.vo.ClearEntityRequestObject;
import com.queplix.core.client.app.vo.ContextMenuMeta;
import com.queplix.core.client.app.vo.CustomizeGridRequestObject;
import com.queplix.core.client.app.vo.EntityData;
import com.queplix.core.client.app.vo.EntityIDRequestObject;
import com.queplix.core.client.app.vo.FamgMeta;
import com.queplix.core.client.app.vo.FamgMeta.Index;
import com.queplix.core.client.app.vo.FieldData;
import com.queplix.core.client.app.vo.FieldDataRequest;
import com.queplix.core.client.app.vo.FieldMeta;
import com.queplix.core.client.app.vo.FieldOnDemandData;
import com.queplix.core.client.app.vo.FocusMeta;
import com.queplix.core.client.app.vo.GridData;
import com.queplix.core.client.app.vo.GridSearchProperties;
import com.queplix.core.client.app.vo.MetaData;
import com.queplix.core.client.app.vo.PrintFormRequestObject;
import com.queplix.core.client.app.vo.PrintGridRequestObject;
import com.queplix.core.client.app.vo.RowData;
import com.queplix.core.client.app.vo.SavedSearchDeleteRequestObject;
import com.queplix.core.client.app.vo.SavedSearchObject;
import com.queplix.core.client.app.vo.SubFocusMeta;
import com.queplix.core.client.app.vo.SubsetData;
import com.queplix.core.client.app.vo.TabMeta;
import com.queplix.core.client.app.vo.chart.ChartRequestObject;
import com.queplix.core.client.app.vo.chart.ChartResponseObject;
import com.queplix.core.client.common.CollectionsHelper;
import com.queplix.core.client.common.crossframes.AdhocData;
import com.queplix.core.client.common.event.Event;
import com.queplix.core.client.common.event.EventListener;
import com.queplix.core.client.common.event.EventSource;
import com.queplix.core.client.common.ui.DialogHelper;
import com.queplix.core.client.common.ui.WindowHelper;
import com.queplix.core.client.common.ui.grid.SelectedRecord;
import com.queplix.core.client.controls.QButton;
import com.queplix.core.client.controls.QFormElementModel;
import com.queplix.core.client.controls.QFormElementView;
import com.queplix.core.client.controls.chart.QChartController;
import com.queplix.core.client.controls.chart.QChartModel;
import com.queplix.core.client.controls.form.QForm;
import com.queplix.core.client.controls.form.QFormController;
import com.queplix.core.client.controls.form.QFormModel;
import com.queplix.core.client.controls.form.event.ControlChangedData;
import com.queplix.core.client.controls.grid.QGrid;
import com.queplix.core.client.controls.grid.QGridController;
import com.queplix.core.client.controls.grid.QGridModel;
import com.queplix.core.client.frames.AboutFrame;
import com.queplix.core.client.frames.email.EmailComposeDialog;
import com.queplix.core.client.frames.mainframe.AdhocOperations;
import com.queplix.core.client.frames.mainframe.BusinessOperationStrategy;
import com.queplix.core.client.frames.mainframe.FormControlEventTypes;
import com.queplix.core.client.frames.mainframe.FormOperations;
import com.queplix.core.client.frames.mainframe.FormState;
import com.queplix.core.client.frames.mainframe.GridOperations;
import com.queplix.core.client.frames.mainframe.IMainFrame;
import com.queplix.core.client.frames.mainframe.MainframeEventsCollection;
import com.queplix.core.client.frames.mainframe.OperationContext;
import com.queplix.core.client.frames.mainframe.OperationTypes;
import com.queplix.core.client.frames.mainframe.StrategiesFactory;
import com.queplix.core.client.frames.mainframe.ViewPropertiesProvider;
import com.queplix.core.client.i18n.I18N;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Application main window contents.
 *
 * @author Sultan Tezadov, Aliaksandr Melnik
 * @since 23 Oct 2006
 */
// TODO: once generics enabled, remove all occurences of '/*]' and '[*/'
class MainFrame extends Composite implements
        RPC.RequestListener,
        HistoryListener,
        LengthyTaskManager.LengthyTaskListener,
        EventListener,
        OperationContext,
        FormOperations,
        GridOperations,
        AdhocOperations,
        IMainFrame {

    private final static String DEFAULT_HELP_PAGE = "HTML/index.html";

    private static final List EMPTY_LIST = new ArrayList();

    // -------------------- public events ------------------------
    private EventSource eventSource = new EventSource(this);
    private StrategiesFactory factory;

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

    private AboutFrame aboutFrame;

    private MetaData appMetaData;
    private MainFrameSA mainFrameSA; // SA -- Static Area
    private MainFrameFA mainFrameFA; // FA -- Forms Area
    private MainFrameGA mainFrameGA; // GA -- Grids Area

    private FormsDataInjector dataInjector = new FormsDataInjector();

    private FamgMeta.Index lastSearchFormIndex;
    private FieldData[] lastSearchFieldData;

    //form to print request data todo this object is re-creating each time user select record, or perform search, remove!!
    private PrintFormRequestObject printFormRequest;
    private PrintGridRequestObject printGridRequest;

    private boolean ignoreGridRecordHistory;
    private GridHistoryRecord gridHistoryRecord;

    //state of MainToolBar Ad Hoc button
    //true - all forms on MainToolBar Ad Hoc button event will be in Report Design state on report design button click
    //false - all forms on MainToolBar Ad Hoc button event will be in state that was before Report Design state on report design button click
    private boolean isReportDesignState = false;

    /**
     * used for ad hoc report
     */
    private FamgMeta.Index initialAdhocFormIndex;

    /**
     * Here we store last form searches and when we change some grid props it takes search params from here.
     * When user do "clean" action map to be cleaned.
     * Map stores {@link com.queplix.core.client.app.vo.AccumulatedEntitiesRequestObject} objects. The keys is form ids.
     */
    private Map lastFormSearches = new HashMap();
    /**
     * This object is to be sent when report event is occured. Browser has pseudo-single model hence don't need to re-create this event.
     */
    private AdhocData adhocEvent;

    /**
     * this list is used for grid pager
     */
    private Collection gridPagerSearchFilters;

    /**
     * init is for MetaData-dependent loading
     * Call when MetaData is received
     *
     * @param appMetaData application metadata
     */
    public void initMainFrame(MetaData appMetaData, StrategiesFactory factory,
                              ViewPropertiesProvider viewPropertiesProvider) {
        this.factory = factory;
        this.appMetaData = appMetaData;
        RPC.addRequestListener(this);
        LengthyTaskManager.addLengthyTaskListener(this);

        mainFrameFA = new MainFrameFA(appMetaData, viewPropertiesProvider);
        mainFrameGA = new MainFrameGA(appMetaData);
        mainFrameSA = new MainFrameSA(appMetaData, mainFrameFA, mainFrameGA);

        dataInjector.initialize(mainFrameFA, mainFrameGA);
        dataInjector.onVisualAreaChanged(appMetaData.getTabMeta(
                mainFrameFA.getActiveFormIndex()));

        mainFrameGA.getEventSource().addEventListener(this); // listen to events
        mainFrameFA.getEventSource().addEventListener(this); // listen to events
        mainFrameSA.getEventSource().addEventListener(this); // listen to events

        History.addHistoryListener(this);

        FocusMeta.Index defaultFocusIndex = appMetaData.getDefaultFocusIndex();
        if(defaultFocusIndex != null) {
            selectFocus(defaultFocusIndex);
        }

        selectForm(mainFrameFA.getActiveFormIndex(), true);
        initWidget(mainFrameSA);
    }

    protected void performDeleteSavedSearchRequest(Long[] rowIDs) {
        SavedSearchDeleteRequestObject request
                = new SavedSearchDeleteRequestObject(rowIDs);
        RPC.QAsyncCallback callback = new SavedSearchDeleteAsyncCallback();
        RPC.getRPC().deleteSavedSearch(request, callback);
    }

    private void performUnlockAfterEditRequest(FamgMeta.Index formIndex,
                                               Long rowID, String formID,
                                               boolean updateAfterUnlock) {
        RPC.QAsyncCallback callback = new UnlockAterEditAsyncCallback(formIndex,
                rowID, updateAfterUnlock);
        EntityIDRequestObject request = new EntityIDRequestObject(formID,
                rowID);
        RPC.getRPC().unlockAfterEditRecord(request, callback);
    }

    private String performHelpCallEvent() {
        String link = mainFrameFA.getTab(mainFrameFA.getActiveFormIndex())
                .getHelpLink();
        if(link != null && !link.equals("")) {
            return link;
        } else {
            return DEFAULT_HELP_PAGE;
        }
    }

    protected void performSearchRecordsRequest(FamgMeta.Index famgIndex,
                                               boolean localSearch) {
        GridSearchProperties props = getGridSearchProperties(famgIndex);

        FamgMeta baseFormMeta = appMetaData.getFamgMeta(famgIndex);
        ArrayList filterEntities = getFormsFilters(baseFormMeta, localSearch);

        lastFormSearches.put(appMetaData.getFormID(famgIndex), filterEntities);

        BusinessOperationStrategy strategy = factory.createStrategy(
                appMetaData.getFormID(famgIndex));
        strategy.searchRecords(filterEntities, props, localSearch);
    }

    private void performCustomizeGridRequest(final FamgMeta.Index gridIndex,
                                             final SubsetData data) {
        String formID = appMetaData.getFormID(gridIndex);
        CustomizeGridRequestObject request = new CustomizeGridRequestObject(
                formID, data);
        RPC.QAsyncCallback callback = new RPC.QAsyncCallback() {
            public void onRequestEnded(boolean success, Object result) {
                if(success) {
                    QGrid grid = mainFrameGA.getGrid(gridIndex);
                    grid.getModel().customizeGrid(data);
                    performGridDrivenRequest(gridIndex);
                }
            }
        };
        RPC.getRPC().customizeGrid(request, callback);
    }

    private void performGridDrivenRequest(FamgMeta.Index gridIndex) {
        String formID = appMetaData.getFormID(gridIndex);
        List filterEntities =
                (List) lastFormSearches.get(formID);

        if(filterEntities
                != null) {//otherwise do nothing, because grid is empty
            //get grid data
            GridSearchProperties props = getGridSearchProperties(gridIndex);

            //do the request
            BusinessOperationStrategy strategy = factory.createStrategy(
                    appMetaData.getFormID(gridIndex));
            strategy.searchRecords(filterEntities, props, false);
        }
    }

    /**
     * Set filters for forms.
     *
     * @param filters          list of the {@link com.queplix.core.client.app.vo.EntityData} objects
     * @param clearFormsBefore should forms be cleaned before data updated
     */
    public void setFormsFilters(Collection filters, boolean clearFormsBefore) {
        if(clearFormsBefore) {
            clearAll(getClearingFormState());
        }
        EntityData[] data = new EntityData[filters.size()];
        CollectionsHelper.copyToArray(filters, data);
        dataInjector.onFormsDataUpdated(null, data, null);
    }

    public Collection getDirectFormFilters(Index index) {
        Collection ret = null;

        QForm form = mainFrameFA.getForm(index);
        FamgMeta famgMeta = appMetaData.getFamgMeta(index);
        String entityName = famgMeta.getEntityName();
        if(form != null) {
            ret = form.getModel().getNonEmptyFilters();
        } else {
            EntityData entityData = dataInjector.getEntityData(entityName);
            if(entityData != null && entityData.countNotEmptyFields() > 0) {
                FieldData[] filters = entityData.getNotEmptyFields();
                ret = new ArrayList();
                CollectionsHelper.copyArrayToCollection(filters, ret);
            }
        }
        return ret;
    }

    public void setButtonEnabled(Index formIndex, String buttonId,
                                 boolean isEnabled) {
        QForm form = mainFrameFA.getForm(formIndex);
        if(form == null) {
            activateForm(formIndex);
        }
        form = mainFrameFA.getForm(formIndex);
        form.getView().setCustomButtonsEnabled(new String[]{buttonId},
                isEnabled);
    }

    private int getClearingFormState() {
        return isReportDesignState ? FormState.REPORT_DESIGN_STATE
                :FormState.SEARCH_STATE;
    }

    /**
     * Build entity of {@link com.queplix.core.client.app.vo.AccumulatedEntitiesRequestObject} with the given params.
     *
     * @param formIndex form index to build
     * @param doCount   should doCount information be retrieved
     * @param page      requested page
     * @param pageSize  preferred page size
     * @param order     order of ret records
     * @param localSearch     whether search action will be performed for only local form
     * @return accumulated entities request object
     *//*
    private AccumulatedEntitiesRequestObject buildFullFormRequest(FamgMeta.Index formIndex, boolean doCount, int page,
            int pageSize, SortField order, boolean localSearch) {
        FamgMeta baseFormMeta = appMetaData.getFamgMeta(formIndex);
        ArrayList filterEntities = getFormsFilters(baseFormMeta, localSearch);
        return new AccumulatedEntitiesRequestObject(baseFormMeta.getFormID(), filterEntities, doCount, page, pageSize, order);
    }*/

    /**
     * Builds forms filters for all bound forms. Forms constraints bound by "external-form" parameter.
     *
     * @param baseFormMeta metadata for meta form. Data for that form is searching.
     * @param localSearch  should this method be performed for local search procedure
     * @return ArrayList of element of type {@link com.queplix.core.client.app.vo.EntityData}
     */
    private ArrayList getFormsFilters(FamgMeta baseFormMeta,
                                      boolean localSearch) {
        FamgMeta.Index baseFormIndex = baseFormMeta.getIndex();

        //create entity filterfs for current form
        ArrayList filterEntities = new ArrayList();
        EntityData baseFormFilters = getStateRelatedFormFilters(baseFormIndex);

        gridPagerSearchFilters = Arrays.asList(
                baseFormFilters.getFields());//local search only
        if(baseFormFilters.getFields().length > 0) {
            filterEntities.add(baseFormFilters);
        }

        if(!localSearch) {
            //iterate external form constraints
            List externalForms = baseFormMeta.getForm().getExternalForms();
            for(int i = 0; i < externalForms.size(); i++) {
                FamgMeta.Index index = (FamgMeta.Index) externalForms.get(i);
                EntityData entityData = getStateRelatedFormFilters(index);
                if(entityData != null && entityData.getFields().length > 0) {
                    filterEntities.add(entityData);
                }
            }
        }

        return filterEntities;
    }

    /**
     * Retrieve form filters either from form object, or data injector if the form doesn't exist yet.
     *
     * @param index form index
     * @return entity data object. Can be null if form wasn't created and data isn't set for it.
     */
    private EntityData getStateRelatedFormFilters(Index index) {
        EntityData entityData;

        QForm form = mainFrameFA.getForm(index);
        FamgMeta famgMeta = appMetaData.getFamgMeta(index);
        String entityName = famgMeta.getEntityName();
        if(form != null) {
            Collection col = getStateRelatedFormFilters(form);
            FieldData[] typedArray = new FieldData[col.size()];
            CollectionsHelper.copyToArray(col, typedArray);

            entityData = new EntityData(entityName, new Long(-1), typedArray);
        } else {
            entityData = dataInjector.getEntityData(entityName);
            if(entityData != null) {
                if(entityData.countNotEmptyFields()
                        > 0) {//if has filters, then create non-empty filters
                    if(dataInjector.isExternalFieldEntity(
                            entityName)) {//just put filters
                        entityData = new EntityData(entityName, new Long(-1),
                                entityData.getNotEmptyFields());
                    } else {//get pkey id
                        String pkeyID = famgMeta.getForm().getPkeyID();
                        FieldData pkeyFieldData = entityData.getFieldById(
                                pkeyID);
                        if(pkeyFieldData != null) {
                            entityData = new EntityData(entityName, new Long(
                                    -1), new FieldData[]{pkeyFieldData});
                        } else {//log warning
                            GWT.log("Incorrect behaviour, pkey field doesn't "
                                    + "have value, but in forms data injector "
                                    + "it was filled out by external-set",
                                    null);
                            entityData = null;
                        }
                    }
                } else {//else null the entity
                    entityData = null;
                }
            }
        }
        return entityData;
    }

    /**
     * Retrieve form filters according to form state.
     *
     * @param form form to retrieve filters
     * @return form filters. If form in selected state - return single pkey filter, otherwise all non empty values in it.
     */
    private Collection getStateRelatedFormFilters(QForm form) {
        Collection formFilters;
        if(form.getModel().getFormState() == FormState.SELECTED_STATE) {
            String pkeyID = form.getModel().getPkeyID();
            formFilters = new ArrayList();
            formFilters.add(form.getModel().getElementData(pkeyID));
        } else {
            formFilters = form.getModel().getNonEmptyFilters();
        }
        return formFilters;
    }

    private void recordSelected(Long recordID, FamgMeta.Index index) {
        if(mainFrameFA.performCommand(new GetCanFormBeTurnedCommand(), index)) {
            EntityIDRequestObject request = new EntityIDRequestObject(
                    appMetaData.getFormID(index), recordID);
            RecordsSelectedAsyncCallback callback
                    = new RecordsSelectedAsyncCallback(index);
            RPC.getRPC().getEntityData(request, callback);
            gridHistoryRecord = new GridHistoryRecord(index,
                    mainFrameFA.getForm(index).getView().getCaption(), recordID,
                    null);
        } else {
            DialogHelper.showModalMessageDialog(
                    I18N.getMessages().couldNotSelectRecord());
        }
    }

    private void newHistoryItem(String indexStr) {
        if(!indexStr.equalsIgnoreCase(History.getToken())) {
            History.newItem(indexStr);
        }
    }

    public void onHistoryChanged(String historyToken) {
        if(historyToken.length() > 0) {
            Index activeIndex = mainFrameFA.getActiveFormIndex();
            Index historyIndex = null;
            try {
                historyIndex = FamgMeta.Index.deSerialize(historyToken);
            } catch (SerializableException e) {
                //do nothing, incorrect data in history string. just put active index string there.
            }
            if(historyIndex != null) {
                if(!activeIndex.equals(
                        historyIndex)) {//equals reloaded in FamgsMeta.Index
                    selectForm(historyIndex, true);
                }
            }
        }
    }

    private void selectFocus(FocusMeta.Index index) {
        selectFocus(index, false);
    }

    private void selectFocus(final FocusMeta.Index index,
                             boolean isLastStatement) {
        if(isLastStatement) {
            LengthyTaskManager.runLengthyTask(
                    new LengthyTaskManager.Runnable() {
                        public void run() {
                            _selectFocus(index);
                        }
                    });
        } else {
            _selectFocus(index);
        }
    }

    private void _selectFocus(FocusMeta.Index index) {
        mainFrameFA.activateFocus(index);
        mainFrameGA.activateFocus(index);
        Index activeFormIndex = mainFrameFA.getActiveFormIndex();
        mainFrameSA.activateFocus(activeFormIndex);
        //event if only focus is selected we choose active area and notify dataInjector.
        dataInjector.onVisualAreaChanged(appMetaData.getTabMeta(
                activeFormIndex));
    }

    /*private void selectSubFocus(SubFocusMeta.Index index) {
        selectSubFocus(index, false);
    }*/

    private void selectSubFocus(final SubFocusMeta.Index index,
                                boolean isLastStatement) {
        if(isLastStatement) {
            LengthyTaskManager.runLengthyTask(
                    new LengthyTaskManager.Runnable() {
                        public void run() {
                            _selectSubFocus(index);
                        }
                    });
        } else {
            _selectSubFocus(index);
        }
    }

    private void _selectSubFocus(SubFocusMeta.Index index) {
        mainFrameFA.activateSubFocus(index);
        mainFrameGA.activateSubFocus(index);
        Index activeFormIndex = mainFrameFA.getActiveFormIndex();
        mainFrameSA.activateSubFocus(activeFormIndex);
        //event if only subfocus is selected we choose active area and notify dataInjector.
        dataInjector.onVisualAreaChanged(appMetaData.getTabMeta(
                activeFormIndex));
    }

    private void selectTab(TabMeta.Index index) {
        selectTab(index, false);
    }

    private void selectTab(final TabMeta.Index index, boolean isLastStatement) {
        if(isLastStatement) {
            LengthyTaskManager.runLengthyTask(
                    new LengthyTaskManager.Runnable() {
                        public void run() {
                            _selectTab(index);
                        }
                    });
        } else {
            _selectTab(index);
        }
    }

    private void _selectTab(TabMeta.Index index) {
        mainFrameFA.activateTab(index);
        mainFrameGA.activateTab(index);
        Index activeFormIndex = mainFrameFA.getActiveFormIndex();
        mainFrameSA.activateTab(activeFormIndex);
        dataInjector.onVisualAreaChanged(appMetaData.getTabMeta(index));
    }

    private void selectForm(FamgMeta.Index index, boolean ensureVisible) {
        selectForm(index, ensureVisible, false);
    }

    private void selectForm(final FamgMeta.Index index,
                            final boolean ensureVisible,
                            boolean isLastStatement) {
        if(isLastStatement) {
            LengthyTaskManager.runLengthyTask(
                    new LengthyTaskManager.Runnable() {
                        public void run() {
                            _selectForm(index, ensureVisible);
                        }
                    });
        } else {
            _selectForm(index, ensureVisible);
        }
    }

    private void _selectForm(FamgMeta.Index index, boolean ensureVisible) {
        selectFormIgnoreHistory(index, ensureVisible);
        mainFrameSA.selectTreeItem(mainFrameFA.getActiveFormIndex(), false);
        newHistoryItem(index.serialize());
    }

    private void selectFormIgnoreHistory(FamgMeta.Index index,
                                         boolean ensureVisible) {
        //TODO remove it somewhere (optimize)
        QForm form = mainFrameFA.getForm(index);
        if(form != null) {
            gridPagerSearchFilters = form.getModel().elementsValues();
        } else {
            gridPagerSearchFilters = EMPTY_LIST;
        }

        if(!index.equals(mainFrameFA.getActiveFormIndex())) {
            mainFrameFA.activateForm(index);
        }

        if(ensureVisible) {
            mainFrameFA.ensureVisibility(mainFrameFA.getActiveFormIndex());
        }

        selectTab(index);
    }

    private boolean parsingDate;
    private ArrayList dateTimers = new ArrayList();

    private void doFireDateTimers() {
        for(int i = 0; i < dateTimers.size(); i++) {
            Timer t = (Timer) dateTimers.get(i);
            t.cancel();
            t.run();
        }
        dateTimers.clear();
    }

    private void removeDateTimer(Timer t) {
        dateTimers.remove(t);
    }

    private boolean resolvingEntity;
    private ArrayList entityTimers = new ArrayList();

    private void doFireEntityTimers() {
        for(int i = 0; i < entityTimers.size(); i++) {
            Timer t = (Timer) entityTimers.get(i);
            t.cancel();
            t.run();
        }
        entityTimers.clear();
    }

    private void removeEntityTimer(Timer t) {
        entityTimers.remove(t);
    }

    private void processFAEvents(Event event) {
        if(event == QFormElementView.Events.ENTITY_REFERANCE_RESOLVE_EVENT) {
            resolvingEntity = ((Boolean) event.getData()).booleanValue();
            if(!resolvingEntity) {
                doFireEntityTimers();
            }
        } else if(event == QFormElementView.Events.DATE_PARSE_EVENT) {
            parsingDate = ((Boolean) event.getData()).booleanValue();
            if(!parsingDate) {
                doFireDateTimers();
            }
        } else if(event == MainFrameFA.Events.TAB_INITIALIZED) {
            if(isReportDesignState) {
                mainFrameFA.setFormsState(FormState.REPORT_DESIGN_STATE);
                if(initialAdhocFormIndex != null) {
                    enableAdhocForJoinable(initialAdhocFormIndex,
                            mainFrameFA.getActiveFormIndex());
                }
            }
            MainframeEventsCollection.TAB_INITIALIZED.setData(
                    mainFrameFA.getActiveFormIndex());
            //initialize operation strategies when form is activated
            TabMeta tabMeta = getMetaData().getTabMeta(
                    mainFrameFA.getActiveFormIndex());
            FamgMeta[] famgs = tabMeta.getFamgs();
            for(int i = 0; i < famgs.length; i++) {
                FamgMeta famg = famgs[i];
                factory.createStrategy(appMetaData.getFormID(famg.getIndex()));
            }
            //fire notification event
            getEventSource().fireEvent(
                    MainframeEventsCollection.TAB_INITIALIZED);
        } else if(event == MainFrameFA.Events.FORM_NEW_BUTTON_EVENT) {
            QForm form = mainFrameFA.getActiveForm();
            FamgMeta.Index formIndex = form.getModel().getIndex();
            BusinessOperationStrategy strategy = factory.createStrategy(
                    appMetaData.getFormID(formIndex));
            strategy.createRecordProtorype(form.getModel().elementsValues());
        } else if(event == MainFrameFA.Events.FORM_CHANGE_BUTTON_EVENT) {
            QForm form = mainFrameFA.getActiveForm();
            FamgMeta.Index formIndex = form.getModel().getIndex();
            Long rowID = form.getModel().getActiveRowID();
            BusinessOperationStrategy strategy = factory.createStrategy(
                    appMetaData.getFormID(formIndex));
            strategy.lockAndEditRecord(rowID);
        } else if(event == MainFrameFA.Events.FORM_UPDATE_BUTTON_EVENT) {
            QForm form = mainFrameFA.getActiveForm();
            FamgMeta.Index formIndex = form.getModel().getIndex();

            if(resolvingEntity) {
                entityTimers.add(getSaveTimer(form, formIndex));
                return;
            }

            if(parsingDate) {
                Timer t = getSaveTimer(form, formIndex);
                t.schedule(5000); //timeout
                dateTimers.add(t);
            } else {
                performSaveEvent(form, formIndex);
            }
        } else if(event == MainFrameFA.Events.FORM_CLEAR_BUTTON_EVENT) {
            QForm form = mainFrameFA.getActiveForm();
            FamgMeta.Index formIndex = form.getModel().getIndex();
            Long rowID = form.getModel().getActiveRowID();
            if(rowID != null) {
                performUnlockAfterEditRequest(formIndex, rowID,
                        appMetaData.getFormID(formIndex), true);
            } else {
                performClearExternalFields(formIndex);
            }
            dataInjector.onClearAction(appMetaData.getFamgMeta(formIndex));
        } else if(event == MainFrameFA.Events.FORM_SEARCH_BUTTON_EVENT) {
            FamgMeta.Index formIndex = mainFrameFA.getActiveFormIndex();

            if(resolvingEntity) {
                entityTimers.add(getSearchTimer(formIndex));
                return;
            }

            if(parsingDate) {
                dateTimers.add(getSearchTimer(formIndex));
            } else {
                performSearchEvent(formIndex);
            }
        } else if(event == MainFrameFA.Events.LINKED_FORM_SELECTION_EVENT) {
            String fieldId = (String) event.getData();
            FamgMeta.Index srcIndex = mainFrameFA.getActiveFormIndex();
            BusinessOperationStrategy strategy = factory.createStrategy(
                    appMetaData.getFormID(srcIndex));
            strategy.handleLinkEvent(fieldId);
        } else if(event == MainFrameFA.Events.FORM_CUSTOM_BUTTON) {
            String id = ((QButton.CustomButtonEventData) event.getData())
                    .getButtonId();
            FamgMeta.Index formIndex = mainFrameFA.getActiveFormIndex();
            boolean isMyQueWebForm = appMetaData.getFamgMeta(formIndex)
                    .isMyQueWeb();
            if(isMyQueWebForm) {
                FocusMeta.Index index = appMetaData.getIndexByID(id);
                if(index != null) {
                    if(index instanceof FamgMeta.Index) {
                        selectForm((FamgMeta.Index) index, true,
                                event.isUserGenerated());
                    } else if(index instanceof TabMeta.Index) {
                        selectTab((TabMeta.Index) index,
                                event.isUserGenerated());
                    } else if(index instanceof SubFocusMeta.Index) {
                        selectSubFocus((SubFocusMeta.Index) index,
                                event.isUserGenerated());
                    } else {
                        selectFocus(index, event.isUserGenerated());
                    }
                }
            } else if(id != null) {
                BusinessOperationStrategy strategy = factory.createStrategy(
                        appMetaData.getFormID(formIndex));
                strategy.handleCustomButtonEvent(id);
            }
        } else if(event == MainFrameFA.Events
                .FORM_CONTROL_NEED_MORE_DATA_EVENT) {
            FamgMeta.Index formIndex = mainFrameFA.getActiveFormIndex();
            FieldDataRequest data = (FieldDataRequest) event.getData();
            BusinessOperationStrategy strategy = factory.createStrategy(
                    appMetaData.getFormID(formIndex));
            strategy.handleControlDataRequest(data);
        } else if(event == MainFrameFA.Events.FORM_CONTROL_DATA_CHANGED) {
            ControlChangedData eventData = (ControlChangedData) event.getData();
            FamgMeta.Index formIndex = eventData.index;
            BusinessOperationStrategy strategy = factory.createStrategy(
                    appMetaData.getFormID(formIndex));

            strategy.handleControlEvent(eventData.controlId,
                    FormControlEventTypes.ON_CHANGE);
        } else if(event == QFormController.Events.
                FORM_ELEMENT_REPORT_DESIGN_EVENT) {
            Index formIndex = mainFrameFA.getActiveForm().getModel().getIndex();
            QForm form = mainFrameFA.getForm(formIndex);

            FieldMeta fieldMeta = (FieldMeta) event.getData();
            int adhocData = form.getView().getAdhocControlState(
                    fieldMeta.getFieldID());

            if(adhocEvent == null) {
                adhocEvent = new AdhocData();
            }
            adhocEvent.setElementDesignReportData(adhocData);
            adhocEvent.setFormIndex(formIndex);
            adhocEvent.setFieldSent(fieldMeta);
            MainframeEventsCollection.MAINFRAME_REPORT_DESIGN.setData(
                    adhocEvent);
            eventSource.fireEvent(
                    MainframeEventsCollection.MAINFRAME_REPORT_DESIGN);
        } else if(event == MainFrameFA.Events.FORM_SELECTION_REQUESTED_EVENT) {
            FamgMeta.Index index = (FamgMeta.Index) event.getData();
            selectForm(index, false);
        } else if(event == MainFrameFA.Events.FORM_ACTIVATED) {
            //add here events that should be performed when form is activated.
            selectForm(mainFrameFA.getActiveFormIndex(), false);
        } else if(event == MainFrameFA.Events.TAB_SELECTED_EVENT) {
            TabMeta.Index index = (TabMeta.Index) event.getData();
            selectTab(index, event.isUserGenerated());
        } else if(event == MainFrameFA.Events.SUBFOCUS_SELECTED_EVENT) {
            selectSubFocus(mainFrameFA.getActiveFormIndex(),
                    event.isUserGenerated());
        } else if(event == MainFrameFA.Events.FOCUS_SELECTED_EVENT) {
            selectFocus(mainFrameFA.getActiveFormIndex(),
                    event.isUserGenerated());
        } else if(event == QChartController.Events.CHART_REQUEST_DATA_EVENT) {
            requestChartData((QChartModel) event.getData());
        } else if(event == QChartController.Events.
                CHART_DRILLDOWN_CLICK_EVENT) {
            processChartDrilldownClick(
                    (QChartController.ChartDrilldownClickEventData) event
                            .getData());
        }
    }

    private void requestChartData(QChartModel chartModel) {
        ChartRequestObject request = new ChartRequestObject(
                chartModel.getMeta());
        RPC.getRPC().buildChart(request, new BuildChartAsyncCallback(
                chartModel));
    }

    private void processChartDrilldownClick(
            QChartController.ChartDrilldownClickEventData data) {
        MetaData metaData = getMetaData();

        FamgMeta.Index formIdx = (FamgMeta.Index) metaData.getIndexByID(
                data.getFormId());

        activateForm(formIdx);
        clearForm(formIdx, false);

        FamgMeta famgMeta = metaData.getFamgMeta(formIdx);
        List externalForms = famgMeta.getForm().getExternalForms();
        for(int i = 0; i < externalForms.size(); i++) {
            clearForm((FamgMeta.Index) externalForms.get(i), false);
        }

        String[] formIds = data.getFilterFormIds();
        for(int i = 0; i < formIds.length; i++) {
            String formId = formIds[i];
            setFormData(data.getFilters(formId), null,
                    (FamgMeta.Index) metaData.getIndexByID(formId), false);
        }

        performOperation(OperationTypes.SEARCH_RECORDS, formIdx);
    }

    private void performSaveEvent(QForm form, FamgMeta.Index formIndex) {
        Long rowID = form.getModel().getActiveRowID();
        Collection data = form.getModel().elementsValues();
        BusinessOperationStrategy strategy = factory.createStrategy(
                appMetaData.getFormID(formIndex));
        if(form.getModel().getFormState() == FormState.NEW_STATE) {
            strategy.insertRecord(rowID, data);
        } else {
            strategy.updateRecord(rowID, data);
        }
    }

    private Timer getSaveTimer(final QForm form,
                               final FamgMeta.Index formIndex) {
        return new Timer() {
            public void run() {
                if(!parsingDate) {
                    performSaveEvent(form, formIndex);
                }
                removeDateTimer(this);
            }
        };
    }

    private Timer getSearchTimer(final FamgMeta.Index formIndex) {
        Timer t = new Timer() {
            public void run() {
                if(!resolvingEntity) {
                    performSearchEvent(formIndex);
                }
                removeEntityTimer(this);
            }
        };
        t.schedule(5000); //timeout for entity resolving
        return t;
    }

    private void performSearchEvent(FamgMeta.Index formIndex) {
        saveLastSearchData(formIndex);
        performSearchRecordsRequest(formIndex, false);
    }

    private void performClearExternalFields(FamgMeta.Index formIndex) {
        ClearEntityRequestObject request = new ClearEntityRequestObject(
                appMetaData.getFormID(formIndex));
        ClearEntityAsyncCallback callback = new ClearEntityAsyncCallback();
        RPC.getRPC().clearEntity(request, callback);
    }

    private void saveLastSearchData(FamgMeta.Index formIndex) {
        lastSearchFormIndex = formIndex;

        QForm baseForm = mainFrameFA.getForm(formIndex);
        Collection fieldsData = baseForm.getModel().getNonEmptyFilters();
        //todo refactor last searched should be accumulated
        lastSearchFieldData = new FieldData[fieldsData.size()];
        int i = 0;
        for(Iterator iterator = fieldsData.iterator(); iterator.hasNext(); i++)
        {
            lastSearchFieldData[i] = ((FieldData) iterator.next()).cloneData();
        }
    }

    private void processDeleteEvent(FamgMeta.Index formIndex) {
        boolean hasDeleteAction = mainFrameFA.getActiveForm().getModel()
                .getContextMenuMeta().contains(ContextMenuMeta.DELETE_ITEM_ID);
        if(!hasDeleteAction) {
            DialogHelper.showModalMessageDialog(
                    I18N.getMessages().noPermissionsToDeleteRecord());
            return;
        }
        QGrid grid = mainFrameGA.getGrid(formIndex);
        List rowsToDelete = grid.getController().getMarkedRecordsIds();
        if(rowsToDelete.size() > 0) {
            int answer = DialogHelper.showModalQuestionDialog(
                    I18N.getMessages().deleteRecordWarning());
            if(answer == DialogHelper.YES) {
                BusinessOperationStrategy strategy = factory.createStrategy(
                        appMetaData.getFormID(formIndex));
                strategy.deleteRecord(rowsToDelete);
            }
        }
    }

    private void processLocalSearch(FamgMeta.Index formIndex) {
        saveLastSearchData(formIndex);
        performSearchRecordsRequest(formIndex, true);
    }

    private boolean processCommonEvents(Event event) {
        if((event == MainFrameFA.Events.FORM_MENU_ITEM) ||
                (event == MainFrameSA.Events.SHORTCUT_MENU_ITEM)) {
            String data = /*]/*[*/(String)/*]*[*//*]/[*/ event.getData();
            FamgMeta.Index formIndex = mainFrameFA.getActiveFormIndex();
            if(ContextMenuMeta.LOCALSEARCH_ITEM_ID.equalsIgnoreCase(data)) {
                processLocalSearch(formIndex);
            } else if(ContextMenuMeta.DELETE_ITEM_ID.equalsIgnoreCase(data)) {
                processDeleteEvent(formIndex);
            } else {
                BusinessOperationStrategy strategy = factory.createStrategy(
                        appMetaData.getFormID(formIndex));
                strategy.handleCustomMenuEvent(data);
            }
        } else {
            return false;
        }
        return true;
    }

    private EmailComposeDialog emailDialog;


    private void processSAEvents(Event event) {
        if(event == MainFrameSA.Events.E_MAIL) {
            if(emailDialog == null) {
                emailDialog = new EmailComposeDialog(
                        appMetaData.getUserProfile().getEmail());
            }
            emailDialog.show();
        } else if(event == MainFrameSA.Events.ABOUT) {
            AboutFrame aboutFrame = getAboutFrame();
            int left = (getOffsetWidth() - aboutFrame.getOffsetWidth()) / 2;
            int top = (getOffsetHeight() - aboutFrame.getOffsetHeight()) / 2;
            aboutFrame.show(left, top);
        } else if(event == MainFrameSA.Events.HELP) {
            WindowHelper.openWindow(performHelpCallEvent());
        } else if(event == MainFrameSA.Events.REPORT_DESIGNER) {
            eventSource.fireEvent(MainframeEventsCollection.SELECT_ADHOCFRAME);
        } else if(event == MainFrameSA.Events.BACK) {
            History.back();
        } else if(event == MainFrameSA.Events.FORWARD) {
            History.forward();
        } else if(event == MainFrameSA.Events.FOCUS_SELECTED) {
            FocusMeta.Index index = (FocusMeta.Index) event.getData();
            selectFocus(index, event.isUserGenerated());
        } else if(event == MainFrameSA.Events.SUBFOCUS_SELECTED) {
            SubFocusMeta.Index index = (SubFocusMeta.Index) event.getData();
            selectSubFocus(index, event.isUserGenerated());
        } else if(event == MainFrameSA.Events.TAB_SELECTED) {
            TabMeta.Index index = (TabMeta.Index) event.getData();
            selectTab(index, event.isUserGenerated());
        } else if(event == MainFrameSA.Events.FORM_SELECTED) {
            if(!mainFrameSA.isIgnoreTreeSelection()) {
                FamgMeta.Index index = (FamgMeta.Index) event.getData();
                selectForm(index, true, event.isUserGenerated());
            }
        } else if(event == MainFrameSA.Events.CLEAR_ALL) {
            clearAll(getClearingFormState());
        } else if(event == MainFrameSA.Events.LAST_SEARCH) {
            performLastSearch();
        } else if(event == MainFrameSA.Events.DO_LOAD_SAVED_SEARCH) {
            SavedSearchObject search = (SavedSearchObject) event.getData();
            clearAll(getClearingFormState());
            FamgMeta.Index tmpIndex = (FamgMeta.Index) appMetaData.getIndexByID(
                    search.getFormId());
            selectForm(tmpIndex, true);
            FieldData[] tmp = search.getFieldsData();

            mainFrameFA.performCommand(new SetDataForFormCommand(tmp, null),
                    tmpIndex);
            mainFrameFA.performCommand(new TurnFormToStateCommand(
                    FormState.SEARCH_STATE), tmpIndex);
        } else if(event == MainFrameSA.Events.DELETE_SAVED_SEARCH) {
            Long activeRowID = (Long) event.getData();
            performDeleteSavedSearchRequest(new Long[]{activeRowID});
        } else if(event == MainFrameSA.Events.DO_SAVE_CURRENT_SEARCH) {
            FamgMeta.Index formIndex = mainFrameFA.getActiveFormIndex();
            String formId = appMetaData.getFormID(formIndex);
            String name = (String) event.getData();
            if(appMetaData.getTabMeta(formIndex).isMyQueWeb()) {
                // TODO Fix the implementation. Rather toolbar buttons should be disabled
                DialogHelper.showModalMessageDialog(
                        I18N.getMessages().cantSaveSearch());
            } else {
                List fieldFilters = (List) lastFormSearches.get(formId);
                if (fieldFilters != null) {
                    EntityData entityData = (EntityData) fieldFilters.get(0);
                    MainFrameSA.saveSearch(name, formId, entityData.getNotEmptyFields());
                }
            }
        } else if(event == MainFrameSA.Events.PRINT_FORM) {
            QForm activeForm = mainFrameFA.getActiveForm();
            Long recordId = activeForm.getModel().getActiveRowID();
            if (activeForm != null && 
                (activeForm.getModel().getFormState() == FormState.SELECTED_STATE ||
                 activeForm.getModel().getFormState() == FormState.EDIT_STATE)) {
                printFormRequest = new PrintFormRequestObject(appMetaData.getFormID(
                    activeForm.getModel().getIndex()), 
                    activeForm.getModel().getActiveRowID(), true, false, 0, 20);
                PrintFormAsyncCallback callback = new PrintFormAsyncCallback();
                RPC.getRPC().printForm(printFormRequest, callback);
            }
            //} else if (event == MainFrameSA.Events.PRINT_GRID) {
            //    printGrid("html", true);
        } else if(event == MainFrameSA.Events.AD_HOC_REPORTS) {
            if(!isReportDesignState) {
                mainFrameFA.setFormsState(FormState.REPORT_DESIGN_STATE);
            } else {
                mainFrameFA.setFormsState(FormState.PREVIOUS_STATE);
            }
            isReportDesignState = !isReportDesignState;
        } else if(event == MainFrameSA.Events.HISTORY_MENU_ITEM) {
            GridHistoryRecord rec = (GridHistoryRecord) event.getData();
            ignoreGridRecordHistory = true;
            selectForm(rec.getFormIndex(), true);
            QForm form = mainFrameFA.getForm(rec.getFormIndex());
            form.getFormController().searchByPkey(rec.getRecordId());
        } else if(event == MainFrameSA.Events.SAVE_SETTINGS) {
            collectUISettings();
            RPC.QAsyncCallback saveSettingCallback = new RPC.QAsyncCallback() {
                public void onRequestEnded(boolean success, Object result) {
                    if(success) {
                        DialogHelper.showModalMessageDialog(
                                I18N.getMessages().uiSettingsSavedSucc());
                    }
                }
            };
            RPC.getRPC().saveSettings(appMetaData, saveSettingCallback);
        } else if(event == MainFrameSA.Events.OPEN_QUEPLIX_SITE) {
            Window.open("http://www.queplix.com", "Queplix", "");
        } else {
            eventSource.fireEvent(event); // event not processed -- retranslate
        }
    }

    public void performLastSearch() {
        if(lastSearchFormIndex != null) {
            selectForm(lastSearchFormIndex, true);
            FieldData[] tmp = new FieldData[lastSearchFieldData.length];
            for(int i = 0; i < tmp.length; i++) {
                tmp[i] = lastSearchFieldData[i].cloneData();
            }
            mainFrameFA.performCommand(new SetDataForFormCommand(tmp, null),
                    lastSearchFormIndex);
            mainFrameFA.performCommand(new TurnFormToStateCommand(
                    FormState.SEARCH_STATE), lastSearchFormIndex);
            performSearchRecordsRequest(lastSearchFormIndex, false);
        }
    }

    private void clearAll(int formStateAfter) {
        if(agreeToResetForms(mainFrameFA.existingFormsIterator())) {
            lastFormSearches.clear();
            unlockLockedRecords(mainFrameFA.existingFormsIterator());
            dataInjector.onClearAllAction(formStateAfter);
            isReportDesignState = (formStateAfter == FormState
                    .REPORT_DESIGN_STATE);
        }
    }

    private void unlockLockedRecords(Iterator formsToClear) {
        while(formsToClear.hasNext()) {
            FamgMeta.Index index = (Index) formsToClear.next();
            QForm form = mainFrameFA.getForm(index);
            QFormController qfc = form.getFormController();
            int state = qfc.getCurrentState();
            if(state == FormState.EDIT_STATE) {
                performUnlockAfterEditRequest(index,
                        form.getModel().getActiveRowID(), appMetaData.getFormID(
                        index), false);
            }
        }
    }

    private boolean agreeToResetForms(Iterator formsToClear) {
        while(formsToClear.hasNext()) {
            FamgMeta.Index index = (Index) formsToClear.next();
            QForm form = mainFrameFA.getForm(index);
            QFormController qfc = form.getFormController();
            int state = qfc.getCurrentState();
            if(state == FormState.EDIT_STATE || state == FormState.NEW_STATE) {
                selectFormIgnoreHistory(index, true);
                if(!discardFormChanges(form)) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean discardFormChanges(QForm form) {
        return DialogHelper.showModalQuestionDialog(
                "Form \"" + form.getModel().getFormTitle()
                        + "\" is editing. \nDiscard your changes? ")
                == DialogHelper.YES;
    }

    private void processGAEvents(Event event) {
        FamgMeta.Index gridIndex = mainFrameGA.getActiveGridIndex();
        if(event == MainFrameGA.Events.GRID_CUSTOMIZE) {
            SubsetData data = /*]/*[*/(SubsetData)/*]*[*//*]/[*/ event
                    .getData();
            performCustomizeGridRequest(gridIndex, data);
        } else if(event == MainFrameGA.Events.GRID_REFRESH) {
            performGridDrivenRequest(gridIndex);
        } else if(event == MainFrameGA.Events.GRID_NEED_DATA) {
            performGridDrivenRequest(gridIndex);
        } else if(event == MainFrameGA.Events.RECORD_TO_BE_SELECTED) {
            Long selectedRecord = (Long) event.getData();
            selectedRecordChanged(gridIndex, selectedRecord);
        } else if(event == MainFrameGA.Events.RECORD_SELECTED) {
            SelectedRecord selected = (SelectedRecord) event.getData();
            recordSelected(selected.getRecordId(), selected.getIndex());
        } else if(event == MainFrameGA.Events.GRID_PRINT) {
            printGrid("html", true);
        } else if(event == MainFrameGA.Events.GRID_EXPORT_TO_WORD) {
            printGrid("word", false);
        } else if(event == MainFrameGA.Events.GRID_EXPORT_TO_EXCEL) {
            printGrid("excel", false);
        } else if(event == QGridController.Events.DELETE_KEY_PRESSED) {
            Index formIndex = mainFrameFA.getActiveForm().getModel().getIndex();
            processDeleteEvent(formIndex);
        }
    }

    private void selectedRecordChanged(FamgMeta.Index gridIndex,
                                       Long recordId) {
        QGrid grid = mainFrameGA.getGrid(gridIndex);
        QForm form = mainFrameFA.getForm(gridIndex);

        int formState = form.getFormController().getCurrentState();
        if(formState == FormState.EDIT_STATE
                || formState == FormState.NEW_STATE) {

                            if(DialogHelper.showModalQuestionDialog(    form.getFormController().getRecordEditingMessage())
                    == DialogHelper.YES) {
                if(formState == FormState.EDIT_STATE) {
                    performUnlockAfterEditRequest(gridIndex, recordId,
                            appMetaData.getFormID(gridIndex), true);
                }
                grid.getModel().setSelectedRecordId(recordId);
            }
        } else {
            grid.getModel().setSelectedRecordId(recordId);
        }
    }

    public void onLengthyTaskStart() {
        mainFrameSA.setIndicatorState(MainFrameSA.Indicator.WORKING);
        WindowHelper.setApplicationBusyMouse(true);
    }

    public void onLengthyTaskEnd() {
        mainFrameSA.setIndicatorState(MainFrameSA.Indicator.IDLE);
        WindowHelper.setApplicationBusyMouse(false);
    }

    public void onRequestStarted() {
        mainFrameSA.setIndicatorState(MainFrameSA.Indicator.COMMUNICATING);
    }

    public void onRequestFinished(boolean success) {
        mainFrameSA.setIndicatorState(MainFrameSA.Indicator.IDLE);
    }

    public void handleError(Throwable caught) {
    }

    private AboutFrame getAboutFrame() {
        if(aboutFrame == null) {
            aboutFrame = new AboutFrame(appMetaData.getProductMeta());
        }
        return aboutFrame;
    }

    private class RecordsSelectedAsyncCallback extends RPC.QAsyncCallback {
        private FamgMeta.Index formIndex;

        public RecordsSelectedAsyncCallback(FamgMeta.Index formIndex) {
            this.formIndex = formIndex;
        }

        public void onRequestEnded(boolean success, Object result) {
            if(success) {
                AccumulatedEntityDataResponse response
                        = (AccumulatedEntityDataResponse) result;
                if(mainFrameFA.performCommand(new TurnFormToStateCommand(
                        FormState.SELECTED_STATE), formIndex)) {
                    dataInjector.onFormsDataUpdated(response.getEntitiesList(),
                            response.getExternalFieldsList(),
                            response.getGridData());
                    if(!ignoreGridRecordHistory) {
                        FamgMeta.Index index = gridHistoryRecord.getFormIndex();
                        gridHistoryRecord.setDescription(mainFrameFA.getForm(
                                index).getModel().getRecordDescription());
                        mainFrameSA.addHistoryItem(gridHistoryRecord);
                    } else {
                        ignoreGridRecordHistory = false;
                    }
                } else {
                    mainFrameGA.performCommand(new ClearRecordGridCommand(),
                            formIndex);
                }
            }
        }
    }

    private class ClearEntityAsyncCallback extends RPC.QAsyncCallback {
        public ClearEntityAsyncCallback() {
        }

        public void onRequestEnded(boolean success, Object result) {
            if(success) {
                AccumulatedEntityDataResponse response
                        = (AccumulatedEntityDataResponse) result;
                dataInjector.onFormsDataUpdated(response.getEntitiesList(),
                        response.getExternalFieldsList(),
                        response.getGridData());
            }
        }
    }

    private class UnlockAterEditAsyncCallback extends RPC.QAsyncCallback {
        private FamgMeta.Index formIndex;
        private Long rowID;
        private boolean updateAfterUnlock;

        public UnlockAterEditAsyncCallback(FamgMeta.Index formIndex, Long rowID,
                                           boolean updateAfterUnlock) {
            this.formIndex = formIndex;
            this.rowID = rowID;
            this.updateAfterUnlock = updateAfterUnlock;
        }

        public void onRequestEnded(boolean success, Object result) {
            if(!success) {
                DialogHelper.showModalMessageDialog(
                        "Couldn't unlock the record with id [" + rowID +
                                "], form [" + appMetaData.getFormID(formIndex)
                                + "].");
            } else {
                if(updateAfterUnlock) {
                    AccumulatedEntityDataResponse response
                            = (AccumulatedEntityDataResponse) result;
                    dataInjector.onFormsDataUpdated(response.getEntitiesList(),
                            response.getExternalFieldsList(),
                            response.getGridData());
                }
            }
        }
    }

    private class SavedSearchDeleteAsyncCallback extends RPC.QAsyncCallback {
        public void onRequestEnded(boolean success, Object result) {
            if(success) {
                MainFrameSA.onSearchDeleted();
            }
        }
    }

    private class PrintFormAsyncCallback extends RPC.QAsyncCallback {
        public void onRequestEnded(boolean success, Object result) {
            if(success) {
                JavaScriptObject printFormWindow = WindowHelper.openWindow(
                        "../getReport/response/html/"
                                + printFormRequest.getProcessId()
                                + "?transletName=form");
                WindowHelper.printWindow(printFormWindow);
            }
        }
    }

    private class PrintGridAsyncCallback extends RPC.QAsyncCallback {
        private static final String validFormats = "'html', 'word', 'excel'";
        private String format;
        private boolean print;

        public PrintGridAsyncCallback() {
            this("html", false);
        }

        public PrintGridAsyncCallback(String format, boolean print) {
            if(validFormats.indexOf("'" + format + "'") == -1) {
                throw new IllegalArgumentException(
                        "Format must be one of the values: " + validFormats);
            }
            this.format = format;
            this.print = print;
        }

        public void onRequestEnded(boolean success, Object result) {
            if(success) {
                String url = "../getReport/response/" + format + "/" +
                        printGridRequest.getProcessId() + "?transletName=grid";
                JavaScriptObject printGridWindow = WindowHelper.openWindow(url);
                if(print) {
                    WindowHelper.printWindow(printGridWindow);
                }
            }
        }
    }

    private class BuildChartAsyncCallback extends RPC.QAsyncCallback {
        private QChartModel chartModel;

        public BuildChartAsyncCallback(QChartModel chartModel) {
            this.chartModel = chartModel;
        }

        public void onRequestEnded(boolean success, Object result) {
            if(success) {
                chartModel.setData(((ChartResponseObject) result).getData());
            }
        }
    }

//    private boolean ignoreFormSelection;

    public void onEvent(Event event, Widget sender) {
        if(processCommonEvents(event)) {
            // processed common event
        } else if(sender == mainFrameSA) {
            processSAEvents(event);
        } else if(sender == mainFrameFA) {
            processFAEvents(event);
        } else if(sender == mainFrameGA) {
            processGAEvents(event);
        }
    }

    public void setAdhocElementIn(String elementId, FamgMeta.Index index) {
        setElementReportState(index, elementId, QFormElementModel.IN_REPORT);
    }

    public void setAdhocElementOut(String elementId, FamgMeta.Index index) {
        setElementReportState(index, elementId,
                QFormElementModel.NOT_IN_REPORT);
    }

    private void setElementReportState(Index index, String elementId,
                                       int state) {
        QForm form = mainFrameFA.getForm(index);
        if(form != null) {
            form.getView().setAdhocControlState(elementId, state);
        }
    }

    /**
     * Set all elements in the given tab that contains in #inReportList to the "in_report" state,
     * and all other in the given tab to "out_of_report" state.
     *
     * @param inReport Set<Adhoc>
     * @param index    tab index
     */
    public void setAdhocData(Set inReport, TabMeta.Index index) {
        FamgMeta[] forms = appMetaData.getTabMeta(index).getFamgs();
        //set out of report for all elements
        for(int i = 0; i < forms.length; i++) {
            FamgMeta form = forms[i];
            FieldMeta[] fields = form.getForm().getEntityMeta().getFields();
            for(int j = 0; j < fields.length; j++) {
                FieldMeta field = fields[j];
                setAdhocElementOut(field.getFieldID(), form.getIndex());
            }
        }
        //set in for selected
        for(Iterator iterator = inReport.iterator(); iterator.hasNext();) {
            AdhocData data = (AdhocData) iterator.next();
            setAdhocElementIn(data.getFieldSent().getFieldID(),
                    data.getFormIndex());
        }
    }

    /**
     * This method disables all forms for report design, which not joined with the given form and set "not_in_report" state other froms.
     *
     * @param formIndex enables all joinable to this index forms.
     */
    public void enableAdhocForJoinable(FamgMeta.Index formIndex) {
        initialAdhocFormIndex = formIndex;
        Set jf = appMetaData.getFamgMeta(formIndex).getForm()
                .getJoinableForms();
        Iterator it = mainFrameFA.existingFormsIterator();
        while(it.hasNext()) {
            FamgMeta.Index famgIndex = (FamgMeta.Index) it.next();
            mainFrameFA.getForm(famgIndex).getView().setAdhocControlsEnabled(
                    jf.contains(famgIndex));
        }
        QForm form = mainFrameFA.getForm(formIndex);
        if(form != null) {
            form.getView().setAdhocControlsEnabled(true);
        }
    }

    /**
     * This method disables all forms in tab with index = tabIndex for report design than
     * enables all joinable forms of form with index = formIndex in tab with index = tabIndex in report design mode.
     *
     * @param formIndex initial adhoc form index
     * @param tabIndex  initialized tab index.
     */
    public void enableAdhocForJoinable(FamgMeta.Index formIndex,
                                       TabMeta.Index tabIndex) {
        Set jf = appMetaData.getFamgMeta(formIndex).getForm()
                .getJoinableForms();
        FamgMeta[] initializedTabForms = appMetaData.getTabMeta(tabIndex)
                .getFamgs();
        for(int j = 0; j < initializedTabForms.length; j++) {
            FamgMeta.Index index = initializedTabForms[j].getIndex();
            mainFrameFA.getForm(index).getView().setAdhocControlsEnabled(
                    jf.contains(index));
        }
    }

    public void enableAdhocForAllForms() {
        initialAdhocFormIndex = null;
        Iterator it = mainFrameFA.existingFormsIterator();
        while(it.hasNext()) {
            FamgMeta.Index index = (Index) it.next();
            mainFrameFA.getForm(index).getView().setAdhocControlsEnabled(true);
        }
    }

    /**
     * @return list of entity data filters. List<EntityData>
     */
    public List getAdhocFilters() {
        ArrayList filters = new ArrayList();
        if(initialAdhocFormIndex != null) {
            //add current form filters
            EntityData baseData = getStateRelatedFormFilters(
                    initialAdhocFormIndex);
            if(baseData != null && baseData.getFields().length > 0) {
                filters.add(baseData);
            }

            //add all joined forms filters
            Set formsToGetFiltersFrom = appMetaData.getFamgMeta(
                    initialAdhocFormIndex).getForm().getJoinableForms();
            for(Iterator iterator = formsToGetFiltersFrom.iterator();
                iterator.hasNext();) {

                FamgMeta.Index index = (Index) iterator.next();

                EntityData data = getStateRelatedFormFilters(index);
                if(data != null && data.getFields().length > 0) {
                    filters.add(data);
                }
            }
        }
        return filters;
    }

    public AdhocOperations getAdhocOperations() {
        return this;
    }

    public GridOperations getGridOperations() {
        return this;
    }

    public FormOperations getFormOperations() {
        return this;
    }

    public MetaData getMetaData() {
        return appMetaData;
    }


    public boolean isInEditMode() {
        return mainFrameFA.isInEditMode();
    }

    public FieldData getFieldData(Index index, String elementId) {
        FieldData data = null;
        QForm form = mainFrameFA.getForm(index);
        if(form != null) {
            data = form.getModel().getElementData(elementId);
            if(data == null) {
                throw new IllegalArgumentException("There is no field [" +
                        elementId + "] on the form ["
                        + form.getModel().getFormTitle()
                        + "]");
            }
        }
        return data;
    }

    public void setFieldData(Index index, FieldData data) {
        if(data == null) {
            throw new NullPointerException(
                    "Field data could not be null object.");
        }
        QForm form = mainFrameFA.getForm(index);
        if(form != null) {
            form.getModel().setDataForElement(data);
        }
    }

    public void setOnDemandData(Index formIndex, FieldOnDemandData data) {
        mainFrameFA.performCommand(new SetOnDemandDataForControlCommand(data),
                formIndex);
    }

    public void setFormData(FieldData[] fields, Long rowId,
                            Index index, boolean clearOtherFields) {
        SetDataForFormCommand cmd = new SetDataForFormCommand(fields, rowId,
                clearOtherFields);
        mainFrameFA.performCommand(cmd, index);
    }

    public void setData(EntityData[] entitiesList,
                        EntityData[] externalFieldsList,
                        Collection gridData) {
        dataInjector.onFormsDataUpdated(entitiesList, externalFieldsList,
                gridData);
    }

    public void setOperationStatus(int operation, boolean isSuccessful,
                                   Index index) {
        switch(operation) {
            case OperationTypes.CREATE_RECORD_PROTOTYPE: {
                int state = isSuccessful ? QFormModel.NEW_SUCCESSFUL
                        :QFormModel.NEW_FAILED;
                mainFrameFA.performCommand(new SetNewEntityStatusFormCommand(
                        state), index);
                break;
            }
            case OperationTypes.DELETE_RECORDS: {
                int state = isSuccessful ? QFormModel.DELETE_SUCCESSFUL
                        :QFormModel.DELETE_FAILED;
                mainFrameFA.performCommand(new SetDeleteStatusFormCommand(
                        state), index);
                break;
            }
            case OperationTypes.UPDATE_RECORD: //same handler, don't break;
            case OperationTypes.INSERT_RECORD: {
                int state = isSuccessful ? QFormModel.UPDATE_SUCCESSFUL
                        :QFormModel.UPDATE_FAILED;
                mainFrameFA.performCommand(new SetUpdateStatusFormCommand(
                        state), index);
                break;
            }
            case OperationTypes.LOCK_AND_EDIT_RECORD: {
                int state = isSuccessful ? QFormModel.LOCK_FOR_EDIT_SUCCESSFUL
                        :QFormModel.LOCK_FOR_EDIT_FAILED;
                mainFrameFA.performCommand(new SetLockForEditStatusFormCommand(
                        state), index);
                break;
            }
            case OperationTypes.LOCAL_SEARCH_RECORDS://do same thing
            case OperationTypes.SEARCH_RECORDS: {
                int state = isSuccessful ? QFormModel.SEARCH_SUCCESSFUL_SINGLE
                        :QFormModel.SEARCH_FAILED;
                mainFrameFA.performCommand(new SetSearchStatusFormCommand(
                        state), index);
                break;
            }
        }
    }

    public void performOperation(int operation, Index index) {
        Event event = null;
        switch(operation) {
            case OperationTypes.CREATE_RECORD_PROTOTYPE: {
                event = QFormController.Events.FORM_NEW_BUTTON_EVENT;
                break;
            }
            case OperationTypes.DELETE_RECORDS: {
                processDeleteEvent(index);
                break;
            }
            case OperationTypes.UPDATE_RECORD: //same handler, don't break;
            case OperationTypes.INSERT_RECORD: {
                event = QFormController.Events.FORM_UPDATE_BUTTON_EVENT;
                break;
            }
            case OperationTypes.LOCK_AND_EDIT_RECORD: {
                event = QFormController.Events.FORM_CHANGE_BUTTON_EVENT;
                break;
            }
            case OperationTypes.SEARCH_RECORDS: {
                event = QFormController.Events.FORM_SEARCH_BUTTON_EVENT;
                break;
            }
            case OperationTypes.CLEAR_FORM: {
                event = QFormController.Events.FORM_CLEAR_BUTTON_EVENT;
                break;
            }
            case OperationTypes.LOCAL_SEARCH_RECORDS: {
                performSearchRecordsRequest(index, true);
                break;
            }
        }
        if(event != null) {
            mainFrameFA.getForm(index).getFormController().performAction(
                    event);
        }
    }

    public void clearForm(Index index, boolean clearExternalFields) {
        QForm form = mainFrameFA.getForm(index);
        if(form == null) {
            dataInjector.onClearAction(appMetaData.getFamgMeta(index));
        } else {
            performOperation(OperationTypes.CLEAR_FORM, index);
        }
    }

    public void clearFormField(Index index, String fieldId) {
        /*FieldData d = getFieldData(index, fieldId);
        FieldData clonedData = d.cloneData();//need to do this, because it is
        //the same object and form won't update view. 
        clonedData.clear();
        setFieldData(index, clonedData);*/
        QForm form = mainFrameFA.getForm(index);
        form.getModel().clearElementData(fieldId);
    }

    public boolean turnFormToState(Index index, int formState) {
        TurnFormToStateCommand cmd = new TurnFormToStateCommand(formState);
        return mainFrameFA.performCommand(cmd, index);
    }

    public void resetAndSetFormState(Index index, int formState) {
        QForm form = mainFrameFA.getForm(index);
        QFormController qfc = form.getFormController();
        qfc.resetAndSetFormState(formState);
    }

    public int getFormState(Index index) {
        QForm form = mainFrameFA.getForm(index);
        return form != null
                ? form.getFormController().getCurrentState()
                :FormState.SEARCH_STATE;
    }

    public Long getSelectedRecordId(Index index) {
        Long recordId = null;
        QForm form = mainFrameFA.getForm(index);
        if(form != null) {
            recordId = form.getModel().getActiveRowID();
        }
        return recordId;
    }

    public void selectRecord(Long id, Index index) {
        ignoreGridRecordHistory = true; //todo check if it needed
        recordSelected(id, index);
    }

    public void activateForm(Index index) {
        selectForm(index, true);
    }

    public void clearGrid(Index index) {
        mainFrameGA.getGrid(index).getController().clearGrid();
    }

    public void clearGridSelection(Index index) {
        QGrid grid = mainFrameGA.getGrid(index);
        if(grid != null) {
            grid.getController().clearSelectedRecord();
        } else {
            mainFrameGA.activateGrid(index);
        }
    }

    public void setDataForGridRow(RowData row, Index index) {
        mainFrameGA.performCommand(new SetDataForRowCommand(row), index);
    }

    public void setDataForGrid(GridData gridData, int totalRecordsCount,
                               int currentPage, Index index) {
        SetDataForGridCommand gridCommand = new SetDataForGridCommand(gridData,
                totalRecordsCount, currentPage);
        mainFrameGA.performCommand(gridCommand, index);
    }

    public void selectGridRecord(Long id, Index index) {
        //todo check if we need ignoreGridRecordHistory == true
        if(!index.equalsToIndex(mainFrameGA.getActiveGridIndex())) {//need to do that, because needs to be sure that select record in active grid.
            activateGrid(index);
        }
        mainFrameGA.getGrid(index).getModel().setSelectedRecordId(id);
    }

    public void activateGrid(Index index) {
        mainFrameGA.activateGrid(index);
    }

    public GridSearchProperties getGridSearchProperties(Index famgIndex) {
        QGrid grid = mainFrameGA.getGrid(famgIndex);
        GridSearchProperties ret = new GridSearchProperties();
        if(grid != null) {
            QGridModel gridModel = grid.getModel();
            ret.setDoCount(gridModel.isCounterToggledOn());
            ret.setPage((int) gridModel.getCurrentPage());
            ret.setPageSize(gridModel.getPageSize());
            ret.setSortField(gridModel.getSortField());
        }

        return ret;
    }

    public List getMarkedRows(Index index) {
        List ret;
        QGrid grid = mainFrameGA.getGrid(index);
        if(grid == null) {
            ret = EMPTY_LIST;
        } else {
            ret = grid.getController().getMarkedRecordsIds();
        }
        return ret;
    }

    private void collectUISettings() {
        mainFrameFA.collectUISettings();
        mainFrameGA.collectUISettings();
    }

    public Widget getView() {
        return this;
    }

    public void activated() {
    }

    public void disabled() {
    }

    private void printGrid(String format, boolean print) {
        FamgMeta.Index printIndex = mainFrameGA.getActiveGridIndex();

        QGrid grid = mainFrameGA.getGrid(printIndex);
        QGridModel model = grid.getModel();
        boolean doCount = model.isCounterToggledOn();
        int page = (int) model.getCurrentPage();
        int pageSize = model.getPageSize();

        if((doCount && model.getRecordsTotal() > 0)
                || model.getModelRowCount() > 0) {
            String formId = appMetaData.getFormID(printIndex);
            printGridRequest = new PrintGridRequestObject(formId,
                    gridPagerSearchFilters, false, doCount, page, pageSize);
            PrintGridAsyncCallback callback = new PrintGridAsyncCallback(format,
                    print);
            RPC.getRPC().printGrid(printGridRequest, callback);
        } else {
            DialogHelper.showModalMessageDialog(
                    "The grid is empty. There is nothing to print.");
        }
    }
    
    public void checkMailStatus() {
        mainFrameSA.checkMailStatus();
    }
}
