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

import com.queplix.core.integrator.security.LogonSession;
import com.queplix.core.jxb.entity.Dataschema;
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.jxb.entity.types.JoinSType;
import com.queplix.core.modules.config.utils.EntityHelper;
import com.queplix.core.modules.eql.EQLNullObject;
import com.queplix.core.modules.eql.EQLObject;
import com.queplix.core.modules.eql.EQLReqEntity;
import com.queplix.core.modules.eql.EQLReqField;
import com.queplix.core.modules.eql.EQLRes;
import com.queplix.core.modules.eql.EQLResCell;
import com.queplix.core.modules.eql.EQLResRecord;
import com.queplix.core.modules.eql.ejb.EQLManagerLocal;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eql.parser.EQLIntPreparedStatement;
import com.queplix.core.modules.eql.utils.cache.EQLResCacheObject;
import com.queplix.core.utils.NumberHelper;
import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.log.AbstractLogger;
import com.queplix.core.utils.log.Log;

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

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

public class EQLUtils {

    // --------------------------------------------------------------- constants

    private static final AbstractLogger logger = Log.getLog(EQLUtils.class);

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

    /**
     * Convert EQL string to Java string
     *
     * @param s initial string
     * @return valid java string
     */
    public static String eqlToJava(String s) {
        return StringHelper.sql2java(s);
    }

    /**
     * Parse EQL number (example: <code>1.234</code>)
     * and construct java.lang.Number object
     *
     * @param value given string
     * @return Number object
     */
    public static Number parseEQLNumber(String value) {
        return NumberHelper.parseNumber(value);
    }

    /**
     * Parse EQL list (like: <code>[elem1, elem2, ...]</code>)
     * and build java.util.List object
     *
     * @param value given string
     * @return List object or null
     */
    public static List parseEQLList(String value) {
        List ret = new ArrayList();
        value = value.substring(1, value.length() - 1);
        StringTokenizer st = new StringTokenizer(",");
        while(st.hasMoreTokens()) {
            ret.add(st.nextToken());
        }

        return ret;
    }

    /**
     * Get list field value for <code>lrefReqField</code> having
     * changed field <code>resCell</code>.
     *
     * @param resRecord    EQLResRecord the changed EQL response record
     * @param resCell      EQLResCell the changed EQL response cell
     * @param lrefReqField EQLReqField the Listref object of changed field <code>resCell</code>
     * @param isNew        flag - retrieve new value or old
     * @param ls           user session
     * @param eqlManager   EQLManager local interface
     * @return EQLObject - Listref value
     * @throws EQLException
     */
    public static EQLObject getListFieldValue(EQLResRecord resRecord,
                                              EQLResCell resCell,
                                              EQLReqField lrefReqField,
                                              boolean isNew,
                                              LogonSession ls,
                                              EQLManagerLocal eqlManager)
            throws EQLException {

        //
        // Init.
        //

        EQLReqField reqField = resCell.getReqField();
        EQLReqEntity reqEntity = reqField.getReqEntity();
        Entity entity = reqEntity.getEntity();
        Efield field = reqField.getField();
        Listref listRef = field.getListref();
        if(listRef == null) {
            throw new NullPointerException(
                    "Cannot find Listref for field '" + field.getId() + "'");
        }

        EQLReqEntity lrefReqEntity = lrefReqField.getReqEntity();
        Entity lrefEntity = lrefReqEntity.getEntity();
        Efield lrefField = lrefReqField.getField();
        Dataschema ds = listRef.getDataschema();
        String[] keys1 = ds.getTable(0).getKey();
        String[] keys2 = ds.getTable(1).getKey();
        int keySize = keys1.length;

        EQLObject ret;
        if(lrefEntity.getCache().booleanValue() && keySize == 1) {

            //
            // Try to find in the cache.
            //

            Efield efield1 = EntityHelper.getEfieldBySrc(keys1[0], entity);
            EQLReqField reqField1 = new EQLReqField(entity, efield1);
            EQLResCell resCell1 = resRecord.getResCell(reqField1);
            if(resCell1 == null) {
                throw new NullPointerException(
                        "Cannot find EQLResCell for field '" + efield1.getId()
                                + "'");
            }
            EQLObject pkeyObj;
            if(isNew) {
                // .. take new value
                pkeyObj = resCell1.getEQLObject();
            } else {
                // .. take old value
                pkeyObj = resCell1.getOldEQLObject();
            }

            if(pkeyObj instanceof EQLNullObject) {
                // Pkey is NULL - return NULL
                ret = EQLNullObject.getInstance();
            } else {
                // Pkey is not null - get value from cache
                EQLResCell cachedResCell = getCachedCell(lrefEntity, lrefField,
                        pkeyObj.getObject(), ls, eqlManager);
                ret = (cachedResCell == null) ? EQLNullObject.getInstance()
                        :cachedResCell.getEQLObject();
            }

            if(logger.getLogger().isDebugEnabled()) {
                logger.DEBUG("Listref value found in the cache: " + ret);
            }

        } else {

            //
            // Build EQL query
            //

            StringBuffer eql = new StringBuffer();
            EQLIntPreparedStatement eqlPS = new EQLIntPreparedStatement();

            // 1. Add Listref field.
            eql.append("SELECT ").append(lrefField.getId()).append(" WHERE ");

            // 2. Add constraints.

            int constraintPos = 1;
            for(int i = 0; i < keySize; i++) {
                Efield efield1 = EntityHelper.getEfieldBySrc(keys1[i], entity);
                Efield efield2 = EntityHelper.getEfieldBySrc(keys2[i],
                        lrefEntity);

                EQLReqField reqField1 = new EQLReqField(entity, efield1);
                EQLResCell resCell1 = resRecord.getResCell(reqField1);
                if(resCell1 == null) {
                    throw new NullPointerException(
                            "Cannot find EQLResCell for field '"
                                    + efield1.getId() + "'");
                }
                EQLObject o1;
                if(isNew) {
                    // .. take new value
                    o1 = resCell1.getEQLObject();
                } else {
                    // .. take old value
                    o1 = resCell1.getOldEQLObject();
                }

                if(i > 0) {
                    eql.append(" AND ");
                }
                if(o1 instanceof EQLNullObject) {
                    eql.append(efield2.getId()).append(" IS NULL");
                } else {
                    eql.append(efield2.getId()).append(" = ?");
                    eqlPS.setObject(constraintPos, o1);
                    constraintPos++;
                }
            }

            //
            // Execute Listref EQL
            //

            if(logger.getLogger().isDebugEnabled()) {
                logger.DEBUG("Listref EQL query.");
                logger.DEBUG("	EQL: " + eql);
                logger.DEBUG("	EQL prepared statement: " + eqlPS);
            }

            EQLRes res = eqlManager.select(ls, eql.toString(), eqlPS);
            if(res.size() == 0) {
                // Record not found - get NULL
                ret = EQLNullObject.getInstance();
            } else {
                // Record found - get value
                ret = res.getRecord(0).getEQLObject(0);
            }

            if(logger.getLogger().isDebugEnabled()) {
                logger.DEBUG("Listref value loaded from DB: " + ret);
            }
        }

        // Ok.
        return ret;
    }

    /**
     * Get cached cell.
     *
     * @param entity     cached entity
     * @param field      cached field
     * @param pkey       field pkey
     * @param ls         user session
     * @param eqlManager EQLManager local interface
     * @return EQLResCell cached value or NULL
     * @throws EQLException
     */
    public static EQLResCell getCachedCell(Entity entity,
                                           Efield field,
                                           Object pkey,
                                           LogonSession ls,
                                           EQLManagerLocal eqlManager)
            throws EQLException {

        if(!entity.getCache().booleanValue()) {
            throw new IllegalStateException(
                    "Entity '" + entity.getName() + "' is not cacheable");
        }
        EQLResCacheObject cache = eqlManager.getCache(ls, entity);
        if(cache == null) {
            throw new NullPointerException(
                    "Cannot find cache for entity '" + entity.getName() + "'");
        }
        return cache.getCell(entity, field, pkey);
    }

    /**
     * Get list field EQLResCell of changed field.
     *
     * @param resRecord    EQLResRecord object of given record
     * @param resCell      EQLResCell object of changed field
     * @param lrefReqField EQLReqField Listref object of changed field
     * @param pkey         value of changed field
     * @param ls           user session
     * @param eqlManager   EQLManager local interface
     * @return EQLResCell - Listref EQLResCell
     * @throws EQLException
     */
    public static EQLResCell getListResCell(EQLResRecord resRecord,
                                            EQLResCell resCell,
                                            EQLReqField lrefReqField,
                                            Object pkey,
                                            LogonSession ls,
                                            EQLManagerLocal eqlManager)
            throws EQLException {

        EQLReqField reqField = resCell.getReqField();
        Listref listref = reqField.getField().getListref();

        EQLResCell listResCell = null;

        // Check list field JOIN type
        JoinSType joinType = listref.getJointype();
        if(joinType.getType() == JoinSType.USE_CACHE_TYPE) {

            // .. try to take value from the cache

            Entity lrefEntity = lrefReqField.getReqEntity().getEntity();
            Efield lrefField = lrefReqField.getField();
            listResCell = getCachedCell(lrefEntity, lrefField, pkey, ls,
                    eqlManager);

        } else {

            // .. try to find List value in database

            EQLObject listObj = getListFieldValue(resRecord, resCell,
                    lrefReqField, false, ls, eqlManager);
            listResCell = new EQLResCell(lrefReqField, listObj);
        }

        return listResCell;
    }

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

    /**
     * Constructor
     */
    private EQLUtils() {
    }

}
