/*
 * Copyright 2011 JBoss Inc 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jbpm.formapi.server.form;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jbpm.formapi.shared.api.FBScript;
import org.jbpm.formapi.shared.api.FBValidation;
import org.jbpm.formapi.shared.api.FormItemRepresentation;
import org.jbpm.formapi.shared.api.FormRepresentation;
import org.jbpm.formapi.shared.api.InputData;
import org.jbpm.formapi.shared.api.Mappable;
import org.jbpm.formapi.shared.api.OutputData;
import org.jbpm.formapi.shared.form.FormEncodingException;
import org.jbpm.formapi.shared.form.FormRepresentationDecoder;
import org.jbpm.formapi.shared.menu.MenuItemDescription;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

public class FormRepresentationDecoderImpl implements FormRepresentationDecoder {

    @Override
    public Object decode(Map<String, Object> data) throws FormEncodingException {
        if (data == null || data.isEmpty()) {
            return null;
        }
        String className = (String) data.get("@className");
        if (className == null) {
            throw new FormEncodingException(
                    "@className attribute cannot be null");
        }
        Object obj = null;
        try {
            obj = Class.forName(className).newInstance();
            if (obj instanceof Mappable) {
                Mappable item = (Mappable) obj;
                item.setDataMap(data);
            } else {
                throw new FormEncodingException("Type "
                        + obj.getClass().getName()
                        + " cannot be casted to FormItemRepresentation");
            }
        } catch (InstantiationException e) {
            throw new FormEncodingException("Couldn't instantiate class "
                    + className, e);
        } catch (IllegalAccessException e) {
            throw new FormEncodingException(
                    "Couldn't access constructor of class " + className, e);
        } catch (ClassNotFoundException e) {
            throw new FormEncodingException("Couldn't find class " + className,
                    e);
        }
        return obj;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Map<String, List<MenuItemDescription>> decodeMenuItemsMap(String json)
            throws FormEncodingException {
        JsonObject jsonObj = new JsonParser().parse(json).getAsJsonObject();
        Map<String, Object> dataMap = asMap(jsonObj);
        Map<String, List<MenuItemDescription>> retval = null;
        if (dataMap != null) {
            retval = new HashMap<String, List<MenuItemDescription>>();
            for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
                List<MenuItemDescription> itemsList = new ArrayList<MenuItemDescription>();
                String key = entry.getKey();
                Object obj = entry.getValue();
                if (obj != null) {
                    List<Object> itemsMapList = (List<Object>) obj;
                    for (Object itemObj : itemsMapList) {
                        Map<String, Object> itemDescMap = (Map<String, Object>) itemObj;
                        MenuItemDescription desc = new MenuItemDescription();
                        desc.setDataMap(itemDescMap);
                        itemsList.add(desc);
                    }
                }
                retval.put(key, itemsList);
            }
        }
        return retval;
    }

    @Override
    public FormRepresentation decode(String code) throws FormEncodingException {
        FormRepresentation form = new FormRepresentation();
        JsonElement json = new JsonParser().parse(code);
        if (json.isJsonObject()) {
            JsonObject jsonObj = json.getAsJsonObject();
            if (jsonObj.entrySet().isEmpty()) {
                return null;
            }
            if (jsonObj.get("action") != null
                    && jsonObj.get("action").isJsonPrimitive()
                    && jsonObj.get("action").getAsJsonPrimitive().isString()) {
                form.setAction(jsonObj.get("action").getAsString());
            }
            if (jsonObj.get("documentation") != null
                    && jsonObj.get("documentation").isJsonPrimitive()
                    && jsonObj.get("documentation").getAsJsonPrimitive()
                            .isString()) {
                form.setDocumentation(jsonObj.get("documentation")
                        .getAsString());
            }
            if (jsonObj.get("enctype") != null
                    && jsonObj.get("enctype").isJsonPrimitive()
                    && jsonObj.get("enctype").getAsJsonPrimitive().isString()) {
                form.setEnctype(jsonObj.get("enctype").getAsString());
            }
            if (jsonObj.get("lastModified") != null
                    && jsonObj.get("lastModified").isJsonPrimitive()
                    && jsonObj.get("lastModified").getAsJsonPrimitive()
                            .isString()) {
                form.setLastModified(Double.valueOf(
                        jsonObj.get("lastModified").getAsString()).longValue());
            } else if (jsonObj.get("lastModified") != null
                    && jsonObj.get("lastModified").isJsonPrimitive()
                    && jsonObj.get("lastModified").getAsJsonPrimitive()
                            .isNumber()) {
                form.setLastModified(jsonObj.get("lastModified").getAsNumber()
                        .longValue());
            }
            if (jsonObj.get("method") != null
                    && jsonObj.get("method").isJsonPrimitive()
                    && jsonObj.get("method").getAsJsonPrimitive().isString()) {
                form.setMethod(jsonObj.get("method").getAsString());
            }
            if (jsonObj.get("name") != null
                    && jsonObj.get("name").isJsonPrimitive()
                    && jsonObj.get("name").getAsJsonPrimitive().isString()) {
                form.setName(jsonObj.get("name").getAsString());
            }
            if (jsonObj.get("taskId") != null
                    && jsonObj.get("taskId").isJsonPrimitive()
                    && jsonObj.get("taskId").getAsJsonPrimitive().isString()) {
                form.setTaskId(jsonObj.get("taskId").getAsString());
            }
            if (jsonObj.get("processName") != null
                    && jsonObj.get("processName").isJsonPrimitive()
                    && jsonObj.get("processName").getAsJsonPrimitive()
                            .isString()) {
                form.setProcessName(jsonObj.get("processName").getAsString());
            }
            form.setFormItems(decodeList(jsonObj.get("formItems"),
                    FormItemRepresentation.class));
            form.setFormValidations(decodeList(jsonObj.get("formValidations"),
                    FBValidation.class));
            form.setInputs(decodeStringIndexedMap(jsonObj.get("inputs"),
                    InputData.class));
            form.setOutputs(decodeStringIndexedMap(jsonObj.get("outputs"),
                    OutputData.class));
            form.setOnLoadScripts(decodeList(jsonObj.get("onLoadScripts"),
                    FBScript.class));
            form.setOnSubmitScripts(decodeList(jsonObj.get("onSubmitScripts"),
                    FBScript.class));
        }
        return form;
    }

    @Override
    public FormItemRepresentation decodeItem(String json)
            throws FormEncodingException {
        JsonElement jsonValue = new JsonParser().parse(json);
        if (jsonValue.isJsonObject()) {
            JsonObject jsonObj = jsonValue.getAsJsonObject();
            Map<String, Object> dataMap = asMap(jsonObj);
            return (FormItemRepresentation) decode(dataMap);
        } else {
            throw new FormEncodingException("Expected json object but found "
                    + jsonValue);
        }
    }

    @SuppressWarnings("unchecked")
    public <V> Map<String, V> decodeStringIndexedMap(JsonElement json,
            Class<V> valueType) throws FormEncodingException {
        Map<String, V> retval = new HashMap<String, V>();
        if (json != null && json.isJsonObject()) {
            for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject()
                    .entrySet()) {
                if (entry.getValue().isJsonObject()) {
                    JsonObject jsonObj = entry.getValue().getAsJsonObject();
                    retval.put(entry.getKey(), (V) decode(asMap(jsonObj)));
                }
            }
        }
        return retval;
    }

    @SuppressWarnings("unchecked")
    public <T> List<T> decodeList(JsonElement json, Class<T> elemType)
            throws FormEncodingException {
        List<T> retval = new ArrayList<T>();
        if (json != null && json.isJsonArray()) {
            JsonArray array = json.getAsJsonArray();
            for (int index = 0; index < array.size(); index++) {
                JsonElement elem = array.get(index);
                JsonObject jsonObj = elem.getAsJsonObject();
                retval.add((T) decode(asMap(jsonObj)));
            }
        }
        return retval;
    }

    private Map<String, Object> asMap(JsonObject jsonObj) {
        Map<String, Object> retval = new HashMap<String, Object>();
        for (Map.Entry<String, JsonElement> entry : jsonObj.entrySet()) {
            retval.put(entry.getKey(), fromJsonValue(entry.getValue()));
        }
        return retval;
    }

    private List<Object> asList(JsonArray array) {
        List<Object> retval = new ArrayList<Object>();
        if (array != null) {
            for (JsonElement elem : array) {
                retval.add(fromJsonValue(elem));
            }
        }
        return retval;
    }

    private Object fromJsonValue(JsonElement elem) {
        if (elem.isJsonPrimitive() && elem.getAsJsonPrimitive().isString()) {
            return elem.getAsString();
        } else if (elem.isJsonPrimitive()
                && elem.getAsJsonPrimitive().isNumber()) {
            return elem.getAsNumber();
        } else if (elem.isJsonArray()) {
            return asList(elem.getAsJsonArray());
        } else if (elem.isJsonNull()) {
            return null;
        } else if (elem.isJsonObject()) {
            return asMap(elem.getAsJsonObject());
        } else {
            return "";
        }
    }
}
