/*
 * 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.modules.eqlext.actions;

import com.queplix.core.error.GenericSystemException;
import com.queplix.core.jxb.entity.Dataset;
import com.queplix.core.jxb.entity.Efield;
import com.queplix.core.jxb.entity.Entity;
import com.queplix.core.jxb.entity.Metainf;
import com.queplix.core.jxb.entity.types.ControlSType;
import com.queplix.core.modules.config.ejb.UserPropertyManagerLocal;
import com.queplix.core.modules.config.jxb.Form;
import com.queplix.core.modules.config.utils.EntityHelper;
import com.queplix.core.modules.eql.EQLReqMetaData;
import com.queplix.core.modules.eql.EQLRes;
import com.queplix.core.modules.eql.ejb.EQLManagerLocal;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eql.error.UserQueryParseException;
import com.queplix.core.modules.eql.parser.EQLIntPreparedStatement;
import com.queplix.core.modules.eqlext.jxb.gr.ReqEntity;
import com.queplix.core.modules.eqlext.jxb.gr.Reqs;
import com.queplix.core.modules.eqlext.jxb.gr.ResHeader;
import com.queplix.core.modules.eqlext.jxb.gr.ResRecord;
import com.queplix.core.modules.eqlext.jxb.gr.types.OrderDirectionSType;
import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.SystemHelper;
import com.queplix.core.utils.xml.XMLBinding;
import com.queplix.core.utils.xml.XMLFactory;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>Entity Get Records Action</p>
 *
 * @author [ALB] Baranov Andrey
 * @version $Revision: 1.3 $ $Date: 2006/06/02 13:42:59 $
 */

public class EntityGRAction
        extends AbstractGRAction {

    // ----------------------------------------------------- variables

    ReqEntity reqEntity;
    Entity entity;
    Form form;
    EQLReqMetaData meta;

    // distinct flag
    boolean distinct = false;
    // Req distinct flag
    boolean reqDistinct = false;

    // use built-in count flag
    boolean builtinCount = false;

    // eql count query
    String eqlCountQuery;

    // ----------------------------------------------------- getters

    /*
     * No javadoc
     * @see GRAction#getHeader
     */

    public ResHeader getHeader() {
        if(form != null) {
            return getHeader(entity, form);
        } else {
            return getHeader(entity);
        }
    }

    /*
     * No javadoc
     * @see AbstractGRAction#getFields
     */
    protected Efield[] getFields() {
        return entity.getEfield();
    }

    /*
     * No javadoc
     * @see AbstractGRAction#getDatasets
     */
    protected Dataset[] getDatasets() {
        return entity.getDataset();
    }

    /**
     * Base entity getter.
     *
     * @return Entity object
     */
    protected Entity getEntity() {
        return entity;
    }

    /**
     * Base form getter.
     *
     * @return Form object
     */
    protected Form getForm() {
        return form;
    }

    /**
     * EQL meta data getter.
     *
     * @return EQLReqMetaData object
     */
    protected EQLReqMetaData getMetaData() {
        return meta;
    }

    // ----------------------------------------------------- protected methods

    /*
     * No javadoc
     * @see AbstractGRAction#init
     */

    protected void init(Reqs reqs) {
        super.init(reqs);

        this.reqEntity = reqs.getReq().getReqEntity();
        this.entity = ctx.getEntityViewConfig(reqEntity.getName());

        String formID = reqEntity.getFormid();
        if(formID != null) {
            this.form = ctx.getFormConfig(formID);
        }

        if(SystemHelper.IGNORE_LOWER != null && SystemHelper.IGNORE_LOWER
                .equals("true")) {
            this.ignoreLower = true;
        }
        this.meta = new EQLReqMetaData();
    }

    /*
     * No javadoc
     * @see AbstractGRAction#buildEQL
     */
    protected void buildEQL()
            throws UserQueryParseException {

        StringBuffer mainEql = new StringBuffer("SELECT ");
        StringBuffer countEql = new StringBuffer("AGGREGATE ");

        // build EQL meta information
        buildEQLRequestMetaInf(mainEql);

        // build EQL select clause
        buildEQLSelectClause(mainEql);
        buildEQLCountClause(countEql);

        boolean hasFilters = (eqlFilters != null) ||
                (reqFilters != null &&
                        reqFilters.getReqFiltersTypeItemCount() > 0);

        if(hasFilters) {
            // build EQL where clause
            StringBuffer whereClause = new StringBuffer(" WHERE ");
            buildEQLWhereClause(whereClause);
            mainEql.append(whereClause.toString());
            countEql.append(whereClause.toString());
        }

        // build EQL order clause
        buildEQLOrderClause(mainEql);

        // set class variables
        eqlQuery = mainEql.toString();
        eqlCountQuery = countEql.toString();
    }

    /*
     * No javadoc
     * @see AbstractGRAction#callEQLManager
     */
    protected EQLRes callEQLManager()
            throws EQLException {

        //
        // Initialization
        //

        // get EQL manager EJB local interface
        EQLManagerLocal local = ctx.getEQLManager();

        // add some field names in LAZY LOAD list
        if(!ignoreSendOnRequest) {
            UserPropertyManagerLocal userPropManager = ctx
                    .getUserPropertyManager();

            String[] fieldNames = userPropManager.getFieldsForGrid(
                    ctx.getSC().getUser(), getEntity().getName());

            if(fieldNames != null) {
                Map<String, Efield> efields = new HashMap<String, Efield>();
                for(Efield efield : getFields()) {
                    efields.put(efield.getName(), efield);
                }
                efields.remove(getEntity().getPkeyfield());
                for(String fieldName : fieldNames) {
                    efields.remove(fieldName);
                }
                Efield[] lazyFields = efields.values().toArray(
                        new Efield[efields.size()]);
                for(Efield field : lazyFields) {
                    DEBUG("Add field in lazy load list: " + field.getName());
                    meta.addLazyLoadField(field);
                }
            } else {//take simple grid set
                for(Efield field : getFields()) {
                    if(!field.getGrid() && !field.getPkey()) {
                        DEBUG("Add field in lazy load list: " + field
                                .getName());
                        meta.addLazyLoadField(field);
                    }
                }
            }
        } else {//take simple grid set
            for(Efield field : getFields()) {
                ControlSType controlType = field.getControl();
                if(controlType != null) {
                    if(controlType.getType() == ControlSType.HISTORY_TYPE) {
                        DEBUG("Add field in lazy load list: " + field
                                .getName());
                        meta.addLazyLoadField(field);
                    }
                }
            }

        }

        // add some dataset names in LAZY LOAD list
        if(!ignoreSendOnRequest) {
            int size = entity.getDatasetCount();
            for(int i = 0; i < size; i++) {
                Dataset dataset = entity.getDataset(i);

                if(!dataset.getSendonrequest().booleanValue()) {
                    DEBUG("Add dataset in lazy load: " + dataset.getName());
                    meta.addLazyLoadDataset(dataset);
                }
            }
        }

        if(getLogger().isDebugEnabled()) {
            DEBUG("\n\nCall EQL manager:");
            DEBUG("	EQL query:\n" + eqlQuery);
            DEBUG("	EQL prepared statement: " + eqlPS);
            DEBUG("	EQL meta: " + meta);
        }

        //
        // execute EQL query
        //
        EQLRes res = local.select(ctx.getSC(), eqlQuery, eqlPS.reset(), meta);

        reqDistinct = res.getMetaData().getReq().getSelect().isDistinct();

        if(getLogger().isDebugEnabled()) {
            DEBUG("\n\nEntity EQL response:");
            DEBUG("	Size:" + res.size());
        }

        return res;
    }

    /*
     * No javadoc
     * @see AbstractGRAction#callEQLManagerForCount
     */
    protected int callEQLManagerForCount()
            throws EQLException {

        int count = 0;

        if(builtinCount || reqDistinct) {

            //
            // Use built-in method in EQLManager.
            //

            if(getLogger().isDebugEnabled()) {
                DEBUG("\n\nCall EQLManager for count");
            }

            count = ctx.getEQLManager().selectCount(ctx.getSC(), getEQLRes());

        } else {

            //
            // Count via aggregate COUNT function!
            //

            // reset EQL prepared statement
            EQLIntPreparedStatement countEqlPS = eqlPS.reset();

            // remove "pageSize" and "page" attribues from meta
            meta.delParam(PAGE_SIZE_PARAM);
            meta.delParam(PAGE_PARAM);

            if(getLogger().isDebugEnabled()) {
                DEBUG("\n\nSend aggregate request to EQLManager for count:");
                DEBUG("	EQL count query:\n" + eqlCountQuery);
                DEBUG("	EQL prepared statement: " + countEqlPS);
                DEBUG("	EQL meta: " + meta);
            }

            EQLRes countRes = ctx.getEQLManager().select(ctx.getSC(),
                    eqlCountQuery, countEqlPS, meta);
            if(countRes.size() > 0) {
                Integer i = countRes.getRecord(0).getResCell(0).getInteger();

                if(i != null) {
                    count = i.intValue();
                }
            }
        }

        if(getLogger().isDebugEnabled()) {
            DEBUG("\n\nEQL count response: " + count);
        }

        return count;
    }

    /*
     * No javadoc
     * @see AbstractGRAction#createResRecords
     */
    protected ResRecord[] createResRecords(EQLRes eqlRes)
            throws EQLException {

        // list of records
        List<ResRecord> resRecordList = new ArrayList<ResRecord>();

        int recordCount = (eqlRes == null) ? 0:eqlRes.size();
        for(int i = 0; i < recordCount; i++) {
            // create new record
            ResRecord resRecord = createResRecord(eqlRes, i);
            resRecordList.add(resRecord);
        }

        return (resRecordList.size() == 0) ? null:
                (ResRecord[]) resRecordList.toArray(new ResRecord[0]);
    }

    /*
     * No javadoc
     * @see AbstractGRAction#createResRecords
     */
    protected void createResRecords(EQLRes eqlRes, Writer writer)
            throws EQLException {

        int recordCount = (eqlRes == null) ? 0:eqlRes.size();
        if(recordCount == 0) {
            return;
        }

        // get XML binding
        XMLBinding xmlBinding = XMLFactory.getXMLBinding();

        // init temp writer
        StringWriter tmpWriter = new StringWriter();

        for(int i = 0; i < recordCount; i++) {
            try {
                // create new record
                ResRecord resRecord = createResRecord(eqlRes, i);

                // serialize ResRecord to temp writer
                xmlBinding.javaToXml(resRecord, tmpWriter);

                // write to the original writer without <?xml ...?>
                writer.write(StringHelper.clearXml(tmpWriter.toString(),
                        false));

                // reset temp writer
                tmpWriter.getBuffer().delete(0,
                        tmpWriter.getBuffer().length() - 1);

            } catch (IOException ex) {
                ERROR(ex);
                throw new GenericSystemException(
                        "IO exception: " + ex.getMessage(), ex);

            } catch (OutOfMemoryError e) {
                // caught OutOfMemoryError...
                ERROR("Out Of Memeory Error occured diring reading result record: ");
                ERROR("	record: " + i);
                ERROR("	total: " + recordCount);
                throw e;
            }
        }
    }

    /*
     * No javadoc
     * @see AbstractGRAction#buildEQLSelectClause
     */
    protected void buildEQLSelectClause(StringBuffer eql) {
        eql.append(reqEntity.getName()).append(EntityHelper.NAME_SEPARATOR)
                .append("*");
    }

    /*
     * No javadoc
     * @see AbstractGRAction#buildEQLOrderClause
     */
    protected void buildEQLOrderClause(StringBuffer eql) {

        // add sort field
        String sortField = reqEntity.getSortfield();
        if(sortField != null) {
            eql.append(" ORDER BY ");
            eql.append(reqEntity.getName()).append(".").append(sortField);

            OrderDirectionSType orderType = reqEntity.getSortdir();
            if(orderType != null) {
                eql.append(" ").append(orderType.toString().toUpperCase());
            }
        }
    }

    /*
     * No javadoc
     * @see AbstractGRAction#buildEQLRequestMetaInf
     */
    protected void buildEQLRequestMetaInf(StringBuffer eql) {

        boolean metaDistinct = false;
        int metaPage = UNDEFINED_PAGE;
        int metaPagesize = UNDEFINED_PAGESIZE;
        boolean metaUseBuiltinCount = false;

        // read meta from XML Entity document
        int count = entity.getMetainfCount();
        for(int i = 0; i < count; i++) {
            Metainf metaInf = entity.getMetainf(i);
            String name = metaInf.getName();
            String value = metaInf.getValue();
            try {
                if(name.equalsIgnoreCase(DISTINCT_PARAM)) {
                    metaDistinct = StringHelper.str2bool(value);
                } else if(name.equalsIgnoreCase(PAGE_PARAM)) {
                    metaPage = Integer.parseInt(value);
                } else if(name.equalsIgnoreCase(PAGE_SIZE_PARAM)) {
                    metaPagesize = Integer.parseInt(value);
                } else if(name.equalsIgnoreCase(USE_BUILTIN_COUNT_PARAM)) {
                    metaUseBuiltinCount = StringHelper.str2bool(value);
                }
            } catch (Exception ex) {
                throw new GenericSystemException(
                        "Incorrect meta parameter value '" +
                                value + "'. Parameter: " + name);
            }
        }

        if(metaPage == UNDEFINED_PAGE) {
            metaPage = page;
        }
        if(metaPagesize == UNDEFINED_PAGESIZE) {
            metaPagesize = pageSize;
        }

        eql.append("/* ");

        // set DISTINCT attribute
        if(metaDistinct) {
            // .. init distinct flag
            distinct = metaDistinct;
            eql.append(DISTINCT_PARAM).append("=").append(metaDistinct).append(
                    ",");
        }

        // set PAGE attribute
        if(metaPage != UNDEFINED_PAGE) {
            eql.append(PAGE_PARAM).append("=").append(metaPage).append(",");
        }

        // set PAGESIZE attribute
        if(metaPagesize != UNDEFINED_PAGESIZE) {
            eql.append(PAGE_SIZE_PARAM).append("=").append(metaPagesize).append(
                    ",");
        }

        // remember 'Use built-in Count method' attribute
        if(metaUseBuiltinCount) {
            builtinCount = metaUseBuiltinCount;
        }

        eql.append(" */ ");
    }

    /**
     * Build EQL COUNT clause
     *
     * @param eql EQL string
     */
    protected void buildEQLCountClause(StringBuffer eql) {
        eql.append("COUNT(").append(getFields()[0].getId()).append(")");
    }
}
