/*
 * 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 com.queplix.core.client.app.vo.ChartFieldData;
import com.queplix.core.client.app.vo.CheckBoxData;
import com.queplix.core.client.app.vo.DateFieldData;
import com.queplix.core.client.app.vo.EntityData;
import com.queplix.core.client.app.vo.EntityLinkFieldData;
import com.queplix.core.client.app.vo.EntityReferenceData;
import com.queplix.core.client.app.vo.FieldData;
import com.queplix.core.client.app.vo.FieldMeta;
import com.queplix.core.client.app.vo.GridData;
import com.queplix.core.client.app.vo.HistoryFieldData;
import com.queplix.core.client.app.vo.InFormGridFieldData;
import com.queplix.core.client.app.vo.ListboxFieldData;
import com.queplix.core.client.app.vo.ListboxFieldMeta;
import com.queplix.core.client.app.vo.MemoFieldData;
import com.queplix.core.client.app.vo.MultiselectFieldData;
import com.queplix.core.client.app.vo.SubsetData;
import com.queplix.core.client.app.vo.SubsetMeta;
import com.queplix.core.client.app.vo.TextareaFieldData;
import com.queplix.core.client.app.vo.TextboxFieldData;
import com.queplix.core.integrator.ActionContext;
import com.queplix.core.integrator.security.LogonSession;
import com.queplix.core.integrator.security.SecurityHelper;
import com.queplix.core.integrator.security.User;
import com.queplix.core.integrator.security.WebLoginManager;
import com.queplix.core.modules.config.ejb.CaptionManagerLocal;
import com.queplix.core.modules.config.error.UnknownEntityException;
import com.queplix.core.modules.eqlext.jxb.gr.ReqFilter;
import com.queplix.core.modules.eqlext.jxb.gr.ResField;
import com.queplix.core.modules.eqlext.jxb.gr.types.ConditionSType;
import com.queplix.core.modules.eqlext.utils.ExtDateParser;
import com.queplix.core.utils.DateHelper;
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.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TimeZone;

/**
 * Provides all the procedures for serialization/deserialization of {@link com.queplix.core.client.app.vo.EntityData} and all the types in it.
 * <p/>
 * Also this class contains operations with 2.6v objects by converting it to new 3.0v objects.
 *
 * @author Sergey Kozmin
 * @since 12.02.2007
 */
public class EntitySerializeHelper {

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

    private static final String CHECKBOX_GRID_TRUE_MESSAGE_ID
            = "grid_checkbox_true_value";
    private static final String CHECKBOX_GRID_FALSE_MESSAGE_ID
            = "grid_checkbox_false_value";

    public static final String ENTITY_DIV = "#";
    public static final String FIELDS_DIV = ";";
    public static final String ID_AND_TEXT_DIV = ":";

    public static final String DATE_RANGE_DIV = "..";

    public static final String DATE_TIME_SEPARATOR = " ";

    /**
     * Could not return a null. Can return empty string object.
     *
     * @param data data to be converted to string value
     * @param meta metadata for field to determine type of it's value and it's RequestProperties
     * @return value string representation of data object
     * @throws IllegalControlTypeException thrown when data and metadata types mistmatch or metadata type doesn't supported
     */
    public static String getValueStringRepresentation(FieldData data,
                                                      FieldMeta meta)
            throws IllegalControlTypeException {
        String repr;
        switch(meta.getDataType()) {
            case FieldMeta.CHECKBOX: {
                CheckBoxData cbd = (CheckBoxData) data;
                repr = (cbd.isChecked() == null) ? "0"
                        :(cbd.isChecked() ? "1":"0");
                break;
            }
            case FieldMeta.DATEFIELD: {
                DateFieldData dfd = (DateFieldData) data;
                String formatedDate = dfd.getStartDate() != null ? dfd
                        .getFormatedDate():"";
                repr = formatedDate == null ? "":formatedDate;
                break;
            }
            case FieldMeta.LISTBOX: {
                ListboxFieldData lfd = (ListboxFieldData) data;
                StringBuffer sb = new StringBuffer("");
                long[] ids = lfd.getItemsSelected().getSelectedIDs();
                for(long id : ids) {
                    if(sb.length() > 0) {
                        sb.append("_");
                    }
                    sb.append(String.valueOf(id));
                }
                repr = sb.toString();
                break;
            }
            case FieldMeta.MEMO: {
                MemoFieldData mfd = (MemoFieldData) data;
                String text = mfd.getText();
                repr = text != null ? text:"";
                break;
            }
            case FieldMeta.HISTORY: {
                HistoryFieldData hfd = (HistoryFieldData) data;
                String text = hfd.getHistoryId();
                repr = text != null ? text:"";
                break;
            }
            case FieldMeta.TEXTAREA: {
                TextareaFieldData tfd = (TextareaFieldData) data;
                String text = tfd.getText();
                repr = text != null ? text:"";
                break;
            }
            case FieldMeta.TEXTBOX: {
                TextboxFieldData tbfm = (TextboxFieldData) data;
                String text = tbfm.getText();
                repr = text != null ? text:"";
                break;
            }
            case FieldMeta.ENTITYREFERENCE: {
                EntityReferenceData erd = (EntityReferenceData) data;
                repr = (erd.getSelectedRowID() != null) ? String.valueOf(
                        erd.getSelectedRowID()):"";
                break;
            }
            case FieldMeta.MULTISELECT: {
                throw new IllegalControlTypeException(
                        "Multiselect field type could not be presented by string value, should be updated as linked dataset. ");
            }
            case FieldMeta.IN_FORM_GRID: {
                throw new IllegalControlTypeException(
                        "Inform grid field type could not be presented by string value, should be updated as linked dataset. ");
            }
            case FieldMeta.ENTITYLINK: {
                throw new IllegalControlTypeException(
                        "EntityLink field type could not be presented by string value, should be updated as linked dataset. ");
            }
            default: {
                throw new IllegalControlTypeException(
                        "Unsupported field type [" + meta.getDataType()
                                + "], for serialization. See FieldMeta class. ");
            }
        }
        return repr;
    }

    /**
     * Could not return a null. Can return empty string object.
     *
     * @param data data to be converted to string value
     * @param meta metadata for field to determine type of it's value and it's RequestProperties
     * @return text string representation of data object
     * @throws IllegalControlTypeException thrown when data and metadata types mistmatch or metadata type doesn't supported
     */
    public static String getTextStringRepresentation(FieldData data,
                                                     FieldMeta meta)
            throws IllegalControlTypeException {
        String repr;
        switch(meta.getDataType()) {
            case FieldMeta.LISTBOX: {
                ListboxFieldData lfd = (ListboxFieldData) data;
                ListboxFieldMeta lbfm = (ListboxFieldMeta) meta;
                long[] ids = lfd.getItemsSelected().getSelectedIDs();
                repr = (ids.length > 0) ? lbfm.getAvailableChoises()
                        .getItemByID(ids[0]).getCaption():"";
                break;
            }
            case FieldMeta.ENTITYREFERENCE: {
                EntityReferenceData erd = (EntityReferenceData) data;
                String filter = erd.getSelectedFilter();
                repr = (filter != null) ? filter:"";
                break;
            }
            default: {
                repr = "";
            }
        }
        return repr;
    }

    public static String getStringRepresentationForGrid(FieldData data,
                                                        FieldMeta meta,
                                                        LogonSession ls,
                                                        ActionContext ctx)
            throws IllegalControlTypeException, CouldntGetEJBException {

        String result = "";
        if(data
                != null) {//if data could be a null object if selectable attribute set to false in entity xml config.
            switch(meta.getDataType()) {
                case FieldMeta.DATEFIELD:
                case FieldMeta.TEXTAREA:
                case FieldMeta.TEXTBOX:
                    result = getValueStringRepresentation(data, meta);
                    break;

                case FieldMeta.CHECKBOX: {
                    CheckBoxData cbd = (CheckBoxData) data;

                    CaptionManagerLocal captionManager = ctx
                            .getCaptionManager();
                    String languageID = WebLoginManager.getLogonLanguage(ls);
                    String trueValue = captionManager.getServerMessage(
                            languageID, CHECKBOX_GRID_TRUE_MESSAGE_ID);
                    String falseValue = captionManager.getServerMessage(
                            languageID, CHECKBOX_GRID_FALSE_MESSAGE_ID);

                    result = (cbd.isChecked() == null) ? falseValue
                            :(cbd.isChecked() ? trueValue:falseValue);
                    break;
                }
                case FieldMeta.ENTITYREFERENCE: {
                    EntityReferenceData entityRefData
                            = (EntityReferenceData) data;
                    String selectedFilter = entityRefData
                            .getSelectedFilter(); // actually this is a listref field
                    result = (selectedFilter == null) ? "":selectedFilter;
                    break;
                }
                case FieldMeta.LISTBOX: {
                    List<String> results = new ArrayList<String>();
                    ListboxFieldData listboxData = (ListboxFieldData) data;
                    ListboxFieldMeta listboxMeta = (ListboxFieldMeta) meta;
                    SubsetMeta choises = listboxMeta.getAvailableChoises();
                    for(long id : listboxData.getItemsSelected()
                            .getSelectedIDs()) {
                        results.add(choises.getItemByID(id).getCaption());
                    }
                    result = results.size() == 0
                            ? "":StringHelper.join(results.toArray(
                            new String[results.size()]), ", ");
                    break;
                }
                case FieldMeta.MEMO: {
                    MemoFieldData memoData = (MemoFieldData) data;
                    result = StringHelper.html2text(memoData.getText());
                    break;
                }
                case FieldMeta.HISTORY: {
                    HistoryFieldData historyData = (HistoryFieldData) data;
                    result = StringHelper.html2text(historyData.getText());
                    break;
                }
                case FieldMeta.ENTITYLINK:
                    throw new IllegalControlTypeException(
                            "EntityLink field type could not be presented in grid");
                case FieldMeta.IN_FORM_GRID:
                    throw new IllegalControlTypeException(
                            "InFormGrid field type could not be presented in grid");
                case FieldMeta.MULTISELECT:
                    throw new IllegalControlTypeException(
                            "MultiSelect field type could not be presented in grid");

                default:
                    throw new IllegalControlTypeException(
                            "Unsupported field type [" + meta.getDataType()
                                    + "], for serialization. See FieldMeta class.");
            }
        }
        return result;
    }

    /**
     * Could not return a null object
     *
     * @param fld       initial field
     * @param fieldMeta meta for field
     * @param recordId  record id where resField is located.
     * @param user      user
     * @param ctx       servlet context @return field data, parsed from initial field
     * @return
     * @throws IllegalControlTypeException if there is no such control presented in metadata
     */
    public static FieldData createFieldDataFromString(ResField fld,
                                                      FieldMeta fieldMeta,
                                                      Long recordId, User user,
                                                      ActionContext ctx)
            throws IllegalControlTypeException {

        boolean hasContent = fld.getHasContent();
        String fieldValue = fld.getResFieldValue();
        String fieldID = fld.getName();
        String fieldText = fld.getResFieldText();
        return createFieldData(fieldID, fieldValue, fieldText, fieldMeta,
                recordId, user, ctx, hasContent);
    }

    public static FieldData createFieldData(String fieldID, String fieldValue,
                                            String fieldText,
                                            FieldMeta fieldMeta, Long recordId,
                                            User user, ActionContext ctx,
                                            boolean hasContent) {
        FieldData data;
        switch(fieldMeta.getDataType()) {
            case FieldMeta.CHECKBOX: {
                Boolean value = false;
                if(!StringHelper.isEmpty(fieldValue)) {
                    value = "1".equalsIgnoreCase(fieldValue);
                }
                data = new CheckBoxData(fieldID, value);
                break;
            }
            case FieldMeta.DATEFIELD: {
                Date startDate = null;
                Date endDate = null;
                Locale locale = SecurityHelper.getJavaLocale(
                        user.getCountryID(), user.getLangID());
                TimeZone tz = SecurityHelper.getJavaTimezone(
                        user.getTimeZoneID());
                String datePattern = user.getDatePattern();
                String timePattern = user.getTimePattern();
                boolean dayPosFirst = user.isDatePositionFirst();

                /*String pattern = datePattern == null ? "" : datePattern;
                if(fieldValue != null && datePattern.length() < fieldValue.length()) {
                    pattern += timePattern == null ? "" : " "+timePattern;
                }*/

                String[] rawDateRange = getDatesFromString(fieldValue);
                if(rawDateRange != null) {
                    try {
                        long sysDateMls = ExtDateParser.parseDate(
                                rawDateRange[0], dayPosFirst, locale, tz,
                                datePattern, timePattern);
                        startDate = new Date(DateHelper.toUser(sysDateMls, tz));
                        if(rawDateRange.length > 1) {
                            sysDateMls = ExtDateParser.parseDate(
                                    rawDateRange[1], dayPosFirst, locale, tz,
                                    datePattern, timePattern);
                            endDate = new Date(DateHelper.toUser(sysDateMls,
                                    tz));
                        }
                    } catch (Exception e) {
                        logger.ERROR("Couldn't create data for startDate field",
                                e);
                    }
                }
                String representation = getDateStringRepresentation(startDate,
                        endDate, datePattern, timePattern, locale);
                data = new DateFieldData(fieldID, startDate,
                        DateHelper.getNowDate(), representation);
                if(endDate != null) {
                    ((DateFieldData) data).setEndDate(endDate);
                }
                break;
            }
            case FieldMeta.LISTBOX: {
                if(null != fieldValue) {
                    String[] fields = fieldValue.split("_");
                    long[] selectedItemsIds = new long[fields.length];
                    for(int i = 0; i < selectedItemsIds.length; i++) {
                        selectedItemsIds[i] = Long.parseLong(fields[i]);
                    }
                    data = new ListboxFieldData(fieldID, new SubsetData(
                            selectedItemsIds));
                } else {
                    data = new ListboxFieldData(fieldID, new SubsetData());
                }
                break;
            }
            case FieldMeta.MEMO: {
                data = new MemoFieldData(fieldID, fieldValue, recordId);
                break;
            }
            case FieldMeta.HISTORY: {
                String s = ctx.getHistoryManager().toHTML(fieldText);
                int historyDbId = -1;
                if(fieldValue != null) {//todo re-check why it is null
                    try {
                        historyDbId = Integer.parseInt(fieldValue);
                    } catch (Exception e) {
                        logger.ERROR(e);
                    }
                }
                data = new HistoryFieldData(fieldID, s, fieldValue, hasContent,
                        recordId, historyDbId);
                break;
            }
            case FieldMeta.TEXTAREA: {
                data = new TextareaFieldData(fieldID, fieldValue);
                break;
            }
            case FieldMeta.TEXTBOX: {
                data = new TextboxFieldData(fieldID, fieldValue);
                break;
            }
            case FieldMeta.ENTITYREFERENCE: {
                Long rowID = null;
                if(!StringHelper.isEmpty(fieldValue)) {
                    rowID = Long.parseLong(fieldValue);
                }
                data = new EntityReferenceData(fieldID, fieldText, rowID);
                break;
            }
            case FieldMeta.CHART: {
                Long rowID = null;
                if(!StringHelper.isEmpty(fieldValue)) {
                    rowID = Long.parseLong(fieldValue);
                }
                data = new ChartFieldData(fieldID, rowID);
                break;
            }
            default: {
                throw new IllegalControlTypeException(
                        "Couldn't fill the field with type ["
                                + fieldMeta.getDataType() +
                                "], because it doesn't supported now. ");
            }
        }
        return data;
    }

    /**
     * Create empty field data for the given type.
     *
     * @param fieldMeta meta for field
     * @param user      user
     * @return empty field data
     * @throws IllegalControlTypeException if there is no such control presented in metadata
     */
    public static FieldData createEmptyFieldData(FieldMeta fieldMeta, User user)
            throws IllegalControlTypeException {
        FieldData data;
        String fieldID = fieldMeta.getFieldID();
        switch(fieldMeta.getDataType()) {
            case FieldMeta.CHECKBOX: {
                data = new CheckBoxData(fieldID, null);
                break;
            }
            case FieldMeta.DATEFIELD: {
                Locale locale = SecurityHelper.getJavaLocale(
                        user.getCountryID(), user.getLangID());
                String datePattern = user.getDatePattern();
                String timePattern = user.getTimePattern();

                /*Date date = DateHelper.getNowDate();

                String representation = getDateStringRepresentation(date, null,
                        datePattern, timePattern, locale);
                data = new DateFieldData(fieldID, date, date, representation);*/
                data = new DateFieldData(fieldID, null, null, "");
                break;
            }
            case FieldMeta.LISTBOX: {
                data = new ListboxFieldData(fieldID, new SubsetData());
                break;
            }
            case FieldMeta.MEMO: {
                data = new MemoFieldData(fieldID, "", -1);
                break;
            }
            case FieldMeta.HISTORY: {
                data = new HistoryFieldData(fieldID, "", "", false, -1, -1);
                break;
            }
            case FieldMeta.TEXTAREA: {
                data = new TextareaFieldData(fieldID, "");
                break;
            }
            case FieldMeta.TEXTBOX: {
                data = new TextboxFieldData(fieldID, "");
                break;
            }
            case FieldMeta.ENTITYREFERENCE: {
                data = new EntityReferenceData(fieldID, "", null);
                break;
            }
            case FieldMeta.IN_FORM_GRID: {
                data = new InFormGridFieldData(fieldID, new GridData());
                break;
            }
            case FieldMeta.MULTISELECT: {
                data = new MultiselectFieldData(fieldID, new SubsetData());
                break;
            }
            case FieldMeta.ENTITYLINK: {
                data = new EntityLinkFieldData(fieldID, null, new HashSet());
                break;
            }
            default: {
                throw new IllegalControlTypeException(
                        "Couldn't create the field with type ["
                                + fieldMeta.getDataType() +
                                "], because it doesn't supported. ");
            }
        }
        return data;
    }

    /**
     * Return string representation of date range.
     *
     * @param startDate   start date
     * @param endDate     end date
     * @param datePattern date pattern
     * @param timePattern time pattern
     * @param locale      locale
     * @return string representation of date range.
     */
    public static String getDateStringRepresentation(Date startDate,
                                                     Date endDate,
                                                     String datePattern,
                                                     String timePattern,
                                                     Locale locale) {
        String formattedDate = "";
        if(startDate != null) {
            if(endDate == null) {
                formattedDate = DateHelper.formatDate(startDate, datePattern,
                        timePattern, locale);
            } else {
                if(startDate.getTime() == endDate.getTime()) {
                    formattedDate = DateHelper.formatDate(startDate,
                            datePattern, locale);
                } else {
                    formattedDate = DateHelper.formatDate(startDate,
                            datePattern, locale) + DATE_RANGE_DIV +
                            DateHelper.formatDate(endDate, datePattern, locale);
                }
            }
        }
        return formattedDate;
    }

    public static String[] getDatesFromString(String formattedDate) {
        if(formattedDate == null) {
            return null;
        }
        int divIndex = formattedDate.indexOf(DATE_RANGE_DIV);
        if(divIndex == -1) {
            return new String[]{formattedDate};
        }
        String first = formattedDate.substring(0, divIndex);
        String second = formattedDate.substring(divIndex + 2,
                formattedDate.length());

        String[] tmp = new String[]{
                first,
                second
        };
        return tmp;
    }

    private static String escape(String string) {
        return string.replaceAll(ENTITY_DIV, ENTITY_DIV + ENTITY_DIV).
                replaceAll(FIELDS_DIV, FIELDS_DIV + FIELDS_DIV).
                replaceAll(ID_AND_TEXT_DIV, ID_AND_TEXT_DIV + ID_AND_TEXT_DIV);
    }

    private static String unEscape(String string) {
        return string.replaceAll(ENTITY_DIV + ENTITY_DIV, ENTITY_DIV).
                replaceAll(FIELDS_DIV + FIELDS_DIV, FIELDS_DIV).
                replaceAll(ID_AND_TEXT_DIV + ID_AND_TEXT_DIV, ID_AND_TEXT_DIV);
    }

    /**
     * Split escaped by double escaping chars string.
     *
     * @param string          string to be splitted
     * @param singleSplitChar split char
     * @return splitted string
     */
    private static String[] splitEscaped(String string,
                                         String singleSplitChar) {
        boolean metChar = false;
        String prevToken = "";
        ArrayList<String> ret = new ArrayList<String>();
        if(!StringHelper.isEmpty(string)) {
            for(StringTokenizer stringTokenizer = new StringTokenizer(string,
                    singleSplitChar, true); stringTokenizer.hasMoreTokens();) {
                String curToken = stringTokenizer.nextToken();
                if(curToken.equalsIgnoreCase(singleSplitChar)) {
                    if(metChar) {
                        metChar = false;
                        prevToken += singleSplitChar
                                + singleSplitChar;//doubled escaped char
                    } else {
                        metChar = true;
                    }
                } else {
                    if(metChar) {
                        ret.add(prevToken);
                        prevToken = curToken;
                    } else {
                        prevToken += curToken;
                    }
                    metChar = false;
                }
            }
            if(!"".equalsIgnoreCase(prevToken)) {
                ret.add(prevToken);
            }
        }
        return ret.toArray(new String[ret.size()]);
    }

    public static String serialize(List<EntityData> data, LogonSession ls,
                                   ActionContext ctx) {
        String ret = "";
        for(EntityData entityData : data) {
            ret += serialize(entityData, ls, ctx) + ENTITY_DIV;
        }
        return ret;
    }

    public static String serialize(EntityData data, LogonSession ls,
                                   ActionContext ctx) {
        String ret = "";
        Map<String, FieldMeta> meta = EntityViewHelper.getMetaForEntity(
                data.getEntityID(),
                EntityViewHelper.FieldsModificator.FORM, false, ls, ctx);
        ret += data.getEntityID() + FIELDS_DIV;
        ret += serialize(data.getFields(), meta);
        return ret;
    }

    public static String serialize(FieldData[] data,
                                   Map<String, FieldMeta> meta) {
        String ret = "";
        for(FieldData fieldData : data) {
            FieldMeta fieldMeta = meta.get(fieldData.getFieldID());
            String valueRepr = getValueStringRepresentation(fieldData,
                    fieldMeta);
            String textRepr = getTextStringRepresentation(fieldData, fieldMeta);
            String includedTextRepr = StringHelper.isEmpty(textRepr) ? ""
                    :(ID_AND_TEXT_DIV + escape(textRepr));
            ret += fieldData.getFieldID() + ID_AND_TEXT_DIV
                    + escape(valueRepr) + includedTextRepr + FIELDS_DIV;
        }
        return ret;
    }

    public static List<EntityData> deSerializeEntities(
            String serializedEntities, LogonSession ls, ActionContext ctx) {
        List<EntityData> ents = new LinkedList<EntityData>();
        if(!StringHelper.isEmpty(serializedEntities)) {
            String[] entities = splitEscaped(serializedEntities, ENTITY_DIV);
            for(String entity : entities) {
                EntityData data = deSerializeEntity(entity, ls, ctx);
                if(data != null) {
                    ents.add(data);
                }
            }
        }
        return ents;
    }

    public static EntityData deSerializeEntity(String serializedEntity,
                                               LogonSession ls,
                                               ActionContext ctx) {
        EntityData ent = null;
        if(!StringHelper.isEmpty(serializedEntity)) {
            int nameEndIndex = serializedEntity.indexOf(FIELDS_DIV);
            if(nameEndIndex < 1) {//no need an empty entity name
                ent = null;
            } else {
                String entityName = serializedEntity.substring(0,
                        nameEndIndex);//cant holds escaping chars in name
                ent = new EntityData();
                ent.setEntityID(entityName);
                try {
                    Map<String, FieldMeta> meta = EntityViewHelper
                            .getMetaForEntity(entityName,
                                    EntityViewHelper.FieldsModificator.FORM,
                                    false, ls, ctx);
                    FieldData[] fields = deSerializeFields(
                            serializedEntity.substring(nameEndIndex + 1),
                            entityName, meta, ls, ctx);
                    ent.setFields(fields);
                } catch (UnknownEntityException e) {
                    logger.WARN("Couldn't find entity [" + entityName
                            + "], fields for this serializedEntity won't be added to the report. ",
                            e);
                }
            }
        }
        return ent;
    }

    public static FieldData[] deSerializeFields(String fieldsStr,
                                                String entityName,
                                                Map<String, FieldMeta> meta,
                                                LogonSession ls,
                                                ActionContext ctx) {
        List<FieldData> fields = new LinkedList<FieldData>();
        if(!StringHelper.isEmpty(fieldsStr)) {
            String[] nameAndValues = splitEscaped(fieldsStr, FIELDS_DIV);
            for(String nameAndValue : nameAndValues) {
                if(nameAndValue.length() > 2) {//don't need empty id or value
                    String[] arr = splitEscaped(nameAndValue, ID_AND_TEXT_DIV);
                    //cant have escape symbols in name, but can have in value and text
                    if(arr.length >= 2) {
                        FieldMeta fMeta = meta.get(arr[0]);
                        if(fMeta != null) {
                            String fieldValue = unEscape(arr[1]);
                            String fieldText = arr.length > 2 ? unEscape(arr[2])
                                    :"";
                            FieldData fieldData = createFieldData(arr[0],
                                    fieldValue,
                                    fieldText,
                                    fMeta, -1L,
                                    ls.getUser(), ctx, true);
                            fields.add(fieldData);
                        } else {
                            logger.WARN("Couldn't find field [" + arr[0]
                                    + "] in entity [" + entityName
                                    + "]. The field won't be added to the report. ");
                        }
                    }
                }
            }
        }
        return fields.toArray(new FieldData[fields.size()]);
    }

    /**
     * Creates filter for the given field meta and field data. It field has unsupported type or value is empty method
     * return null object.
     *
     * @param locationEntityName
     * @param fieldMeta
     * @param filterData
     * @return
     */
    public static ReqFilter createEFieldFilter(String locationEntityName,
                                               FieldMeta fieldMeta,
                                               FieldData filterData,
                                               LogonSession ls) {
        ReqFilter filter = new ReqFilter();
        filter.setEntity(locationEntityName);
        filter.setName(fieldMeta.getFieldID());

        filter.setConditiontype(ConditionSType.AND);

        switch(fieldMeta.getDataType()) {
            case FieldMeta.CHECKBOX: {
                CheckBoxData cbd = (CheckBoxData) filterData;
                Boolean isChecked = cbd.isChecked();
                //zero value is the value that not equal to 1
                if(isChecked != null) {//if value is null, don't add the filter.
                    if(isChecked.equals(Boolean.TRUE)) {
                        filter.addReqFilterValue("1");
                    } else {
                        filter.setConditiontype(ConditionSType.OR);
                        filter.addReqFilterValue("!1");
                        filter.addReqFilterValue(StringHelper.NULL_VALUE);
                    }
                }
                break;
            }
            case FieldMeta.DATEFIELD: {
                User user = ls.getUser();
                String datePattern = user.getDatePattern();
                String timePattern = user.getTimePattern();
                DateFieldData dfd = (DateFieldData) filterData;
                if(dfd.getFormatedDate() != null
                        && dfd.getFormatedDate().equalsIgnoreCase(
                                StringHelper.NULL_VALUE)) {
                    filter.addReqFilterValue(StringHelper.NULL_VALUE);
                } else
                if(dfd.getStartDate() != null && dfd.getEndDate() == null) {
                    String dateTimePattern = datePattern + DATE_TIME_SEPARATOR
                            + timePattern;
                    filter.addReqFilterValue(DateHelper.formatDate(
                            dfd.getStartDate(), dateTimePattern));
                } else
                if(dfd.getStartDate() != null && dfd.getEndDate() != null) {
                    Date end = new Date(dfd.getEndDate().getTime());
                    String startDateFormated = DateHelper.formatDate(
                            dfd.getStartDate(), datePattern);
                    String endDateFormated = DateHelper.formatDate(end,
                            datePattern);
                    String dateRange = startDateFormated + ".."
                            + endDateFormated;
                    filter.addReqFilterValue(dateRange);
                } else {//if we don't need the filter
                    filter = null;
                }

                break;
            }
            case FieldMeta.LISTBOX: {
                filter.setConditiontype(ConditionSType.OR);
                ListboxFieldData lfd = (ListboxFieldData) filterData;
                long[] ids = lfd.getItemsSelected().getSelectedIDs();
                if(ids.length > 0) {
                    for(long id : ids) {
                        if(id == -1) {
                            filter.addReqFilterValue(StringHelper.NULL_VALUE);
                        } else {
                            filter.addReqFilterValue(String.valueOf(id));
                        }
                    }
                } else {
                    filter = null;
                }
                break;
            }
            case FieldMeta.MEMO: {
                MemoFieldData mfd = (MemoFieldData) filterData;
                if(!StringHelper.isEmpty(mfd.getText())) {
                    filter.addReqFilterValue(mfd.getText());
                } else {
                    filter = null;
                }
                break;
            }
            case FieldMeta.TEXTAREA: {
                TextareaFieldData tfd = (TextareaFieldData) filterData;
                if(!StringHelper.isEmpty(tfd.getText())) {
                    filter.addReqFilterValue(tfd.getText());
                } else {
                    filter = null;
                }
                break;
            }
            case FieldMeta.TEXTBOX: {
                TextboxFieldData tbfm = (TextboxFieldData) filterData;
                if(!StringHelper.isEmpty(tbfm.getText())) {
                    filter.addReqFilterValue(tbfm.getText());
                } else {
                    filter = null;
                }
                break;
            }
            case FieldMeta.ENTITYREFERENCE: {
                EntityReferenceData erd = (EntityReferenceData) filterData;
                Long rowID = erd.getSelectedRowID();
                if(rowID != null) {
                    filter.addReqFilterValue(String.valueOf(rowID));
                } else {
                    String selectedFilter = erd.getSelectedFilter();
                    if(!StringHelper.isEmpty(selectedFilter)) {
                        filter.setUsetextfield(true);//old core emulation
                        filter.addReqFilterValue(selectedFilter);
                        filter.addReqFilterText(selectedFilter);
                    } else {
                        filter = null;
                    }
                }
                break;
            }
        }
        return filter;
    }

    public static FieldData initializeEmptyFieldData(FieldData data,
                                                     FieldMeta fieldMeta,
                                                     Long recordId) {
        switch(fieldMeta.getDataType()) {
            case FieldMeta.CHECKBOX:
            case FieldMeta.DATEFIELD:
            case FieldMeta.LISTBOX:
            case FieldMeta.MULTISELECT:
            case FieldMeta.TEXTAREA:
            case FieldMeta.TEXTBOX:
            case FieldMeta.IN_FORM_GRID:
            case FieldMeta.ENTITYREFERENCE:
            case FieldMeta.ENTITYLINK:
            case FieldMeta.CHART:
                break;
            case FieldMeta.MEMO: {
                ((MemoFieldData) data).setRecordId(recordId);
                break;
            }
            case FieldMeta.HISTORY: {
                ((HistoryFieldData) data).setRecordId(recordId);
                break;
            }
            default: {
                throw new IllegalControlTypeException(
                        "Couldn't initialize the field with type ["
                                + fieldMeta.getDataType() +
                                "], because it doesn't supported now. ");
            }
        }
        return data;
    }

    /**
     * Create field data for the given type, where rowID value (record id) will be stored. This method is used when
     * client code needs to create data for the field which is set to be "external-field" for some form.
     * For now only 4 types of the external field is supported.
     *
     * @param fieldMeta         meta for field
     * @param rowID             external field id
     * @param listRefFieldValue value, that is string representation of the entity
     * @return empty field data
     * @throws IllegalControlTypeException if there is no such control presented in metadata
     */
    public static FieldData createExternalFieldDataForPkey(FieldMeta fieldMeta,
                                                           Long rowID,
                                                           String listRefFieldValue)
            throws IllegalControlTypeException {

        FieldData data;
        String fieldID = fieldMeta.getFieldID();
        switch(fieldMeta.getDataType()) {
            case FieldMeta.MEMO: {
                data = new MemoFieldData(fieldID, Long.toString(rowID), rowID);
                break;
            }
            case FieldMeta.TEXTAREA: {
                data = new TextareaFieldData(fieldID, Long.toString(rowID));
                break;
            }
            case FieldMeta.TEXTBOX: {
                data = new TextboxFieldData(fieldID, Long.toString(rowID));
                break;
            }
            case FieldMeta.ENTITYREFERENCE: {
                data = new EntityReferenceData(fieldID, listRefFieldValue,
                        rowID);
                break;
            }
            default: {
                throw new IllegalControlTypeException(
                        "Couldn't create external field data for the field with type ["
                                + fieldMeta.getDataType() +
                                "], because this field could not store Long value. ");
            }
        }
        return data;
    }

    /**
     * Retrieve rowId from field data.
     *
     * @param fieldMeta meta for field
     * @param data      field data
     * @return rowId row id
     * @throws IllegalControlTypeException if there is no such control presented
     *                                     in metadata or such control doesn't hold rowId.
     * @throws IncorrectFormDescriptionException
     *                                     thrown if data doesn't hold
     *                                     appropriate rowId value. This value should be filled automaticaly.
     */
    public static Long retrieveRowId(FieldMeta fieldMeta, FieldData data)
            throws IllegalControlTypeException,
            IncorrectFormDescriptionException {
        Long rowId;
        try {
            switch(fieldMeta.getDataType()) {
                case FieldMeta.MEMO: {
                    rowId = Long.parseLong(((MemoFieldData) data).getText());
                    break;
                }
                case FieldMeta.TEXTAREA: {
                    rowId = Long.parseLong(
                            ((TextareaFieldData) data).getText());
                    break;
                }
                case FieldMeta.TEXTBOX: {
                    rowId = Long.parseLong(((TextboxFieldData) data).getText());
                    break;
                }
                case FieldMeta.ENTITYREFERENCE: {
                    rowId = ((EntityReferenceData) data).getSelectedRowID();
                    break;
                }
                default: {
                    throw new IllegalControlTypeException(
                            "Couldn't retrieve rowId from field of type ["
                                    + fieldMeta.getDataType() +
                                    "], because this field could not store Long value. ");
                }
            }
        } catch (NumberFormatException e) {
            throw new IncorrectFormDescriptionException(
                    "Couldn't retrieve rowId from field of type ["
                            + fieldMeta.getDataType() +
                            "], because this field could not store Long value. ");
        }
        return rowId;
    }
}
