/*
 * 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.modules.config.utils.EntityHelper;
import com.queplix.core.modules.eql.EQLRes;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eqlext.jxb.gr.ReqField;
import com.queplix.core.modules.eqlext.jxb.gr.Reqs;
import com.queplix.core.modules.eqlext.jxb.gr.ResField;
import com.queplix.core.modules.eqlext.jxb.gr.ResHeader;
import com.queplix.core.modules.eqlext.jxb.gr.ResHeaderField;
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.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>Fields Get Records Action</p>
 *
 * @author [ALB] Baranov Andrey
 * @version $Revision: 1.1.1.1 $ $Date: 2005/09/12 15:30:37 $
 */

public class FieldsGRAction
        extends AbstractGRAction {

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

    ReqField[] reqFields;
    Efield[] fields;
    ResField[] groupByFields;
    Map captionMap;
    int groupBy = StringHelper.EMPTY_NUMBER;

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

    /**
     * Construct response header
     *
     * @return ResHeader object
     */
    public ResHeader getHeader() {
        ResHeader resHeader = getHeader(getFields(), getDatasets());

        // Set captions from request
        for(int i = 0; i < resHeader.getResHeaderFieldCount(); i++) {
            ResHeaderField resHeaderField = resHeader.getResHeaderField(i);
            String entityName = resHeaderField.getEntity();
            String fieldName = resHeaderField.getName();
            String fieldId = EntityHelper.getFieldId(entityName, fieldName);
            String caption = (String) captionMap.get(fieldId);
            if(caption != null) {
                resHeaderField.setCaption(caption);
            }
        }

        return resHeader;
    }

    /**
     * Get array of Efield objects take part in query
     *
     * @return array of Efield objects
     */
    protected Efield[] getFields() {
        return fields;
    }

    /**
     * Get array of Dataset objects take part in query
     *
     * @return array of Dataset objects
     */
    protected Dataset[] getDatasets() {
        return null;
    }

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

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

    protected void init(Reqs reqs) {

        super.init(reqs);

        this.ignoreSendOnRequest = true;
        this.reqFields = reqs.getReq().getReqField();
        this.fields = new Efield[reqFields.length];
        this.captionMap = new HashMap(reqFields.length);

        for(int i = 0; i < reqFields.length; i++) {
            ReqField reqField = reqFields[i];
            String entityName = reqField.getEntity();
            String fieldName = reqField.getName();
            Entity entity = ctx.getEntityViewConfig(entityName);
            fields[i] = EntityHelper.getEfield(fieldName, entity);
            String caption = reqField.getCaption();

            // add caption from request
            if(!StringHelper.isEmpty(caption)) {
                captionMap.put(fields[i].getId(), caption);
            }

            // set "group by" parameter - the column for groupping
            if(groupBy == StringHelper.EMPTY_NUMBER &&
                    reqField.getGroup().booleanValue()) {
                groupBy = i;
            }
        }

        if(groupBy > 0) {
            // initailize special array to store last field for groupping in certain column
            groupByFields = new ResField[groupBy];
        }
    }

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

        // build EQL query
        if(getLogger().isDebugEnabled()) {
            DEBUG("\n\nField EQL query:");
            DEBUG("  Group By column index:" + groupBy);
            DEBUG("  EQL query:\n" + eqlQuery);
            DEBUG("  EQL Prepared statement:\n" + eqlPS);
        }

        // execute EQL query
        return ctx.getEQLManager().select(ctx.getSC(), eqlQuery, eqlPS.reset());
    }

    /*
     * No javadoc
     * @see AbstractGRAction#createResRecords
     */
    protected int callEQLManagerForCount()
            throws EQLException {
        return ctx.getEQLManager().selectCount(ctx.getSC(), getEQLRes());
    }

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

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

        ResRecord[] resRecords = new ResRecord[recordCount];
        for(int i = 0; i < recordCount; i++) {
            // create new record
            resRecords[i] = createResRecord(eqlRes, i);
            // format it
            formatResRecord(resRecords[i], i);
        }

        return resRecords;
    }

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

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

        // special temporary buffer
        List tmpBuffer = new ArrayList();

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

            // format it
            boolean flush = formatResRecord(resRecord, i);

            if(flush) {
                // flash records
                flushTmpBuffer(xmlBinding, tmpBuffer);
            }

            // remember it in the temporary buffer
            tmpBuffer.add(resRecord);
        }

        // flush the rest of records
        flushTmpBuffer(xmlBinding, tmpBuffer);
    }

    /*
     * No javadoc
     * @see AbstractGRAction#buildEQLSelectClause
     */
    protected void buildEQLSelectClause(StringBuffer eql) {
        for(int i = 0; i < reqFields.length; i++) {
            ReqField reqField = reqFields[i];

            if(i > 0) {
                eql.append(",");
            }

            String fieldName = reqField.getEntity() + "." + reqField.getName();
            if (reqField.getAggregate() != null) {
                eql.append(reqField.getAggregate().toString()).append("(");
                eql.append(fieldName);
                eql.append(")");
            } else {
                eql.append(fieldName);
            }
        }
    }

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

        boolean orderByAdded = false;
        int size = reqFields.length;
        int j = 0;

        for(int i = 0; i < size; i++) {
            ReqField reqField = reqFields[i];
            if(reqField.getSort().booleanValue()) {

                if(!orderByAdded) {
                    eql.append(" ORDER BY ");
                    orderByAdded = true;
                }
                if(j > 0) {
                    eql.append(",");
                }

                eql.append(reqField.getEntity());
                eql.append(".");
                eql.append(reqField.getName());

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

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

    /**
     * Format records
     *
     * @param resRecord current record
     * @param i         position in response
     * @return true if record differs from previos one
     * @throws EQLException
     */
    private boolean formatResRecord(ResRecord resRecord, int i)
            throws EQLException {

        if(groupBy <= 0) {
            return true;
        }

        boolean differ = false;
        boolean equals = true;

        for(int j = 0; j < groupBy; j++) {
            ResField curField = resRecord.getResField(j);
            String curValue = curField.getResFieldValue();
            ResField prevField = groupByFields[j];

            if(i > 0) {
                String prevValue = prevField.getResFieldValue();

                if(equals &&
                        ((prevValue != null && curValue != null && prevValue
                                .equals(curValue)) ||
                                (prevValue == null && curValue == null))) {

                    // equal - set field to NULL
                    curField.setResFieldValue(StringHelper.EMPTY_VALUE);
                    if(!StringHelper.isEmpty(curField.getResFieldText())) {
                        curField.setResFieldText(StringHelper.EMPTY_VALUE);
                    }

                    // increase <code>recordspan</code> attribute
                    Integer recordSpan = prevField.getRecordspan();
                    if(recordSpan == null) {
                        recordSpan = new Integer(1);
                    } else {
                        recordSpan = new Integer(recordSpan.intValue() + 1);
                    }

                    prevField.setRecordspan(recordSpan);

                } else {
                    // not equal
                    equals = false;
                }

                if(j == 0 && !equals) {
                    // first column value was changed
                    differ = true;
                }
            }

            if(i == 0 || !equals) {
                // simply remember current field
                groupByFields[j] = curField;
            }
        }

        return differ;
    }

    // Flush temporary buffer.
    private void flushTmpBuffer(XMLBinding xmlBinding, List tmpBuffer) {

        int size = tmpBuffer.size();
        if(size == 0) {
            return;
        }

        DEBUG("Try to flush " + size + " records...");

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

        for(int i = 0; i < size; i++) {
            try {
                ResRecord resRecord = (ResRecord) tmpBuffer.get(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);
            }
        }
        tmpBuffer.clear();
    }

}
