/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package usda.weru.weps;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import usda.weru.util.PropertyStackContext;
import usda.weru.util.RelativeFileContext;

/**
 *
 * @author Joseph Levin <joelevin@weru.ksu.edu>
 */
public class RunFileDataBeanBridge {
    private static final Logger LOGGER = Logger.getLogger(RunFileDataBeanBridge.class);
    private final RunFileData c_data;
    private final RunFileBean c_bean;
    private final static Map<String, String> DATA_TO_BEAN;
    private final static Map<String, String> BEAN_TO_DATA;
    private final static RunFileBeanPersistenceHandler<String> PERSISTENCE;

    static {
        PERSISTENCE = new RunFileBeanStringPersistence();

        DATA_TO_BEAN = new HashMap <String, String> ();
        BEAN_TO_DATA = new HashMap <String, String> ();

        //loop over all the fields in the RunFileData class looking for contansts that are annotated with a BeanProperty
        for (Field field : RunFileData.class.getFields()) {
            try {
                if (field.isAnnotationPresent(BeanProperty.class)) {
                    BeanProperty annotation = field.getAnnotation(BeanProperty.class);
                    String beanProperty = annotation.value();
                    String dataProperty = field.get(null).toString();

                    DATA_TO_BEAN.put(dataProperty, beanProperty);
                    BEAN_TO_DATA.put(beanProperty, dataProperty);
                }
            } catch (Exception e) {
                LOGGER.error("Initilizing", e);
            }
        }
    }

    private RunFileDataBeanBridge(RunFileData rfd, RunFileBean rfb) {
        c_data = rfd;
        c_bean = rfb;

        initilize();

        c_data.addPropertyChangeListener(new InternalDataListener());
        c_bean.addPropertyChangeListener(new InternalBeanListener());
    }

    public static void bridge(RunFileData rfd, RunFileBean rfb) {
        new RunFileDataBeanBridge(rfd, rfb);
    }

    private void initilize() {
        //c_bean.setLoading(true);
        try {
            for (String dataProperty : DATA_TO_BEAN.keySet()) {
                String dataValue = c_data.getData(dataProperty);

                //deserialize the value
                String beanProperty = DATA_TO_BEAN.get(dataProperty);

                Object value = deserialize(beanProperty, dataValue);
                if(value != null){
                    setBeanValue(beanProperty, value);
                }
            }
        } finally {
            //c_bean.setLoading(false);
        }
    }

    private Object deserialize(String beanProperty, String dataValue) {
        if (dataValue == null) {
            return null;
        }
        final boolean useRelativeFileContext = c_data != null && c_data.fileObject != null;
        if(useRelativeFileContext){
            RelativeFileContext.enter(c_data.fileObject);
        }
        try {
            Method deserializer = PERSISTENCE.getClass().getMethod("deserialize" + capitalize(beanProperty), String.class);
            Object value = deserializer.invoke(PERSISTENCE, dataValue);

            return value;


        } catch (Exception e) {
            LOGGER.error(beanProperty +"="+ dataValue, e);
            return null;
        }
        finally{
            if(useRelativeFileContext){
                RelativeFileContext.exit();
            }
        }
    }

    private String serialize(String beanProperty, Object beanValue) {
        if (beanValue == null) {
            return null;
        }
        final boolean useRelativeFileContext = c_data != null && c_data.fileObject != null;
        if(useRelativeFileContext){
            RelativeFileContext.enter(c_data.fileObject);
        }
        try {
            for (Method serializer : PERSISTENCE.getClass().getMethods()) {
                if (serializer.getName().equals("serialize" + capitalize(beanProperty)) && serializer.getParameterTypes().length == 1) {
                    Object value = serializer.invoke(PERSISTENCE, beanValue);

                    if (value instanceof String) {
                        return (String) value;
                    } else {
                        return null;
                    }
                }
            }
            //no method found
            return null;


        } catch (Exception e) {
            LOGGER.error(beanProperty +"="+ (beanValue != null ? beanValue : "NULL"), e);
            return null;
        }
        finally{
            if(useRelativeFileContext){
                RelativeFileContext.exit();
            }
        }
    }

    private void setBeanValue(String beanProperty, Object beanValue) {
        try {
            if(PropertyStackContext.isPropertyOnStack(c_bean, beanProperty)){
                return;
            }
            for (Method setter : RunFileBean.class.getMethods()) {
                if (setter.getName().equals("set" + capitalize(beanProperty)) && setter.getParameterTypes().length == 1) {
                    try{
                        setter.invoke(c_bean, beanValue);
                        return;
                    }
                    catch(Exception e){
                        LOGGER.error("Error setting beanValue: " + beanProperty + "=" + (beanValue != null ? beanValue.toString() : "NULL"));
                    }
                }
            }


        } catch (Exception e) {
            LOGGER.error(beanProperty +"="+ (beanValue != null ? beanValue : "NULL"), e);
        }
    }

    private void setDataValue(String dataProperty, String dataValue) {
        if(PropertyStackContext.isPropertyOnStack(c_data, dataProperty)){
            return;
        }
        c_data.setData(dataProperty, dataValue);
    }

    private class InternalDataListener implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            assert c_data == evt.getSource() : "Unexpected property change event source.";

            String dataProperty = evt.getPropertyName();
            if (!DATA_TO_BEAN.containsKey(dataProperty)) {
                return;
            }
            String beanProperty = DATA_TO_BEAN.get(dataProperty);

            String dataValue = c_data.getData(dataProperty);
            Object beanValue = deserialize(beanProperty, dataValue);

            setBeanValue(beanProperty, beanValue);
        }
    }

    private class InternalBeanListener implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            assert c_bean == evt.getSource() : "Unexpected property change event source.";

            String beanProperty = evt.getPropertyName();
            if (!BEAN_TO_DATA.containsKey(beanProperty)) {
                return;
            }
            String dataProperty = BEAN_TO_DATA.get(beanProperty);

            Object beanValue = evt.getNewValue();
            String dataValue = serialize(beanProperty, beanValue);

            setDataValue(dataProperty, dataValue);
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public static @interface BeanProperty {

        String value();
    }

    private static String capitalize(String name) {
        char[] c = name.toCharArray();
        c[0] = Character.toUpperCase(c[0]);
        return new String(c);
    }
}
