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

import com.queplix.core.error.GenericSystemException;
import com.queplix.core.integrator.security.LogonSession;
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.ejb.EntityViewConfigManagerLocal;
import com.queplix.core.modules.config.ejb.EntityViewConfigManagerLocalHome;
import com.queplix.core.modules.config.utils.EntityHelper;
import com.queplix.core.modules.eql.EQLDRes;
import com.queplix.core.modules.eql.EQLERes;
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.ejb.EQLManagerLocal;
import com.queplix.core.modules.eql.ejb.EQLManagerLocalHome;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eql.utils.EQLUtils;
import com.queplix.core.modules.jeo.ejb.JEOManagerLocal;
import com.queplix.core.modules.jeo.ejb.JEOManagerLocalHome;
import com.queplix.core.utils.JNDINames;
import com.queplix.core.utils.cache.CacheObjectManager;
import com.queplix.core.utils.log.AbstractLogger;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

/**
 * Base class for JEO handlers.
 * <p/>
 * <p/>
 * Manages state of Java Entity Objects.
 * </p>
 *
 * @author Baranov Andrey [ALB]
 * @author [ONZ] Oleg N. Zhovtanyuk
 * @version $Revision: 1.4 $ $Date: 2006/01/31 14:39:01 $
 */
public abstract class JEObjectHandler
        extends AbstractLogger implements Serializable, InvocationHandler {

    // ================================================================== Fields

    private LogonSession ls;
    private EQLERes eqlRes;
    private EQLResRecord eqlResRecord;

    private JEObject jeObject;
    //    private JEO jeObject;
    private Map dsJEOHndListMap = new HashMap(2);

    private boolean isValid = false;
    private boolean isExist = false;

    // ========================================================== Initialization

    /**
     * Handler initialization.
     *
     * @param ls           user logon session
     * @param eqlRes       entity EQL response
     * @param eqlResRecord concrete EQL record in EQL response
     */
    public final void init(LogonSession ls, EQLERes eqlRes,
                           EQLResRecord eqlResRecord) {

        if(isExist) {
            throw new IllegalStateException("Initialization already completed");
        }

        // Set parameters.
        this.ls = ls;
        this.eqlRes = eqlRes;
        this.eqlResRecord = eqlResRecord;

        // Create a new proxy object.
        jeObject = (JEObject)
                Proxy.newProxyInstance(getObjectClass().getClassLoader(),
                        new Class[]{getObjectClass()}, this);

        DEBUG("Creating new Object Handler:\nentity = " + getEntity().getName()
                + "\nobject class = " + getObjectClass());

        // Handler is valid and exists.
        isValid = true;
        isExist = true;

    }

    // ======================================================== Abstract methods

    /**
     * Gets the bound entity name.
     *
     * @return entity name attribute
     */
    public abstract String getEntityName();

    /**
     * Gets JEO object class.
     *
     * @return class
     */
    public abstract Class getObjectClass();

    // ========================================================== Access methods

    /**
     * Logon session getter.
     *
     * @return LogonSession object
     */
    public final LogonSession getLogonSession() {
        return ls;
    }

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

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

    /**
     * EQL response record getter.
     *
     * @return EQLResRecord object
     */
    public final EQLResRecord getEqlResRecord() {
        return eqlResRecord;
    }

    /**
     * JEObject object getter.
     *
     * @return JEObject object
     */
    public final JEObject getJEObject() {
        return jeObject;
    }

    public final <T extends JEObject> T getJEObject(Class<T> clazz) {
        return clazz.cast(jeObject);
    }

    // ========================================================== Helper methods

    /**
     * Removes JEO object.
     */
    public final void remove() {
        checkIsExist();
        eqlResRecord.markAsDelete();
        isValid = false;
    }

    /**
     * Updates JEO object.
     *
     * @throws EQLException
     */
    public final int commit()
            throws EQLException {

        boolean debug = getLogger().isDebugEnabled();
        if(debug) {
            DEBUG("Updating object, entity - '" + getEntity().getName() + "'");
        }
        int rows = getJEOManagerLocal().commit(this);
        if(debug) {
            DEBUG("Object updated, entity - '" + getEntity().getName() + "'");
        }
        return rows;
    }

    /**
     * Releases all handler resources.
     */
    public final void release() {
        checkIsExist();
        if(getLogger().isDebugEnabled()) {
            DEBUG("Releasing JEO handler, entity - '" + getEntity().getName()
                    + "'");
        }
        isValid = false;
        isExist = false;
    }

    /**
     * Check for handler existance.
     */
    public final void checkIsExist() {
        if(!isExist) {
            throw new GenericSystemException("Handler does not exist");
        }
    }

    /**
     * Check for handler validity.
     */
    public final void checkIsValid() {
        if(!isValid) {
            throw new GenericSystemException("Handler is invalid");
        }
    }

    /**
     * Processes a method invocation on a proxy instance and returns
     * the result. This method will be invoked on an invocation handler
     * when a method is invoked on a proxy instance that it is
     * associated with.
     *
     * @param proxy  the proxy instance that the method was invoked on
     * @param method the <code>Method</code> instance
     * @throws Throwable
     * @param    args an array of objects containing the values of the arguments
     * @return the value to return from the method invocation
     */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {

        //
        // Initialization.
        //

        String methodName = method.getName();
        boolean debug = getLogger().isDebugEnabled();
        if(debug) {
            DEBUG("Invoking method '" + methodName + "', entity - '"
                    + getEntity().getName() + "'");
        }

        //
        // Method switch.
        //

        Object value;
        if(methodName.equals("getJEObjectHandler")) {
            // Master handler.
            //   return this reference
            return this;

        } else if(methodName.equals("hashCode")) {
            // Hash code.
            //   return object's hashcode
            return new Integer(eqlResRecord.hashCode());

        } else if(methodName.equals("equals")) {
            // Equals.
            //   return boolean
            return Boolean.FALSE;

        } else if(methodName.startsWith("get")) {
            // Field value getter.
            //   return current field value
            EQLResCell resCell = getEQLResCell(methodName, 3, false);
            value = resCell.getObject(method.getReturnType());

        } else if(methodName.startsWith("old")) {
            if(methodName.startsWith("oldText")) {
                // Old field listfield value getter.
                //   return old field text value
                EQLResCell listResCell = getEQLResCell(methodName, 7, false)
                        .getListField();
                value = listResCell.getOldObject(method.getReturnType());
            } else {
                // Old field value getter.
                //   return old field value
                EQLResCell resCell = getEQLResCell(methodName, 3, false);
                value = resCell.getOldObject(method.getReturnType());
            }
        } else if(methodName.startsWith("text")) {
            // Listfield value getter.
            //   return text value
            EQLResCell listResCell = getEQLResCell(methodName, 4, false)
                    .getListField();
            value = listResCell.getObject(method.getReturnType());

        } else if(methodName.startsWith("isChanged")) {
            // Is value changed?
            //   return boolean
            EQLResCell resCell = getEQLResCell(methodName, 9, false);
            value = resCell.isValueChanged() ? Boolean.TRUE:Boolean.FALSE;

        } else if(methodName.startsWith("isNull")) {
            // Is value NULL?
            //   return boolean
            EQLResCell resCell = getEQLResCell(methodName, 6, false);
            value = resCell.isNull() ? Boolean.TRUE:Boolean.FALSE;

        } else if(methodName.startsWith("set")) {
            // Field value setter.
            //   arg[0] - new field value
            //   arg[1] - indicates if we should set the ListField or not
            //   return void
            checkIsValid();
            EQLResCell resCell = getEQLResCell(methodName, 3, true);
            resCell.setObject(args[0]);
            Boolean isListField = true;
            if(args.length > 1) {
                Object o = args[1];
                if(o instanceof Boolean) {
                    isListField = (Boolean) o;
                }
            }

            // Set list field value.
            if(isListField) {
                setListFieldValue(resCell, resCell.getListField(), null);
            }

            value = null;

        } else if(methodName.startsWith("_set")) {
            // Special field value setter (use it to update external memo).
            //   arg[0] - new listfield value
            //   return void
            checkIsValid();
            EQLResCell resCell = getEQLResCell(methodName, 4, true);

            // Set list field value.
            setListFieldValue(resCell, resCell.getListField(),
                    EQLObject.getInstance(args[0]));

            // Value changed!
            resCell.forceChanged();
            value = null;

        } else if(methodName.startsWith("ds")) {
            // Dataset getter.
            //   arg[0] - dataset hnd class
            //   return List of dataset handlers
            EQLDRes dRes = getEQLDRes(methodName, 2);
            Class hndClass = (Class) args[0];
            String datasetName = dRes.getReqDataset().getDataset().getName();

            // try find in map
            value = dsJEOHndListMap.get(datasetName);
            if(value == null) {
                // .. create new hnd list
                value = new DatasetJEOHndList(dRes, hndClass);
                dsJEOHndListMap.put(datasetName, value);
            }

        } else {
            // Unknown method.
            throw new GenericSystemException(
                    "Unsupported method '" + methodName + "'");
        }

        // Ok.
        if(debug) {
            DEBUG("Method returned '" + value + "', entity - '"
                    + getEntity().getName() + "'");
        }
        return value;

    }

    // ========================================================= Protected methods

    /**
     * Helper method that returns the JEO instance from <tt>hnd</tt> JEO handler
     * casted to the type represented by <tt>clazz</tt> class.
     *
     * @throws ClassCastException if the JEO instance is not null
     *                            and is not assignable to the type T.
     */
    protected final static <T extends JEObject> T getJEObject(
            JEObjectHandler hnd, Class<T> clazz) {
        return hnd != null ? hnd.getJEObject(clazz):null;
    }

    /**
     * Helper method that returns a collection of the JEO instances from <tt>hndList</tt> collection of JEO handlers
     * casted to the type represented by <tt>clazz</tt> class.
     *
     * @throws ClassCastException if the JEO instance is not null
     *                            and is not assignable to the type T.
     */
    protected final static <T extends JEObject> List<T> getJEObjectList(
            List hndList, Class<T> clazz) {
        if(hndList == null) {
            return Collections.emptyList();
        }

        List<T> list = new ArrayList<T>();
        for(Iterator it = hndList.iterator(); it.hasNext();) {
            JEObjectHandler hnd = (JEObjectHandler) it.next();
            if(hnd != null) {
                list.add(hnd.getJEObject(clazz));
            }
        }
        return Collections.unmodifiableList(list);
    }

    // ========================================================= Private methods

    // Gets EQLResCell by the JEO object method name.
    // If forceInsert = true and EQLResCell is null -> create empty

    private EQLResCell getEQLResCell(String methodName, int prefixSize,
                                     boolean forceInsert) {
        Entity entity = getEntity();
        String fieldName = methodName.substring(prefixSize).toLowerCase();
        Efield field = EntityHelper.getEfield(fieldName, entity);
        EQLReqField reqField = new EQLReqField(entity, field);
        EQLResCell resCell = eqlResRecord.getResCell(reqField);

        if(resCell == null) {

            if(forceInsert) {
                DEBUG("Insert new null cell: " + reqField);

                resCell = new EQLResCell(reqField, true);
                eqlResRecord.insertData(resCell);

            } else {
                ERROR("Incorrect method '" + methodName + "'.");
                ERROR("	eqlResRecord: " + eqlResRecord);

                throw new NullPointerException(
                        "Can't find appropriate EQLResCell object for method  '"
                                +
                                methodName + "'. Entity: " + entity.getName() +
                                ". Field: " + fieldName + ".");
            }
        }

        return resCell;
    }

    // Gets EQLDRes by the JEO object method name.
    private EQLDRes getEQLDRes(String methodName, int prefixSize) {

        Entity entity = getEntity();
        String datasetName = methodName.substring(prefixSize).toLowerCase();
        Dataset dataset = EntityHelper.getDataset(datasetName, entity);
        Entity datasetEntity = getEntityViewConfigManagerLocal()
                .getEntityViewConfig(dataset.getEntity());

        EQLReqDataset reqDataset = new EQLReqDataset(entity, dataset,
                datasetEntity);
        EQLDRes dRes = eqlResRecord.getDRes(reqDataset);

        if(dRes == null) {
            ERROR("Incorrect method '" + methodName + "'.");
            ERROR("	eqlResRecord: " + eqlResRecord);

            throw new NullPointerException(
                    "Can't find appropriate EQLDRes object for method  '" +
                            methodName + "'. Entity: " + entity.getName() +
                            ". Dataset: " + datasetName + ".");
        }

        return dRes;
    }

    /**
     * Set list field value to changed field
     *
     * @param resCell     EQLResCell object of changed field
     * @param listResCell EQLResCell list ref object of changed field
     * @param listObj     EQLObject list ref value (optional)
     * @throws EQLException
     */
    private void setListFieldValue(EQLResCell resCell,
                                   EQLResCell listResCell,
                                   EQLObject listObj)
            throws EQLException {

        Listref lref = resCell.getReqField().getField().getListref();
        if(lref == null) {
            // No list field found - skip process.
            return;
        }

        if(listResCell == null) {
            // Create new empty list EQLResCell object.
            Entity listEntity = getEntityViewConfigManagerLocal()
                    .getEntityViewConfig(lref.getEntity());
            Efield listField = EntityHelper.getEfield(lref.getEfield(),
                    listEntity);
            EQLReqField listReqField = new EQLReqField(listEntity, listField);
            listResCell = new EQLResCell(listReqField, true);
            resCell.addListField(listResCell);
        }

        // Set value.
        if(listObj == null) {
            listObj = EQLUtils.getListFieldValue(eqlResRecord, resCell,
                    listResCell.getReqField(), true, ls, getEQLManagerLocal());
        }
        listResCell.setEQLObject(listObj);
    }

    // Entity View Config Manager EJB reference getter.
    private EntityViewConfigManagerLocal getEntityViewConfigManagerLocal() {
        return (EntityViewConfigManagerLocal) new CacheObjectManager().
                getLocalObject(JNDINames.EntityViewConfigManager,
                        EntityViewConfigManagerLocalHome.class);
    }

    // EQL Manager EJB reference getter.
    private EQLManagerLocal getEQLManagerLocal() {
        return (EQLManagerLocal) new CacheObjectManager().
                getLocalObject(JNDINames.EQLManager, EQLManagerLocalHome.class);
    }

    // JEO Manager EJB reference getter.
    private JEOManagerLocal getJEOManagerLocal() {
        return (JEOManagerLocal) new CacheObjectManager().
                getLocalObject(JNDINames.JEOManager, JEOManagerLocalHome.class);
    }

    // ========================================================= Inner class

    /**
     * List of dataset JEO handlers
     * Some methods are not implemented.
     * <p>@author [ALB] Baranov Andrey</p>
     */
    public class DatasetJEOHndList
            implements List, Serializable {

        private EQLDRes dRes;
        private Class hndClass;
        private List hndList;

        /**
         * Constructor
         *
         * @param dRes     EQLDRes object
         * @param hndClass dataset handler class
         */
        public DatasetJEOHndList(EQLDRes dRes, Class hndClass) {
            this.dRes = dRes;
            this.hndClass = hndClass;
            this.hndList = new ArrayList();

            for(int i = 0; i < dRes.size(); i++) {
                addHnd(dRes.getRecord(i));
            }
        }

        /**
         * Add dataset handler
         *
         * @param dsResRecord EQLResRecord object
         */
        public void addHnd(EQLResRecord dsResRecord) {
            JEObjectHandler dsHnd = getJEOManagerLocal().getJEO(ls, dRes,
                    dsResRecord, hndClass);
            hndList.add(dsHnd);
        }

        // ------------------------------------------------- list interface methods

        public Object get(int index) {
            return hndList.get(index);
        }

        public boolean add(Object o) {
            if(!(o instanceof JEObjectHandler)) {
                throw new IllegalStateException();
            }
            JEObjectHandler hnd = (JEObjectHandler) o;
            boolean added = hndList.add(hnd);
            if(added) {
                dRes.addRecord(hnd.eqlResRecord);
            }
            return added;
        }

        public void add(int index, Object o) {
            throw new UnsupportedOperationException();
        }

        public boolean addAll(Collection c) {
            throw new UnsupportedOperationException();
        }

        public boolean addAll(int index, Collection c) {
            throw new UnsupportedOperationException();
        }

        public Object set(int index, Object element) {
            throw new UnsupportedOperationException();
        }

        public Iterator iterator() {
            throw new UnsupportedOperationException();
        }

        public ListIterator listIterator() {
            throw new UnsupportedOperationException();
        }

        public ListIterator listIterator(int index) {
            throw new UnsupportedOperationException();
        }

        public int size() {
            return hndList.size();
        }

        public boolean remove(Object o) {
            if(!(o instanceof JEObjectHandler)) {
                throw new IllegalStateException();
            }
            JEObjectHandler hnd = (JEObjectHandler) o;
            boolean removed = hndList.remove(hnd);
            if(removed) {
                dRes.removeRecord(hnd.eqlResRecord);
            }
            return removed;
        }

        public Object remove(int index) {
            throw new UnsupportedOperationException();
        }

        public boolean removeAll(Collection c) {
            throw new UnsupportedOperationException();
        }

        public boolean retainAll(Collection c) {
            throw new UnsupportedOperationException();
        }

        public void clear() {
            /** @todo implement it */
            hndList.clear();
        }

        public boolean contains(Object o) {
            return hndList.contains(o);
        }

        public boolean containsAll(Collection c) {
            return hndList.containsAll(c);
        }

        public int indexOf(Object o) {
            return hndList.indexOf(o);
        }

        public int lastIndexOf(Object o) {
            return hndList.lastIndexOf(o);
        }

        public boolean isEmpty() {
            return hndList.isEmpty();
        }

        public List subList(int fromIndex, int toIndex) {
            throw new UnsupportedOperationException();
        }

        public Object[] toArray() {
            return hndList.toArray();
        }

        public Object[] toArray(Object[] a) {
            return hndList.toArray(a);
        }
    }
}
