/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.program.api.vo.item;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.xml.bind.annotation.XmlTransient;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.fluidbpm.program.api.util.UtilGlobal;
import com.fluidbpm.program.api.vo.ABaseFluidJSONObject;
import com.fluidbpm.program.api.vo.attachment.Attachment;
import com.fluidbpm.program.api.vo.field.Field;
import com.fluidbpm.program.api.vo.form.Form;

/**
 * Represents an Electronic Form with all possible Meta-Data for an item
 * in {@code Flow} / Workflow.
 *
 * @author jasonbruwer
 * @since v1.0
 *
 * @see Form
 * @see Field
 * @see Attachment
 * @see FlowState
 * @see Properties
 */
public class FluidItem extends ABaseFluidJSONObject {

    public static final long serialVersionUID = 1L;

    private List<Field> userFields;
    private List<Field> routeFields;
    private List<Field> globalFields;

    private Form form;

    private List<Attachment> attachments;

    private FlowState flowState;

    private String flow;

    private Boolean inCaseOfCreateLinkToParent;

    private Form tableFieldParentForm;
    private String tableFieldNameOnParentForm;

    /**
     * The JSON mapping for the {@code FluidItem} object.
     */
    public static class JSONMapping
    {
        public static final String CUSTOM_PROPERTIES = "customProperties";

        public static final String USER_FIELDS = "userFields";
        public static final String ROUTE_FIELDS = "routeFields";
        public static final String GLOBAL_FIELDS = "globalFields";

        public static final String FORM = "form";

        public static final String ATTACHMENTS = "attachments";
        public static final String FLOW_STATE = "flowState";
        public static final String FLOW = "flow";
    }

    /**
     * The JSON mapping for the {@code FluidItem} object as a flat object.
     */
    public static class FlatFormJSONMapping
    {
        //Fluid Item...
        public static final String FLUID_ITEM_ID = "fluid_item_id";
        public static final String FLOW_STATE = "flow_state";

        //FIELDS
        //User...
        public static final String USER_FIELD_PREFIX = "user_field_";
        public static final String USER_FIELD_ID_PREFIX = "user_field_id_";

        //Route...
        public static final String ROUTE_FIELD_PREFIX = "route_field_";
        public static final String ROUTE_FIELD_ID_PREFIX = "route_field_id_";

        //Global...
        public static final String GLOBAL_FIELD_PREFIX = "global_field_";
        public static final String GLOBAL_FIELD_ID_PREFIX = "global_field_id_";
    }

    /**
     * Additional properties for {@code this} {@code FluidItem}.
     */
    public static class FluidItemProperty extends ABaseFluidJSONObject
    {
        private String name;
        private String value;

        /**
         * The JSON mapping for the {@code FluidItemProperty} object.
         */
        public static class JSONMapping
        {
            public static final String NAME = "name";
            public static final String VALUE = "value";
        }

        /**
         * Default constructor.
         */
        public FluidItemProperty() {
            super();
        }

        /**
         * Sets the Name and Value of the property.
         *
         * @param nameParam The Property Name.
         * @param valueParam The Property Value.
         */
        public FluidItemProperty(String nameParam, String valueParam) {

            this.setName(nameParam);
            this.setValue(valueParam);
        }

        /**
         * Populates local variables with {@code jsonObjectParam}.
         *
         * @param jsonObjectParam The JSON Object.
         */
        public FluidItemProperty(JSONObject jsonObjectParam) {
            super(jsonObjectParam);

            if(this.jsonObject == null)
            {
                return;
            }

            //Name...
            if (!this.jsonObject.isNull(JSONMapping.NAME)) {
                this.setName(this.jsonObject.getString(JSONMapping.NAME));
            }

            //Value...
            if (!this.jsonObject.isNull(JSONMapping.VALUE)) {
                this.setValue(this.jsonObject.getString(JSONMapping.VALUE));
            }
        }

        /**
         * Conversion to {@code JSONObject} from Java Object.
         *
         * @return {@code JSONObject} representation of {@code StepProperty}
         * @throws JSONException If there is a problem with the JSON Body.
         *
         * @see ABaseFluidJSONObject#toJsonObject()
         */
        @Override
        public JSONObject toJsonObject() throws JSONException
        {
            JSONObject returnVal = super.toJsonObject();

            //Name...
            if(this.getName() != null)
            {
                returnVal.put(JSONMapping.NAME, this.getName());
            }

            //Value...
            if(this.getValue() != null)
            {
                returnVal.put(JSONMapping.VALUE, this.getValue());
            }

            return returnVal;
        }

        /**
         * Gets the Name of the Property.
         *
         * @return Property Name.
         */
        public String getName() {
            return this.name;
        }

        /**
         * Sets the Name of the Property.
         *
         * @param nameParam Property Name.
         */
        public void setName(String nameParam) {
            this.name = nameParam;
        }

        /**
         * Gets the Value of the Property.
         *
         * @return Property Value.
         */
        public String getValue() {
            return this.value;
        }

        /**
         * Sets the Value of the Property.
         *
         * @param valueParam The value of the Fluid Item property.
         */
        public void setValue(String valueParam) {
            this.value = valueParam;
        }
    }

    /**
     * The {@code FlowState} that a {@code FluidItem}
     * is currently in in terms of the Workflow.
     *
     *

     *
Flow States for an {@code FluidItem}.

     *

     *         Flow State
     *         Description
     *

     *

     *         {@code NotInFlow}
     *         Item that is not currently in Flow. Item could have been in flow
     *         previously.
     *

     *

     *         {@code WorkInProgress}
     *         Item that is currently part of a {@code Flow} and needs attention.
     *

     *

     *         {@code UserSend}
     *         Electronic Form that is not currently part of a {@code Flow}, but shared with a
     *         colleague.
     *

     *

     *         {@code UserSend}
     *         Electronic Form that is not currently part of a {@code Flow}, but shared with a
     *         colleague.
     *

     *

     *         {@code UserSendWorkInProgress}
     *         Electronic Form that is currently part of a {@code Flow}, and shared with a
     *         colleague.
     *

     *

     *         {@code Archive}
     *         Electronic Form that has been completed and Archived for long term storage.
     *

     *

     */
    public enum FlowState {
        NotInFlow,
        WorkInProgress,
        UserSend,
        UserSendWorkInProgress,
        Archive;

        /**
         * Returns the {@code FlowState} based on {@code flowStateStringParam}.
         *
         * The {@code flowStateStringParam} value is not case sensitive.
         *
         * @param flowStateStringParam The name of the {@code enum} {@code FlowState}.
         * @return {@code FlowState}, if {@code enum} from {@code flowStateStringParam}
         * is not found, {@code null} will be returned.
         */
        public static FlowState valueOfSafe(String flowStateStringParam)
        {
            if(flowStateStringParam == null || flowStateStringParam.trim().isEmpty())
            {
                return null;
            }

            String paramLower = flowStateStringParam.trim().toLowerCase();

            for(FlowState flowState : FlowState.values())
            {
                if(paramLower.equals(flowState.name().toLowerCase()))
                {
                    return flowState;
                }
            }

            return null;
        }
    }

    /**
     * Default constructor.
     */
    public FluidItem() {
        super();
    }

    /**
     * Sets the form on object creation.
     *
     * @param formParam The form to set.
     *
     * @see Form
     */
    public FluidItem(Form formParam) {
        super();

        this.setForm(formParam);
    }

    /**
     * Populates local variables with {@code jsonObjectParam}.
     *
     * @param jsonObjectParam The JSON Object.
     */
    public FluidItem(JSONObject jsonObjectParam){
        super(jsonObjectParam);

        if(this.jsonObject == null)
        {
            return;
        }

        //Custom Properties...
        if (!this.jsonObject.isNull(JSONMapping.CUSTOM_PROPERTIES)) {
            JSONArray jsonPropArray = this.jsonObject.getJSONArray(JSONMapping.CUSTOM_PROPERTIES);

            List<FluidItemProperty> fluidItemProperties = new ArrayList();

            for(int index = 0;index < jsonPropArray.length();index++)
            {
                fluidItemProperties.add(
                        new FluidItemProperty(jsonPropArray.getJSONObject(index)));
            }
        }

        //User Fields...
        if (!this.jsonObject.isNull(JSONMapping.USER_FIELDS)) {

            JSONArray fieldsArr = this.jsonObject.getJSONArray(
                    JSONMapping.USER_FIELDS);

            List<Field> assUserFields = new ArrayList();
            for(int index = 0;index < fieldsArr.length();index++)
            {
                assUserFields.add(new Field(fieldsArr.getJSONObject(index)));
            }

            this.setUserFields(assUserFields);
        }

        //Route Fields...
        if (!this.jsonObject.isNull(JSONMapping.ROUTE_FIELDS)) {

            JSONArray fieldsArr = this.jsonObject.getJSONArray(
                    JSONMapping.ROUTE_FIELDS);

            List<Field> assRouteFields = new ArrayList();
            for(int index = 0;index < fieldsArr.length();index++)
            {
                assRouteFields.add(new Field(fieldsArr.getJSONObject(index)));
            }

            this.setRouteFields(assRouteFields);
        }

        //Global Fields...
        if (!this.jsonObject.isNull(JSONMapping.GLOBAL_FIELDS)) {

            JSONArray fieldsArr = this.jsonObject.getJSONArray(
                    JSONMapping.GLOBAL_FIELDS);

            List<Field> assGlobalFields = new ArrayList();
            for(int index = 0;index < fieldsArr.length();index++)
            {
                assGlobalFields.add(new Field(fieldsArr.getJSONObject(index)));
            }

            this.setGlobalFields(assGlobalFields);
        }

        //Attachments...
        if (!this.jsonObject.isNull(JSONMapping.ATTACHMENTS)) {

            JSONArray fieldsArr = this.jsonObject.getJSONArray(
                    JSONMapping.ATTACHMENTS);

            List<Attachment> assAttachments = new ArrayList();
            for(int index = 0;index < fieldsArr.length();index++)
            {
                assAttachments.add(new Attachment(fieldsArr.getJSONObject(index)));
            }

            this.setAttachments(assAttachments);
        }

        //Form...
        if (!this.jsonObject.isNull(JSONMapping.FORM)) {
            this.setForm(new Form(this.jsonObject.getJSONObject(JSONMapping.FORM)));
        }

        //Flow...
        if (!this.jsonObject.isNull(JSONMapping.FLOW)) {
            this.setFlow(this.jsonObject.getString(JSONMapping.FLOW));
        }

        //Flow State...
        if (!this.jsonObject.isNull(JSONMapping.FLOW_STATE)) {
            this.setFlowStateString(this.jsonObject.getString(JSONMapping.FLOW_STATE));
        }
    }

    /**
     * Conversion to {@code JSONObject} from Java Object.
     *
     * @return {@code JSONObject} representation of {@code FluidItem}
     * @throws JSONException If there is a problem with the JSON Body.
     *
     * @see ABaseFluidJSONObject#toJsonObject()
     */
    @Override
    public JSONObject toJsonObject() throws JSONException
    {
        JSONObject returnVal = super.toJsonObject();

        //Flow...
        if(this.getFlow() != null)
        {
            returnVal.put(JSONMapping.FLOW,this.getFlow());
        }

        //Form...
        if(this.getForm() != null)
        {
            returnVal.put(JSONMapping.FORM,this.getForm().toJsonObject());
        }

        //User Fields...
        if(this.getUserFields() != null && !this.getUserFields().isEmpty())
        {
            JSONArray fieldsArr = new JSONArray();
            for(Field toAdd :this.getUserFields())
            {
                fieldsArr.put(toAdd.toJsonObject());
            }

            returnVal.put(JSONMapping.USER_FIELDS, fieldsArr);
        }

        //Route Fields...
        if(this.getRouteFields() != null && !this.getRouteFields().isEmpty())
        {
            JSONArray fieldsArr = new JSONArray();
            for(Field toAdd :this.getRouteFields())
            {
                fieldsArr.put(toAdd.toJsonObject());
            }

            returnVal.put(JSONMapping.ROUTE_FIELDS, fieldsArr);
        }

        //Global Fields...
        if(this.getGlobalFields() != null && !this.getGlobalFields().isEmpty())
        {
            JSONArray fieldsArr = new JSONArray();
            for(Field toAdd :this.getGlobalFields())
            {
                fieldsArr.put(toAdd.toJsonObject());
            }

            returnVal.put(JSONMapping.GLOBAL_FIELDS, fieldsArr);
        }

        //Attachments...
        if(this.getAttachments() != null)
        {
            JSONArray jsonArray = new JSONArray();
            for(Attachment toAdd : this.getAttachments())
            {
                jsonArray.put(toAdd.toJsonObject());
            }

            returnVal.put(JSONMapping.ATTACHMENTS, jsonArray);
        }

        //Flow State...
        if(this.getFlowState() != null)
        {
            returnVal.put(JSONMapping.FLOW_STATE, this.getFlowState().toString());
        }

        return returnVal;
    }

    /**
     * Serialize {@code this} object into a JSONObject.
     *
     * Any fields provided with a Java {@code null} value will be stored
     * as {@code JSONObject.Null}. {@code Field.Type.Table} fields are not supported and will be skipped.
     *
     * @return Flat {@code JSON} object (No inner fields).
     *
     * @see JSONObject
     */
    @XmlTransient
    public JSONObject convertToFlatJSONObject()
    {
        JSONObject returnVal = new JSONObject();

        //Id...
        returnVal.put(
                FlatFormJSONMapping.FLUID_ITEM_ID,
                this.getId() == null ?
                        JSONObject.NULL : this.getId());

        //Flow State...
        returnVal.put(
                FlatFormJSONMapping.FLOW_STATE,
                (this.getFlowState() == null) ?
                        JSONObject.NULL : this.getFlowState().name());

        //Populate the Form...
        JSONObject formJSONObjFlat =
                (this.getForm() == null) ? null :
                        this.getForm().convertToFlatJSONObject();

        if(formJSONObjFlat != null)
        {
            formJSONObjFlat.keySet().forEach(
                (toAdd) ->
                {
                    returnVal.put(toAdd, formJSONObjFlat.get(toAdd));
                }
            );
        }

        //Fields...
        UtilGlobal utilGlobal = new UtilGlobal();

        //Route Fields...
        if(this.getRouteFields() != null)
        {
            this.getRouteFields().forEach(
                    (routeFieldItem) ->
                    {
                        utilGlobal.setFlatFieldOnJSONObj(
                                FlatFormJSONMapping.ROUTE_FIELD_PREFIX,
                                FlatFormJSONMapping.ROUTE_FIELD_ID_PREFIX,
                                routeFieldItem,
                                returnVal
                        );
                    }
            );
        }

        //User Fields...
        if(this.getUserFields() != null)
        {
            this.getUserFields().forEach(
                    (userFieldItem) ->
                    {
                        utilGlobal.setFlatFieldOnJSONObj(
                                FlatFormJSONMapping.USER_FIELD_PREFIX,
                                FlatFormJSONMapping.USER_FIELD_ID_PREFIX,
                                userFieldItem,
                                returnVal
                        );
                    }
            );
        }

        //Global Fields...
        if(this.getGlobalFields() != null)
        {
            this.getGlobalFields().forEach(
                    (globalFieldItem) ->
                    {
                        utilGlobal.setFlatFieldOnJSONObj(
                                FlatFormJSONMapping.GLOBAL_FIELD_PREFIX,
                                FlatFormJSONMapping.GLOBAL_FIELD_ID_PREFIX,
                                globalFieldItem,
                                returnVal
                        );
                    }
            );
        }

        return returnVal;
    }

    /**
     *

     *     Returns the value of the {@code fieldNameParam} requested.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     A {@code null} will be returned if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getUserFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the User Field as in Fluid.
     * @return The value for the User Field as {@code String}.
     *
     * @see Field.Type#Text
     */
    public String getUserFieldValueAsString(String fieldNameParam)
    {
        Object obj = this.getFieldValueForField(
                fieldNameParam, this.getUserFields());

        if(obj == null)
        {
            return null;
        }

        return obj.toString();
    }

    /**
     *

     *     Returns the value of the {@code fieldNameParam} requested.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     A {@code null} will be returned if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getUserFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the User Field as in Fluid.
     * @return The value for the User Field as {@code Object}.
     *
     * @see Field.Type
     */
    public Object getUserFieldValue(String fieldNameParam)
    {
        return this.getFieldValueForField(fieldNameParam, this.getUserFields());
    }

    /**
     *

     *     Returns the value of the {@code fieldNameParam} requested.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     A {@code null} will be returned if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getUserFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the Route Field as in Fluid.
     * @return The value for the Route Field as {@code Object}.
     *
     * @see Field.Type
     */
    public Object getRouteFieldValue(String fieldNameParam) {
        return this.getFieldValueForField(fieldNameParam, this.getRouteFields());
    }

    /**
     *

     *     Returns the value of the {@code fieldNameParam} requested.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     A {@code null} will be returned if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getUserFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the Global Field as in Fluid.
     * @return The value for the Global Field as {@code Object}.
     *
     * @see Field.Type
     */
    public Object getGlobalFieldValue(String fieldNameParam) {
        return this.getFieldValueForField(fieldNameParam,this.getGlobalFields());
    }

    /**
     *

     *     Returns the value of the {@code fieldNameParam} requested.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     A {@code null} will be returned if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getUserFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the Route Field as in Fluid.
     * @return The value for the Route Field as {@code String}.
     *
     * @see Field.Type#Text
     */
    public String getRouteFieldValueAsString(String fieldNameParam) {
        Object obj = this.getFieldValueForField(
                fieldNameParam,this.getRouteFields());

        if(obj == null) {
            return null;
        }

        return obj.toString();
    }

    /**
     *

     *     Returns the value of the {@code fieldNameParam} requested.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     A {@code null} will be returned if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getUserFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the Route Field as in Fluid.
     * @return The value for the Route Field as {@code Double}.
     *
     * @see Field.Type#Decimal
     */
    public Double getRouteFieldValueAsDouble(String fieldNameParam) {
        Object obj = this.getRouteFieldValue(fieldNameParam);

        if(obj == null) {
            return null;
        }

        if(obj instanceof Double) {
            return ((Double)obj);
        }

        if(obj instanceof Number) {
            return ((Number)obj).doubleValue();
        }

        return null;
    }

    /**
     *

     *     Returns the value of the {@code fieldNameParam} requested.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     A {@code null} will be returned if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getUserFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the Route Field as in Fluid.
     * @return The value for the Route Field as {@code Boolean}.
     *
     * @see Field.Type#TrueFalse
     */
    public Boolean getRouteFieldValueAsBoolean(String fieldNameParam) {
        Object obj = this.getRouteFieldValue(fieldNameParam);

        if(obj == null)
        {
            return null;
        }

        if(obj instanceof Boolean)
        {
            return ((Boolean)obj);
        }

        return null;
    }

    /**
     *

     *     Returns the value of the {@code fieldNameParam} requested.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     A {@code null} will be returned if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getUserFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the Global Field as in Fluid.
     * @return The value for the Global Field as {@code String}.
     *
     * @see Field.Type#Text
     */
    public String getGlobalFieldValueAsString(String fieldNameParam)
    {
        Object obj = this.getFieldValueForField(
                fieldNameParam, this.getGlobalFields());

        if(obj == null)
        {
            return null;
        }

        return obj.toString();
    }

    /**
     *

     *     Returns the value of the {@code fieldNameParam} requested.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     A {@code null} will be returned if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getUserFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the Global Field as in Fluid.
     * @return The value for the Global Field as {@code Double}.
     *
     * @see Field.Type#Decimal
     */
    public Double getGlobalFieldValueAsDouble(String fieldNameParam) {
        Object obj = this.getFieldValueForField(fieldNameParam, this.getGlobalFields());

        if(obj == null) {
            return null;
        }

        if(obj instanceof Number) {
            return ((Number)obj).doubleValue();
        }

        if(obj instanceof Number) {
            return ((Number)obj).doubleValue();
        }

        return null;
    }

    /**
     *

     *     Returns the value of the {@code fieldNameParam} requested.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     A {@code null} will be returned if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getUserFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the Global Field as in Fluid.
     * @return The value for the Global Field as {@code Integer}.
     *
     * @see Field.Type#Decimal
     */
    public Integer getGlobalFieldValueAsInt(String fieldNameParam)
    {
        Object obj = this.getFieldValueForField(fieldNameParam,this.getGlobalFields());

        if(obj == null)
        {
            return null;
        }

        if(obj instanceof Number)
        {
            return ((Number)obj).intValue();
        }

        return null;
    }

    /**
     *

     *     Sets the value of the {@code fieldNameParam} requested.
     *

     *     If there is an existing value, the value will be override with
     *     the value of {@code fieldValueParam}.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     The value won't be set if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getFormFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the Global Field as in Fluid.
     * @param fieldValueParam The value of the {@code Field}.
     *
     * @see Field.Type
     */
    public void setGlobalFieldValue(String fieldNameParam, Object fieldValueParam)
    {
        if(fieldNameParam == null || fieldNameParam.trim().length() == 0)
        {
            return;
        }

        if(this.getGlobalFields() == null || this.getGlobalFields().isEmpty())
        {
            this.setGlobalFields(new ArrayList());
        }

        String fieldNameParamLower = fieldNameParam.toLowerCase();

        boolean fieldFound = false;

        for(Field field : this.getGlobalFields())
        {
            if(field.getFieldName() == null || field.getFieldName().trim().length() == 0)
            {
                continue;
            }

            String fieldNameLower = field.getFieldName().toLowerCase();
            if(fieldNameParamLower.equals(fieldNameLower))
            {
                field.setFieldValue(fieldValueParam);
                fieldFound = true;
                break;
            }
        }

        //When the Field is not added...
        if(!fieldFound)
        {
            this.getGlobalFields().add(new Field(fieldNameParam,fieldValueParam));
        }
    }

    /**
     *

     *     Sets the value of the {@code fieldNameParam} requested.
     *

     *     If there is an existing value, the value will be override with
     *     the value of {@code fieldValueParam}.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     The value won't be set if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getRouteFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the Route Field as in Fluid.
     * @param fieldValueParam The value of the {@code Field}.
     * @param typeParam The {@code Field.Type} of {@code Field}.
     *
     * @see Field.Type
     */
    public void setRouteFieldValue(String fieldNameParam, Object fieldValueParam, Field.Type typeParam) {
        if (fieldNameParam == null) {
            return;
        }

        if (this.routeFields == null) {
            this.routeFields = new ArrayList<Field>();
        }

        String paramLower = fieldNameParam.toLowerCase().trim();

        boolean valueFound = false;

        //Iterate the Route Fields...
        for (Field existingField : this.routeFields) {
            String toCheckNameLower = existingField.getFieldName();
            if (toCheckNameLower == null || toCheckNameLower.trim().isEmpty()) {
                continue;
            }

            toCheckNameLower = toCheckNameLower.trim().toLowerCase();
            if (paramLower.equals(toCheckNameLower)) {
                valueFound = true;

                existingField.setTypeAsEnum(typeParam);
                existingField.setFieldValue(fieldValueParam);
                break;
            }
        }

        if (!valueFound) {
            this.routeFields.add(new Field(fieldNameParam, fieldValueParam, typeParam));
        }
    }

    /**
     *

     *     Sets the value of the {@code fieldNameParam} requested.
     *

     *     If there is an existing value, the value will be override with
     *     the value of {@code fieldValueParam}.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     The value won't be set if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getFormFields()} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the Global Field as in Fluid.
     * @param fieldValueParam The value of the {@code Field}.
     * @param typeParam The {@code Field.Type} of {@code Field}.
     *
     * @see Field.Type
     */
    public void setGlobalFieldValue(String fieldNameParam, Object fieldValueParam, Field.Type typeParam) {
        if (fieldNameParam == null) {
            return;
        }

        if (this.getGlobalFields() == null || this.getGlobalFields().isEmpty()) {
            this.setGlobalFields(new ArrayList());
        }

        String paramLower = fieldNameParam.toLowerCase().trim();

        boolean valueFound = false;

        //Iterate the Form Fields...
        int fieldIndex = -1;
        for (Field field : this.getGlobalFields()) {
            fieldIndex++;

            String toCheckNameLower = field.getFieldName();
            if (toCheckNameLower == null || toCheckNameLower.trim().isEmpty()) {
                continue;
            }

            toCheckNameLower = toCheckNameLower.trim().toLowerCase();

            if (paramLower.equals(toCheckNameLower)) {
                valueFound = true;
                this.getGlobalFields().get(fieldIndex).setFieldValue(fieldValueParam);
                this.getGlobalFields().get(fieldIndex).setTypeAsEnum(typeParam);
                break;
            }
        }

        if (!valueFound) {
            this.getGlobalFields().add(new Field(fieldNameParam, fieldValueParam, typeParam));
        }
    }

    /**
     *

     *     Returns the value of the {@code fieldNameParam} requested.
     *
     *

     *     The {@code fieldNameParam} is not case sensitive.
     *
     *

     *     A {@code null} will be returned if;
     *

     *
{@code fieldNameParam} is {@code null} or empty.

     *
{@code getUserFields()} is {@code null} or empty.

     *
{@code toGetPropertyFromParam} is {@code null} or empty.

     *
Field is not found by {@code fieldNameParam}.

     *

     *
     * @param fieldNameParam The name of the Global Field as in Fluid.
     * @param toGetPropertyFromParam The {@code List<Field>} where the value
     *                               will be retrieved from.
     *
     * @return The value for the Field as {@code Object}.
     *
     * @see Field
     */
    public Object getFieldValueForField(
            String fieldNameParam, List<Field> toGetPropertyFromParam) {

        if (fieldNameParam == null || fieldNameParam.trim().isEmpty()) {
            return null;
        }

        if (toGetPropertyFromParam == null || toGetPropertyFromParam.isEmpty()) {
            return null;
        }

        String fieldNameParamLower = fieldNameParam.trim().toLowerCase();

        for (Field field : toGetPropertyFromParam) {

            String fieldName = field.getFieldName();

            if (fieldName == null || fieldName.trim().isEmpty()) {
                continue;
            }

            String fieldNameLower = fieldName.trim().toLowerCase();

            if (fieldNameParamLower.equals(fieldNameLower)) {
                return field.getFieldValue();
            }
        }

        return null;
    }

    /**
     * Gets the attachments.
     *
     * @return {@code List<Attachment>} of attachments.
     *
     * @see Attachment
     */
    public List<Attachment> getAttachments() {
        return this.attachments;
    }

    /**
     * Sets the attachments.
     *
     * @param attachmentsParam {@code List<Attachment>} of attachments.
     */
    public void setAttachments(List<Attachment> attachmentsParam) {
        this.attachments = attachmentsParam;
    }

    /**
     * Adds {@code toAddParam} to the list of {@code Attachment}s.
     *
     * @param toAddParam {@code Attachment} to add.
     *
     * @see Attachment
     */
    public void addAttachment(Attachment toAddParam) {
        if (!this.containsAttachments()) {
            this.setAttachments(new ArrayList());
        }

        this.getAttachments().add(toAddParam);
    }

    /**
     * Gets all the {@code User} {@code Field}s.
     *
     * @return All the User Fields.
     *
     * @see Field
     */
    public List<Field> getUserFields() {
        return this.userFields;
    }

    /**
     * Sets all the {@code User}{@code Field}s.
     *
     * @param userFieldsParam The new {@code User}{@code Field}s.
     *
     * @see Field
     */
    public void setUserFields(List<Field> userFieldsParam) {
        this.userFields = userFieldsParam;
    }

    /**
     * Gets all the {@code Route} {@code Field}s.
     *
     * @return All the Route Fields.
     *
     * @see Field
     */
    public List<Field> getRouteFields() {
        return this.routeFields;
    }

    /**
     * Sets all the {@code Route}{@code Field}s.
     *
     * @param routeFieldsParam The new {@code Route}{@code Field}s.
     *
     * @see Field
     */
    public void setRouteFields(List<Field> routeFieldsParam) {
        this.routeFields = routeFieldsParam;
    }

    /**
     * Sets all the {@code Route}{@code Field}s.
     *
     * @param routeFieldsParam The new {@code Route}{@code Field}s.
     *
     * @see Field
     */
    @XmlTransient
    public void setRouteFieldsArrayList(ArrayList<Field> routeFieldsParam) {
        this.routeFields = routeFieldsParam;
    }

    /**
     * Gets all the {@code Global} {@code Field}s.
     *
     * @return All the Global Fields.
     *
     * @see Field
     */
    public List<Field> getGlobalFields() {
        return this.globalFields;
    }

    /**
     * Sets all the {@code Global}{@code Field}s.
     *
     * @param globalFieldsParam The new {@code Global}{@code Field}s.
     *
     * @see Field
     */
    public void setGlobalFields(List<Field> globalFieldsParam) {
        this.globalFields = globalFieldsParam;
    }

    /**
     * Gets the Form for {@code this}
     *
     * @return {@code Form} associated with the {@code FluidItem}.
     */
    public Form getForm() {
        return this.form;
    }

    /**
     * Sets the Form for {@code this}
     *
     * @param formParam {@code Form} associated with the {@code FluidItem}.
     */
    public void setForm(Form formParam) {
        this.form = formParam;
    }

    /**
     * Gets the {@code FlowState}.
     *
     * @return {@code FlowState} for {@code this} {@code FluidItem}
     *
     * @see FlowState
     */
    public FlowState getFlowState() {
        return this.flowState;
    }

    /**
     * Sets the {@code FlowState}.
     *
     * @param flowStateParam {@code FlowState} for {@code this} {@code FluidItem}
     *
     * @see FlowState
     */
    public void setFlowState(FlowState flowStateParam) {
        this.flowState = flowStateParam;
    }

    /**
     * Sets the {@code FlowState}.
     *
     * @param flowStateParam {@code FlowState} for {@code this} {@code FluidItem}
     *
     * @see FlowState
     */
    @XmlTransient
    public void setFlowStateString(String flowStateParam) {
        this.flowState = FlowState.valueOfSafe(flowStateParam);
    }

    /**
     * Checks whether the {@code getAttachments()} is empty.
     *
     * @return If the list of attachments is empty.
     */
    public boolean containsAttachments() {
        return (this.attachments != null && !this.attachments.isEmpty());
    }

    /**
     * Gets the {@code Flow} the {@code FluidItem} is associated with.
     *
     * @return Name of the {@code Flow}.
     *
     * @see com.fluidbpm.program.api.vo.flow.Flow
     */
    public String getFlow() {
        return this.flow;
    }

    /**
     * Sets the {@code Flow} the {@code FluidItem} is associated with.
     *
     * @param flowParam Name of the {@code Flow}.
     *
     * @see com.fluidbpm.program.api.vo.flow.Flow
     */
    public void setFlow(String flowParam) {
        this.flow = flowParam;
    }

    /**
     * If {@code this} is a newly created {@code FluidItem} as part of the
     * {@code Flow} {@code ICustomProgram}. This will be used as a "flag" to
     * make {@code this} {@code FluidItem} a descendant for the "parent"
     * {@code FluidItem}.
     *
     * @return Whether {@code this} {@code FluidItem} should be linked to the "parent"
     *
     * @see Form
     * @see com.fluidbpm.program.api.ICustomProgram
     */
    public Boolean getInCaseOfCreateLinkToParent() {
        return this.inCaseOfCreateLinkToParent;
    }

    /**
     * If {@code this} is a newly created {@code FluidItem} as part of the
     * {@code Flow} {@code ICustomProgram}. This will be used as a "flag" to
     * make {@code this} {@code FluidItem} a descendant for the "parent"
     * {@code FluidItem}.
     *
     * @param inCaseOfCreateLinkToParentParam Whether {@code this} {@code FluidItem} should be linked to the "parent"
     *
     * @see Form
     * @see com.fluidbpm.program.api.ICustomProgram
     */
    public void setInCaseOfCreateLinkToParent(Boolean inCaseOfCreateLinkToParentParam) {
        this.inCaseOfCreateLinkToParent = inCaseOfCreateLinkToParentParam;
    }

    /**
     * If {@code this} {@code FluidItem.getForm} is a {@code Field.Type.Table} {@code Field},
     * the parent {@code Form} / Electronic Form needs to be set to indicate which {@code Form} is
     * the container {@code Form}.
     *
     * @return The Table containing {@code Form}.
     *
     * @see Field.Type#Table
     * @see Form
     */
    public Form getTableFieldParentForm() {
        return this.tableFieldParentForm;
    }

    /**
     * If {@code this} {@code FluidItem.getForm} is a {@code Field.Type.Table} {@code Field},
     * the parent {@code Form} / Electronic Form needs to be set to indicate which {@code Form} is
     * the container {@code Form}.
     *
     * @param tableFieldParentFormParam The Table containing {@code Form}.
     *
     * @see Field.Type#Table
     * @see Form
     */
    public void setTableFieldParentForm(Form tableFieldParentFormParam) {
        this.tableFieldParentForm = tableFieldParentFormParam;
    }

    /**
     * If {@code this} {@code TableFieldParentForm} is set.
     * The name of the {@code Field.Type.Table} {@code Field} also needs to be
     * provided.
     *
     * Sets the name of the Table Field.
     *
     * @return The Table containing {@code Form}.
     *
     * @see Field.Type#Table
     * @see Form
     */
    public String getTableFieldNameOnParentForm() {
        return this.tableFieldNameOnParentForm;
    }

    /**
     * If {@code this} {@code TableFieldParentForm} is set.
     * The name of the {@code Field.Type.Table} {@code Field} also needs to be
     * provided.
     *
     * Sets the name of the Table Field.
     *
     * @param tableFieldNameParam The Table containing {@code Form}.
     *
     * @see Field.Type#Table
     * @see Form
     */
    public void setTableFieldNameOnParentForm(String tableFieldNameParam) {
        this.tableFieldNameOnParentForm = tableFieldNameParam;
    }

    /**
     * Compares {@code objParam} against {@code this} to see
     * if they are equal.
     *
     * @param objParam The object to compare against.
     * @return Whether {@code objParam} is equal.
     */
    @Override
    @XmlTransient
    public boolean equals(Object objParam) {
        if(objParam == null || this.getId() == null) {
            return false;
        }

        if(objParam instanceof FluidItem) {
            FluidItem paramCasted = (FluidItem)objParam;
            if(paramCasted.getId() == null) {
                return false;
            }

            return (this.getId().equals(paramCasted.getId()));
        }

        return false;
    }
}


/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.program.api.vo.role;

import org.json.JSONException;
import org.json.JSONObject;

import com.fluidbpm.program.api.vo.ABaseFluidJSONObject;
import com.fluidbpm.program.api.vo.form.Form;

/**
 * <p>
 *     Represents what a {@code Role} permits a {@code User} to do
 *     with a {@code Form} Definition type.
 * </p>
 *
 * @author jasonbruwer
 * @since v1.1
 *
 * @see Form
 * @see Role
 */
public class FormFieldToFormDefinition extends ABaseFluidJSONObject {

    public static final long serialVersionUID = 1L;

    private Form formDefinition;
    private Boolean canCreate;

    /**
     * The JSON mapping for the {@code RoleToFormDefinition} object.
     */
    public static class JSONMapping
    {
        public static final String FORM_DEFINITION = "formDefinition";
        public static final String CAN_CREATE = "canCreate";
    }

    /**
     * Default constructor.
     */
    public FormFieldToFormDefinition() {
        super();
    }

    /**
     * Sets the Id associated with a 'Form Field To Form Definition'.
     *
     * @param formFieldToFormDefinitionIdParam Field Id.
     */
    public FormFieldToFormDefinition(Long formFieldToFormDefinitionIdParam) {
        super();

        this.setId(formFieldToFormDefinitionIdParam);
    }

    /**
     * Populates local variables with {@code jsonObjectParam}.
     *
     * @param jsonObjectParam The JSON Object.
     */
    public FormFieldToFormDefinition(JSONObject jsonObjectParam){
        super(jsonObjectParam);

        if(this.jsonObject == null)
        {
            return;
        }

        //Form Definition...
        if (!this.jsonObject.isNull(JSONMapping.FORM_DEFINITION)) {
            this.setFormDefinition(new Form(this.jsonObject.getJSONObject(
                    JSONMapping.FORM_DEFINITION)));
        }

        //Can Create...
        if (!this.jsonObject.isNull(JSONMapping.CAN_CREATE)) {
            this.setCanCreate(this.jsonObject.getBoolean(JSONMapping.CAN_CREATE));
        }
    }

    /**
     * Gets the {@code Form} Definition associated with the
     * {@code Role} to Form Definition permission.
     *
     * @return {@code Form} Definition.
     */
    public Form getFormDefinition() {
        return this.formDefinition;
    }

    /**
     * Sets the {@code Form} Definition associated with the
     * {@code Role} to Form Definition permission.
     *
     * @param formDefinitionParam {@code Form} Definition.
     */
    public void setFormDefinition(Form formDefinitionParam) {
        this.formDefinition = formDefinitionParam;
    }

    /**
     * Gets whether the {@code Role} allow for {@code Form} creation.
     *
     * @return Does the {@code Role} allow for {@code Form} creation.
     */
    public Boolean isCanCreate() {
        return this.canCreate;
    }

    /**
     * Sets whether the {@code Role} allow for {@code Form} creation.
     *
     * @param canCreateParam Does the {@code Role} allow for {@code Form} creation.
     */
    public void setCanCreate(Boolean canCreateParam) {
        this.canCreate = canCreateParam;
    }

    /**
     * Conversion to {@code JSONObject} from Java Object.
     *
     * @return {@code JSONObject} representation of {@code RoleToFormDefinition}
     * @throws JSONException If there is a problem with the JSON Body.
     *
     * @see ABaseFluidJSONObject#toJsonObject()
     */
    @Override
    public JSONObject toJsonObject() throws JSONException {

        JSONObject returnVal = super.toJsonObject();

        //Can Create...
        if(this.isCanCreate() != null)
        {
            returnVal.put(JSONMapping.CAN_CREATE,
                    this.isCanCreate().booleanValue());
        }

        //Form Definition...
        if(this.getFormDefinition() != null)
        {
            returnVal.put(JSONMapping.FORM_DEFINITION,
                    this.getFormDefinition().toJsonObject());
        }

        return returnVal;
    }
}


/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.program.api.vo.auth0;

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

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.fluidbpm.program.api.vo.ABaseFluidJSONObject;
import com.fluidbpm.program.api.vo.user.User;

/**
 * Fluid consolidation of a workflow.
 *
 * @author jasonbruwer
 * @since v1.0
 *
 * @see User
 */
public class NormalizedUserProfile extends ABaseFluidJSONObject {

    public static final long serialVersionUID = 1L;

    private String userId;
    private String name;
    private String email;
    private boolean emailVerified;
    private String nickname;
    private String picture;
    private String givenName;
    private String familyName;
    private String locale;

    private List<Identity> identities;

    /**
     * The JSON mapping for the {@code NormalizedUserProfile} object.
     */
    public static class JSONMapping
    {
        public static final String USER_ID = "user_id";
        public static final String NAME = "name";
        public static final String EMAIL = "email";
        public static final String EMAIL_VERIFIED = "email_verified";
        public static final String NICKNAME = "nickname";
        public static final String PICTURE = "picture";
        public static final String GIVEN_NAME = "given_name";
        public static final String FAMILY_NAME = "family_name";

        public static final String IDENTITIES = "identities";//Array...
        //Done with identities...

        public static final String LOCALE = "locale";
    }

    /**
     * The Header mapping for the {@code NormalizedUserProfile} object.
     */
    public static class HeaderMapping
    {
        public static final String AUTHORIZATION = "Authorization";
    }

    /**
     * Identity related value object.
     *
     * @see ABaseFluidJSONObject
     */
    public static class Identity extends ABaseFluidJSONObject
    {
        private String accessToken;
        private String provider;
        private String userId;
        private String connection;
        private boolean isSocial;

        /**
         * The JSON mapping for the {@code Identity} object.
         */
        public static class JSONMapping
        {
            public static final String USER_ID = "user_id";
            public static final String ACCESS_TOKEN = "access_token";
            public static final String PROVIDER = "provider";
            public static final String CONNECTION = "connection";
            public static final String IS_SOCIAL = "isSocial";
        }

        /**
         * Default constructor.
         */
        public Identity() {
            super();
        }

        /**
         * Populates local variables with {@code jsonObjectParam}.
         *
         * @param jsonObjectParam The JSON Object.
         */
        public Identity(JSONObject jsonObjectParam) {
            super(jsonObjectParam);

            if(this.jsonObject == null)
            {
                return;
            }

            //Access Token...
            if (!this.jsonObject.isNull(JSONMapping.ACCESS_TOKEN)) {
                this.setAccessToken(this.jsonObject.getString(
                        JSONMapping.ACCESS_TOKEN));
            }

            //Provider...
            if (!this.jsonObject.isNull(JSONMapping.PROVIDER)) {
                this.setProvider(this.jsonObject.getString(
                        JSONMapping.PROVIDER));
            }

            //User Id...
            if (!this.jsonObject.isNull(NormalizedUserProfile.JSONMapping.USER_ID)) {
                this.setUserId(this.jsonObject.getString(
                        JSONMapping.USER_ID));
            }

            //Connection...
            if (!this.jsonObject.isNull(JSONMapping.CONNECTION)) {
                this.setConnection(this.jsonObject.getString(
                        JSONMapping.CONNECTION));
            }

            //Is Social...
            if (!this.jsonObject.isNull(JSONMapping.IS_SOCIAL)) {
                this.setSocial(this.jsonObject.getBoolean(
                        JSONMapping.IS_SOCIAL));
            }
        }

        /**
         * Gets the Access Token.
         *
         * @return Access Token.
         */
        public String getAccessToken() {
            return this.accessToken;
        }

        /**
         * Sets the Access Token.
         *
         * @param accessTokenParam Access Token.
         */
        public void setAccessToken(String accessTokenParam) {
            this.accessToken = accessTokenParam;
        }

        /**
         * Gets the provider.
         *
         * @return Authentication provider.
         */
        public String getProvider() {
            return this.provider;
        }

        /**
         * Sets the provider.
         *
         * @param providerParam Authentication provider.
         */
        public void setProvider(String providerParam) {
            this.provider = providerParam;
        }

        /**
         * Gets the User Unique Id.
         *
         * @return User Id.
         */
        public String getUserId() {
            return this.userId;
        }

        /**
         * Sets the User Unique Id.
         *
         * @param userIdParam User Id.
         */
        public void setUserId(String userIdParam) {
            this.userId = userIdParam;
        }

        /**
         * Gets the Connection.
         *
         * @return The Connection.
         */
        public String getConnection() {
            return this.connection;
        }

        /**
         * Sets the Connection.
         *
         * @param connectionParam The Connection.
         */
        public void setConnection(String connectionParam) {
            this.connection = connectionParam;
        }

        /**
         * Is the authenticated server a social one.
         *
         * @return Whether the connection is a social network connection.
         */
        public boolean isSocial() {
            return this.isSocial;
        }

        /**
         * Sets whether the connection is social.
         *
         * @param socialParam Whether the connection is sociall
         */
        public void setSocial(boolean socialParam) {
            this.isSocial = socialParam;
        }

        /**
         * Conversion to {@code JSONObject} from Java Object.
         *
         * @return {@code JSONObject} representation of {@code Identity}
         * @throws JSONException If there is a problem with the JSON Body.
         *
         * @see ABaseFluidJSONObject#toJsonObject()
         */
        @Override
        public JSONObject toJsonObject() throws JSONException {

            JSONObject returnVal = super.toJsonObject();

            //Access Token...
            if(this.getAccessToken() != null)
            {
                returnVal.put(JSONMapping.ACCESS_TOKEN, this.getAccessToken());
            }

            //Provider...
            if(this.getProvider() != null)
            {
                returnVal.put(JSONMapping.PROVIDER, this.getProvider());
            }

            //User Id...
            if(this.getUserId() != null)
            {
                returnVal.put(JSONMapping.USER_ID, this.getUserId());
            }

            //Connection...
            if(this.getConnection() != null)
            {
                returnVal.put(JSONMapping.CONNECTION, this.getConnection());
            }

            //Is Social...
            returnVal.put(JSONMapping.IS_SOCIAL, this.isSocial());

            return returnVal;
        }
    }

    /**
     * Default constructor.
     */
    public NormalizedUserProfile() {
        super();
    }

    /**
     * Populates local variables with {@code jsonObjectParam}.
     *
     * @param jsonObjectParam The JSON Object.
     */
    public NormalizedUserProfile(JSONObject jsonObjectParam) {
        super(jsonObjectParam);

        if(this.jsonObject == null)
        {
            return;
        }

        //User Id...
        if (!this.jsonObject.isNull(JSONMapping.USER_ID)) {
            this.setUserId(this.jsonObject.getString(JSONMapping.USER_ID));
        }

        //Name...
        if (!this.jsonObject.isNull(JSONMapping.NAME)) {
            this.setName(this.jsonObject.getString(JSONMapping.NAME));
        }

        //Email...
        if (!this.jsonObject.isNull(JSONMapping.EMAIL)) {
            this.setEmail(this.jsonObject.getString(JSONMapping.EMAIL));
        }

        //Email Verified...
        if (!this.jsonObject.isNull(JSONMapping.EMAIL_VERIFIED)) {
            this.setEmailVerified(this.jsonObject.getBoolean(JSONMapping.EMAIL_VERIFIED));
        }

        //Nickname...
        if (!this.jsonObject.isNull(JSONMapping.NICKNAME)) {
            this.setNickname(this.jsonObject.getString(JSONMapping.NICKNAME));
        }

        //Picture...
        if (!this.jsonObject.isNull(JSONMapping.PICTURE)) {
            this.setPicture(this.jsonObject.getString(JSONMapping.PICTURE));
        }

        //Given Name...
        if (!this.jsonObject.isNull(JSONMapping.GIVEN_NAME)) {
            this.setGivenName(this.jsonObject.getString(JSONMapping.GIVEN_NAME));
        }

        //Family Name...
        if (!this.jsonObject.isNull(JSONMapping.FAMILY_NAME)) {
            this.setFamilyName(this.jsonObject.getString(JSONMapping.FAMILY_NAME));
        }

        //Locale...
        if (!this.jsonObject.isNull(JSONMapping.LOCALE)) {
            this.setLocale(this.jsonObject.getString(JSONMapping.LOCALE));
        }

        //Roles...
        if (!this.jsonObject.isNull(JSONMapping.IDENTITIES)) {

            this.identities = new ArrayList<Identity>();

            JSONArray identityListing = this.jsonObject.getJSONArray(JSONMapping.IDENTITIES);

            for(int index = 0;index < identityListing.length();index++)
            {
                this.identities.add(new Identity(identityListing.getJSONObject(index)));
            }
        }
    }

    /**
     * Gets the User id.
     *
     * @return User Id.
     */
    public String getUserId() {
        return this.userId;
    }

    /**
     * Sets the User id.
     *
     * @param userIdParam User Id.
     */
    public void setUserId(String userIdParam) {
        this.userId = userIdParam;
    }

    /**
     * Gets the User name.
     *
     * @return Name of the User.
     */
    public String getName() {
        return this.name;
    }

    /**
     * Sets the User name.
     *
     * @param nameParam Name of the User.
     */
    public void setName(String nameParam) {
        this.name = nameParam;
    }

    /**
     * Gets the users associated email.
     *
     * @return User Email.
     */
    public String getEmail() {
        return this.email;
    }

    /**
     * Sets the users associated email.
     *
     * @param emailParam User Email.
     */
    public void setEmail(String emailParam) {
        this.email = emailParam;
    }

    /**
     * Gets whether the email is verified.
     *
     * @return Whether email is verified.
     */
    public boolean isEmailVerified() {
        return this.emailVerified;
    }

    /**
     * Sets whether the email is verified.
     *
     * @param emailVerifiedParam Whether email is verified.
     */
    public void setEmailVerified(boolean emailVerifiedParam) {
        this.emailVerified = emailVerifiedParam;
    }

    /**
     * Gets the User nickname.
     *
     * @return User Nickname.
     */
    public String getNickname() {
        return this.nickname;
    }

    /**
     * Sets the User nickname.
     *
     * @param nicknameParam User Nickname.
     */
    public void setNickname(String nicknameParam) {
        this.nickname = nicknameParam;
    }

    /**
     * Gets URL to the Users picture.
     *
     * @return Link to picture.
     */
    public String getPicture() {
        return this.picture;
    }

    /**
     * Sets URL to the Users picture.
     *
     * @param pictureParam Link to picture.
     */
    public void setPicture(String pictureParam) {
        this.picture = pictureParam;
    }

    /**
     * Gets the User's given name.
     *
     * @return Given name.
     */
    public String getGivenName() {
        return this.givenName;
    }

    /**
     * Sets the User's given name.
     *
     * @param givenNameParam Given name.
     */
    public void setGivenName(String givenNameParam) {
        this.givenName = givenNameParam;
    }

    /**
     * Gets the User's given name.
     *
     * @return User Family Name.
     */
    public String getFamilyName() {
        return this.familyName;
    }

    /**
     * Sets the User's given name.
     *
     * @param familyNameParam User Family Name.
     */
    public void setFamilyName(String familyNameParam) {
        this.familyName = familyNameParam;
    }

    /**
     * Gets the User's locale.
     *
     * @return Users Locale.
     */
    public String getLocale() {
        return this.locale;
    }

    /**
     * Sets the User's locale.
     *
     * @param localeParam Users Locale.
     */
    public void setLocale(String localeParam) {
        this.locale = localeParam;
    }

    /**
     * Gets the Identities associated with the user.
     *
     * @return User's Identities.
     */
    public List<Identity> getIdentities() {
        return this.identities;
    }

    /**
     * Sets the Identities associated with the user.
     *
     * @param identitiesParam User's Identities.
     */
    public void setIdentities(List<Identity> identitiesParam) {
        this.identities = identitiesParam;
    }

    /**
     * Gets a comma seperated list of providers from {@code Identity}'s.
     *
     * @return Text list of Providers for user {@code Identities}.
     *
     * @see NormalizedUserProfile#getIdentities()
     */
    public String getListOfProvidersFromIdentities()
    {
        if(this.getIdentities() == null || this.getIdentities().isEmpty())
        {
            return "";
        }

        StringBuilder returnVal = new StringBuilder();

        for(Identity identity : this.getIdentities())
        {
            returnVal.append(identity.getProvider());
            returnVal.append(",");
        }

        String toString = returnVal.toString();
        return toString.substring(0,toString.length() - 1);
    }

    /**
     * Conversion to {@code JSONObject} from Java Object.
     *
     * @return {@code JSONObject} representation of {@code NormalizedUserProfile}
     * @throws JSONException If there is a problem with the JSON Body.
     *
     * @see ABaseFluidJSONObject#toJsonObject()
     */
    @Override
    public JSONObject toJsonObject() throws JSONException {

        JSONObject returnVal = super.toJsonObject();

        //User Id...
        if(this.getUserId() != null)
        {
            returnVal.put(JSONMapping.USER_ID, this.getUserId());
        }

        //Name...
        if(this.getName() != null)
        {
            returnVal.put(JSONMapping.NAME, this.getName());
        }

        //Email...
        if(this.getEmail() != null)
        {
            returnVal.put(JSONMapping.EMAIL, this.getEmail());
        }

        //Email Verified...
        returnVal.put(JSONMapping.EMAIL_VERIFIED, this.isEmailVerified());

        //Nickname...
        if(this.getNickname() != null)
        {
            returnVal.put(JSONMapping.NICKNAME, this.getNickname());
        }

        //Picture...
        if(this.getPicture() != null)
        {
            returnVal.put(JSONMapping.PICTURE, this.getPicture());
        }

        //Given Name...
        if(this.getGivenName() != null)
        {
            returnVal.put(JSONMapping.GIVEN_NAME, this.getGivenName());
        }

        //Family Name...
        if(this.getFamilyName() != null)
        {
            returnVal.put(JSONMapping.FAMILY_NAME, this.getFamilyName());
        }

        //Locale...
        if(this.getLocale() != null)
        {
            returnVal.put(JSONMapping.LOCALE, this.getLocale());
        }

        //Identities...
        if(this.getIdentities() != null && !this.getIdentities().isEmpty())
        {
            JSONArray identitiesArr = new JSONArray();
            for(Identity toAdd :this.getIdentities())
            {
                identitiesArr.put(toAdd.toJsonObject());
            }

            returnVal.put(JSONMapping.IDENTITIES,identitiesArr);
        }

        return returnVal;
    }
}


/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.program.api.util.command.impl;

import java.io.File;
import java.io.IOException;

import com.fluidbpm.program.api.util.command.CommandUtil;
import com.fluidbpm.program.api.util.exception.UtilException;

/**
 * Utility class used for executing document to pdf conversion.
 *
 * @author jasonbruwer
 * @since v1.6
 *
 * @see com.fluidbpm.program.api.util.command.CommandUtil
 */
public class DocumentToPDFConvert {

	private CommandUtil commandUtil;

	//fluid-cli doc-to-pdf-convert -i /fluid_host_system_mapping/1.docx -o /fluid_host_system_mapping/1.pdf
	private static final String COMMAND_CONVERT_DOC_TO_PDF = "doc-to-pdf-convert";

	/**
	 * Default constructor creating instance of {@code CommandUtil}.
	 */
	public DocumentToPDFConvert() {
		super();

		this.commandUtil = new CommandUtil();
	}

	/**
	 * Makes use of the Fluid Core to convert a document into a PDF file.
	 *
	 * @param inputDocumentParam The file to convert to PDF.
	 * @return The {@code File} object of the result PDF.
	 *
	 * @throws UtilException If anything goes wrong during execution.
	 */
	public File convertDocumentToPDF(File inputDocumentParam) {

		if(inputDocumentParam == null ||
				!inputDocumentParam.exists())
		{
			throw new UtilException(
					"Input document to convert not provided or does not exist.",
					UtilException.ErrorCode.COMMAND);
		}

		if(!inputDocumentParam.isFile())
		{
			throw new UtilException(
					"Input document '' is not a file.",
					UtilException.ErrorCode.COMMAND);
		}

		File parentFolder = inputDocumentParam.getParentFile();
		String inputFilenameWithoutExt = inputDocumentParam.getName();

		int indexOfDot = -1;
		if((indexOfDot = inputFilenameWithoutExt.indexOf('.')) > -1)
		{
			inputFilenameWithoutExt = inputFilenameWithoutExt.substring(0,indexOfDot);
		}

		File generatedPdfFileOut = new File(parentFolder.getAbsolutePath().concat(
				File.separator).concat(inputFilenameWithoutExt).concat(".pdf"));

		String completeOutputPath = generatedPdfFileOut.getAbsolutePath();

		try {
			CommandUtil.CommandResult commandResult =
					this.commandUtil.executeCommand(
							CommandUtil.FLUID_CLI,
							COMMAND_CONVERT_DOC_TO_PDF,
							"-i",
							inputDocumentParam.getAbsolutePath(),
							"-o",
							completeOutputPath);

			//There is a problem...
			if(commandResult.getExitCode() != 0)
			{
				throw new UtilException(
						"Unable to convert '"+
								inputDocumentParam.getName()+
								"' to PDF. "+ commandResult.toString(),
						UtilException.ErrorCode.COMMAND);
			}

			File returnVal = new File(completeOutputPath);
			if(!returnVal.exists())
			{
				throw new UtilException(
						"Command executed, but no output file. Expected PDF at '"+
								completeOutputPath+"'.",
						UtilException.ErrorCode.GENERAL);
			}

			return returnVal;
		}
		//
		catch (IOException eParam) {

			throw new UtilException(
					"Problem executing command. "+eParam.getMessage(),
					eParam,UtilException.ErrorCode.GENERAL);
		}
	}
}


/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.ws.client.v1.sqlutil.wrapper;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import com.fluidbpm.program.api.util.UtilGlobal;
import com.fluidbpm.program.api.vo.field.Field;
import com.fluidbpm.program.api.vo.form.Form;
import com.fluidbpm.program.api.vo.form.FormFieldListing;
import com.fluidbpm.program.api.vo.form.FormListing;
import com.fluidbpm.program.api.vo.user.User;
import com.fluidbpm.ws.client.FluidClientException;
import com.fluidbpm.ws.client.v1.sqlutil.*;

/**
 * Wrapper class used for when WebSockets is not a
 * possibility, the wrapper will fallback to REST.
 *
 * @author jasonbruwer on 3/20/18.
 * @since 1.8
 */
public class SQLUtilWebSocketRESTWrapper {

	//Instance...
	private String baseURL;
	private User loggedInUser;

	private long timeoutMillis;

	//Clients...
	private SQLUtilWebSocketGetAncestorClient getAncestorClient = null;
	private SQLUtilWebSocketGetDescendantsClient getDescendantsClient = null;
	private SQLUtilWebSocketGetTableFormsClient getTableFormsClient = null;
	private SQLUtilWebSocketGetFormFieldsClient getFormFieldsClient = null;

	private final SQLUtilClient sqlUtilClient;

	public static boolean COMPRESS_RSP = true;
	public static String COMPRESS_RSP_CHARSET = UtilGlobal.EMPTY;

	//Mode...
	private Mode mode = null;

	private static boolean DISABLE_WS;

	/**
	 * Aliases for properties used by the {@code SQLUtilWebSocketRESTWrapper}.
	 */
	private static class PropName {
		/**
		 * Keep Web Sockets disabled.
		 */
		public static final String FLUID_API_DISABLE_WEB_SOCKETS = "FluidAPIDisableWebSocket";
	}

	//Assign the property whether ti dis
	static {

		try {
			DISABLE_WS = Boolean.parseBoolean(
					System.getProperty(
							PropName.FLUID_API_DISABLE_WEB_SOCKETS,
							String.valueOf(false)).trim());

		} catch (NumberFormatException nfe) {
			DISABLE_WS = false;
		}
	}

	/**
	 * The mode.
	 */
	private enum Mode {
		WebSocketActive,
		RESTfulActive
	}

	/**
	 * New wrapper instance.
	 *
	 * @param baseURLParam Example {@code https://[instance].fluidbpm.com/fluid-ws/}
	 * @param serviceTicketParam The service ticket for the logged in user.
	 * @param timeoutMillisParam The timeout of the request in millis.
	 */
	public SQLUtilWebSocketRESTWrapper(
			String baseURLParam,
			String serviceTicketParam,
			long timeoutMillisParam
	) {
		super();

		this.baseURL = baseURLParam;

		this.loggedInUser = new User();
		this.loggedInUser.setServiceTicket(serviceTicketParam);
		this.timeoutMillis = timeoutMillisParam;

		this.sqlUtilClient = new SQLUtilClient(
				this.baseURL,
				this.loggedInUser.getServiceTicket());
	}

	/**
	 * New wrapper instance.
	 *
	 * @param baseURLParam Example {@code https://[instance].fluidbpm.com/fluid-ws/}
	 * @param loggedInUserParam The currently logged in user.
	 * @param timeoutMillisParam The timeout of the request in millis.
	 */
	public SQLUtilWebSocketRESTWrapper(
			String baseURLParam,
			User loggedInUserParam,
			long timeoutMillisParam
	) {
		super();

		this.baseURL = baseURLParam;
		this.loggedInUser = loggedInUserParam;
		this.timeoutMillis = timeoutMillisParam;

		this.sqlUtilClient = new SQLUtilClient(
				this.baseURL,
				this.loggedInUser.getServiceTicket());
	}

	/**
	 * Retrieves the Ancestor for the {@code formToGetAncestorForParam}.
	 *
	 * @param formToGetAncestorForParam The Fluid Form to get Ancestor for.
	 * @param includeFieldDataParam Should Ancestor (Form) Field data be included?
	 * @param includeTableFieldsParam Should Table Record (Form) Field data be included?
	 *
	 * @return The {@code formToGetAncestorForParam} Ancestor as {@code Form}'s.
	 */
	public Form getAncestor(
			Form formToGetAncestorForParam,
			boolean includeFieldDataParam,
			boolean includeTableFieldsParam
	) {
		if(DISABLE_WS) {
			this.mode = Mode.RESTfulActive;
		}

		//ANCESTOR...
		try {
			//When mode is null or [WebSocketActive]...
			if(this.getAncestorClient == null &&
					Mode.RESTfulActive != this.mode) {
				this.getAncestorClient = new SQLUtilWebSocketGetAncestorClient(
						this.baseURL,
						null,
						this.loggedInUser.getServiceTicketAsHexUpper(),
						this.timeoutMillis,
						includeFieldDataParam,
						includeTableFieldsParam,
						COMPRESS_RSP,
						COMPRESS_RSP_CHARSET);

				this.mode = Mode.WebSocketActive;
			}
		} catch (FluidClientException clientExcept) {
			if(clientExcept.getErrorCode() !=
					FluidClientException.ErrorCode.WEB_SOCKET_DEPLOY_ERROR) {
				throw clientExcept;
			}

			this.mode = Mode.RESTfulActive;
		}

		Form formToUse = (formToGetAncestorForParam == null) ? null:
				new Form(formToGetAncestorForParam.getId());

		return (this.getAncestorClient == null) ?
				this.sqlUtilClient.getAncestor(
						formToUse,
						includeFieldDataParam,
						includeTableFieldsParam):
				this.getAncestorClient.getAncestorSynchronized(formToUse);
	}

	/**
	 * Retrieves all the Descendants (Forms) for the {@code formsToGetDescForParam}.
	 *
	 * @param includeFieldDataParam Should Ancestor (Form) Field data be included?
	 * @param includeTableFieldsParam Should Table Record (Form) Field data be included?
	 * @param includeTableFieldFormRecordInfoParam Does table record form data need to be included.
	 * @param massFetchParam Is the fetch a large fetch.
	 * @param formsToGetDescForParam The Fluid Form to get Descendants for.
	 *
	 * @return The {@code formsToGetDescForParam} Descendants as {@code Form}'s.
	 */
	public List<FormListing> getDescendants(
			boolean includeFieldDataParam,
			boolean includeTableFieldsParam,
			boolean includeTableFieldFormRecordInfoParam,
			boolean massFetchParam,
			Form ... formsToGetDescForParam)
	{
		if(DISABLE_WS) {
			this.mode = Mode.RESTfulActive;
		}

		//DESCENDANTS...
		try {
			//When mode is null or [WebSocketActive]...
			if(this.getDescendantsClient == null && Mode.RESTfulActive != this.mode) {
				this.getDescendantsClient = new SQLUtilWebSocketGetDescendantsClient(
						this.baseURL,
						null,
						this.loggedInUser.getServiceTicketAsHexUpper(),
						this.timeoutMillis,
						includeFieldDataParam,
						includeTableFieldsParam,
						includeTableFieldFormRecordInfoParam,
						massFetchParam,
						COMPRESS_RSP,
						COMPRESS_RSP_CHARSET);

				this.mode = Mode.WebSocketActive;
			}
		} catch (FluidClientException clientExcept) {
			if(clientExcept.getErrorCode() !=
					FluidClientException.ErrorCode.WEB_SOCKET_DEPLOY_ERROR) {
				throw clientExcept;
			}
			this.mode = Mode.RESTfulActive;
		}

		if(formsToGetDescForParam == null || formsToGetDescForParam.length < 1) {
			return null;
		}

		Form[] formsToFetchFor =
				new Form[formsToGetDescForParam.length];
		for(int index = 0;index < formsToFetchFor.length;index++) {
			formsToFetchFor[index] = new Form(formsToGetDescForParam[index].getId());
		}

		if(this.getDescendantsClient != null) {
			return this.getDescendantsClient.getDescendantsSynchronized(
					formsToFetchFor);
		} else {
			List<FormListing> returnVal = new ArrayList<>();

			for(Form formToFetchFor : formsToFetchFor) {
				List<Form> listOfForms =
						this.sqlUtilClient.getDescendants(
								formToFetchFor,
								includeFieldDataParam,
								includeTableFieldsParam,
								includeTableFieldFormRecordInfoParam);

				FormListing toAdd = new FormListing();
				toAdd.setListing(listOfForms);
				toAdd.setListingCount((listOfForms == null) ? 0 : listOfForms.size());
				returnVal.add(toAdd);
			}

			return returnVal;
		}
	}

	/**
	 * Retrieves all the Table (Forms) for the {@code formsToGetDescForParam}.
	 *
	 * @param includeFieldDataParam Should Field data be included?
	 * @param formsToGetTableFormsForParam The Fluid Form to get Descendants for.
	 *
	 * @return The {@code formsToGetDescForParam} Descendants as {@code Form}'s.
	 */
	public List<FormListing> getTableForms(
			boolean includeFieldDataParam,
			Form ... formsToGetTableFormsForParam
	) {
		if(DISABLE_WS) {
			this.mode = Mode.RESTfulActive;
		}

		//DESCENDANTS...
		try {
			//When mode is null or [WebSocketActive]...
			if(this.getTableFormsClient == null &&
					Mode.RESTfulActive != this.mode) {
				this.getTableFormsClient = new SQLUtilWebSocketGetTableFormsClient(
						this.baseURL,
						null,
						this.loggedInUser.getServiceTicketAsHexUpper(),
						this.timeoutMillis,
						includeFieldDataParam,
						COMPRESS_RSP,
						COMPRESS_RSP_CHARSET);

				this.mode = Mode.WebSocketActive;
			}
		} catch (FluidClientException clientExcept) {
			if(clientExcept.getErrorCode() !=
					FluidClientException.ErrorCode.WEB_SOCKET_DEPLOY_ERROR) {
				throw clientExcept;
			}

			this.mode = Mode.RESTfulActive;
		}

		if(formsToGetTableFormsForParam == null || formsToGetTableFormsForParam.length < 1) {
			return null;
		}

		Form[] formsToFetchFor =
				new Form[formsToGetTableFormsForParam.length];
		for(int index = 0;index < formsToFetchFor.length;index++) {
			formsToFetchFor[index] = new Form(formsToGetTableFormsForParam[index].getId());
		}

		if(this.getTableFormsClient != null) {
			return this.getTableFormsClient.getTableFormsSynchronized(formsToFetchFor);
		} else {
			List<FormListing> returnVal = new ArrayList<>();

			for(Form formToFetchFor : formsToFetchFor) {
				List<Form> listOfForms =
						this.sqlUtilClient.getTableForms(
								formToFetchFor,
								includeFieldDataParam);

				FormListing toAdd = new FormListing();
				toAdd.setListing(listOfForms);
				toAdd.setListingCount((listOfForms == null) ? 0 : listOfForms.size());
				returnVal.add(toAdd);
			}

			return returnVal;
		}
	}

	/**
	 * Retrieves all the (Fields) for the {@code formsToGetDescForParam}.
	 *
	 * @param includeFieldDataParam Should Field data be included?
	 * @param formsToGetFieldsForParam The Fluid Form to get Descendants for.
	 *
	 * @return The {@code formsToGetFieldsForParam} Fields as {@code Field}'s.
	 */
	public List<FormFieldListing> getFormFields(
			boolean includeFieldDataParam,
			Form ... formsToGetFieldsForParam
	) {
		if(DISABLE_WS) {
			this.mode = Mode.RESTfulActive;
		}

		//FORM FIELDS...
		try {
			//When mode is null or [WebSocketActive]...
			if(this.getFormFieldsClient == null && Mode.RESTfulActive != this.mode) {
				this.getFormFieldsClient = new SQLUtilWebSocketGetFormFieldsClient(
						this.baseURL,
						null,
						this.loggedInUser.getServiceTicketAsHexUpper(),
						this.timeoutMillis,
						includeFieldDataParam,
						COMPRESS_RSP,
						COMPRESS_RSP_CHARSET);

				this.mode = Mode.WebSocketActive;
			}
		} catch (FluidClientException clientExcept) {
			if(clientExcept.getErrorCode() !=
					FluidClientException.ErrorCode.WEB_SOCKET_DEPLOY_ERROR) {
				throw clientExcept;
			}
			this.mode = Mode.RESTfulActive;
		}

		if(formsToGetFieldsForParam == null ||
				formsToGetFieldsForParam.length < 1) {
			return null;
		}

		Form[] formsToFetchFor =
				new Form[formsToGetFieldsForParam.length];
		for(int index = 0;index < formsToFetchFor.length;index++) {
			formsToFetchFor[index] = new Form(formsToGetFieldsForParam[index].getId());
		}

		if(this.getFormFieldsClient != null) {
			return this.getFormFieldsClient.getFormFieldsSynchronized(
					formsToFetchFor);
		} else {
			List<FormFieldListing> returnVal = new ArrayList<>();

			for(Form formToFetchFor : formsToFetchFor) {
				List<Field> listOfFields =
						this.sqlUtilClient.getFormFields(
								formToFetchFor,
								includeFieldDataParam);

				FormFieldListing toAdd = new FormFieldListing();
				toAdd.setListing(listOfFields);
				toAdd.setListingCount((listOfFields == null) ? 0 : listOfFields.size());
				returnVal.add(toAdd);
			}

			return returnVal;
		}
	}

	/**
	 * Retrieves all the (Fields) for the {@code formsToGetDescForParam}.
	 *
	 * @param includeFieldDataParam Should Field data be included?
	 * @param formsToPopulateFormFieldsForParam The Fluid Form to get Descendants for.
	 */
	public void massPopulateFormFields(
			boolean includeFieldDataParam,
			Form ... formsToPopulateFormFieldsForParam
	) {
		if(DISABLE_WS) {
			this.mode = Mode.RESTfulActive;
		}

		//FORM FIELDS...
		try {
			//When mode is null or [WebSocketActive]...
			if(this.getFormFieldsClient == null && Mode.RESTfulActive != this.mode) {
				this.getFormFieldsClient = new SQLUtilWebSocketGetFormFieldsClient(
						this.baseURL,
						null,
						this.loggedInUser.getServiceTicketAsHexUpper(),
						this.timeoutMillis,
						includeFieldDataParam,
						COMPRESS_RSP,
						COMPRESS_RSP_CHARSET);

				this.mode = Mode.WebSocketActive;
			}
		} catch (FluidClientException clientExcept) {
			if(clientExcept.getErrorCode() !=
					FluidClientException.ErrorCode.WEB_SOCKET_DEPLOY_ERROR) {
				throw clientExcept;
			}

			this.mode = Mode.RESTfulActive;
		}

		//Nothing to do...
		if(formsToPopulateFormFieldsForParam == null ||
				formsToPopulateFormFieldsForParam.length < 1) {
			return;
		}

		//Populate a known echo for all of the local form caches...
		Form[] formsToFetchForLocalCacheArr =
				new Form[formsToPopulateFormFieldsForParam.length];
		for(int index = 0;index < formsToFetchForLocalCacheArr.length;index++) {

			formsToFetchForLocalCacheArr[index] = new Form(formsToPopulateFormFieldsForParam[index].getId());
			formsToFetchForLocalCacheArr[index].setEcho(UUID.randomUUID().toString());
		}

		List<FormFieldListing> listingReturnFieldValsPopulated = new ArrayList<>();

		//Fetch all of the values in a single go...
		if(this.getFormFieldsClient != null) {
			listingReturnFieldValsPopulated =
					this.getFormFieldsClient.getFormFieldsSynchronized(formsToFetchForLocalCacheArr);
		} else {
			//Old Rest way of fetching all of the values...
			for(Form formToFetchFor : formsToFetchForLocalCacheArr) {
				List<Field> listOfFields =
						this.sqlUtilClient.getFormFields(
								formToFetchFor,
								includeFieldDataParam);

				FormFieldListing toAdd = new FormFieldListing();
				toAdd.setListing(listOfFields);
				toAdd.setListingCount((listOfFields == null) ? 0 : listOfFields.size());
				toAdd.setEcho(formToFetchFor.getEcho());
				listingReturnFieldValsPopulated.add(toAdd);
			}
		}

		//Populate each of the form from the param...
		for(Form formToSetFieldsOn : formsToPopulateFormFieldsForParam) {
			formToSetFieldsOn.setFormFields(
					this.getFieldValuesForFormFromCache(
							formToSetFieldsOn.getId(),
							listingReturnFieldValsPopulated,
							formsToFetchForLocalCacheArr));
		}
	}

	/**
	 * Populate the field values from the cache.
	 *
	 * @param formIdParam The id of the form to populate.
	 * @param listingReturnFieldValsPopulatedParam The cache of field values.
	 * @param formsToFetchForLocalCacheArrParam The forms that contain the ids and echos.
	 *
	 * @return The field values for form with id {@code formIdParam}.
	 */
	private List<Field> getFieldValuesForFormFromCache(
			Long formIdParam,
			List<FormFieldListing> listingReturnFieldValsPopulatedParam,
			Form[] formsToFetchForLocalCacheArrParam){

		if(formIdParam == null || formIdParam.longValue() < 1) {
			return null;
		}

		if(listingReturnFieldValsPopulatedParam == null ||
				listingReturnFieldValsPopulatedParam.isEmpty()) {
			return null;
		}

		if(formsToFetchForLocalCacheArrParam == null ||
				formsToFetchForLocalCacheArrParam.length == 0) {
			return null;
		}

		for(Form formIter : formsToFetchForLocalCacheArrParam) {
			//Form is a match...
			if(formIdParam.equals(formIter.getId())) {
				String echoToUse = formIter.getEcho();
				for(FormFieldListing fieldListing : listingReturnFieldValsPopulatedParam) {
					if(echoToUse.equals(fieldListing.getEcho())) {
						return fieldListing.getListing();
					}
				}
			}
		}

		return null;
	}

	/**
	 * Close any clients used during lifetime of {@code this} object.
	 */
	public void closeAndClean() {
		if(this.sqlUtilClient != null) {
			this.sqlUtilClient.closeAndClean();
		}

		if(this.getAncestorClient != null) {
			this.getAncestorClient.closeAndClean();
		}

		if(this.getDescendantsClient != null) {
			this.getDescendantsClient.closeAndClean();
		}

		if(this.getTableFormsClient != null) {
			this.getTableFormsClient.closeAndClean();
		}

		if(this.getFormFieldsClient != null) {
			this.getFormFieldsClient.closeAndClean();
		}
	}
}


/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.program.api.vo.sqlutil.sqlnative;

import org.json.JSONException;
import org.json.JSONObject;

import com.fluidbpm.program.api.vo.ABaseFluidJSONObject;

/**
 * <p>
 * The input value used for a prepared statement or stored procedure that
 * would be set on the Fluid BPM Core.
 * </p>
 *
 * @see java.sql.SQLType
 * @see java.sql.PreparedStatement
 * @see java.sql.ResultSet
 *
 * @author jasonbruwer on 2018-03-12
 * @since v1.8
 */
public class SQLColumn extends ABaseFluidJSONObject {

    public static final long serialVersionUID = 1L;

    private String columnName;
    private Integer columnIndex;

    private Integer sqlType;
    private Object sqlValue;

    /**
     * The JSON mapping for the {@code Input} object.
     */
    public static class JSONMapping
    {
        public static final String COLUMN_NAME = "columnName";
        public static final String COLUMN_INDEX = "columnIndex";
        public static final String SQL_TYPE = "sqlType";
        public static final String SQL_VALUE = "sqlValue";
    }

    /**
     * Default constructor.
     */
    public SQLColumn() {
        super();
    }

    /**
     * Sets all the values for the column.
     *
     * @param columnNameParam The column name.
     * @param columnIndexParam The column index.
     * @param sqlTypeParam The SQL Type. See {@code java.sql.Types}
     * @param sqlValueParam The value of the param at index {@code columnIndexParam}.
     *
     * @see java.sql.Types
     */
    public SQLColumn(
            String columnNameParam,
            Integer columnIndexParam,
            Integer sqlTypeParam,
            Object sqlValueParam) {
        super();

        this.columnName = columnNameParam;
        this.columnIndex = columnIndexParam;
        this.sqlType = sqlTypeParam;
        this.sqlValue = sqlValueParam;
    }

    /**
     * Populates local variables with {@code jsonObjectParam}.
     *
     * @param jsonObjectParam The JSON Object.
     */
    public SQLColumn(JSONObject jsonObjectParam){
        super(jsonObjectParam);

        if(this.jsonObject == null)
        {
            return;
        }

        //Name...
        if (!this.jsonObject.isNull(JSONMapping.COLUMN_NAME)) {

            this.setColumnName(
                    this.jsonObject.getString(
                            JSONMapping.COLUMN_NAME));
        }

        //Index...
        if (!this.jsonObject.isNull(JSONMapping.COLUMN_INDEX)) {

            this.setColumnIndex(
                    this.jsonObject.getInt(JSONMapping.COLUMN_INDEX));
        }

        //SQL Type...
        if (!this.jsonObject.isNull(JSONMapping.SQL_TYPE)) {

            this.setSqlType(
                    this.jsonObject.getInt(JSONMapping.SQL_TYPE));
        }

        //Value...
        if (!this.jsonObject.isNull(JSONMapping.SQL_VALUE)) {

            this.setSqlValue(this.jsonObject.get(JSONMapping.SQL_VALUE));
        }
    }

    /**
     * Conversion to {@code JSONObject} from Java Object.
     *
     * @return {@code JSONObject} representation of {@code UserQuery}
     * @throws JSONException If there is a problem with the JSON Body.
     *
     * @see ABaseFluidJSONObject#toJsonObject()
     */
    @Override
    public JSONObject toJsonObject() throws JSONException {

        JSONObject returnVal = super.toJsonObject();

        //Name...
        if(this.getColumnName() != null)
        {
            returnVal.put(JSONMapping.COLUMN_NAME,
                    this.getColumnName());
        }

        //Index...
        if(this.getColumnIndex() != null)
        {
            returnVal.put(JSONMapping.COLUMN_INDEX,
                    this.getColumnIndex());
        }

        //SQL Type...
        if(this.getSqlType() != null)
        {
            returnVal.put(
                    JSONMapping.SQL_TYPE,this.getSqlType());
        }

        //SQL Value...
        if(this.getSqlValue() != null)
        {
            returnVal.put(JSONMapping.SQL_VALUE,
                    this.getSqlValue());
        }

        return returnVal;
    }

    /**
     * Get the SQL column name.
     *
     * @return The SQL column name.
     */
    public String getColumnName() {
        return this.columnName;
    }

    /**
     * Set the SQL column name.
     *
     * @param columnNameParam The SQL column name.
     */
    public void setColumnName(String columnNameParam) {
        this.columnName = columnNameParam;
    }

    /**
     * Get the SQL parameter index.
     * The first parameter is 1.
     *
     * @return The SQL parameter index.
     *
     * @see java.sql.PreparedStatement#setObject(int, Object)
     */
    public Integer getColumnIndex() {
        return this.columnIndex;
    }

    /**
     * Set the SQL parameter index.
     * The first parameter is 1.
     *
     * @param indexParam The SQL parameter index.
     *
     * @see java.sql.PreparedStatement#setObject(int, Object)
     */
    public void setColumnIndex(Integer indexParam) {
        this.columnIndex = indexParam;
    }

    /**
     * Get the SQL Type as {@code int}.
     *
     * @return The SQL Type.
     *
     * @see java.sql.Types
     */
    public Integer getSqlType() {
        return this.sqlType;
    }

    /**
     * Set the SQL Type as {@code Integer}.
     *
     * @param sqlTypeParam The SQL Type.
     *
     * @see java.sql.Types
     */
    public void setSqlType(Integer sqlTypeParam) {
        this.sqlType = sqlTypeParam;
    }

    /**
     * Get the param value.
     * The type will be converted at time of use.
     *
     * @return The value of the parameter.
     *
     * @see java.sql.PreparedStatement#setObject(int, Object)
     */
    public Object getSqlValue() {
        return this.sqlValue;
    }

    /**
     * Get the param value.
     * The type will be converted at time of use.
     *
     * @param paramValueParam The value of the parameter.
     *
     * @see java.sql.PreparedStatement#setObject(int, Object)
     */
    public void setSqlValue(Object paramValueParam) {
        this.sqlValue = paramValueParam;
    }
}


/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.program.api.vo;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.xml.bind.annotation.XmlTransient;

import org.json.JSONException;
import org.json.JSONObject;

/**
 *

 *     The Base class for any sub-class that wants to make use of the
 *     JSON based message format used by the Fluid RESTful Web Service.
 *
 * @author jasonbruwer
 * @since v1.0
 *
 * @see ABaseFluidVO
 * @see JSONObject
 */
public abstract class ABaseFluidJSONObject extends ABaseFluidVO {

    public static final long serialVersionUID = 1L;

    @XmlTransient
    protected JSONObject jsonObject;

    private static SimpleDateFormat DATE_FORMAT_001 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
    private static SimpleDateFormat DATE_FORMAT_002 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    private static SimpleDateFormat DATE_FORMAT_003 = new SimpleDateFormat("h:mma");
    private static SimpleDateFormat DATE_FORMAT_004 = new SimpleDateFormat("yyyy-MM-dd");

    private static SimpleDateFormat[] SUPPORTED_FORMATS = {
            DATE_FORMAT_001, DATE_FORMAT_002, DATE_FORMAT_003, DATE_FORMAT_004
    };

    /**
     * The JSON mapping for the {@code ABaseFluidJSONObject} object.
     */
    public static class JSONMapping
    {
        public static final String ID = "id";
        public static final String SERVICE_TICKET = "serviceTicket";
        public static final String ECHO = "echo";

        /**
         * Elastic specific properties.
         */
        public static final class Elastic
        {
            public static final String PROPERTIES = "properties";
        }
    }

    /**
     * Default constructor.
     */
    public ABaseFluidJSONObject() {
        super();
    }

    /**
     * Populates local variables Id and Service Ticket with {@code jsonObjectParam}.
     *
     * @param jsonObjectParam The JSON Object.
     */
    public ABaseFluidJSONObject(JSONObject jsonObjectParam) {
        this();

        this.jsonObject = jsonObjectParam;

        if(this.jsonObject == null)
        {
            return;
        }

        //Id...
        if (!this.jsonObject.isNull(JSONMapping.ID)) {

            Object idObject = this.jsonObject.get(JSONMapping.ID);

            //Long Id...
            if(idObject instanceof Number)
            {
                this.setId(this.jsonObject.getLong(JSONMapping.ID));
            }
            //String Id...
            else if(idObject instanceof String)
            {
                String idStr = this.jsonObject.getString(JSONMapping.ID);

                try
                {
                    this.setId(Long.parseLong(idStr));
                }
                catch (NumberFormatException nfe)
                {
                    this.setId(null);
                }
            }
            else
            {
                throw new IllegalArgumentException(
                        "Unable to parse Field '"+JSONMapping.ID+"'.");
            }
        }

        //Service Ticket...
        if (!this.jsonObject.isNull(JSONMapping.SERVICE_TICKET)) {
            this.setServiceTicket(this.jsonObject.getString(JSONMapping.SERVICE_TICKET));
        }

        //Echo...
        if (!this.jsonObject.isNull(JSONMapping.ECHO)) {
            this.setEcho(this.jsonObject.getString(JSONMapping.ECHO));
        }
    }

    /**
     *

     * Base {@code toJsonObject} that creates a {@code JSONObject}
     * with the Id and ServiceTicket set.
     *

     *
     * @return {@code JSONObject} representation of {@code ABaseFluidJSONObject}
     * @throws JSONException If there is a problem with the JSON Body.
     *
     * @see org.json.JSONObject
     */
    @XmlTransient
    public JSONObject toJsonObject() throws JSONException
    {
        JSONObject returnVal = new JSONObject();

        //Id...
        if(this.getId() != null)
        {
            returnVal.put(JSONMapping.ID,this.getId());
        }

        //Service Ticket...
        if(this.getServiceTicket() != null)
        {
            returnVal.put(JSONMapping.SERVICE_TICKET, this.getServiceTicket());
        }

        //Echo...
        if(this.getEcho() != null)
        {
            returnVal.put(JSONMapping.ECHO, this.getEcho());
        }

        return returnVal;
    }

    /**
     * Converts the {@code Long} timestamp into a {@code Date} object.
     *
     * Returns {@code null} if {@code longValueParam} is {@code null}.
     *
     * @param longValueParam The milliseconds since January 1, 1970, 00:00:00 GMT
     * @return {@code Date} Object from {@code longValueParam}
     *
     */
    @XmlTransient
    private Date getLongAsDateFromJson(Long longValueParam)
    {
        if(longValueParam == null)
        {
            return null;
        }

        return new Date(longValueParam);
    }

    /**
     * Retrieves the value of field {@code fieldNameParam} as a timestamp.
     *
     * @param fieldNameParam The name of the JSON field to retrieve.
     * @return The value of the JSON Object as a {@code java.util.Date}.
     */
    @XmlTransient
    public Date getDateFieldValueFromFieldWithName(String fieldNameParam)
    {
        if((fieldNameParam == null || fieldNameParam.trim().isEmpty()) ||
                (this.jsonObject == null || this.jsonObject.isNull(fieldNameParam)))
        {
            return null;
        }

        Object objectAtIndex = this.jsonObject.get(fieldNameParam);

        if(objectAtIndex instanceof Number)
        {
            return this.getLongAsDateFromJson(((Number)objectAtIndex).longValue());
        }
        else if(objectAtIndex instanceof String)
        {
            Date validDate = null;
            for(SimpleDateFormat format : SUPPORTED_FORMATS)
            {
                try
                {
                    validDate = format.parse((String)objectAtIndex);
                    if(validDate != null)
                    {
                        break;
                    }
                }
                catch (ParseException parseExcept)
                {
                    validDate = null;
                }
            }

            return validDate;
        }

        return null;
    }

    /**
     * Converts the {@code Date} object into a {@code Long} timestamp.
     *
     * Returns {@code null} if {@code dateValueParam} is {@code null}.
     *
     * @param dateValueParam {@code Long} Object from {@code dateValueParam}
     * @return The milliseconds since January 1, 1970, 00:00:00 GMT
     */
    @XmlTransient
    public Long getDateAsLongFromJson(Date dateValueParam)
    {
        if(dateValueParam == null)
        {
            return null;
        }

        return dateValueParam.getTime();
    }

    /**
     * Returns the local JSON object.
     * Only set through constructor.
     *
     * @return The local set {@code JSONObject} object.
     */
    @XmlTransient
    public JSONObject getJSONObject()
    {
        return this.jsonObject;
    }

    /**
     * Return the Text representation of {@code this} object.
     *
     * @return JSON body of {@code this} object.
     */
    @Override
    @XmlTransient
    public String toString() {

        JSONObject jsonObject = this.toJsonObject();

        return (jsonObject == null) ? null : jsonObject.toString();
    }
}