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

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.queplix.core.client.app.vo.BaseFieldMeta;
import com.queplix.core.client.app.vo.ChartFieldMeta;
import com.queplix.core.client.app.vo.CheckBoxMeta;
import com.queplix.core.client.app.vo.DateFieldMeta;
import com.queplix.core.client.app.vo.EntityLinkFieldMeta;
import com.queplix.core.client.app.vo.EntityMeta;
import com.queplix.core.client.app.vo.EntityReferenceMeta;
import com.queplix.core.client.app.vo.FieldData;
import com.queplix.core.client.app.vo.FieldMeta;
import com.queplix.core.client.app.vo.FormMeta;
import com.queplix.core.client.app.vo.GridMeta;
import com.queplix.core.client.app.vo.HistoryFieldMeta;
import com.queplix.core.client.app.vo.InFormGridFieldMeta;
import com.queplix.core.client.app.vo.ListboxFieldMeta;
import com.queplix.core.client.app.vo.MemoFieldMeta;
import com.queplix.core.client.app.vo.MultiselectFieldMeta;
import com.queplix.core.client.app.vo.SubsetItemMeta;
import com.queplix.core.client.app.vo.SubsetMeta;
import com.queplix.core.client.app.vo.TextBoxFieldMeta;
import com.queplix.core.client.app.vo.TextareaFieldMeta;
import com.queplix.core.client.common.StringUtil;
import com.queplix.core.integrator.ActionContext;
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.Fkeys;
import com.queplix.core.jxb.entity.Listref;
import com.queplix.core.jxb.entity.types.ControlSType;
import com.queplix.core.modules.config.ejb.CaptionManagerLocal;
import com.queplix.core.modules.config.ejb.EntityViewConfigManagerLocal;
import com.queplix.core.modules.config.ejb.UserPropertyManagerLocal;
import com.queplix.core.modules.config.error.UnknownEntityException;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eqlext.jxb.gr.Reqs;
import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.log.AbstractLogger;
import com.queplix.core.utils.log.Log;

/**
 * Class helps to get entity view config
 *
 * @author Sergey Kozmin
 * @since 25.10.2006, 17:21:17
 */
public class EntityViewHelper {
    /**
     * Describes what fields should be used when performing an operation with entities.
     */
    public static enum FieldsModificator {
        /**
         * All fields should be included to entity except of hidden ones.
         */
        FORM,
        /**
         * Fields, that marked <code>grid="true"</code> to be included.
         */
        GRID,
        /**
         * Fields, that marked <code>grid="true"</code> and choosen by user in customizer tool to be included.
         */
        CUSTOMIZED_GRID
    }

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

    private static final String FIELD_AND_ENTITY_SEPARATOR = ".";

    private static final EntityViewHelper instance = new EntityViewHelper();

    /**
     * Cache of the objects, that is to be cleared when entity metadata updates.
     * Here we assume that client code, that will clear that cache when entity meta updates will be loaded on the same
     * JVM (classloader). Same assumption exists with other caches in the system. For instance:
     * <code>Cache cache = ConfigPropertyFactory.getInstance().getViewObjectsCache();</code>
     */
    private static final Map<String, FieldMeta> fieldsMetaCache = Collections
            .synchronizedMap(new HashMap<String, FieldMeta>());

//    final Lock lock = new ReentrantLock();

    private EntityViewHelper() {
    }

    public void putFieldMeta(String key, FieldMeta meta) {
        if(meta.getDataType() != FieldMeta.LISTBOX) {
            fieldsMetaCache.put(key, meta);
        }
    }

    public void clearCache(String key) {
        fieldsMetaCache.remove(key);
    }

    public void clearCache() {
        fieldsMetaCache.clear();
    }

    public FieldMeta getFieldMeta(String key) {
        //todo upgrade it. no need to sync users, that need just to get field, if nothing updating now. updating is very rare operation.
        return fieldsMetaCache.get(key);
    }


    public static EntityViewHelper getInstance() {
        return instance;
    }

    /**
     * Retrieve array of FieldMeta for the given entity.
     *
     * @param entityName   entity name
     * @param entityType   the purpose of requesting.
     * @param withCaptions should captions be included to meta information.
     * @param ls           logon session
     * @param ctx          action context
     * @return array of FieldMeta
     * @see com.queplix.core.integrator.entity.EntityViewHelper.FieldsModificator
     */
    public static FieldMeta[] getMetaInfoForEntity(String entityName,
                                                   FieldsModificator entityType,
                                                   boolean withCaptions,
                                                   LogonSession ls,
                                                   ActionContext ctx) {

        EntityViewConfigManagerLocal entityManager = ctx
                .getEntityViewConfigManager();

        // Get Entity object
        List<FieldMeta> listOfMeta = new LinkedList<FieldMeta>();
        Entity entity;
        try {
            entity = entityManager.getEntityViewConfig(entityName);
            Efield[] fields = getFieldsToBeProcessessed(entity.getEfield(),
                    entityName, entityType, ls, ctx);
            Efield pkeyField = searchPkeyField(entity);

            for(Efield field : fields) {
                try {
                    String key = entityName + FIELD_AND_ENTITY_SEPARATOR + field
                            .getName();
                    FieldMeta meta = getInstance().getFieldMeta(key);
                    if(meta == null) {
                        meta = createFieldMetaFromEfield(field, pkeyField,
                                withCaptions, ls, ctx);
                        getInstance().putFieldMeta(key, meta);
                    }
                    listOfMeta.add(meta);
                } catch (IllegalControlTypeException e) {
                    logger.WARN(e);
                } catch (EQLException e) {
                    logger.WARN(e);
                } catch (IncorrectEntityDescriptionException e) {
                    logger.WARN(e);
                }
            }

            if(entityType == FieldsModificator.FORM) {
                Dataset[] datasets = entity.getDataset();
                for(Dataset dataset : datasets) {
                    try {
                        String key = entityName + FIELD_AND_ENTITY_SEPARATOR
                                + dataset.getName();
                        FieldMeta meta = getInstance().getFieldMeta(key);
                        if(meta == null) {
                            meta = createFieldMetaFromDataset(dataset,
                                    entityName, withCaptions, ls, ctx);
                            getInstance().putFieldMeta(key, meta);
                        }
                        listOfMeta.add(meta);
                    } catch (IllegalControlTypeException e) {
                        logger.ERROR(e);
                    } catch (EQLException e) {
                        logger.ERROR(e);
                    } catch (IncorrectEntityDescriptionException e) {
                        logger.ERROR(e);
                    }
                }
            }

        } catch (UnknownEntityException e) {
            logger.ERROR(e);
        }

        return listOfMeta.toArray(new FieldMeta[listOfMeta.size()]);
    }

    private static Efield[] getFieldsToBeProcessessed(Efield[] initialEfields,
                                                      String entityName,
                                                      FieldsModificator entityType,
                                                      LogonSession ls,
                                                      ActionContext ctx) {
        Efield[] fieldsToBeProcesses;
        switch(entityType) {
            case FORM: {
                fieldsToBeProcesses = initialEfields;
                break;
            }
            case GRID: {
                ArrayList<Efield> resList = new ArrayList<Efield>();
                for(Efield initialEfield : initialEfields) {
                    if(initialEfield.getGrid()) {
                        resList.add(initialEfield);
                    }
                }
                fieldsToBeProcesses = resList.toArray(
                        new Efield[resList.size()]);
                break;
            }
            case CUSTOMIZED_GRID: {
                UserPropertyManagerLocal userPropManager = ctx
                        .getUserPropertyManager();
                String[] fieldNames = userPropManager.getFieldsForGrid(
                        ls.getUser(), entityName);
                if(fieldNames != null) {
                    Map<String, Efield> efields = new HashMap<String, Efield>();
                    for(Efield efield : initialEfields) {
                        efields.put(efield.getName(), efield);
                    }
                    ArrayList<Efield> resList = new ArrayList<Efield>();
                    for(String fieldName : fieldNames) {
                        resList.add(efields.get(fieldName));
                    }
                    fieldsToBeProcesses = resList.toArray(
                            new Efield[resList.size()]);
                } else {//take simple grid set
                    ArrayList<Efield> resList = new ArrayList<Efield>();
                    for(Efield initialEfield : initialEfields) {
                        if(initialEfield.getGrid()) {
                            resList.add(initialEfield);
                        }
                    }
                    fieldsToBeProcesses = resList.toArray(
                            new Efield[resList.size()]);
                }
                break;
            }
            default: {
                fieldsToBeProcesses = initialEfields;
            }
        }
        return fieldsToBeProcesses;
    }

    public static Set<String> getGridFields(String entityName,
                                            FieldsModificator entityType,
                                            boolean withCaptions,
                                            LogonSession ls,
                                            ActionContext ctx) {
        FieldMeta[] meta = getMetaInfoForEntity(entityName, entityType,
                withCaptions, ls, ctx);
        LinkedHashSet<String> ret = new LinkedHashSet<String>(meta.length);
        for(FieldMeta fieldMeta : meta) {
            ret.add(fieldMeta.getFieldID());
        }
        return ret;
    }

    private static FieldMeta createFieldMetaFromDataset(Dataset dataset,
                                                        String entityName,
                                                        boolean withCaptions,
                                                        LogonSession ls,
                                                        ActionContext ctx)
            throws IllegalControlTypeException, EQLException,
            IncorrectEntityDescriptionException {
        BaseFieldMeta ret;
        if(dataset == null) {
            throw new IllegalControlTypeException(
                    "Couldn't fill field meta data, because field objectis null");
        } else {
            ControlSType type = dataset.getControl();
            String datasetID = dataset
                    .getName();//field name is field id within entity
            String datasetCaption;
            if(withCaptions) {
                datasetCaption = getFieldCaption(ls.getUser().getLangID(),
                        dataset.getEntityName(), dataset.getName(), ctx);
            } else {
                datasetCaption = "";
            }
            String linkedEntityName = dataset.getLinkedEntity();
            if(type == null) {
                throw new IncorrectEntityDescriptionException(
                        dataset.getEntityName(),
                        "Could not find \"control\" attribute. ");
            } else {
                switch(type.getType()) {
                    case ControlSType.GRID_TYPE: {
                        ret = buildInformGridMeta(datasetID, datasetCaption,
                                entityName, linkedEntityName, ls, ctx);
                        break;
                    }
                    case ControlSType.M2M_TYPE: {
                        ret = buildMultiselectMeta(datasetID, datasetCaption,
                                linkedEntityName,
                                Collections.<FieldData>emptyList(),
                                dataset.getDynamic(), ls, ctx);
                        break;
                    }
                    case ControlSType.LINK_TYPE: {
                        ret = buildEntityLinkMeta(datasetID, datasetCaption,
                                entityName, linkedEntityName, ctx);
                        break;
                    }
                    default: {
                        throw new IllegalControlTypeException(
                                "Couldn't find field meta type for xml type:"
                                        + type.getType()
                                        + " (" + type.toString() + ")");
                    }
                }
            }
            ret.setRequired(dataset.getRequired());
            // descriptive is always false for datasets 
            ret.setDescriptive(false);
            // clear attribute
            ret.setClearAttribute(dataset.getClear());
        }
        return ret;
    }

    private static BaseFieldMeta buildInformGridMeta(String datasetID,
                                                     String datasetCaption,
                                                     String entityName,
                                                     String linkedEntityName,
                                                     LogonSession ls,
                                                     ActionContext ctx) {
        if(StringHelper.isEmpty(linkedEntityName)) {
            throw new IncorrectEntityDescriptionException(entityName,
                    "Couldn't find linked entity for inform grid dataset."
                            + "Entity name [" + entityName + "], dataset name ["
                            + datasetID + "]. ");
        }

        FieldMeta[] itemsList = EntityViewHelper.getMetaInfoForEntity(
                linkedEntityName, FieldsModificator.GRID, true, ls,
                ctx);

        long[] selectedIDs = new long[itemsList.length];
        for(int i = 0; i < itemsList.length; i++) {
            selectedIDs[i] = i;
        }

        GridMeta meta = new GridMeta(itemsList, selectedIDs);
        FormMeta baseFormMeta = new FormMeta(new EntityMeta(linkedEntityName,
                itemsList), datasetCaption);
        return new InFormGridFieldMeta(datasetID, datasetCaption,
                linkedEntityName, meta, meta, baseFormMeta);
    }

    private static BaseFieldMeta buildEntityLinkMeta(String datasetID,
                                                     String datasetCaption,
                                                     String entityName,
                                                     String linkedEntityName,
                                                     ActionContext ctx)
            throws IncorrectEntityDescriptionException {

        EntityLinkFieldMeta ret;

        DatasetDescriptor descriptor = getDatasetMetadata(entityName, datasetID,
                ctx);
        if(descriptor.getBindingEntityFkeyFieldNameToLinked() == null ||
                descriptor.getBindingEntityFkeyFieldNameToLocation() == null ||
                descriptor.getLinkedEntityPkeyFieldName() == null) {
            throw new IncorrectEntityDescriptionException(entityName,
                    "Couldn't find foreign key which bound [" + entityName
                            + "] entity and [" + linkedEntityName
                            + "] entity. ");
        } else {
            ret = new EntityLinkFieldMeta(datasetID, datasetCaption,
                    linkedEntityName, descriptor.getLinkedEntityFieldName());
        }
        return ret;
    }

    /**
     * Retrieve foreign key for the two bound entities.
     *
     * @param entityName        first bound entity
     * @param bindingEntityName second bound entity
     * @param ctx               servlet context
     * @return foreign key for the second bound entity. If return is null object, then there is no known connection between entities.
     *         It can be necessarily to re-run installation tools.
     */
    private static Fkeys retriveFkColumnName(String entityName,
                                             String bindingEntityName,
                                             ActionContext ctx) {
        EntityViewConfigManagerLocal evcm = ctx.getEntityViewConfigManager();
        //get two entities and trying to find foreign key, that bound it. If key is found, get the field name.
        Entity thisEn = evcm.getEntityViewConfig(entityName);
        Entity bindingEn = evcm.getEntityViewConfig(bindingEntityName);
        String bindingTableName = bindingEn.getDbobject();
        Fkeys[] forenKeys = thisEn.getFkeys();

        Fkeys fk = null;
        for(Fkeys forenKey : forenKeys) {
            //compare binding entity name and binding table name
            if(forenKey.getFkEntity() != null && forenKey.getFkEntity()
                    .equalsIgnoreCase(bindingEntityName)) {
                fk = forenKey;
                break;
            }
            if(forenKey.getFkTable() != null && forenKey.getFkTable()
                    .equalsIgnoreCase(bindingTableName)) {
                fk = forenKey;
                break;
            }
        }
        return fk;
    }

    public static MultiselectFieldMeta buildMultiselectMeta(String datasetID,
                                                            String caption,
                                                            String linkedEntityName,
                                                            List<FieldData> additionalFilters,
                                                            Boolean dynamic,
                                                            LogonSession ls,
                                                            ActionContext ctx)
            throws EQLException, IncorrectEntityDescriptionException {
        if(StringHelper.isEmpty(linkedEntityName)) {
            throw new IncorrectEntityDescriptionException(linkedEntityName,
                    "Incorrect \"linked-entity\" attribute, " +
                            "can not find linked entity and build metadata. ");
        } else {
            LinkedList<SubsetItemMeta> options = getOptionsForSubsetMeta(
                    linkedEntityName, additionalFilters,
                    null, ls, ctx);
            return new MultiselectFieldMeta(datasetID, linkedEntityName,
                    caption, new SubsetMeta(options.toArray(
                    new SubsetItemMeta[options.size()])), dynamic);
        }
    }

    /**
     * Gets the option list for subset meta.
     *
     * @param linkedEntityName  requesting entityty
     * @param additionalFilters additional filters
     * @param eqlFilters
     * @param ls                logon session
     * @param ctx               servlet context
     * @return options list
     * @throws EQLException if eql exception is occured
     */
    public static LinkedList<SubsetItemMeta> getOptionsForSubsetMeta(
            String linkedEntityName, List<FieldData> additionalFilters,
            String eqlFilters, LogonSession ls, ActionContext ctx)
            throws EQLException {
        //retrieve all possible variants
        RequestProperties props = new RequestProperties();
        props.setPage((int) StringHelper.EMPTY_NUMBER);
        Reqs request = EntityFacade.buildReqsFromThisEntityFields(
                additionalFilters, linkedEntityName,
                eqlFilters, props, ls, ctx);

        IntegratedRecordSet result = EntityFacade.performRequest(request,
                linkedEntityName, ls, ctx, FieldsModificator.FORM);

        // Pasting results into multiselect metadata.
        // Get the pkey and the value, value is taken from the listref field
        LinkedList<SubsetItemMeta> options = new LinkedList<SubsetItemMeta>();

        List<IntegratedRecord> records = result.getRecordSet();
        for(IntegratedRecord record : records) {
            Map<String, FieldData> fields = record.getFieldSet();
            String key = result.getPkeyFieldName();
            try {
                String idData = EntitySerializeHelper
                        .getStringRepresentationForGrid(fields.get(key),
                                result.getRecordMeta().get(key), ls, ctx);
                String captionData = EntitySerializeHelper
                        .getStringRepresentationForGrid(fields.get(
                                result.getListRefFieldName()),
                                result.getRecordMeta().get(
                                        result.getListRefFieldName()), ls, ctx);
                long id = Long.parseLong(idData);
                options.add(new SubsetItemMeta(id, captionData));
            } catch (NumberFormatException e) {
                logger.ERROR(
                        "Could not add multiselect value to list, because of incorrect value format. ",
                        e);
            }
        }
        return options;
    }

    private static FieldMeta createFieldMetaFromEfield(Efield field,
                                                       Efield pkeyField,
                                                       boolean withCaptions,
                                                       LogonSession ls,
                                                       ActionContext ctx)
            throws IllegalControlTypeException, EQLException,
            IncorrectEntityDescriptionException {
        BaseFieldMeta ret;
        if(field == null) {
            throw new IllegalControlTypeException(
                    "Couldn't fill field meta data, because field objectis null. ");
        }

        ControlSType type = field.getControl();
        String fieldID = field.getName();//field name is field id within entity
        String fieldCaption;
        if(withCaptions) {
            fieldCaption = getFieldCaption(ls.getUser().getLangID(),
                    field.getEntityName(), field.getName(), ctx);
        } else {
            fieldCaption = "";
        }
        if(type
                == null) {//no control property. there is only one control type: edit type
            ret = new TextBoxFieldMeta(fieldID, fieldCaption);
        } else {
            switch(type.getType()) {
                case ControlSType.CALENDAR_TYPE: {
                    ret = new DateFieldMeta(fieldID, fieldCaption);
                    break;
                }
                case ControlSType.CHECKBOX_TYPE: {
                    ret = new CheckBoxMeta(fieldID, fieldCaption);
                    break;
                }
                case ControlSType.EDIT_TYPE: {
                    ret = new TextBoxFieldMeta(fieldID, fieldCaption,
                            field.getPattern(), field.getMasked());
                    break;
                }
                case ControlSType.ENTITY_TYPE: {
                    GridMeta gridMeta;
                    String entityName;

                    Listref listref = field.getListref();
                    if(listref != null) {
                        entityName = listref.getEntity();

                        FieldMeta[] fieldMetas = getMetaInfoForEntity(
                                entityName, FieldsModificator.GRID, true, ls,
                                ctx);
                        long[] selectedIDs = new long[fieldMetas.length];
                        for(int i = 0; i < fieldMetas.length; i++) {
                            selectedIDs[i] = i;
                        }

                        gridMeta = new GridMeta(fieldMetas, selectedIDs);
                    } else {
                        throw new IncorrectEntityDescriptionException(
                                field.getEntityName(), "Control ["
                                + field.getName()
                                + "] has type ControlSType.ENTITY_TYPE, but hasn't listref entity. ");
                    }

                    ret = new EntityReferenceMeta(fieldID, fieldCaption,
                            gridMeta, entityName);
                    break;
                }
                case ControlSType.INLINE_MEMO_TYPE: {
                    ret = new TextareaFieldMeta(fieldID, fieldCaption);
                    break;
                }
                case ControlSType.MEMO_TYPE: {
                    String memoTypeString = StringUtil.nullToEmpty(
                            field.getMemotype());
                    int memoType = MemoFieldMeta.PREPPEND_TYPE;
                    if(memoTypeString.equalsIgnoreCase("append")) {
                        memoType = MemoFieldMeta.APPEND_TYPE;
                    } else if(memoTypeString.equalsIgnoreCase("edit")) {
                        memoType = MemoFieldMeta.EDIT_TYPE;
                    }
                    String pkeyFieldCaption = getFieldCaption(
                            ls.getUser().getLangID(), pkeyField.getEntityName(),
                            pkeyField.getName(), ctx);
                    ret = new MemoFieldMeta(fieldID, fieldCaption,
                            pkeyFieldCaption, "Select " + fieldCaption,
                            memoType);
                    break;
                }
                case ControlSType.HISTORY_TYPE: {
                    String pkeyFieldCaption = getFieldCaption(
                            ls.getUser().getLangID(), pkeyField.getEntityName(),
                            pkeyField.getName(), ctx);
                    ret = new HistoryFieldMeta(fieldID, fieldCaption,
                            pkeyFieldCaption, fieldCaption);
                    break;
                }
                case ControlSType.SELECT_TYPE: {
                    ret = retrieveMetaForListbox(field, fieldID, fieldCaption,
                            ctx, ls);
                    break;

                }
                case ControlSType.TEXTAREA_TYPE: {
                    ret = new TextareaFieldMeta(fieldID, fieldCaption);
                    break;
                }
                case ControlSType.CHART_TYPE: {
                    ret = new ChartFieldMeta(fieldID, field.getChartName());
                    break;
                }
                default: {
                    throw new IllegalControlTypeException(
                            "Couldn't find field meta type for xml type:"
                                    + type.getType()
                                    + " (" + type.toString() + ")");
                }
            }
        }
        ret.setRequired(field.getRequired());
        ret.setDescriptive(field.getDescriptive());
        ret.setReadOnly(field.getReadonly());
        ret.setSearchable(field.getSearchable());
        ret.setHasDefSrcAttribute(!StringHelper.isEmpty(field.getEqlDefsrc()));
        ret.setInline(field.getInline());
        return ret;
    }

    public static long[] getSelectedIndexes(String entityName,
                                            FieldsModificator entityType,
                                            ActionContext ctx,
                                            LogonSession ls) {
        EntityViewConfigManagerLocal entityManager = ctx
                .getEntityViewConfigManager();

        Entity entity = entityManager.getEntityViewConfig(entityName);
        Efield[] selectedFields = getFieldsToBeProcessessed(entity.getEfield(),
                entityName, entityType, ls, ctx);
        Efield[] fullFields = getFieldsToBeProcessessed(entity.getEfield(),
                entityName, FieldsModificator.FORM, ls, ctx);

        long[] ret = new long[selectedFields.length];
        for(int i = 0; i < selectedFields.length; i++) {
            int index = getIndexInArray(selectedFields[i], fullFields);
            if(index >= 0) {
                ret[i] = index;
            }
        }
        return ret;
    }

    /**
     * Search efield in array.
     *
     * @param field field to search
     * @param array array
     * @return index in array
     */
    private static int getIndexInArray(Efield field, Efield[] array) {
        int ret = -1;
        for(int i = 0; i < array.length; i++) {
            String name = array[i].getName();
            if(name != null && name.equalsIgnoreCase(field.getName())) {
                ret = i;
                break;
            }
        }
        return ret;
    }

    private static BaseFieldMeta retrieveMetaForListbox(Efield field,
                                                        String fieldID,
                                                        String fieldCaption,
                                                        ActionContext ctx,
                                                        LogonSession ls)
            throws EQLException, IncorrectEntityDescriptionException {
        BaseFieldMeta ret;// Get listref entity data.
        Listref listref = field.getListref();
        if(listref != null) {
            String lrEntityName = listref.getEntity();
            LinkedList<SubsetItemMeta> options = getOptionsForSubsetMeta(
                    lrEntityName, Collections.<FieldData>emptyList(),
                    null, ls, ctx
            );
            ret = new ListboxFieldMeta(fieldID, lrEntityName, fieldCaption,
                    new SubsetMeta(options.toArray(
                            new SubsetItemMeta[options.size()])),
                    field.getDynamic());
        } else {
            throw new IncorrectEntityDescriptionException(field.getEntityName(),
                    "Control ["
                            + field.getName()
                            + "] has type ControlSType.SELECT_TYPE, but hasn't listref entity. ");
        }
        return ret;
    }

    /**
     * Retrieve entity name on which #referencingFieldName in #entityName is referencing. If there is no such field
     * returns null object, otherwise returns referencing entity name.
     *
     * @param entityName           entity, where referencing field is located
     * @param referencingFieldName field name in entity
     * @param ctx                  servlet context
     * @return referenced entity name
     * @throws IncorrectEntityDescriptionException
     *          thrown if entity has unknown referencing entity.
     */
    public static String getListRefEntityName(String entityName,
                                              String referencingFieldName,
                                              ActionContext ctx)
            throws IncorrectEntityDescriptionException {

        String referencedEntityName = null;
        EntityViewConfigManagerLocal evcm = ctx.getEntityViewConfigManager();
        try {
            // Get Entity object
            Entity entity = evcm.getEntityViewConfig(entityName);
            Efield[] fields = entity.getEfield();

            for(Efield field : fields) {
                if(field.getName().equalsIgnoreCase(referencingFieldName)) {
                    referencedEntityName = field.getListref().getEntity();
                    break;
                }
            }
        } catch (UnknownEntityException e) {
            throw new IncorrectEntityDescriptionException(entityName,
                    "Could not get entity list filed. ", e);
        }
        return referencedEntityName;
    }

    public static String getListFieldNameForEntity(String entityName,
                                                   ActionContext ctx)
            throws IncorrectEntityDescriptionException {
        String listFieldName;
        EntityViewConfigManagerLocal evcm = ctx.getEntityViewConfigManager();
        try {
            // Get Entity object
            Entity entity = evcm.getEntityViewConfig(entityName);
            listFieldName = entity.getListfield();
        } catch (UnknownEntityException e) {
            throw new IncorrectEntityDescriptionException(entityName,
                    "Could not get entity list filed. ", e);
        }
        return listFieldName;
    }

    private static String getFieldCaption(String langID, String entityName,
                                          String fieldName, ActionContext ctx) {
        String fieldCaption;
        CaptionManagerLocal captionManager = null;
        try {
            captionManager = ctx.getCaptionManager();
        } catch (Exception e) {
            logger.ERROR("Could't get caption for field", e);
        }

        if(captionManager != null) {
            fieldCaption = captionManager.getEfieldCaption(langID, entityName,
                    fieldName);
        } else {
            fieldCaption = fieldName;
        }
        return fieldCaption;
    }

    public static void setFieldsForGrid(LogonSession ls, String entity,
                                        FieldMeta[] fields, ActionContext ctx) {

        List<String> fieldNames = new ArrayList<String>();
        for(FieldMeta field : fields) {
            fieldNames.add(field.getFieldID());
        }
        setFieldsForGrid(ls, entity, fieldNames.toArray(new String[0]), ctx);
    }

    public static void setFieldsForGrid(LogonSession ls, String entity,
                                        String[] fields, ActionContext ctx) {

        UserPropertyManagerLocal userPropManager = ctx.getUserPropertyManager();
        userPropManager.setFieldsForGrid(ls.getUser(), entity, fields);
    }

    /**
     * Retrieve entity pkey id. If there is no field with pkey = true in entity returns null.
     *
     * @param entityName entity to retrieve name
     * @param ctx        current servlet context
     * @return pkey field name
     * @throws IncorrectEntityDescriptionException
     *          if there is no pkey defined in entity
     */
    public static String getPkeyID(String entityName, ActionContext ctx)
            throws IncorrectEntityDescriptionException {
        String pkeyID = null;
        EntityViewConfigManagerLocal evcm = ctx.getEntityViewConfigManager();
        if(evcm != null) {
            // Get Entity object
            Entity entity;
            try {
                entity = evcm.getEntityViewConfig(entityName);
                Efield pkeyField = searchPkeyField(entity);
                if(pkeyField != null) {
                    pkeyID = pkeyField.getName();
                } else {
                    throw new IncorrectEntityDescriptionException(
                            "Could not find pkey field in entity [" + entityName
                                    + "]. ");
                }
            } catch (UnknownEntityException e) {
                logger.ERROR(e);
            }
        }
        return pkeyID;
    }

    private static Efield searchPkeyField(Entity entity) {
        Efield pkeyField = null;
        Efield[] fields = entity.getEfield();
        for(Efield field : fields) {
            if(field.getPkey()) {
                pkeyField = field;
                break;
            }
        }
        return pkeyField;
    }

    public static Map<String, FieldMeta> getMetaForEntity(String entityName,
                                                          FieldsModificator entityType,
                                                          boolean withCaptions,
                                                          LogonSession ls,
                                                          ActionContext ctx) {

        Map<String, FieldMeta> metas = new LinkedHashMap<String, FieldMeta>();
        FieldMeta[] fieldsMeta = getMetaInfoForEntity(entityName, entityType,
                withCaptions, ls, ctx);
        for(FieldMeta meta : fieldsMeta) {
            metas.put(meta.getFieldID(), meta);
        }
        return metas;
    }

    public static FieldMeta getMetaForField(String entityName, String fieldName,
                                            boolean withCaptions,
                                            LogonSession ls,
                                            ActionContext ctx) {

        Map<String, FieldMeta> metas = getMetaForEntity(
                entityName, FieldsModificator.FORM, withCaptions, ls, ctx);

        return metas.get(fieldName);
    }

    /**
     * Returns {@link DatasetDescriptor} for the given dataset for the given entity
     *
     * @param entityName  entity name
     * @param datasetName dataset name
     * @param ctx         servlet context
     * @return {@link DatasetDescriptor} object if given dataset exists in entity null object
     *         if dataset doesn't exists.
     * @ thrown if ejb config manager could not be retrieved.
     */
    public static DatasetDescriptor getDatasetMetadata(String entityName,
                                                       String datasetName,
                                                       ActionContext ctx) {
        DatasetDescriptor desc = null;
        EntityViewConfigManagerLocal evcm = ctx.getEntityViewConfigManager();
        if(evcm != null) {
            // Get Entity object
            try {
                Entity entity = evcm.getEntityViewConfig(entityName);
                Dataset[] datasets = entity.getDataset();
                for(Dataset dataset : datasets) {
                    if(dataset.getName().equalsIgnoreCase(datasetName)) {
                        //retrieve binding and location information from fkeys parameters.
                        Fkeys fk = retriveFkColumnName(entityName,
                                dataset.getEntity(), ctx);
                        String bindingEntityFkColumnToLocationEntity = null;
                        String locationEntityPkColumn = null;
                        if(fk != null) {
                            bindingEntityFkColumnToLocationEntity = fk
                                    .getFkColumn();
                            locationEntityPkColumn = fk.getPkColumn();
                        }
                        //retrieve binding and linked entities information from fkeys params
                        Fkeys secondfk = retriveFkColumnName(
                                dataset.getLinkedEntity(), dataset.getEntity(),
                                ctx);
                        String bindingEntityFkeyFieldNameToLinked = null;
                        String linkedEntityPkeyFieldName = null;
                        if(secondfk != null) {
                            bindingEntityFkeyFieldNameToLinked = secondfk
                                    .getFkColumn();
                            linkedEntityPkeyFieldName = secondfk.getPkColumn();
                        }
                        String linkedEntityFieldName = getDatasetName(
                                dataset.getLinkedEntity(), dataset.getEntity(),
                                ctx);

                        ControlSType type = dataset.getControl();
                        desc = new DatasetDescriptor(getDatasetType(type),
                                entityName, locationEntityPkColumn,
                                datasetName, dataset.getEntity(),
                                bindingEntityFkColumnToLocationEntity,
                                bindingEntityFkeyFieldNameToLinked,
                                dataset.getLinkedEntity(),
                                linkedEntityPkeyFieldName,
                                linkedEntityFieldName,
                                true
                        );
                        break;//don't need to iterate entity if we already found field
                    }
                }
            } catch (UnknownEntityException e) {
                logger.ERROR(e);
            }
        }
        return desc;
    }

    private static DatasetType getDatasetType(ControlSType type) {
        DatasetType retType = null;
        switch(type.getType()) {
            case ControlSType.GRID_TYPE: {
                retType = DatasetType.IN_FORM_GRID;
                break;
            }
            case ControlSType.LINK_TYPE: {
                retType = DatasetType.ENYTITYLINK;
                break;
            }
            case ControlSType.M2M_TYPE: {
                retType = DatasetType.MULTISELECT;
                break;
            }
            default: {
                throw new IllegalControlTypeException(
                        "Couldn't process dataset with type: "
                                + type.toString());
            }
        }
        return retType;
    }

    private static String getDatasetName(String entityName,
                                         String pointedToEntityName,
                                         ActionContext ctx) {
        String ret = null;
        EntityViewConfigManagerLocal evcm = ctx.getEntityViewConfigManager();
        if(evcm != null) {
            // Get Entity object
            Entity entity = evcm.getEntityViewConfig(entityName);
            Dataset[] datasets = entity.getDataset();
            for(Dataset dataset : datasets) {
                if(dataset.getEntity().equalsIgnoreCase(pointedToEntityName)) {
                    ret = dataset.getName();
                }
            }
        }
        return ret;
    }
}
