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

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.queplix.core.client.app.rpc.RPC;
import com.queplix.core.client.app.vo.EntityElement;
import com.queplix.core.client.app.vo.FamgMeta;
import com.queplix.core.client.app.vo.FieldMeta;
import com.queplix.core.client.app.vo.LoadReportResponseObject;
import com.queplix.core.client.app.vo.LoginRequestObject;
import com.queplix.core.client.app.vo.LoginResult;
import com.queplix.core.client.app.vo.MetaData;
import com.queplix.core.client.app.vo.PortalCssRequest;
import com.queplix.core.client.app.vo.PortalCssResponse;
import com.queplix.core.client.app.vo.TabMeta;
import com.queplix.core.client.app.vo.UserProfile;
import com.queplix.core.client.common.CSSUtil;
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.ui.DialogHelper;
import com.queplix.core.client.common.ui.WindowHelper;
import com.queplix.core.client.controls.QFormElementModel;
import com.queplix.core.client.controls.datefield.DateHelper;
import com.queplix.core.client.frames.LoginFrame;
import com.queplix.core.client.frames.QFrame;
import com.queplix.core.client.frames.adhoc.AdhocReportFrame;
import com.queplix.core.client.frames.mainframe.AdhocOperations;
import com.queplix.core.client.frames.mainframe.MainFrameBuilder;
import com.queplix.core.client.frames.mainframe.MainframeEventsCollection;
import com.queplix.core.client.frames.mainframe.impl.DefaultMainFrameBuilder;
import com.queplix.core.client.i18n.I18N;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Main application class.
 *
 * @author Sultan Tezadov
 * @since 23 Sep 2006
 */
// TODO: once generics enabled, remove all occurences of '/*]' and '[*/'
public class Application implements EntryPoint
        , RPC.RequestListener
        , LengthyTaskManager.LengthyTaskListener
        , EventListener
        , IApplication {

    private MainFrameBuilder mainFrameBuilder;

    private LoginFrame loginFrame;
    //    private IMainFrame mainFrame;
    //    private OperationContext mainFrameContext;
    private AdhocReportFrame adhocReportFrame;

    private MetaData appMetaData;

    public final static String BROWSER_MSIE = "MSIE";
    public final static String BROWSER_FIREFOX = "Firefox";
    
    public final static String PORTAL_TYPE_PARAM = "portal_type";

    public static final int LOGIN_FRAME = -1;
    public static final int MAIN_FRAME = 0;
    public static final int AD_HOC_REPORT_FRAME = 1;

    private static UserProfile userProfile;
    
    private final String portalType;

    public static UserProfile getUserProfile() {
        return userProfile;
    }
    

    public Application() {
        portalType = new RequestParamsDecoder().getParam(PORTAL_TYPE_PARAM);
        PortalCssRequest request = new PortalCssRequest(portalType);
        RPC.getRPC().retrievePortalCss(request, new RPC.QAsyncCallback() {
            public void onRequestEnded(boolean success, Object result) {
                if (success) {
                    CSSUtil.addCSS(((PortalCssResponse) result).getCssUrl());
                }
            }
        });
    }

    private AdhocReportFrame getAdhocReport() {
        if(adhocReportFrame == null) {
            adhocReportFrame = new AdhocReportFrame(appMetaData);
            adhocReportFrame.getEventSource().addEventListener(this);
        }
        return adhocReportFrame;
    }

    /**
     * To be overriden in inheritances.
     */
    protected void displayModuleContent() {
        String portalType = new RequestParamsDecoder().
                            getParam(PORTAL_TYPE_PARAM);
        WindowHelper.disableContextMenu();
        RPC.addRequestListener(this);
        LengthyTaskManager.addLengthyTaskListener(this);
        loginFrame = createLoginFrame();
        selectFrame(Application.LOGIN_FRAME);
        loginFrame.getLoginWidget().setFocus(true);
        loginFrame.getEventSource().addEventListener(this);

        setStatus("Ready to log in");
        IdleUtilizer.onLoginPrompted();
    }

    public void onModuleLoad() {
        checkAndLoad();
    }

    /**
     * Check if this module should be loaded, and if it is
     */
    final protected void checkAndLoad() {
        String thisClassName = getModuleName();
        if(getGwtModuleClassName().equalsIgnoreCase(thisClassName)) {
            displayModuleContent();
        }
    }

    /**
     * To be reloaded in all deriving classes.
     */
    protected String getModuleName() {
        return "com.queplix.core.Core";
    }

    public static String getGwtModuleClassName() {
        return GWT.getModuleName();
    }

    /*hand-made implementation
    public static native String getGwtModuleClassName(){
        metas = $doc.getElementsByTagName("META");
        ret = "";
        for (i = 0; i < metas.length; i++) {
             if(metas[i].name == 'gwt:module') {
                  ret = metas[i].content;
                  break;
             }
        }
        return ret;
    };*/

    /**
     * Force login. Useful during development to bypass authentication
     *
     * @param login login name
     */
    protected void forceLogin(String login) {
        loginFrame.forceSubmit(login);
    }

    private void loginSuccess(final MetaData meta) {
        UserSessionManager.init(meta.getSessionTimeoutMinutes(),
                meta.getSessionTimeoutWarnBeforeMinutes());
        userProfile = meta.getUserProfile();
        DateHelper.timeOffset = meta.getTimeZoneOffset();
        appMetaData = meta;
        loginFrame.displaySuccessMessage();
        WindowHelper.setApplicationBusyMouse(true);
        // let the browser process events and display the success message:
        LengthyTaskManager.yieldAndRun(new LengthyTaskManager.Runnable() {
            public void run() {
                initMainFrameBuilder(meta);
                selectFrame(Application.MAIN_FRAME);
                setStatus("Ready");
                loginFrame.clearLoginPage(true);
                WindowHelper.setApplicationBusyMouse(false);
            }
        });
    }

    private void loginFailure() {
        setStatus("Login failed");
        loginFrame.setLoginPageEnabled(true);
    }

    private void remoteLogin(String login, String password, String portalType) {
        LoginRequestObject lro = new LoginRequestObject(login, password, 
                                                        portalType);

        RPC.QAsyncCallback callback = new RPC.QAsyncCallback() {
            public void onRequestEnded(boolean success, Object result) {
                if(success) {
                    LoginResult loginResult = (LoginResult) result;
                    MetaData meta = loginResult.getMetaData();
                    loginSuccess(meta);
                }
            }

            protected void handleError(Throwable error) {
                loginFailure();
                super.handleError(error);
            }
        };
        RPC.getRPC().login(lro, callback);
        setStatus("Request sent. Waiting for reply...");
        IdleUtilizer.onLoginSubmitted();
    }

    public void onRequestStarted() {
        WindowHelper.setApplicationProgressMouse(true);
    }

    public void onRequestFinished(boolean success) {
        WindowHelper.setApplicationProgressMouse(false);
        UserSessionManager.resetTimer();
    }

    public void handleError(Throwable caught) {
        DialogHelper.showModalErrorDialog(caught);
    }

    /**
     * Displays current status message in the application's status line
     */
    public static native void setStatus(String text) /*-{
        $wnd.status = text;
    }-*/;

    /**
     * Adds text to current status message in the application's status line
     */
    public static native String getStatus() /*-{
        return $wnd.status;
    }-*/;

    /**
     * Clears application's status line.
     * Equivalent to showStatus(null) and showStatus("");
     */
    public static void clearStatus() {
        setStatus("");
    }

    public static native String getUserAgent()/*-{
        return $wnd.navigator.userAgent;
    }-*/;

    public static boolean isInternetExplorer() {
        return getUserAgent().indexOf(BROWSER_MSIE) != -1;
    }

    public static boolean isFireFox() {
        return getUserAgent().indexOf(BROWSER_FIREFOX) != -1;
    }

    private QFrame previousFrame;

    public void selectFrame(int frameToSelect) {
        //removing current frame
        if(previousFrame != null) {
            RootPanel.get().remove(previousFrame.getView());
            previousFrame.disabled();
        }

        //selecting new frame
        switch(frameToSelect) {
            case Application.AD_HOC_REPORT_FRAME:
                previousFrame = getAdhocReport();
                break;
            case Application.MAIN_FRAME:
                previousFrame = mainFrameBuilder.getMainFrame();
                break;
            case Application.LOGIN_FRAME:
                previousFrame = loginFrame;
                break;
        }

        RootPanel.get().add(previousFrame.getView());
        previousFrame.activated();
    }

    public String getFocusId(FamgMeta.Index index) {
        return appMetaData.getFocuses()[index.focus].getFocusName();
    }

    public void onEvent(Event event, Widget sender) {
        if(event == AdhocReportFrame.Events.REPORT_RUN) {
            List filters = getAdhocContext().getAdhocFilters();
            getAdhocReport().runReport(filters);
        } else if(event == AdhocReportFrame.Events.EXPORT_GRID_EXCEL) {
            List filters = getAdhocContext().getAdhocFilters();
            getAdhocReport().exportGridToExcel(filters);
        } else if(event == AdhocReportFrame.Events.EXPORT_GRID_WORD) {
            List filters = getAdhocContext().getAdhocFilters();
            getAdhocReport().exportGridToWord(filters);
        } else if(event == AdhocReportFrame.Events.EXPORT_GRID_HTML) {
            List filters = getAdhocContext().getAdhocFilters();
            getAdhocReport().exportGridToHtml(filters);
        } else if(event == AdhocReportFrame.Events.SAVE_REPORT) {
            List filters = getAdhocContext().getAdhocFilters();
            getAdhocReport().saveAdhocReport(filters);
        } else if(event == AdhocReportFrame.Events.MAIN_FRAME) {
            selectFrame(Application.MAIN_FRAME);
        } else if(event == AdhocReportFrame.Events.ELEMENT_REMOVED_EVENT) {
            AdhocData adhocData = ((AdhocData) event.getData());
            if(getAdhocReport().reportElementsSize() == 0) {
                getAdhocContext().enableAdhocForAllForms();
            }
            getAdhocContext().setAdhocElementOut(
                    adhocData.getFieldSent().getFieldID(),
                    adhocData.getFormIndex());
        } else if(event == AdhocReportFrame.Events.REPORT_LOADED) {
            loadReport((LoadReportResponseObject) event.getData());
        } else if(event == LoginFrame.Events.LOGIN) {
            LoginFrame.LoginEventData eventData
                    = /*]/*[*/(LoginFrame.LoginEventData)/*]*[*//*]/[*/ LoginFrame
                    .Events.LOGIN.getData();
            remoteLogin(eventData.login, eventData.password, portalType);
        } else if(event == MainframeEventsCollection.LOGOUT) {
            remoteLogout();
        } else if(event == MainframeEventsCollection.SELECT_ADHOCFRAME) {
            selectFrame(Application.AD_HOC_REPORT_FRAME);
        } else if(event == MainframeEventsCollection.MAINFRAME_REPORT_DESIGN) {
            AdhocData eventData = ((AdhocData) event.getData());
            invertControlState(eventData);
        } else if(event == MainframeEventsCollection.TAB_INITIALIZED) {
            TabMeta.Index index = ((TabMeta.Index) event.getData());
            List elements = getAdhocReport().getReportElements();
            Set inReport = new HashSet();
            for(int i = 0; i < elements.size(); i++) {
                AdhocData data = (AdhocData) elements.get(i);
                if(data.getFormIndex().equalsToIndex(index)) {
                    inReport.add(data);
                }
            }
            getAdhocContext().setAdhocData(inReport, index);
        }
    }

    private AdhocOperations getAdhocContext() {
        return mainFrameBuilder.getOperationContext().getAdhocOperations();
    }

    /**
     * Invert control state to opposite one, and turn other forms controls to apropriate state if needed.
     *
     * @param eventData adhoc event data.
     */
    private void invertControlState(AdhocData eventData) {
        if(eventData.getElementDesignReportData() == QFormElementModel
                .NOT_IN_REPORT) {
            getAdhocReport().addReportElement(eventData);
            if(getAdhocReport().reportElementsSize() == 1) {
                getAdhocContext().enableAdhocForJoinable(
                        eventData.getFormIndex());
            }
            getAdhocContext().setAdhocElementIn(
                    eventData.getFieldSent().getFieldID(),
                    eventData.getFormIndex());
        } else {
            getAdhocReport().removeReportElement(eventData);
            if(getAdhocReport().reportElementsSize() == 0) {
                getAdhocContext().enableAdhocForAllForms();
            }
            getAdhocContext().setAdhocElementOut(
                    eventData.getFieldSent().getFieldID(),
                    eventData.getFormIndex());
        }
    }

    private void loadReport(LoadReportResponseObject resp) {
        //set filters to mainframe
        getAdhocContext().setFormsFilters(resp.getFilters(), true);
        //create adhoc data elements for adhoc frame
        List reportFields = resp.getReportFields();
        List adhocElements = new ArrayList();
        FamgMeta.Index indexToEnable = null;
        for(int i = 0; i < reportFields.size(); i++) {
            EntityElement element = (EntityElement) reportFields.get(i);
            AdhocData data = getData(element);
            if(data != null) {
                adhocElements.add(data);
                indexToEnable = data.getFormIndex();
            }
        }
        //process those elements in adhoc frame. (removes selection from old adhoc elements from mainframe)
        getAdhocReport().loadReport(resp.getReportName(), adhocElements);
        //enable all joinable forms, disable all other. In order if we have bound graph it is enaught to use single element form to enable whole graph
        getAdhocContext().enableAdhocForJoinable(indexToEnable);
        //set loaded elements in mainframe
        for(int i = 0; i < adhocElements.size(); i++) {
            AdhocData data = (AdhocData) adhocElements.get(i);
            FamgMeta.Index index = data.getFormIndex();
            getAdhocContext().setAdhocElementIn(
                    data.getFieldSent().getFieldID(), index);
        }
    }

    private AdhocData getData(EntityElement element) {
        FamgMeta.Index formIndex = (FamgMeta.Index) appMetaData.getIndexByID(
                element.getFormId());
        if(formIndex == null) {
            return null;
        }
        FamgMeta meta = appMetaData.getFamgMeta(formIndex);
        FieldMeta fieldMeta = meta.getForm().getEntityMeta().getField(
                element.getElementId());
        if(fieldMeta == null) {
            return null;
        }
        fieldMeta.setCaption(element.getElementCaption());
        return new AdhocData(formIndex, fieldMeta, 0);
    }

    private void remoteLogout() {
        if(cancelLogout()) {
            return;
        }
        UserSessionManager.logout();
        setStatus("Request sent. Waiting for reply...");
    }

    private void initMainFrameBuilder(MetaData meta) {
        initMainFrameBuilder();
        mainFrameBuilder.initBuilder(meta);
        mainFrameBuilder.getOperationContext().getEventSource().addEventListener(this);
    }

    /**
     * Initialize mainFrame builder only at once, because we don't need
     * to re-create structure of the mainframe during re-login or any other
     * user actions.
     */
    private void initMainFrameBuilder() {
        if(mainFrameBuilder == null) {
            mainFrameBuilder = createMainFrameBuilder();
        }
    }

    protected LoginFrame createLoginFrame() { // can be overriden
        return new LoginFrame(isFireFox(), portalType);
    }

    private boolean cancelLogout() {
        if(!getAdhocContext().isInEditMode()) {
            return false;
        }
        String confirmation = I18N.getMessages()
                .applicationCloseConfirmationLong();
        boolean ok = Window.confirm(confirmation);
        return (!ok);
    }

    public void onLengthyTaskStart() {
        WindowHelper.setApplicationBusyMouse(true);
    }

    public void onLengthyTaskEnd() {
        WindowHelper.setApplicationBusyMouse(false);
    }

    public MainFrameBuilder createMainFrameBuilder() {
        return new DefaultMainFrameBuilder();
    }
}
