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

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

import org.jbpm.formapi.common.reflect.ReflectionHelper;
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.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;

/**
 * Decodes json to different objects on the client side
 */
public class FormRepresentationDecoderClient 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");
        Object obj = null;
        try {
            obj = ReflectionHelper.newInstance(className);
            if (obj instanceof Mappable) {
                Mappable item = (Mappable) obj;
                item.setDataMap(data);
            } else {
                throw new FormEncodingException("Cannot cast class "
                        + obj.getClass().getName() + " to Mappable");
            }
        } catch (Exception e) {
            throw new FormEncodingException("Couldn't instantiate class "
                    + className, e);
        }
        return obj;
    }

    @Override
    public FormRepresentation decode(String code) throws FormEncodingException {
        FormRepresentation form = new FormRepresentation();
        JSONValue json = JSONParser.parseLenient(code);
        if (json.isObject() != null) {
            JSONObject jsonObj = json.isObject();
            if (jsonObj.get("action").isString() != null) {
                form.setAction(jsonObj.get("action").isString().stringValue());
            }
            if (jsonObj.get("documentation").isString() != null) {
                form.setDocumentation(jsonObj.get("documentation").isString()
                        .stringValue());
            }
            if (jsonObj.get("enctype").isString() != null) {
                form.setEnctype(jsonObj.get("enctype").isString().stringValue());
            }
            if (jsonObj.get("lastModified").isNumber() != null) {
                form.setLastModified(Double.valueOf(
                        jsonObj.get("lastModified").isNumber().doubleValue())
                        .longValue());
            } else if (jsonObj.get("lastModified").isString() != null) {
                form.setLastModified(Double.valueOf(
                        jsonObj.get("lastModified").isString().stringValue())
                        .longValue());
            }
            if (jsonObj.get("method").isString() != null) {
                form.setMethod(jsonObj.get("method").isString().stringValue());
            }
            if (jsonObj.get("name").isString() != null) {
                form.setName(jsonObj.get("name").isString().stringValue());
            }
            if (jsonObj.get("taskId").isString() != null) {
                form.setTaskId(jsonObj.get("taskId").isString().stringValue());
            }
            if (jsonObj.get("processName").isString() != null) {
                form.setProcessName(jsonObj.get("processName").isString()
                        .stringValue());
            }
            form.setFormItems(decodeItems(jsonObj.get("formItems")));
            form.setFormValidations(decodeValidations(jsonObj
                    .get("formValidations")));
            form.setInputs(decodeInputs(jsonObj.get("inputs")));
            form.setOutputs(decodeOutputs(jsonObj.get("outputs")));
            form.setOnLoadScripts(decodeScripts(jsonObj.get("onLoadScripts")));
            form.setOnSubmitScripts(decodeScripts(jsonObj
                    .get("onSubmitScripts")));
        }
        return form;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Map<String, List<MenuItemDescription>> decodeMenuItemsMap(String json)
            throws FormEncodingException {
        JSONObject jsonObj = JSONParser.parseLenient(json).isObject();
        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 FormItemRepresentation decodeItem(String json)
            throws FormEncodingException {
        JSONValue jsonValue = JSONParser.parseLenient(json);
        if (jsonValue.isObject() != null) {
            JSONObject jsonObj = jsonValue.isObject();
            Map<String, Object> dataMap = asMap(jsonObj);
            return (FormItemRepresentation) decode(dataMap);
        } else {
            throw new FormEncodingException("Expected JSON Object: "
                    + jsonValue);
        }
    }

    public Map<String, InputData> decodeInputs(JSONValue json)
            throws FormEncodingException {
        Map<String, InputData> retval = new HashMap<String, InputData>();
        if (json != null && json.isObject() != null) {
            for (String key : json.isObject().keySet()) {
                JSONValue value = json.isObject().get(key);
                if (value.isObject() != null) {
                    JSONObject jsonObj = value.isObject();
                    retval.put(key, (InputData) decode(asMap(jsonObj)));
                }
            }
        }
        return retval;
    }

    public Map<String, OutputData> decodeOutputs(JSONValue json)
            throws FormEncodingException {
        Map<String, OutputData> retval = new HashMap<String, OutputData>();
        if (json != null && json.isObject() != null) {
            for (String key : json.isObject().keySet()) {
                JSONValue value = json.isObject().get(key);
                if (value.isObject() != null) {
                    JSONObject jsonObj = value.isObject();
                    retval.put(key, (OutputData) decode(asMap(jsonObj)));
                }
            }
        }
        return retval;
    }

    public List<FBScript> decodeScripts(JSONValue json)
            throws FormEncodingException {
        List<FBScript> retval = new ArrayList<FBScript>();
        if (json != null && json.isArray() != null) {
            JSONArray array = json.isArray();
            for (int index = 0; index < array.size(); index++) {
                JSONValue elem = array.get(index);
                JSONObject jsonObj = elem.isObject();
                retval.add((FBScript) decode(asMap(jsonObj)));
            }
        }
        return retval;
    }

    public List<FormItemRepresentation> decodeItems(JSONValue json)
            throws FormEncodingException {
        List<FormItemRepresentation> retval = new ArrayList<FormItemRepresentation>();
        if (json != null && json.isArray() != null) {
            JSONArray array = json.isArray();
            for (int index = 0; index < array.size(); index++) {
                JSONValue elem = array.get(index);
                JSONObject jsonObj = elem.isObject();
                retval.add((FormItemRepresentation) decode(asMap(jsonObj)));
            }
        }
        return retval;
    }

    public List<FBValidation> decodeValidations(JSONValue json)
            throws FormEncodingException {
        List<FBValidation> retval = new ArrayList<FBValidation>();
        if (json != null && json.isArray() != null) {
            JSONArray array = json.isArray();
            for (int index = 0; index < array.size(); index++) {
                JSONValue elem = array.get(index);
                JSONObject jsonObj = elem.isObject();
                retval.add((FBValidation) decode(asMap(jsonObj)));
            }
        }
        return retval;
    }

    private Map<String, Object> asMap(JSONObject jsonObj) {
        Map<String, Object> retval = new HashMap<String, Object>();
        for (String key : jsonObj.keySet()) {
            JSONValue value = jsonObj.get(key);
            retval.put(key, fromJsonValue(value));
        }
        return retval;
    }

    private List<Object> asList(JSONArray array) {
        List<Object> retval = new ArrayList<Object>();
        if (array != null) {
            for (int index = 0; index < array.size(); index++) {
                retval.add(fromJsonValue(array.get(index)));
            }
        }
        return retval;
    }

    private Object fromJsonValue(JSONValue elem) {
        if (elem.isString() != null) {
            return elem.isString().stringValue();
        } else if (elem.isNumber() != null) {
            return elem.isNumber().doubleValue();
        } else if (elem.isArray() != null) {
            return asList(elem.isArray());
        } else if (elem.isNull() != null) {
            return null;
        } else if (elem.isObject() != null) {
            return asMap(elem.isObject());
        } else {
            return "";
        }
    }
}
