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

import com.queplix.core.client.app.rpc.RPC;
import com.queplix.core.client.app.vo.AccumulatedEntitiesRequestObject;
import com.queplix.core.client.app.vo.AccumulatedEntityDataResponse;
import com.queplix.core.client.app.vo.EntityData;
import com.queplix.core.client.app.vo.EntityDataResponseObject;
import com.queplix.core.client.app.vo.EntityDeleteRequestObject;
import com.queplix.core.client.app.vo.EntityIDRequestObject;
import com.queplix.core.client.app.vo.EntityMeta;
import com.queplix.core.client.app.vo.EntityUpdateRequestObject;
import com.queplix.core.client.app.vo.EntityUpdateResponseObject;
import com.queplix.core.client.app.vo.FamgMeta;
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.FieldType;
import com.queplix.core.client.app.vo.FieldsHelper;
import com.queplix.core.client.app.vo.FormMeta;
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.MoreDataResponseObject;
import com.queplix.core.client.app.vo.NewEntityRequestObject;
import com.queplix.core.client.app.vo.RowData;
import com.queplix.core.client.app.vo.SearchGridRecordsResponseObject;
import com.queplix.core.client.app.vo.TextboxFieldData;

import java.util.Collection;
import java.util.List;

/**
 * Default operation strategy implementation.
 * It simply performs the action using RPC, and provide set of response methods
 * that can be re-loaded
 *
 * @author Sergey Kozmin
 * @since 10.04.2007
 */
public class DefaultOperationStrategy implements BusinessOperationStrategy,
        BusinessCommandExecutor {
    private OperationContext operationContext;
    private String formId;
    private FamgMeta.Index formIndex;

    public void init(OperationContext operationContext, String formId,
                     FamgMeta.Index formIndex) {
        this.operationContext = operationContext;
        this.formId = formId;
        this.formIndex = formIndex;
    }

    protected OperationContext getOperationContext() {
        return operationContext;
    }

    public String getFormId() {
        return formId;
    }

    public FamgMeta.Index getFormIndex() {
        return formIndex;
    }

    public void createRecordProtorype(Collection data) {
        String formId = operationContext.getMetaData().getFormID(formIndex);

        RPC.QAsyncCallback callback = new BusinessCommand(this,
                BusinessCommandType.CREATE_PROTOTYPE);
        NewEntityRequestObject request = new NewEntityRequestObject(formId,
                data);
        RPC.getRPC().createEntity(request, callback);
    }

    /**
     * If success equals to false, fields and rowId equal to nulls.
     *
     * @param success was creation succesfull
     * @param fields  updated fields
     * @param rowId   updated row id
     */
    protected void createRecordProtorypeResponse(boolean success,
                                                 FieldData[] fields,
                                                 Long rowId) {
        operationContext.setOperationStatus(
                OperationTypes.CREATE_RECORD_PROTOTYPE, success, formIndex);
        if(success) {
            operationContext.getFormOperations().setFormData(fields, rowId,
                    formIndex, true);
            operationContext.getGridOperations().clearGridSelection(formIndex);
        }
    }

    public void lockAndEditRecord(Long rowId) {
        String formId = operationContext.getMetaData().getFormID(formIndex);

        RPC.QAsyncCallback callback = new BusinessCommand(this,
                BusinessCommandType.LOCK_AND_EDIT);
        EntityIDRequestObject request = new EntityIDRequestObject(formId,
                rowId);
        RPC.getRPC().getLockForEditRecord(request, callback);
    }


    protected void lockAndEditResponse(boolean success,
                                       EntityData[] entitiesList,
                                       EntityData[] externalFieldsList,
                                       Collection gridData) {
        if(success) {
            operationContext.setData(entitiesList, externalFieldsList,
                    gridData);
        }
        operationContext.setOperationStatus(OperationTypes.LOCK_AND_EDIT_RECORD,
                success,
                formIndex);//todo see FormsDataInjector comment, about the dedicated form. that is why we need to send status response after we set data
    }

    public void updateRecord(Long rowId, Collection data) {
        saveRecord(rowId, data, BusinessCommandType.UPDATE);
    }

    /**
     * update or insert records
     *
     * @param rowId        updating row id
     * @param data         updating data
     * @param callbackType type of the callback, can
     *                     be {@link com.queplix.core.client.frames.mainframe.BusinessCommand#UPDATE} or {@link com.queplix.core.client.frames.mainframe.BusinessCommand#INSERT}
     */
    protected void saveRecord(Long rowId, Collection data, int callbackType) {
        if(callbackType != BusinessCommandType.UPDATE
                && callbackType != BusinessCommandType.INSERT) {
            throw new IllegalArgumentException("You could not update the record"
                    + " with command types another from INSERT or UPDATE.");
        }
        RPC.QAsyncCallback callback = new BusinessCommand(this,
                callbackType);
        String formId = operationContext.getMetaData().getFormID(formIndex);
        EntityUpdateRequestObject entityDataRequestObject =
                new EntityUpdateRequestObject(formId, data, rowId);

        if(callbackType == BusinessCommandType.UPDATE) {
            RPC.getRPC().updateRecord(entityDataRequestObject, callback);
        } else {
            RPC.getRPC().insertRecord(entityDataRequestObject, callback);
        }
    }

    protected void updateRecordResponse(boolean success,
                                        EntityData[] entitiesList,
                                        EntityData[] externalFieldsList,
                                        Collection gridData,
                                        RowData updatedRow) {
        saveRecordResponse(success, entitiesList, externalFieldsList, gridData,
                updatedRow);
    }

    public void insertRecord(Long rowId, Collection data) {
        saveRecord(rowId, data, BusinessCommandType.INSERT);
    }

    protected void insertRecordResponse(boolean success,
                                        EntityData[] entitiesList,
                                        EntityData[] externalFieldsList,
                                        Collection gridData,
                                        RowData updatedRow) {
        saveRecordResponse(success, entitiesList, externalFieldsList, gridData,
                updatedRow);
    }

    protected void saveRecordResponse(boolean success,
                                      EntityData[] entitiesList,
                                      EntityData[] externalFieldsList,
                                      Collection gridData, RowData updatedRow) {
        operationContext.setOperationStatus(OperationTypes.UPDATE_RECORD,
                success, formIndex);
        if(success) {
            operationContext.setData(entitiesList, externalFieldsList,
                    gridData);
            GridOperations gridOperations = operationContext
                    .getGridOperations();
            gridOperations.activateGrid(formIndex);
            gridOperations.setDataForGridRow(updatedRow, formIndex);
            gridOperations.selectGridRecord(updatedRow.getId(), formIndex);
        }
    }

    public void deleteRecord(Collection rowIds) {
        RPC.QAsyncCallback callback = new BusinessCommand(this,
                BusinessCommand.DELETE);
        String formId = operationContext.getMetaData().getFormID(formIndex);
        EntityDeleteRequestObject request = new EntityDeleteRequestObject(
                formId, rowIds);
        RPC.getRPC().deleteRecord(request, callback);
    }

    public void deleteRecordResponse(boolean success) {
        operationContext.setOperationStatus(OperationTypes.DELETE_RECORDS,
                success, formIndex);
        if(success) {
            operationContext.getFormOperations().clearForm(formIndex, true);
        }
    }

    public void searchRecords(Collection entitiesFilters,
                              GridSearchProperties props,
                              boolean isLocalSearch) {
        String formId = operationContext.getMetaData().getFormID(formIndex);

        AccumulatedEntitiesRequestObject searchRequest =
                new AccumulatedEntitiesRequestObject(formId, entitiesFilters,
                        props);

        RPC.QAsyncCallback callback = new SearchBusinessCommand(this, formIndex,
                isLocalSearch);
        RPC.getRPC().searchWithMultipleFormConstraints(searchRequest, callback);
    }

    protected void searchRecordsResponse(boolean success,
                                         GridData gridData,
                                         int totalRecordsCount,
                                         int currentPage,
                                         boolean localSearch) {
        if(success) {
            operationContext.getGridOperations().activateGrid(formIndex);
            operationContext.getGridOperations().setDataForGrid(gridData,
                    totalRecordsCount, currentPage, formIndex);
            RowData[] rows = gridData.getRows();
            if(rows.length == 1) {
                operationContext.setOperationStatus(
                        OperationTypes.SEARCH_RECORDS, true, formIndex);
                operationContext.getGridOperations().selectGridRecord(
                        rows[0].getId(), formIndex);
            } else if(rows.length >= 2) {
                operationContext.setOperationStatus(
                        OperationTypes.SEARCH_RECORDS, false, formIndex);
            } else if(rows.length < 0) {
                operationContext.setOperationStatus(
                        OperationTypes.SEARCH_RECORDS, false, formIndex);
            }
        } else {
            operationContext.setOperationStatus(OperationTypes.SEARCH_RECORDS,
                    success, formIndex);
        }
    }

    public void handleLinkEvent(String fromLinkFieldId) {
        FormOperations formOperations = operationContext.getFormOperations();
        FamgMeta formMeta = operationContext.getMetaData().getFamgMeta(
                formIndex);
        EntityMeta entityMeta = formMeta.getForm().getEntityMeta();
        FieldMeta srcFieldMetaMeta = entityMeta.getField(fromLinkFieldId);
        FamgMeta.Index toIndex = srcFieldMetaMeta.getLinkedForm();

        operationContext.getGridOperations().activateGrid(toIndex);
        formOperations.activateForm(toIndex);
        FieldData data = formOperations.getFieldData(formIndex,
                fromLinkFieldId);

        if(data != null && !data.isEmpty()) {
            Long recordId = FieldsHelper.extractReferencedRecordId(
                    srcFieldMetaMeta, data);
            //check if a record to be searched and
            //check if we can switch to search state, otherwise do nothing,
            if(recordId != null) {
                //clear form
                operationContext.performOperation(OperationTypes.CLEAR_FORM,
                        toIndex);
                //if form was cleared => form state == FormState.SEARCH_STATE
                if(formOperations.getFormState(toIndex) == FormState
                        .SEARCH_STATE) {
                    //create id filter
                    FamgMeta toFormMeta = operationContext.getMetaData()
                            .getFamgMeta(toIndex);
                    String pkeyFieldName = toFormMeta.getForm().getPkeyID();
                    //we assume, that pkey field exists on the form (at least it shall be hidden) and has textfield type
                    TextboxFieldData pkeyData = new TextboxFieldData(
                            pkeyFieldName, recordId.toString());

                    //set filter to field
                    operationContext.getFormOperations().setFieldData(toIndex,
                            pkeyData);

                    //do local search
                    operationContext.performOperation(
                            OperationTypes.LOCAL_SEARCH_RECORDS, toIndex);
                }
            }
        }
    }

    public void handleControlEvent(String elementId,
                                   FormControlEventTypes type) {
        //to be implemented in descendant classes
    }

    public void addControlCustomFilters(FieldDataRequest request) {
        OperationContext ctx = getOperationContext();

        FormOperations formOp = ctx.getFormOperations();
        Long recordId = formOp.getSelectedRecordId(formIndex);

        MetaData metaData = ctx.getMetaData();

        FormMeta formMeta = metaData.getFormMeta(formIndex);
        String fieldId = request.getElementID();
        FormMeta.FieldFilterMeta[] filtersMeta = formMeta.getFieldFiltersMeta(fieldId);

        // apply default chart filter {{{
        if (filtersMeta.length == 0 && request.getRequestType() == FieldType.CHART) {
            filtersMeta = new FormMeta.FieldFilterMeta[] {
                    new FormMeta.FieldFilterMeta(
                            fieldId, formIndex, formMeta.getPkeyID())
            };
        }
        // apply default chart filter }}}
        
        for (int i = 0; i < filtersMeta.length; i++) {
            FormMeta.FieldFilterMeta filterMeta = filtersMeta[i];
            FamgMeta.Index byFormIdx = filterMeta.getByFormIndex();
            String byEntityId = metaData.getFormMeta(byFormIdx).getEntityMeta().getEntityID();
            FieldData filterData = formOp.getFieldData(
                    byFormIdx, filterMeta.getByFieldId());
            if (!filterData.isEmpty()) {
                EntityData filter = new EntityData(
                        byEntityId, recordId, new FieldData[] { filterData });

                request.addFilter(filter);
            }
        }
    }

    public void handleControlDataRequest(FieldDataRequest request) {
        RPC.QAsyncCallback callback = new BusinessCommand(this,
                BusinessCommandType.CONTROL_DATA_REQUEST);

        addControlCustomFilters(request);

        RPC.getRPC().getMoreData(request, callback);
    }

    protected void handleControlDataResponse(FieldOnDemandData resp) {
        operationContext.getFormOperations().setOnDemandData(formIndex, resp);
    }

    public void handleCustomMenuEvent(String eventId) {
        //to be implemented in descendant classes
    }

    public void handleCustomButtonEvent(String buttonId) {
        //to be implemented in descendant classes
    }

    private void handleSaveResponse(int commandType, boolean success,
                                    Object result) {
        EntityData[] entitiesToFill = null;
        EntityData[] externalFields = null;
        List gridData = null;
        RowData updatedRow = null;
        if(success) {
            EntityUpdateResponseObject response
                    = (EntityUpdateResponseObject) result;
            entitiesToFill = response.getEntitiesList();
            externalFields = response.getExternalFieldsList();
            gridData = response.getGridData();
            updatedRow = response.getUpdatedRow();
        }
        if(commandType == BusinessCommandType.UPDATE) {
            updateRecordResponse(success, entitiesToFill, externalFields,
                    gridData, updatedRow);
        } else if(commandType == BusinessCommandType.INSERT) {
            insertRecordResponse(success, entitiesToFill, externalFields,
                    gridData, updatedRow);
        }
    }

    /**
     * This method is to be overwriten if customn command types added.
     *
     * @param commandType   command type
     * @param commandObject command object. In default implementation is
     *                      RPC callback object BusinessCommand
     * @param success       was performed succesfully
     * @param result        result object
     */
    public void execute(int commandType, Object commandObject, boolean success,
                        Object result) {
        BusinessCommand command = (BusinessCommand) commandObject;
        switch(commandType) {
            case BusinessCommandType.CREATE_PROTOTYPE: {
                FieldData[] fields = null;
                Long id = null;
                if(success) {
                    EntityDataResponseObject response
                            = (EntityDataResponseObject) result;
                    fields = response.getEntityData().getFields();
                    id = response.getEntityData().getRowID();
                }
                createRecordProtorypeResponse(success, fields, id);
                break;
            }
            case BusinessCommandType.CONTROL_DATA_REQUEST: {
                if(success) {
                    MoreDataResponseObject response
                            = (MoreDataResponseObject) result;
                    handleControlDataResponse(response.getFieldData());
                }
                break;
            }
            case BusinessCommandType.LOCK_AND_EDIT: {
                EntityData[] entitiesToFill = null;
                EntityData[] externalFields = null;
                List gridData = null;
                if(success) {
                    AccumulatedEntityDataResponse response
                            = (AccumulatedEntityDataResponse) result;
                    entitiesToFill = response.getEntitiesList();
                    externalFields = response.getExternalFieldsList();
                    gridData = response.getGridData();
                }
                lockAndEditResponse(success, entitiesToFill, externalFields,
                        gridData);
                break;
            }
            case BusinessCommandType.INSERT://don't break have one handler here
            case BusinessCommandType.UPDATE: {
                handleSaveResponse(command.getCommandType(), success, result);
                break;
            }
            case BusinessCommandType.SEARCH: {
                SearchBusinessCommand com = (SearchBusinessCommand) command;

                GridData gridData = null;
                int totalRecordsCount = -1;
                int currentPage = -1;
                if(success) {
                    SearchGridRecordsResponseObject response
                            = (SearchGridRecordsResponseObject) result;
                    gridData = response.getGridData();
                    totalRecordsCount = response.getTotalRecordsCount();
                    currentPage = response.getCurrentPage();
                }

                searchRecordsResponse(success, gridData, totalRecordsCount,
                        currentPage, com.isLocalSearch());
                break;
            }

            case BusinessCommandType.DELETE: {
                deleteRecordResponse(success);
                break;
            }
        }
    }

    public void handleError(int commandType, Object commandObject,
                            Throwable error) {
        BusinessCommand command = (BusinessCommand) commandObject;
        switch(commandType) {
            case BusinessCommandType.CREATE_PROTOTYPE: {
                break;
            }
            default:
                command.defaultHandleError(error);
        }
    }
}
