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

import com.queplix.core.client.app.rpc.DisplayableException;
import com.queplix.core.client.app.vo.AdhocDeleteReportRequest;
import com.queplix.core.client.app.vo.AdhocPrintRequestObject;
import com.queplix.core.client.app.vo.AdhocSearchGridRecordsRequest;
import com.queplix.core.client.app.vo.BaseGridRequest;
import com.queplix.core.client.app.vo.CheckBoxData;
import com.queplix.core.client.app.vo.DateFieldData;
import com.queplix.core.client.app.vo.EntityData;
import com.queplix.core.client.app.vo.EntityElement;
import com.queplix.core.client.app.vo.EntityIDRequestObject;
import com.queplix.core.client.app.vo.EntityReferenceData;
import com.queplix.core.client.app.vo.FieldData;
import com.queplix.core.client.app.vo.FieldMeta;
import com.queplix.core.client.app.vo.GridData;
import com.queplix.core.client.app.vo.LoadReportResponseObject;
import com.queplix.core.client.app.vo.MemoFieldData;
import com.queplix.core.client.app.vo.RowData;
import com.queplix.core.client.app.vo.SaveAdhocReportRequestObject;
import com.queplix.core.client.app.vo.SearchGridRecordsResponseObject;
import com.queplix.core.client.app.vo.TextboxFieldData;
import com.queplix.core.integrator.entity.EntityFacade;
import com.queplix.core.integrator.entity.EntityOperationsHelper;
import com.queplix.core.integrator.entity.EntitySerializeHelper;
import com.queplix.core.integrator.entity.EntityViewHelper;
import com.queplix.core.integrator.entity.IntegratedRecord;
import com.queplix.core.integrator.entity.IntegratedRecordSet;
import com.queplix.core.integrator.entity.RecordDoesntExistsException;
import com.queplix.core.integrator.entity.RequestProperties;
import com.queplix.core.integrator.security.AccessRightsManager;
import com.queplix.core.integrator.security.LogonSession;
import com.queplix.core.integrator.security.PermissionObjectType;
import com.queplix.core.integrator.security.PermissionSet;
import com.queplix.core.integrator.security.User;
import com.queplix.core.integrator.security.WebLoginManager;
import com.queplix.core.integrator.util.AdhocReport;
import com.queplix.core.integrator.util.ReportSerializeHelper;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eql.error.EQLSystemException;
import com.queplix.core.modules.eql.error.UserQueryParseException;
import com.queplix.core.modules.eqlext.jxb.gr.Report;
import com.queplix.core.modules.eqlext.jxb.gr.Req;
import com.queplix.core.modules.eqlext.jxb.gr.Reqs;
import com.queplix.core.modules.eqlext.jxb.gr.Ress;
import com.queplix.core.modules.eqlext.utils.ReportBuilder;
import com.queplix.core.utils.DateHelper;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * This object stores the operations related to reports such us save report, load report, load reports list, print report.
 *
 * @author Sergey Kozmin
 * @since 12.02.2007
 */
public class IntegratorReports {
    public static final String REPORT_SAVING_ENTITY = "report.report";

    private static final String REPORT_NAME_FIELD_ID = "name";

    private static final String ADHOC_REPORT_HAVENT_ACCESS_MESSAGE_ID
            = "adhoc_report_havent_access";
    private static final String ADHOC_REPORT_COULDNT_SET_FILTER_MESSAGE_ID
            = "adhoc_report_couldnt_set_filter";
    private static final String ADHOC_REPORT_COULDNT_GET_FIELD_MESSAGE_ID
            = "adhoc_report_couldnt_get_field";
    private static final String ENTITY_ELEMENT_DESCRIPTION_MESSAGE_ID
            = "entity_element_description";
    private static final String ENTITY_DATA_DESCRIPTION_MESSAGE_ID
            = "entity_data_description";

    private static final int MAX_FORMS_ENUMERATION_LENGHT = 400;
    private static final String VALUES_DELIM = ",\n ";

    public static SearchGridRecordsResponseObject customEntitiesFieldsSearch(
            AdhocSearchGridRecordsRequest searchReq, HttpServletRequest request)
            throws DisplayableException {
        ServletContext ctx = request.getSession().getServletContext();
        SearchGridRecordsResponseObject gridResult = null;

        try {
            LogonSession ls = WebLoginManager.getLogonSession(request);
            RequestProperties props = new RequestProperties();
            props.setDoCount(searchReq.getProps().isDoCount());
            props.setPage(searchReq.getProps().getPage());
            props.setPagesize(searchReq.getProps().getPageSize());
            props.setSortField(searchReq.getProps().getSortField());
            props.setDoHeader(false);
            props.setGetRequest(true);

            Collection<EntityData> entityFilters = searchReq.getEntityFilters();
            List<EntityElement> entityElements = searchReq.getFields();
            ActionContext actx = IntegratorHelper.getActionContext(ctx);
            IntegratedRecordSet integratedRecordSet = EntityFacade
                    .getCombinedEntitiesResult(entityElements, entityFilters,
                            props, ls, actx);

            Ress ress = integratedRecordSet.getInitialResultSet().getRess();

            int totalRecordsCount = ress.getCount();
            int recordsCount = ress.getRes().getResRecordCount();
            int currentPage = ress.getPage();

            IntegratedRecordSet correctResp = integratedRecordSet;
            if(recordsCount < 1 && currentPage
                    != 0) {//when get empty result, then request first page
                props.setPage(0);
                IntegratedRecordSet iterCastorRequest = EntityFacade
                        .getCombinedEntitiesResult(entityElements,
                                entityFilters,
                                props, ls, actx);
                Ress iterRess = iterCastorRequest.getInitialResultSet()
                        .getRess();

                totalRecordsCount = iterRess.getCount();
                currentPage = iterRess.getPage();

                correctResp = iterCastorRequest;
            }
            RowData[] records = EntityOperationsHelper
                    .retrieveGridRowsDataFromResult(correctResp, ls, actx);
            GridData gd = new GridData(records, "fake");
            gridResult = new SearchGridRecordsResponseObject(gd,
                    totalRecordsCount, currentPage);
        } catch (UserQueryParseException ex) {
            IntegratorHelper.throwException("Incorrect value: " + ex.getValue()
                    + ". Entity: " + ex.getEntityName() + ". Field: " +
                    ex.getFieldCaption(), ex);
        } catch (EQLSystemException ex) {
            IntegratorHelper.throwException(
                    "EQL System Exception. " + ex.getMessage(), ex);
        } catch (EQLException ex) {
            IntegratorHelper.throwException("EQL Exception. " + ex.getMessage(),
                    ex);
        } catch (RuntimeException th) {
            IntegratorHelper.reThrowException(th);
        }
        return gridResult;
    }

    public static void printCustomReport(AdhocPrintRequestObject printRequest,
                                         HttpServletRequest req) {
        ServletContext ctx = req.getSession().getServletContext();
        LogonSession ls = WebLoginManager.getLogonSession(req);
        long processId = printRequest.getProcessId();

        Report report = new Report();
        report.setProcessId(processId);
        Reqs rs = new Reqs();
        Req r = new Req();
        RequestProperties props = new RequestProperties();
        r.setReqField(EntityFacade.getReqFields(printRequest.getFields(),
                props));
        rs.setReq(r);
        List<EntityData> list = printRequest.getFilters();
        rs.setReqFilters(EntityFacade.getReqFilters(list, props, ls,
                IntegratorHelper.getActionContext(ctx)));
        report.setReqs(new Reqs[]{rs});
        report.setPrintPage(Boolean.FALSE);

        // Construct ReportBuilder object.
        ReportBuilder reportBuilder = new ReportBuilder(ls, processId, report);

        // Put it in cache.
        IntegratorHelper.getReportBuilderCache(req.getSession())
                .putReportBuilder(reportBuilder);
    }

    public static void saveReport(SaveAdhocReportRequestObject request,
                                  HttpServletRequest httpRequest)
            throws DisplayableException {
        ServletContext ctx = httpRequest.getSession().getServletContext();
        LogonSession ls = WebLoginManager.getLogonSession(httpRequest);

        try {
            //check if the report with this name exists
            FieldData[] nameFilters = new FieldData[]{
                    new TextboxFieldData(REPORT_NAME_FIELD_ID,
                            "=" + request.getReportName())
            };
            //searchs equal reports, not like %...%
            EntityData searchingEntityData = new EntityData(
                    REPORT_SAVING_ENTITY, (long) -1, nameFilters);
            List<EntityData> filters = new ArrayList<EntityData>();
            filters.add(searchingEntityData);
            ActionContext actx = IntegratorHelper.getActionContext(ctx);
            IntegratedRecordSet integratedRecordSet = EntityFacade
                    .getResultByEntitiesFilters(filters,
                            REPORT_SAVING_ENTITY, new RequestProperties(),
                            EntityViewHelper.FieldsModificator.GRID, ls, actx);
            if(integratedRecordSet.getRowsCount()
                    > 0) {//such report with the given name exists
                throw new DisplayableException(
                        "Could not create the report with name ["
                                + request.getReportName()
                                + "], such report already exists.");
            }

            EntityData d = EntityFacade.createEntityPrototype(
                    REPORT_SAVING_ENTITY, new LinkedList<FieldData>(), ls,
                    actx);
            ArrayList<FieldData> l = new ArrayList<FieldData>();

            String serializedReport = ReportSerializeHelper.serializeReport(
                    request.getFields(), request.getFilters(), ls, actx);

            l.add(new TextboxFieldData("report_id", d.getRowID().toString()));
            l.add(new TextboxFieldData(REPORT_NAME_FIELD_ID,
                    request.getReportName()));
            l.add(new EntityReferenceData("owner_id", "",
                    ls.getUser().getUserID()));
            l.add(new DateFieldData("timestamp", DateHelper.getNowDate(),
                    DateHelper.getNowDate(), DateHelper.formatDate(
                    DateHelper.getNowDate())));
            l.add(new MemoFieldData(
                    ReportSerializeHelper.SERIALIZED_REPORTS_FIELD_ID,
                    serializedReport, d.getRowID()));
            l.add(new CheckBoxData("public_report", true));

            EntityData entityData = new EntityData(REPORT_SAVING_ENTITY,
                    (long) -1, l.toArray(new FieldData[l.size()]));
            EntityFacade.insertRecord(entityData, actx, ls);
        } catch (UserQueryParseException ex) {
            IntegratorHelper.throwException("Incorrect value: " + ex.getValue()
                    + ". Entity: " + ex.getEntityName() + ". Field: " +
                    ex.getFieldCaption(), ex);
        } catch (EQLSystemException ex) {
            IntegratorHelper.throwException(
                    "EQL System Exception. " + ex.getMessage(), ex);
        } catch (EQLException ex) {
            IntegratorHelper.throwException("EQL Exception. " + ex.getMessage(),
                    ex);
        } catch (RecordDoesntExistsException e) {
            IntegratorHelper.reThrowException(e);
        } catch (RuntimeException th) {
            IntegratorHelper.reThrowException(th);
        }
    }


    public static SearchGridRecordsResponseObject loadReportList(
            BaseGridRequest gridRequest, HttpServletRequest request)
            throws DisplayableException {
        SearchGridRecordsResponseObject gridResult
                = new SearchGridRecordsResponseObject();
        ActionContext ctx = IntegratorHelper.getActionContext(request);

        try {
            LogonSession ls = WebLoginManager.getLogonSession(request);

            RequestProperties props = new RequestProperties(
                    gridRequest.getProps().isDoCount(),
                    gridRequest.getProps().getPage(),
                    gridRequest.getProps().getPageSize(),
                    gridRequest.getProps().getSortField());
            RequestBuilder requestBuilder
                    = RequestBuilderFactory.createRequestBuilder(
                    REPORT_SAVING_ENTITY,
                    Collections.<EntityData>emptyList(), props, ls, ctx);

            gridResult = IntegratorHelper.getNotEmptyResult(0, requestBuilder,
                    REPORT_SAVING_ENTITY,
                    EntityViewHelper.FieldsModificator.CUSTOMIZED_GRID, ls,
                    ctx);
        } catch (UserQueryParseException ex) {
            IntegratorHelper.throwException("Incorrect value: " + ex.getValue()
                    + ". Entity: " + ex.getEntityName() + ". Field: " +
                    ex.getFieldCaption(), ex);
        } catch (EQLSystemException ex) {
            IntegratorHelper.throwException(
                    "EQL System Exception. " + ex.getMessage(), ex);
        } catch (EQLException ex) {
            IntegratorHelper.throwException("EQL Exception. " + ex.getMessage(),
                    ex);
        } catch (RuntimeException th) {
            IntegratorHelper.reThrowException(th);
        }
        return gridResult;
    }

    public static LoadReportResponseObject loadReport(
            EntityIDRequestObject request,
            HttpServletRequest httpServletRequest)
            throws DisplayableException {
        LoadReportResponseObject response = null;

        ServletContext ctx = httpServletRequest.getSession()
                .getServletContext();
        LogonSession ls = WebLoginManager.getLogonSession(httpServletRequest);

        try {
            ActionContext actx = IntegratorHelper.getActionContext(ctx);
            IntegratedRecordSet result = EntityFacade.getEntityByIDRequest(
                    REPORT_SAVING_ENTITY, request.getRowID(), ls, actx);
            if(result.getRowsCount() > 0) {
                IntegratedRecord resultRecord = result.getRecordSet().get(0);
                Map<String, FieldData> fieldsData = resultRecord.getFieldSet();
                Map<String, FieldMeta> fieldsMeta = result.getRecordMeta();

                FieldData serializedReportData = fieldsData.get(
                        ReportSerializeHelper.SERIALIZED_REPORTS_FIELD_ID);
                String serializedReport = EntitySerializeHelper
                        .getValueStringRepresentation(serializedReportData,
                                fieldsMeta.get(
                                        ReportSerializeHelper.SERIALIZED_REPORTS_FIELD_ID));

                AdhocReport report = ReportSerializeHelper.deSerializeReport(
                        serializedReport, ls, actx);

                ReportInconsistency inconsistency = checkPrivileges(report, ls,
                        actx);
                String warningMessage;
                switch(inconsistency.getType()) {
                    case WARNING:
                        //do not break here
                    case OK: {
                        warningMessage = buildWarningMessage(inconsistency,
                                ls.getUser(), actx);
                        FieldData reportNameData = fieldsData.get(
                                REPORT_NAME_FIELD_ID);
                        String reportName = EntitySerializeHelper
                                .getValueStringRepresentation(reportNameData,
                                        fieldsMeta.get(REPORT_NAME_FIELD_ID));
                        response = new LoadReportResponseObject(reportName,
                                warningMessage, report.getFilters(),
                                report.getReportFields());
                        break;
                    }
                    case ERROR: {
                        IntegratorHelper.throwException(buildErrorMessage(
                                inconsistency, ls.getUser(), actx));
                    }
                }
            }
        } catch (EQLException ex) {
            IntegratorHelper.throwException("EQL Exception. " + ex.getMessage(),
                    ex);
        } catch (RuntimeException th) {
            IntegratorHelper.reThrowException(th);
        }
        return response;
    }

    private static String buildWarningMessage(ReportInconsistency inc,
                                              User user,
                                              ActionContext actx) {
        String message = "";
        int size = inc.getAnavailableReportFields().size();
        if(size > 0) {
            Collection<EntityElement> reportFields = inc
                    .getAnavailableReportFields();
            Collection<String> values = new ArrayList<String>(size);
            for(EntityElement field : reportFields) {
                Object[] params = new Object[]{
                        field.getFormId(),
                        field.getElementId(),
                        field.getElementCaption()
                };
                values.add(EntityOperationsHelper.
                        getLocalizedServerMessage(
                        ENTITY_ELEMENT_DESCRIPTION_MESSAGE_ID,
                        params, user.getLangID(), actx));
            }
            String joined = join(values, VALUES_DELIM,
                    MAX_FORMS_ENUMERATION_LENGHT / 2);

            message += EntityOperationsHelper.
                    getLocalizedServerMessage(
                            ADHOC_REPORT_COULDNT_GET_FIELD_MESSAGE_ID,
                            new Object[]{joined}, user.getLangID(), actx);
            message += "\n";
        }

        Collection<EntityData> filters = inc.getAnavailableFilterField();
        if(filters.size() > 0) {
            Collection<String> values = new ArrayList<String>(size);
            for(EntityData filter : filters) {
                String fieldsEnum = getFields(filter.getFields());
                Object[] params = new Object[]{
                        filter.getEntityID(),
                        fieldsEnum
                };
                values.add(EntityOperationsHelper.
                        getLocalizedServerMessage(
                        ENTITY_DATA_DESCRIPTION_MESSAGE_ID,
                        params, user.getLangID(), actx));
            }
            String joined = join(values, VALUES_DELIM,
                    MAX_FORMS_ENUMERATION_LENGHT / 2);

            message += EntityOperationsHelper.
                    getLocalizedServerMessage(
                            ADHOC_REPORT_COULDNT_SET_FILTER_MESSAGE_ID,
                            new Object[]{joined}, user.getLangID(), actx);
        }

        return message;
    }

    private static String getFields(FieldData[] fields) {
        Collection<String> fieldsNames = new ArrayList<String>(fields.length);
        for(FieldData fieldData : fields) {
            fieldsNames.add(fieldData.getFieldID());
        }
        return join(fieldsNames, VALUES_DELIM, MAX_FORMS_ENUMERATION_LENGHT);
    }

    private static String buildErrorMessage(ReportInconsistency inconsistency,
                                            User user,
                                            ActionContext actx) {

        String forms = join(inconsistency.getAnavailableForms(), VALUES_DELIM,
                MAX_FORMS_ENUMERATION_LENGHT);

        return EntityOperationsHelper.
                getLocalizedServerMessage(ADHOC_REPORT_HAVENT_ACCESS_MESSAGE_ID,
                        new Object[]{forms}, user.getLangID(), actx);
    }

    private static String join(Collection<String> col, String delim,
                               int maxSize) {
        String ret = "";
        boolean firstCome = true;
        for(String value : col) {
            if(!firstCome) {
                ret += delim;
            }
            ret += value;
            firstCome = false;
        }
        if(ret.length() > maxSize) {
            ret = ret.substring(0, maxSize) + "...";
        }
        return ret;
    }

    private static ReportInconsistency checkPrivileges(AdhocReport report,
                                                       LogonSession ls,
                                                       ActionContext actx) {
        ReportInconsistency inc = new ReportInconsistency();
        PermissionSet permissions = AccessRightsManager.getPermissionSetForUser(
                ls.getUser());

        Map<String, Map<String, FieldMeta>> entityMetaCache
                = new HashMap<String, Map<String, FieldMeta>>();

        List<EntityElement> reportFields = report.getReportFields();
        for(EntityElement field : reportFields) {
            //check on permission
            if(permissions.getPermissionObject(PermissionObjectType.FORM,
                    field.getFormId()) == null) {
                inc.formNotAvailable(field.getFormId());
            }
            String entityName = EntityOperationsHelper.getEntityNameFromFormID(
                    field.getFormId());
            //check if all fields are in entity
            Map<String, FieldMeta> meta;
            if(entityMetaCache.containsKey(entityName)) {
                meta = entityMetaCache.get(entityName);
            } else {
                meta = EntityViewHelper.getMetaForEntity(entityName,
                        EntityViewHelper.FieldsModificator.FORM, false, ls,
                        actx);
                entityMetaCache.put(entityName, meta);
            }
            if(!meta.containsKey(field.getElementId())) {
                inc.reportElementNotAvailable(field);
            }
        }

        //check if all fields are in entity 
        List<EntityData> filters = report.getFilters();
        for(EntityData filter : filters) {
            String entityName = filter.getEntityID();
            Map<String, FieldMeta> meta;
            if(entityMetaCache.containsKey(entityName)) {
                meta = entityMetaCache.get(entityName);
            } else {
                meta = EntityViewHelper.getMetaForEntity(entityName,
                        EntityViewHelper.FieldsModificator.FORM, false, ls,
                        actx);
                entityMetaCache.put(entityName, meta);
            }
            for(FieldData data : filter.getFields()) {
                if(!meta.containsKey(data.getFieldID())) {
                    inc.reportFilterNotAvailable(filter);
                }
            }
        }
        return inc;
    }

    public static SearchGridRecordsResponseObject deleteReport(
            AdhocDeleteReportRequest reportId,
            HttpServletRequest httpServletRequest)
            throws DisplayableException {
        SearchGridRecordsResponseObject response = null;

        ServletContext ctx = httpServletRequest.getSession()
                .getServletContext();
        LogonSession ls = WebLoginManager.getLogonSession(httpServletRequest);
        ActionContext actx = IntegratorHelper.getActionContext(ctx);
        try {
            EntityFacade.deleteRecord(REPORT_SAVING_ENTITY,
                    reportId.getReportId(), actx, ls);

            response = loadReportList(reportId, httpServletRequest);
        } catch (EQLException ex) {
            IntegratorHelper.throwException("EQL Exception. " + ex.getMessage(),
                    ex);
        } catch (RuntimeException th) {
            IntegratorHelper.reThrowException(th);
        }
        return response;
    }
}
