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

import com.queplix.core.error.GenericSystemException;
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.jxb.entity.Listref;
import com.queplix.core.modules.config.ejb.CustomConfigManagerLocal;
import com.queplix.core.modules.config.ejb.CustomConfigManagerLocalHome;
import com.queplix.core.modules.config.error.UnknownEfieldException;
import com.queplix.core.modules.config.jxb.CustomField;
import com.queplix.core.modules.config.utils.EntityHelper;
import com.queplix.core.modules.config.utils.EntitySchema;
import com.queplix.core.modules.eql.EQLDateObject;
import com.queplix.core.modules.eql.EQLEReq;
import com.queplix.core.modules.eql.EQLERes;
import com.queplix.core.modules.eql.EQLMemoObject;
import com.queplix.core.modules.eql.EQLNullObject;
import com.queplix.core.modules.eql.EQLNumberObject;
import com.queplix.core.modules.eql.EQLObject;
import com.queplix.core.modules.eql.EQLReq;
import com.queplix.core.modules.eql.EQLReqField;
import com.queplix.core.modules.eql.EQLReqOp;
import com.queplix.core.modules.eql.EQLReqSelect;
import com.queplix.core.modules.eql.EQLReqSelectAttr;
import com.queplix.core.modules.eql.EQLReqSubOpMember;
import com.queplix.core.modules.eql.EQLReqSubOpMemberField;
import com.queplix.core.modules.eql.EQLReqSubWhere;
import com.queplix.core.modules.eql.EQLReqWhere;
import com.queplix.core.modules.eql.EQLRes;
import com.queplix.core.modules.eql.EQLResCell;
import com.queplix.core.modules.eql.EQLResMetaData;
import com.queplix.core.modules.eql.EQLResRecord;
import com.queplix.core.modules.eql.EQLSession;
import com.queplix.core.modules.eql.EQLStringObject;
import com.queplix.core.modules.eql.conds.EqCond;
import com.queplix.core.modules.eql.conds.LikeCond;
import com.queplix.core.modules.eql.ejb.EQLManagerLocal;
import com.queplix.core.modules.eql.ejb.EQLManagerLocalHome;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eql.error.UserQueryParseException;
import com.queplix.core.modules.eql.funcs.LowerFunc;
import com.queplix.core.modules.eql.jxb.eqlagent.Property;
import com.queplix.core.modules.eql.utils.EQLUtils;
import com.queplix.core.utils.JNDINames;

import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>EQL agent stub</p>
 *
 * @author Baranov Andrey [ALB]
 * @version $Revision: 1.1.1.1 $ $Date: 2005/09/12 15:30:27 $
 */

public abstract class EQLAgentStub
        implements EQLAgent {

    // --------------------------------------------------------------- fields/constants

    // Schema.
    private EntitySchema schema;

    // Cache of EQLReqField objects.
    private Map reqFieldCache = Collections.synchronizedMap(new HashMap());

    // --------------------------------------------------------------- interface implementation

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

    public void init(EntitySchema schema, Property[] properties) {
        this.schema = schema;
    }

    /*
     * No javadoc
     * @see EQLAgent#getEntitySchema
     */
    public final EntitySchema getEntitySchema() {
        return schema;
    }

    // --------------------------------------------------------------- new methods

    /**
     * Add new select constraint.
     *
     * @param result a search bean
     * @param field  EQLReqSubOpMemberField constraint field
     * @param value  EQLObject constraint value
     */
    protected abstract void addSelectConstraint(Object result,
                                                EQLReqField field,
                                                EQLObject value);

    // --------------------------------------------------------------- helper methods

    //
    // Parse EQL select request
    // Supported combinations:
    // 1) WHERE field1 = .. AND field2 = ..
    // 2) WHERE (field1 = .. AND field2 = ..)
    // 3) WHERE (LOWER(field1) = .. AND field2 = LOWER(..))
    //

    protected void parseSelectRequest(EQLEReq req, Object result)
            throws EQLException {

        // Build EQLRes.
        EQLRes res = new EQLRes();
        EQLResMetaData metaData = new EQLResMetaData();
        metaData.setReq(req);
        res.setMetaData(metaData);

        EQLReqWhere where = req.getWhere();
        if(where != null) {
            // .. remove all braces around condition
            while(where.size() == 1 && where.getSubWhere(0)
                    .getCond() instanceof EQLReqWhere) {
                where = (EQLReqWhere) where.getSubWhere(0).getCond();
            }

            for(int i = 0; i < where.size(); i++) {
                EQLReqSubWhere subWhere = where.getSubWhere(i);
                EQLReqOp leftOp;
                EQLReqOp rightOp;

                // .. remove all braces around condition
                while(subWhere.getCond() instanceof EQLReqWhere) {
                    subWhere = ((EQLReqWhere) subWhere.getCond()).getSubWhere(
                            0);
                }

                // .. parse condition's left and right operations
                if(subWhere.getCond() instanceof EqCond) {
                    EqCond cond = (EqCond) subWhere.getCond();
                    leftOp = cond.getLeftMember();
                    rightOp = cond.getRightMember();

                } else if(subWhere.getCond() instanceof LikeCond) {
                    LikeCond cond = (LikeCond) subWhere.getCond();
                    leftOp = cond.getLeftMember();
                    rightOp = cond.getRightMember();

                } else {
                    throw new GenericSystemException(
                            "Unsupported EQL condition class: " +
                                    subWhere.getCond().getClass());
                }

                // .. pull out the members of theses operations
                EQLReqSubOpMember leftSubOpMember = leftOp.getSubOp(0)
                        .getMember();
                if(leftSubOpMember instanceof LowerFunc) {
                    leftSubOpMember = ((LowerFunc) leftSubOpMember)
                            .getParameter(0).getSubOp(0).getMember();
                }
                EQLReqSubOpMember rightSubOpMember = rightOp.getSubOp(0)
                        .getMember();
                if(rightSubOpMember instanceof LowerFunc) {
                    rightSubOpMember = ((LowerFunc) rightSubOpMember)
                            .getParameter(0).getSubOp(0).getMember();
                }

                // .. get left field member
                EQLReqSubOpMemberField field;
                if(leftSubOpMember instanceof EQLReqSubOpMemberField) {
                    field = (EQLReqSubOpMemberField) leftSubOpMember;
                } else {
                    throw new GenericSystemException(
                            "Unsupported left EQL member class: " +
                                    leftSubOpMember.getClass());
                }

                // .. get right object member
                EQLObject value;
                if(rightSubOpMember instanceof EQLObject) {
                    value = (EQLObject) rightSubOpMember;
                } else {
                    throw new GenericSystemException(
                            "Unsupported right EQL member class: " +
                                    rightSubOpMember.getClass());
                }

                // Add constraint.
                addSelectConstraint(result, field.getReqField(), value);
            }
        }
    }

    //
    // Parse EQL new request
    // Supported combinations:
    // 1) SELECT NULL
    // 2) SELECT 1
    // 3) SELECT 'text'
    //
    protected EQLRes parseNewRequest(EQLReq req)
            throws EQLException {

        // Build EQLRes.
        EQLRes res = new EQLRes();
        EQLResMetaData metaData = new EQLResMetaData();
        metaData.setReq(req);
        res.setMetaData(metaData);

        // Parse SELECT clause
        // Try to find constant value.
        EQLReqSelect reqSelect = req.getSelect();
        EQLReqSelectAttr reqSelectAttr = reqSelect.getAttr(0);
        if(reqSelectAttr == null) {
            throw new GenericSystemException(
                    "Cannot find EQLReqSelectAttr in request");
        }
        EQLReqField reqField = reqSelectAttr.getReqField();
        EQLReqSubOpMember rightSubOpMember = reqSelectAttr.getReqOp().getSubOp(
                0).getMember();
        if(!(rightSubOpMember instanceof EQLObject)) {
            throw new GenericSystemException(
                    "Unsupported right EQL member class: " +
                            rightSubOpMember.getClass());
        }
        EQLObject o = (EQLObject) rightSubOpMember;

        // Build new EQLResRecord.
        EQLResRecord record = new EQLResRecord(true, 1);
        res.addRecord(record);

        // Add data.
        record.addData(new EQLResCell(reqField, o), 0);

        // Ok.
        return res;
    }

    //
    // Get new Number cell or NULL if <code>fieldName</code> not found.
    //
    protected EQLResCell getNumberCell(Entity entity,
                                       String fieldName,
                                       Number fieldValue) {

        EQLReqField reqField = getReqField(entity, fieldName);
        if(reqField == null) {
            return null;
        }

        EQLObject o;
        if(fieldValue != null) {
            o = new EQLNumberObject(fieldValue);
        } else {
            o = EQLNullObject.getInstance();
        }
        return new EQLResCell(reqField, o);
    }

    //
    // Get new Boolean cell or NULL if <code>fieldName</code> not found.
    //
    protected EQLResCell getBooleanCell(Entity entity,
                                        String fieldName,
                                        Boolean fieldValue) {

        EQLReqField reqField = getReqField(entity, fieldName);
        if(reqField == null) {
            return null;
        }

        EQLObject o;
        if(fieldValue != null) {
            o = new EQLNumberObject(fieldValue.booleanValue() ? new Integer(1)
                    :new Integer(0));
        } else {
            o = EQLNullObject.getInstance();
        }
        return new EQLResCell(reqField, o);
    }

    //
    // Get new String cell or NULL if <code>fieldName</code> not found.
    //
    protected EQLResCell getStringCell(Entity entity,
                                       String fieldName,
                                       String fieldValue) {

        EQLReqField reqField = getReqField(entity, fieldName);
        if(reqField == null) {
            return null;
        }

        EQLObject o;
        if(fieldValue != null) {
            o = new EQLStringObject(fieldValue);
        } else {
            o = EQLNullObject.getInstance();
        }
        return new EQLResCell(reqField, o);
    }

    //
    // Get new String ListRef cell or NULL.
    //
    protected EQLResCell getStringListCell(EQLSession eqlSession,
                                           EQLResCell cell,
                                           String listFieldValue) {

        EQLReqField listReqField = getListReqField(eqlSession,
                cell.getReqField().getField());
        if(listReqField == null) {
            return null;
        }

        EQLObject o;
        if(listFieldValue != null) {
            o = new EQLStringObject(listFieldValue);
        } else {
            o = EQLNullObject.getInstance();
        }
        return new EQLResCell(listReqField, o);
    }

    //
    // Get new Memo cell or NULL if <code>fieldName</code> not found.
    //
    protected EQLResCell getMemoCell(Entity entity,
                                     String fieldName,
                                     String fieldValue) {

        EQLReqField reqField = getReqField(entity, fieldName);
        if(reqField == null) {
            return null;
        }

        EQLObject o;
        if(fieldValue != null) {
            o = new EQLMemoObject(fieldValue.toCharArray());
        } else {
            o = EQLNullObject.getInstance();
        }
        return new EQLResCell(reqField, o);
    }

    //
    // Get new Date cell or NULL if <code>fieldName</code> not found.
    //
    protected EQLResCell getDateCell(Entity entity,
                                     String fieldName,
                                     Date fieldValue) {

        EQLReqField reqField = getReqField(entity, fieldName);
        if(reqField == null) {
            return null;
        }

        EQLObject o;
        if(fieldValue != null) {
            o = new EQLDateObject(fieldValue);
        } else {
            o = EQLNullObject.getInstance();
        }
        return new EQLResCell(reqField, o);
    }

    //
    // Construct/Take from cache EQLReqField object or return NULL
    // if no field found in entity
    //
    protected EQLReqField getReqField(Entity entity, String fieldName) {

        // try to find in cache
        String id = EntityHelper.getFieldId(entity.getName(), fieldName);
        EQLReqField reqField = (EQLReqField) reqFieldCache.get(id);
        if(reqField == null) {
            // create new object and remember instance in cache
            try {
                reqField = new EQLReqField(entity, EntityHelper.getEfield(
                        fieldName, entity));
                reqFieldCache.put(id, reqField);

            } catch (UnknownEfieldException e) {
                // field not found - return NULL
                return null;
            }
        }

        // ok
        return reqField;
    }

    //
    // Constructs new List EQLReqField object or return NULL
    // if no List field found in entity
    //
    protected EQLReqField getListReqField(EQLSession eqlSession,
                                          Efield masterField) {

        Listref listref = masterField.getListref();
        if(listref == null) {
            return null;
        }

        // try to find in cache
        String id = EntityHelper.getFieldId(listref.getEntity(),
                listref.getEfield());
        EQLReqField lreqField = (EQLReqField) reqFieldCache.get(id);
        if(lreqField == null) {
            // create new object and remember instance in cache
            try {
                Entity lrefEntity = eqlSession.findEntity(listref.getEntity());
                Efield lrefField = EntityHelper.getEfield(listref.getEfield(),
                        lrefEntity);
                lreqField = new EQLReqField(lrefEntity, lrefField);
                reqFieldCache.put(id, lreqField);

            } catch (UnknownEfieldException e) {
                // field not found - return NULL
                return null;
            }
        }

        // ok
        return lreqField;
    }

    //
    // Constructs new List EQLResCell or take it from the cache.
    // This method calls EQLManager EJB.
    //
    protected EQLResCell getListResCell(EQLSession eqlSession,
                                        EQLResRecord resRecord,
                                        EQLResCell masterResCell)
            throws EQLException {

        EQLReqField listReqField = getListReqField(eqlSession,
                masterResCell.getReqField().getField());
        if(listReqField == null) {
            return null;
        }

        Object pkey = masterResCell.getEQLObject().getObject();
        return getListResCell(eqlSession, resRecord, masterResCell,
                listReqField, pkey);
    }

    //
    // Constructs new List EQLResCell or take it from the cache.
    // This method calls EQLManager EJB.
    //
    protected EQLResCell getListResCell(EQLSession eqlSession,
                                        EQLResRecord resRecord,
                                        EQLResCell masterResCell,
                                        EQLReqField listReqField,
                                        Object pkey)
            throws EQLException {

        LogonSession ls = eqlSession.getLogonSession();
        EQLManagerLocal local = (EQLManagerLocal) eqlSession.getCOM().
                getLocalObject(JNDINames.EQLManager, EQLManagerLocalHome.class);

        return EQLUtils.getListResCell(resRecord, masterResCell, listReqField,
                pkey, ls, local);
    }

    /**
     * Throws UserQueryParseException exception.
     *
     * @param eqlSession EQLSession
     * @param res        EQLERes
     * @param fieldName  Efield name
     * @param value      incorrect field value
     * @param comments   optional comment
     * @throws UserQueryParseException
     */
    protected void throwUserQueryParseException(EQLSession eqlSession,
                                                EQLERes res,
                                                String fieldName,
                                                String value,
                                                String comments)
            throws UserQueryParseException {

        Entity entity = res.getEntity();
        String entityName = entity.getName();
        String langID = eqlSession.getLogonSession().getUser().getLangID();

        CustomConfigManagerLocal customManager = (CustomConfigManagerLocal)
                eqlSession.getCOM().getLocalObject(
                        JNDINames.CustomConfigManager,
                        CustomConfigManagerLocalHome.class);

        CustomField customField = customManager.getLocalizedCustomField(langID,
                entityName, fieldName);
        String caption = customField.getCaption();

        throw new UserQueryParseException(value, entityName, caption, comments);
    }
}
