package no.ntnu.capgeminitest.binding.android;

import java.util.HashMap;
import java.util.Map;

import no.ntnu.capgeminitest.binding.android.propertyprovider.PropertyProvider;
import no.ntnu.capgeminitest.binding.android.propertyprovider.PropertyProviderFactory;
import no.ntnu.capgeminitest.data.Property;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;

public class BindingFactory implements LayoutInflater.Factory {

    public static final String TAG = "BindingFactory";
    
    private PropertyProviderFactory propertyProviderFactory;
    private LayoutInflater originalInflater;
    private Map<String, Property<?>> boundProperties;
    
    public BindingFactory(PropertyProviderFactory propertyProviderFactory,
            LayoutInflater originalInflater) {
        this.propertyProviderFactory = propertyProviderFactory;
        this.originalInflater = originalInflater;
    }
    
    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        Log.d(TAG, name);
        
        Map<String, String> bindingAttrs = getBindingAttrs(attrs);
        
        if (bindingAttrs != null) {
            Log.d(TAG, "Binding view: " + name);
            Log.d(TAG, "Trying to inflate with properties.");
            try {
                String prefix = AndroidPrefix.getComponentPrefix(name);
                View view = originalInflater.createView(name, prefix, attrs);
                Log.d(TAG, "Inflated view of type: " + view.getClass().getName());
                
                performBindings(bindingAttrs, view);
                
                return view;
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("Tried to bind view: " + name +
                        " but could not find bindable class: " + e.getMessage());
            }
        } else {       
            return null;
        }
    }
    
    private void performBindings(Map<String, String> attrBindings, View view) {
        PropertyProvider provider = propertyProviderFactory.create(view.getClass());
        
        for (Map.Entry<String, String> entry : attrBindings.entrySet()) {
            String bindingName = entry.getKey();
            String propertyName = entry.getValue();
            
            Property<?> property = provider.getBoundProperty(view, bindingName);
            if (property == null) {
                throw new RuntimeException("Unable to perform binding '" + bindingName +
                        "'='" + propertyName + "': Unknown property.");
            }
            registerProperty(propertyName, property);
        }
    }
    
    private void registerProperty(String propertyName, Property<?> property) {
        if (boundProperties.get(propertyName) == null) {
            boundProperties.put(propertyName, property);
        } else {
            throw new RuntimeException("Property already bound: " + propertyName + ". "
                    + "Support for multiple bindings will appear in a future version.");
        }
    }

    /**
     * Get the binding attributes. Returns a map similar to the following:
     * 
     * <pre>
     * { "textFrom" => "name", "textTo" => "inputName" }
     * </pre>
     * 
     * Meaning that bindings should be created from a property called "name"
     * to the text, and from the text to a property called "inputName".
     * 
     * This corresponds to the (attrName,attrValue) values from the XML.
     */
    private Map<String, String> getBindingAttrs(AttributeSet attrs) {
        Map<String, String> bindingAttrs = new HashMap<String, String>();
        
        int count = attrs.getAttributeCount();
        for (int i = 0; i < count; i++) {
            String attrName = attrs.getAttributeName(i);
            String attrValue = attrs.getAttributeValue(Constants.NAMESPACE, attrName);
            if (attrValue != null) {
                Log.d(TAG, "\tBinding attribute: " + attrName + " = " + attrValue);
                bindingAttrs.put(attrName, attrValue);
            }
        }
        
        if (!bindingAttrs.isEmpty()) {
            return bindingAttrs;
        } else {
            return null;
        }        
    }

    public boolean hasBindings(AttributeSet attrs) {
        return attrs.getAttributeBooleanValue(Constants.NAMESPACE, "bindable", false);
    }
}

@@ -392,16 +392,54 @@
 il.Log;%0A
+import android.view.InflateException;%0A
 import a
@@ -462,24 +462,24 @@
 utInflater;%0A
-
 import andro
@@ -558,16 +558,736 @@
 tory %7B%0A%0A
+    /**%0A     * The ViewFactory is used for testing.%0A     */%0A    interface ViewFactory %7B%0A        public View createView(String name, String prefix, AttributeSet attrs)%0A                throws InflateException, ClassNotFoundException;%0A    %7D%0A    %0A    static class InflaterViewFactory implements ViewFactory %7B%0A        private LayoutInflater inflater;%0A        %0A        public InflaterViewFactory(LayoutInflater inflater) %7B%0A            this.inflater = inflater;%0A        %7D%0A        %0A        @Override%0A        public View createView(String name, String prefix, AttributeSet attrs) %0A                throws InflateException, ClassNotFoundException %7B%0A            return inflater.createView(name, prefix, attrs);%0A        %7D%0A    %7D%0A    %0A
     publ
@@ -1415,39 +1415,31 @@
 ate 
-LayoutInflater originalInflater
+ViewFactory viewFactory
 ;%0A  
@@ -1698,43 +1698,346 @@
 his.
-originalInflater = originalInflater
+viewFactory = new InflaterViewFactory(originalInflater);%0A    %7D%0A    %0A    /**%0A     * Constructor used for testing.%0A     * %0A     */%0A    BindingFactory(PropertyProviderFactory propertyProviderFactory,%0A            ViewFactory viewFactory) %7B%0A        this.propertyProviderFactory = propertyProviderFactory;%0A        this.viewFactory = viewFactory
 ;%0A  
@@ -2516,32 +2516,27 @@
  view = 
-originalInflater
+viewFactory
 .createV
@@ -4606,29 +4606,31 @@
    */%0A    pr
-ivate
+otected
  Map%3CString,

Refactor BindingFactory for testing.