/*
 * 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.Listref;
import com.queplix.core.modules.config.utils.EntityHelper;
import com.queplix.core.modules.eql.CompoundKey;
import com.queplix.core.modules.eql.EQLDRes;
import com.queplix.core.modules.eql.EQLERes;
import com.queplix.core.modules.eql.EQLNullObject;
import com.queplix.core.modules.eql.EQLObject;
import com.queplix.core.modules.eql.EQLReqDataset;
import com.queplix.core.modules.eql.EQLReqField;
import com.queplix.core.modules.eql.EQLResCell;
import com.queplix.core.modules.eql.EQLResRecord;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eqlext.jxb.sr.Sreq;
import com.queplix.core.modules.eqlext.jxb.sr.SreqDataset;
import com.queplix.core.modules.eqlext.jxb.sr.SreqField;
import com.queplix.core.modules.eqlext.jxb.sr.SreqRecord;
import com.queplix.core.modules.eqlext.jxb.sr.types.TodoSType;
import com.queplix.core.utils.StringHelper;

import java.util.ArrayList;
import java.util.List;

/**
 * Abstract Set Records Action.
 *
 * @author [ALB] Baranov Andrey
 * @author [ONZ] Oleg N. Zhovtanyuk
 * @version $Revision: 1.1.1.1 $ $Date: 2005/09/12 15:30:36 $
 */

public abstract class AbstractSRAction
        extends AbstractAction {

    // ----------------------------------------------------- fields

    // IN (request) parameters:
    protected Sreq sreq;
    protected Entity entity;

    // OUT (response) parameters:
    private List compoundKeyList;
    private EQLERes eqlERes;

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

    //
    // Do process
    //

    public static final UpdateSRAction process(ActionContext ctx, Sreq sreq)
            throws EQLException {

        // get action
        UpdateSRAction sra = new UpdateSRAction();

        // set ActionContext object
        sra.setActionContext(ctx);

        // init action
        sra.init(sreq);

        // do action
        sra.process();

        // Ok.
        return sra;
    }

    // ----------------------------------------------------- abstract methods

    /**
     * Calls EQL Manager EJB to execute an EQL query.
     *
     * @param eqlRes EQLERes object
     * @throws EQLException
     */
    protected abstract void callEQLManager(EQLERes eqlRes)
            throws EQLException;

    /**
     * Call EQL Manager EJB get default value for Efield <code>field</code>.
     *
     * @param field Efield
     * @return EQLObject
     * @throws EQLException
     */
    protected abstract EQLObject callEQLManager(Efield field)
            throws EQLException;

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

    /**
     * EQL response getter.
     *
     * @return EQLERes object.
     */
    public EQLERes getEQLRes() {
        return eqlERes;
    }

    /**
     * Initial compound keys getter came from client.
     * CompoundKey[0] - key for main entity, the rest - for datasets
     *
     * @return <code>CompoundKey</code> objects array
     */
    public CompoundKey[] getCompoundKeys() {
        return (compoundKeyList.size() == 0) ? null:
                (CompoundKey[]) compoundKeyList.toArray(new CompoundKey[0]);
    }

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

    /**
     * Entity fields getter.
     *
     * @return fields array
     */
    protected Efield[] getFields() {
        return entity.getEfield();
    }

    /**
     * Entity datasets getter.
     *
     * @return datasets array
     */
    protected Dataset[] getDatasets() {
        return entity.getDataset();
    }

    // ----------------------------------------------------- process method

    /*
     * No javadoc
     * @see Action#process
     */

    public void process()
            throws EQLException {

        // Select records.
        int records = sreq.getSreqRecord().length;
        for(int i = 0; i < records; i++) {
            SreqRecord sreqRecord = sreq.getSreqRecord(i);

            // Build EQLResRecord.
            EQLResRecord eqlResRecord = buildEqlResRecord(getEntity(),
                    sreqRecord);
            eqlERes.addRecord(eqlResRecord);
        }

        // Call EQL manager.
        callEQLManager(eqlERes);
    }

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

    //
    // Initialization.
    //

    protected void init(Sreq _sreq) {
        this.sreq = _sreq;
        this.entity = ctx.getEntityViewConfig(sreq.getName());
        this.compoundKeyList = new ArrayList();
        this.eqlERes = new EQLERes(entity);
    }

    /**
     * Makes the <code>EQLResRecord</code> object.
     *
     * @param entity     Entity object
     * @param sreqRecord SreqRecord object
     * @return EQLResRecord object
     * @throws EQLException
     */
    protected final EQLResRecord buildEqlResRecord(Entity entity,
                                                   SreqRecord sreqRecord)
            throws EQLException {

        EQLResRecord eqlResRecord;

        // Operation switch (insert/update/delete).
        int fields = sreqRecord.getSreqFieldCount();
        int todo = sreqRecord.getTodo().getType();
        switch(todo) {
            case TodoSType.INSERT_TYPE:
                eqlResRecord = new EQLResRecord(true, fields);
                break;
            case TodoSType.UPDATE_TYPE:
                eqlResRecord = new EQLResRecord(false, fields);
                eqlResRecord.setDoUpdate(true);
                break;
            case TodoSType.DELETE_TYPE:
                eqlResRecord = new EQLResRecord(false, fields);
                eqlResRecord.markAsDelete();
                break;
            default:
                throw new GenericSystemException(
                        "Unsupported todo '" + todo + "'");
        }

        // Create a new compound key.
        CompoundKey compoundKey = new CompoundKey();
        compoundKeyList.add(compoundKey);

        // Build new EQL response cell for every entity field.
        for(int j = 0; j < fields; j++) {
            EQLResCell eqlResCell =
                    buildEqlResCell(entity, sreqRecord.getSreqField(j),
                            compoundKey, todo);
            eqlResRecord.addData(eqlResCell, j);
        }

        // Select datasets.
        int datasets = sreqRecord.getSreqDatasetCount();
        for(int j = 0; j < datasets; j++) {
            SreqDataset sreqDataset = sreqRecord.getSreqDataset(j);
            Dataset ds = EntityHelper.getDataset(sreqDataset.getName(), entity);
            Entity dsEntity = ctx.getEntityViewConfig(ds.getEntity());

            // Build new dataset EQL response.
            EQLReqDataset reqDataset = new EQLReqDataset(entity, ds, dsEntity);
            EQLDRes eqlDRes = new EQLDRes(reqDataset);
            eqlResRecord.addDRes(eqlDRes);

            int dsRecords = sreqDataset.getSreqRecordCount();
            for(int k = 0; k < dsRecords; k++) {
                EQLResRecord dsEqlResRecord = buildEqlResRecord(dsEntity,
                        sreqDataset.getSreqRecord(k));
                eqlDRes.addRecord(dsEqlResRecord);
            }
        }

        // Add not modified datasets.
        datasetsSucks(entity, eqlResRecord);

        return eqlResRecord;

    }

    /**
     * Makes the <code>EQLResCell</code> object.
     *
     * @param entity      Entity object
     * @param sreqField   SreqField object
     * @param compoundKey CompoundKey object
     * @param todo        int
     * @return EQLResCell object
     * @throws EQLException
     */
    protected final EQLResCell buildEqlResCell(Entity entity,
                                               SreqField sreqField,
                                               CompoundKey compoundKey,
                                               int todo)
            throws EQLException {

        // Get the entity field data.
        String fieldName = sreqField.getName();
        Efield field = EntityHelper.getEfield(fieldName, entity);
        boolean isPkey = field.getPkey().booleanValue();
        EQLReqField eqlReqField = new EQLReqField(entity, field);

        String oldValue = sreqField.getSreqFieldOldValue();
        String newValue = sreqField.getSreqFieldValue();
        boolean hasChanged = hasChanged(sreqField, oldValue, newValue);

        // If a key, add it into the compound key.
        if(isPkey) {
            compoundKey.addKey(newValue);
        }

        // If the field was not changed on client, old value must be empty.
        EQLObject oldEQLObj;
        EQLObject newEQLObj = convertString2EQLObject(entity, field, newValue);

        EQLResCell eqlResCell;
        if(hasChanged) {
            oldEQLObj = convertString2EQLObject(entity, field, oldValue);
        } else {
            oldEQLObj = newEQLObj;
        }

        // If field not set on client but needs to be initialized...
        if((newEQLObj instanceof EQLNullObject) &&
                todo == TodoSType.INSERT_TYPE &&
                sreqField.getRequiredNew().booleanValue()) {

            // Explicitly get default value
            newEQLObj = callEQLManager(field);
        }

        eqlResCell = new EQLResCell(eqlReqField, oldEQLObj);
        eqlResCell.setEQLObject(newEQLObj);

        // Add the list field, if present.
        Listref listRef = field.getListref();
        if(listRef != null) {
            String listEntityName = listRef.getEntity();
            String listEfieldName = listRef.getEfield();
            Entity listEntity = ctx.getEntityViewConfig(listEntityName);
            Efield listEfield = EntityHelper.getEfield(listEfieldName,
                    listEntity);

            String oldText = sreqField.getSreqFieldOldText();
            String newText = sreqField.getSreqFieldText();
            boolean hasListChanged = hasChanged(sreqField, oldText, newText);

            EQLObject oldEQLListObj;
            EQLObject newEQLListObj = convertString2EQLObject(listEntity,
                    listEfield, newText);
            if(hasListChanged) {
                oldEQLListObj = convertString2EQLObject(listEntity, listEfield,
                        oldText);
            } else {
                oldEQLListObj = newEQLListObj;
            }

            EQLResCell listResCell = new EQLResCell(new EQLReqField(listEntity,
                    listEfield), oldEQLListObj);
            listResCell.setEQLObject(newEQLListObj);
            eqlResCell.addListField(listResCell);
        }

        return eqlResCell;
    }

    /**
     * Checks if value of <code>sreqField</code> changed
     *
     * @param sreqField SreqField object
     * @param oldValue  some old value
     * @param newValue  some new value
     * @return boolean
     */
    protected boolean hasChanged(SreqField sreqField, String oldValue,
                                 String newValue) {
        boolean hasChanged = sreqField.getHasChanged().booleanValue();
        if(hasChanged) {
            hasChanged = !StringHelper.cmp(oldValue, newValue);
        }
        return hasChanged;
    }

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

    /**
     * Add necessary datasets in record <code>eqlResRecord</code>.
     *
     * @param baseEntity   main entity
     * @param eqlResRecord given record
     */
    private final void datasetsSucks(Entity baseEntity,
                                     EQLResRecord eqlResRecord) {

        // Select all dataset for base entity in cycle.
        for(int i = 0; i < baseEntity.getDatasetCount(); i++) {
            Dataset ds = entity.getDataset(i);
            Entity dsEntity = ctx.getEntityViewConfig(ds.getEntity());

            // Get existent dataset.
            EQLReqDataset reqDataset = new EQLReqDataset(baseEntity, ds,
                    dsEntity);
            EQLDRes eqlDRes = eqlResRecord.getDRes(reqDataset);
            if(eqlDRes == null) {
                // .. if doesn't exist - create new
                eqlDRes = new EQLDRes(reqDataset);
                eqlResRecord.addDRes(eqlDRes);
            }

            if(getLogger().isDebugEnabled()) {
                DEBUG("Load additionally dataset '" + ds.getName()
                        + "' data...");
            }

            // Get data for this dataset from database.
            EQLERes dRes;
            try {
                dRes = ctx.getEQLManager().select(
                        ctx.getSC(),
                        dsEntity,
                        entity,
                        new CompoundKey[]{getCompoundKeys()[0]}
                        ,
                        null);

            } catch (EQLException ex) {
                throw new GenericSystemException(
                        "EQL exception: " + ex.getMessage(), ex);
            }

            // Select all selected records in cycle.
            for(int j = 0; j < dRes.size(); j++) {
                EQLResRecord dResRecord = dRes.getRecord(j);

                // .. is record already added - skip
                if(eqlDRes.contains(dResRecord)) {
                    continue;
                }

                // .. otherwise - add
                eqlDRes.addRecord(dResRecord);

                if(getLogger().isDebugEnabled()) {
                    DEBUG(".. added new dataset record: " + dResRecord);
                }
            }
        }
    }
}
