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

import com.queplix.core.error.ErrorHelper;
import com.queplix.core.integrator.security.LogonSession;
import com.queplix.core.jxb.entity.Efield;
import com.queplix.core.jxb.entity.Entity;
import com.queplix.core.modules.config.jxb.ExternalSet;
import com.queplix.core.modules.config.jxb.Form;
import com.queplix.core.modules.config.utils.EntityHelper;
import com.queplix.core.modules.eql.CompoundKey;
import com.queplix.core.modules.eql.ejb.AbstractEQLSupportedEJB;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eqlext.GetRecordsRes;
import com.queplix.core.modules.eqlext.actions.AbstractGRAction;
import com.queplix.core.modules.eqlext.actions.ActionContext;
import com.queplix.core.modules.eqlext.actions.GRAction;
import com.queplix.core.modules.eqlext.actions.NewGRAction;
import com.queplix.core.modules.eqlext.error.QueryTooBigException;
import com.queplix.core.modules.eqlext.jxb.gr.ExtPkey;
import com.queplix.core.modules.eqlext.jxb.gr.ExtReqs;
import com.queplix.core.modules.eqlext.jxb.gr.Req;
import com.queplix.core.modules.eqlext.jxb.gr.ReqEntity;
import com.queplix.core.modules.eqlext.jxb.gr.ReqFilter;
import com.queplix.core.modules.eqlext.jxb.gr.ReqFilters;
import com.queplix.core.modules.eqlext.jxb.gr.ReqFiltersTypeItem;
import com.queplix.core.modules.eqlext.jxb.gr.Reqs;
import com.queplix.core.modules.eqlext.jxb.gr.Res;
import com.queplix.core.modules.eqlext.jxb.gr.ResHeader;
import com.queplix.core.modules.eqlext.jxb.gr.Ress;
import com.queplix.core.modules.eqlext.jxb.gr.types.ConditionSType;
import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.xml.XMLHelper;

import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.Properties;

/**
 * <p>Get records session EJB</p>
 *
 * @author [ALB] Baranov Andrey
 * @version $Revision: 1.1.1.1 $ $Date: 2005/09/12 15:30:38 $
 */

public class GetRecordsEJB
        extends AbstractEQLSupportedEJB {

    // ----------------------------------------------------- public methods

    /**
     * Initialize bean
     */
    public void ejbCreate() {
        INFO("GetRecordsEJB create - " + hashCode());
    }

    /**
     * Execute get records procedure
     *
     * @param entityName   entity name
     * @param compoundPkey compound primary key
     * @param prop         GetRecords properties
     * @param sc           security config
     * @return response object
     * @throws EQLException
     */
    public GetRecordsRes process(String entityName,
                                 CompoundKey compoundPkey,
                                 Properties prop,
                                 LogonSession sc)
            throws EQLException {

        if(compoundPkey == null) {
            return process(entityName, (CompoundKey[]) null, prop, sc);
        } else {
            return process(entityName, new CompoundKey[]{compoundPkey}
                    , prop, sc);
        }
    }

    /**
     * Execute get records procedure
     *
     * @param entityName    entity name
     * @param compoundPkeys compound primary keys array
     * @param prop          GetRecords properties
     * @param sc            security config
     * @return response object
     * @throws EQLException
     */
    public GetRecordsRes process(String entityName,
                                 CompoundKey[] compoundPkeys,
                                 Properties prop,
                                 LogonSession sc)
            throws EQLException {

        return process(entityName, entityName, compoundPkeys, prop, sc);
    }

    /**
     * Execute get records procedure
     *
     * @param entityName     entity name
     * @param pkeyEntityName entity name of primary keys
     * @param compoundPkeys  compound primary keys array
     * @param prop           GetRecords properties
     * @param sc             security config
     * @return response object
     * @throws EQLException
     */
    public GetRecordsRes process(String entityName,
                                 String pkeyEntityName,
                                 CompoundKey[] compoundPkeys,
                                 Properties prop,
                                 LogonSession sc)
            throws EQLException {

        // get base entity
        Entity entity = getEntityViewConfigManager().getEntityViewConfig(
                entityName);

        // get pkey entity and fields
        Entity pkeyEntity = getEntityViewConfigManager().getEntityViewConfig(
                pkeyEntityName);
        Efield[] pkeyFields = EntityHelper.getPkeys(pkeyEntity);

        // do process
        return process(entity, pkeyFields, compoundPkeys, prop, sc);
    }

    /**
     * Execute get records procedure
     *
     * @param entityName     entity name
     * @param pkeyEntityName entity name of primary keys
     * @param compoundPkeys  compound primary keys array
     * @param prop           GetRecords properties
     * @param out            PrintWriter object to write Ress response
     * @param sc             security config
     * @throws EQLException
     */
    public void process(String entityName,
                        String pkeyEntityName,
                        CompoundKey[] compoundPkeys,
                        Properties prop,
                        PrintWriter out,
                        LogonSession sc)
            throws EQLException {

        // get base entity
        Entity entity = getEntityViewConfigManager().getEntityViewConfig(
                entityName);

        // get pkey entity and fields
        Entity pkeyEntity = getEntityViewConfigManager().getEntityViewConfig(
                pkeyEntityName);
        Efield[] pkeyFields = EntityHelper.getPkeys(pkeyEntity);

        // do process
        process(out, entity, pkeyFields, compoundPkeys, prop, sc, false);
    }

    /**
     * Execute get records procedure
     *
     * @param reqs request object
     * @param sc   security config
     * @return response object
     * @throws EQLException
     */
    public GetRecordsRes process(Reqs reqs, LogonSession sc)
            throws EQLException {

        long time = System.currentTimeMillis();
        GetRecordsRes grRes = null;

        try {
            // request parameters
            int page = reqs.getPage();
            int pageSize = reqs.getPagesize();
            boolean doHeader = reqs.getDoheader();
            boolean getRequest = reqs.getGetrequest();

            // create context
            ActionContext ctx = new ActionContext(sc,
                    getEntityViewConfigManager(),
                    getFocusConfigManager(),
                    getCustomConfigManager(),
                    getEQLManager(),
                    getUserPropertyManager());

            // call action
            GRAction gra = AbstractGRAction.process(ctx, reqs, null);

            // object contains response object
            Ress ress = new Ress();
            Res res = new Res();
            ress.setRes(res);

            // get set of records
            res.setResRecord(gra.getResRecords());

            // add request object
            if(getRequest) {
                ress.setReqs(reqs);
            }

            // add header object
            if(doHeader) {
                ResHeader resHeader = gra.getHeader();
                ress.setResHeader(resHeader);
            }

            // set Ress attributes
            ress.setRows(gra.getRows());
            ress.setCount(gra.getCount());
            if(page > 0) {
                ress.setPrev(Boolean.TRUE);
            } else {
                ress.setPrev(Boolean.FALSE);
            }
            ress.setNext(gra.hasMore());
            ress.setPagesize(pageSize);
            ress.setPage(page);
            ress.setNew(gra instanceof NewGRAction);

            // build Get Records response
            grRes = new GetRecordsRes(ress, gra.getEQLRes());

        } catch (EQLException ex) {
            throw ex;

        } catch (OutOfMemoryError ex) {
            throw new QueryTooBigException();

        } catch (Throwable t) {
            ErrorHelper.throwSystemException(t, this);
        }

        if(getLogger().isDebugEnabled()) {
            DEBUG("...getRecords complete time(ms)=" +
                    (System.currentTimeMillis() - time));
        }

        return grRes;
    }

    /**
     * Execute get records procedure
     *
     * @param in  Reader object to read Reqs request
     * @param out PrintWriter object to write Ress response
     * @param sc  security config
     * @throws EQLException
     */
    public void process(Reader in,
                        PrintWriter out,
                        LogonSession sc)
            throws EQLException {

        // Deserialize request.
        Reqs reqs = (Reqs) XMLHelper.getParsedObject(Reqs.class, in);

        // Do process.
        process(reqs, out, sc);
    }

    /**
     * Execute get records procedure
     *
     * @param reqs request object
     * @param out  PrintWriter object to write Ress response
     * @param sc   security config
     * @throws EQLException
     */
    public void process(Reqs reqs,
                        PrintWriter out,
                        LogonSession sc)
            throws EQLException {

        // Do process.
        process(reqs, out, sc, false);
    }

    /**
     * Execute get records extended procedure
     *
     * @param in  Reader object to read Reqs request
     * @param out PrintWriter object to write Ress response
     * @param sc  security config
     * @throws EQLException
     */
    public void extProcess(Reader in,
                           PrintWriter out,
                           LogonSession sc)
            throws EQLException {

        // Deserialize request.
        ExtReqs extReqs = (ExtReqs) XMLHelper.getParsedObject(ExtReqs.class,
                in);

        // Call extended process.
        extProcess(extReqs, out, sc);
    }

    /**
     * Execute get records extended procedure
     *
     * @param extReqs ExtReqs request object
     * @param out     PrintWriter object to write Ress response
     * @param sc      security config
     * @throws EQLException
     */
    public void extProcess(ExtReqs extReqs,
                           PrintWriter out,
                           LogonSession sc)
            throws EQLException {

        //
        // Initialization.
        //

        long time = System.currentTimeMillis();

        if(getLogger().isDebugEnabled()) {
            DEBUG("Start ext process:");
            printMemoryUsage();

            DEBUG("ExtGetRecords request:");
            DEBUG("===================");
            DEBUG(new String(XMLHelper.writeObject(extReqs)));
            DEBUG("===================");
        }

        // Get Form.
        String formName = extReqs.getFormid();
        Form form = getFocusConfigManager().getForm(formName);

        // Get base entity.
        Entity entity = getEntityViewConfigManager().getEntityViewConfig(
                form.getEntity());

        //
        // Do writing.
        //

        // 1. add ext response attributes
        {
            out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            out.print("<ext-ress ");
            out.print("formid=\"" + formName + "\" ");
            out.println(">");
        }

        // 2. add ext request object
        if(extReqs.getGetextrequest()) {
            XMLHelper.writeObject(extReqs, out, true);
        }

        // 3. Call recursively SUB extended process.
        int pkeys = extReqs.getExtPkeyCount();
        Efield[] pkeyFields = new Efield[pkeys];
        CompoundKey compoundKey = new CompoundKey();
        for(int i = 0; i < pkeys; i++) {
            ExtPkey extPkey = extReqs.getExtPkey(i);
            pkeyFields[i] = EntityHelper.getEfield(extPkey.getName(), entity);
            compoundKey.addKey(extPkey.getValue());
        }
        extSubProcess(out, form, entity, pkeyFields, compoundKey, sc);

        // Ok.
        out.println("</ext-ress>");

        if(getLogger().isDebugEnabled()) {
            DEBUG("...ext getRecords complete time(ms)=" + (
                    System.currentTimeMillis() - time));
        }
    }

    // ----------------------------------------------------- private methods

    //
    // Execute get records procedure
    //

    private GetRecordsRes process(Entity baseEntity,
                                  Efield[] pkeyFields,
                                  CompoundKey[] compoundPkeys,
                                  Properties prop,
                                  LogonSession sc)
            throws EQLException {

        // get Reqs object
        Reqs reqs = buildReqs(baseEntity, pkeyFields, compoundPkeys, prop);

        // do process
        return process(reqs, sc);
    }

    //
    // Execute get records procedure
    //
    private void process(PrintWriter out,
                         Entity baseEntity,
                         Efield[] pkeyFields,
                         CompoundKey[] compoundPkeys,
                         Properties prop,
                         LogonSession sc,
                         boolean clearXml)
            throws EQLException {

        // get Reqs object
        Reqs reqs = buildReqs(baseEntity, pkeyFields, compoundPkeys, prop);

        // do process
        process(reqs, out, sc, clearXml);
    }

    //
    // Execute get records procedure
    //
    private void process(Reqs reqs,
                         PrintWriter out,
                         LogonSession sc,
                         boolean clearXml)
            throws EQLException {

        long time = System.currentTimeMillis();

        if(getLogger().isDebugEnabled()) {
            DEBUG("Start process:");
            printMemoryUsage();

            DEBUG("GetRecords request:");
            DEBUG("-------------------");
            DEBUG(new String(XMLHelper.writeObject(reqs)));
            DEBUG("-------------------");
        }

        try {
            // Get request parameters.
            int page = reqs.getPage();
            int pageSize = reqs.getPagesize();
            boolean doHeader = reqs.getDoheader();
            boolean getRequest = reqs.getGetrequest();

            // Call action and redirect output to special writer.
            if(getLogger().isDebugEnabled()) {
                DEBUG("Try to get records: ");
                printMemoryUsage();
            }

            // create context
            ActionContext ctx = new ActionContext(sc,
                    getEntityViewConfigManager(),
                    getFocusConfigManager(),
                    getCustomConfigManager(),
                    getEQLManager(),
                    getUserPropertyManager());

            // call action
            CharArrayWriter recordsCaw = new CharArrayWriter();
            GRAction gra = AbstractGRAction.process(ctx, reqs, recordsCaw);

            if(getLogger().isDebugEnabled()) {
                DEBUG("Got records: ");
                printMemoryUsage();
            }

            //
            // Do writing.
            //

            // 1. add response attributes
            {
                if(!clearXml) {
                    out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
                }
                out.print("<ress ");
                out.print("count=\"" + gra.getCount() + "\" ");
                out.print("rows=\"" + gra.getRows() + "\" ");
                out.print("page=\"" + page + "\" ");
                out.print("pagesize=\"" + pageSize + "\" ");
                out.print("next=\"" + gra.hasMore() + "\" ");
                if(page > 0) {
                    out.print("prev=\"true\" ");
                } else {
                    out.print("prev=\"false\" ");
                }
                out.print("new=\"" + (gra instanceof NewGRAction) + "\" ");
                out.println(">");
            }

            // 2. add request object
            if(getRequest) {
                XMLHelper.writeObject(reqs, out, true);
            }

            // 3. add header object
            if(doHeader) {
                ResHeader resHeader = gra.getHeader();
                XMLHelper.writeObject(resHeader, out, true);
            }

            // 4. add records
            {
                if(getLogger().isDebugEnabled()) {
                    DEBUG("Try to add records: ");
                    printMemoryUsage();
                }

                out.println("<res>");
                recordsCaw.writeTo(out);
                recordsCaw.close();
                out.println("</res>");
            }

            // 5. ok
            {
                if(getLogger().isDebugEnabled()) {
                    DEBUG("Ok: ");
                    printMemoryUsage();
                }

                out.println("</ress>");
            }

        } catch (EQLException ex) {
            throw ex;

        } catch (OutOfMemoryError ex) {
            ERROR(ex);
            throw new QueryTooBigException();

        } catch (Throwable t) {
            ErrorHelper.throwSystemException(t, this);
        }

        if(getLogger().isDebugEnabled()) {
            DEBUG("...getRecords complete time(ms)=" +
                    (System.currentTimeMillis() - time));
        }
    }

    //
    // Execute get records SUB extended procedure
    //
    private void extSubProcess(PrintWriter out,
                               Form baseForm,
                               Entity baseEntity,
                               Efield[] pkeyFields,
                               CompoundKey compoundPkey,
                               LogonSession sc)
            throws EQLException {

        if(getLogger().isDebugEnabled()) {
            DEBUG("Call ext sub process:");
            DEBUG("		entity: " + baseEntity.getName());
            DEBUG("		form: " + baseForm.getName());
            DEBUG("		pkey[0]: " + pkeyFields[0].getId());
            DEBUG("		key[0]: " + compoundPkey.getKey(0));
        }

        int ext_count = baseForm.getExternalSetCount();
        for(int i = 0; i < ext_count; i++) {
            // get ExternalSet object
            ExternalSet extSet = baseForm.getExternalSet(i);
            String formName = extSet.getName();

            if(getLogger().isDebugEnabled()) {
                DEBUG("Found externaset: " + formName);
            }

            // get external Form
            Form extForm = getFocusConfigManager().getForm(formName);
            if(extForm == null) {
                throw new NullPointerException(
                        "Cannot find external form '" + formName + "'.");
            }

            // get external Entity
            String extEntityName = extForm.getEntity();
            Entity extEntity = getEntityViewConfigManager().getEntityViewConfig(
                    extEntityName);

            // add form reference in property
            Properties prop = new Properties();
            prop.setProperty(GRAction.FORM_ID_PARAM, formName);

            // call GetRecords process for external entity
            GetRecordsRes extGRRes = process(extEntity,
                    pkeyFields,
                    new CompoundKey[]{compoundPkey}
                    ,
                    prop,
                    sc);

            // write response
            XMLHelper.writeObject(extGRRes.getRess(), out, true);
        }
    }

    //
    // Build Reqs object
    //
    private Reqs buildReqs(Entity baseEntity,
                           Efield[] pkeyFields,
                           CompoundKey[] compoundPkeys,
                           Properties prop)
            throws EQLException {

        String entityName = baseEntity.getName();

        if(getLogger().isDebugEnabled()) {
            DEBUG("Get records for entity '" + entityName + "'.");
            DEBUG("  records: " + (compoundPkeys == null ? 0
                    :compoundPkeys.length));
            if(compoundPkeys != null) {
                DEBUG("  record[0]: " + compoundPkeys[0]);
            }
        }

        // Reqs object
        Reqs reqs = new Reqs();
        if(prop != null && prop.getProperty(GRAction.IGNORE_SEND_ON_REQ_PARAM)
                != null) {
            reqs.setIgnoreSendOnRequest(Boolean.TRUE);
        } else {
            reqs.setIgnoreSendOnRequest(Boolean.FALSE);
        }

        // Req object
        Req req = new Req();
        reqs.setReq(req);

        // ReqEntity object
        ReqEntity reqEntity = new ReqEntity();
        reqEntity.setName(entityName);
        req.setReqEntity(reqEntity);
        if(prop != null) {
            reqEntity.setFormid(prop.getProperty(GRAction.FORM_ID_PARAM));
        }

        if(compoundPkeys != null) {

            // create base ReqFilters object
            ReqFilters reqFilters = new ReqFilters();
            reqFilters.setType(ConditionSType.OR);
            reqs.setReqFilters(reqFilters);

            // add filters
            for(int i = 0; i < compoundPkeys.length; i++) {
                CompoundKey compoundPkey = compoundPkeys[i];

                // concrete ReqFilters object for one record
                ReqFilters _reqFilters = new ReqFilters();
                _reqFilters.setType(ConditionSType.AND);
                ReqFiltersTypeItem _reqFilterItem = new ReqFiltersTypeItem();
                _reqFilterItem.setReqFilters(_reqFilters);
                reqFilters.addReqFiltersTypeItem(_reqFilterItem);

                for(int j = 0; j < pkeyFields.length; j++) {
                    Efield field = pkeyFields[j];
                    Object key = compoundPkey.getKey(j);
                    if(key == null) {
                        key = StringHelper.NULL_VALUE;
                    }

                    // create ReqFilter object for concrete filter
                    ReqFilter __reqFilter = new ReqFilter();
                    __reqFilter.addReqFilterValue(key.toString());
                    __reqFilter.setEntity(field.getEntityName());
                    __reqFilter.setName(field.getName());

                    ReqFiltersTypeItem __reqFilterItem
                            = new ReqFiltersTypeItem();
                    __reqFilterItem.setReqFilter(__reqFilter);
                    _reqFilters.addReqFiltersTypeItem(__reqFilterItem);
                }
            }
        }

        return reqs;
    }

    // Debug.
    private void printMemoryUsage() {
        if(getLogger().isDebugEnabled()) {
            DEBUG("=======> free memory: " + Runtime.getRuntime().freeMemory() +
                    ", total memory: " + Runtime.getRuntime().totalMemory() +
                    "<========");
        }
    }
}
