package io.cattle.platform.object.postinit;

import static io.cattle.platform.object.util.DataUtils.*;
import io.cattle.platform.json.JsonMapper;
import io.cattle.platform.util.type.Priority;

import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

import javax.inject.Inject;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

public class ObjectDataPostInstantiationHandler implements ObjectPostInstantiationHandler, Priority {

    JsonMapper jsonMapper;

    @Override
    public <T> T postProcess(T obj, Class<T> clz, Map<String, Object> properties) {
        if (!hasDataField(obj))
            return obj;

        try {
            Map<String, Object> data = getData(obj, properties);
            setData(obj, data);
            return obj;
        } catch (IOException e) {
            throw new IllegalStateException("Failed to handle object data for [" + obj + "] and properties [" + properties + "]", e);
        }
    }

    protected boolean hasDataField(Object obj) {
        PropertyDescriptor desc;
        try {
            desc = PropertyUtils.getPropertyDescriptor(obj, "data");
        } catch (IllegalAccessException e) {
            return false;
        } catch (InvocationTargetException e) {
            return false;
        } catch (NoSuchMethodException e) {
            return false;
        }

        if (desc == null || desc.getReadMethod() == null || desc.getPropertyType() != Map.class) {
            return false;
        }

        return true;
    }

    protected void setData(Object instance, Map<String, Object> data) throws IOException {
        try {
            BeanUtils.setProperty(instance, DATA, data);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Failed to set data [" + data + "] on [" + instance + "]", e);
        } catch (InvocationTargetException e) {
            throw new IllegalStateException("Failed to set data [" + data + "] on [" + instance + "]", e);
        }
    }

    @SuppressWarnings("unchecked")
    protected Map<String, Object> getData(Object instance, Map<String, Object> properties) throws IOException {
        Map<String, Object> objectData = null;
        Map<String, Object> inputData = getMap(properties.get(DATA));

        try {
            objectData = (Map<String, Object>) PropertyUtils.getProperty(instance, DATA);
        } catch (IllegalAccessException e) {
        } catch (InvocationTargetException e) {
        } catch (NoSuchMethodException e) {
        }

        Map<String, Object> finalData = new TreeMap<String, Object>();

        if (objectData != null) {
            finalData.putAll(objectData);
        }
        finalData.putAll(inputData);

        overlay(finalData, OPTIONS, getMap(inputData.get(OPTIONS)));
        overlay(finalData, FIELDS, getFieldData(instance, properties));

        return finalData;
    }

    protected Map<String, Object> getFieldData(Object instance, Map<String, Object> properties) {
        Map<String, Object> fields = new HashMap<String, Object>();
        for (String name : properties.keySet()) {
            try {
                if (PropertyUtils.getPropertyDescriptor(instance, name) != null)
                    continue;
            } catch (IllegalAccessException e) {
                continue;
            } catch (InvocationTargetException e) {
                continue;
            } catch (NoSuchMethodException e) {
                // This exception would be okay, but it never seems to happen
            }

            fields.put(name, properties.get(name));
        }

        return fields;
    }

    protected void overlay(Map<String, Object> data, String key, Map<String, Object> overlay) {
        Map<String, Object> existing = getMap(data.get(key));
        existing.putAll(overlay);
        if (existing.size() == 0) {
            data.remove(key);
        } else {
            data.put(key, existing);
        }
    }

    @SuppressWarnings("unchecked")
    protected Map<String, Object> getMap(Object obj) {
        if (obj instanceof Map) {
            return (Map<String, Object>) obj;
        }
        return new HashMap<String, Object>();
    }

    public JsonMapper getJsonMapper() {
        return jsonMapper;
    }

    @Inject
    public void setJsonMapper(JsonMapper jsonMapper) {
        this.jsonMapper = jsonMapper;
    }

    @Override
    public int getPriority() {
        return Priority.DEFAULT;
    }

}
