package com.sigopt.model;

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

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.lang.reflect.Type;

public abstract class APIObject {
    Map<String, Object> model;

    public APIObject() {
        this.model = new HashMap<String, Object>();
    }

    protected Object mapGet(String key) {
        return this.model.get(key);
    }

    private Object adaptForStorage(Object value) {
        if (value instanceof APIObject) {
            return ((APIObject) value).model;
        } else if (value instanceof Collection) {
            List<Object> ret = new ArrayList<Object>();
            for (Object v: (Collection) value) {
                ret.add(this.adaptForStorage(v));
            }
            return ret;
        } else if (value instanceof Map) {
            Map<Object, Object> ret = new HashMap<Object, Object>();
            Map valueAsMap = (Map) value;
            for (Object key: valueAsMap.keySet()) {
                ret.put(key, this.adaptForStorage(valueAsMap.get(key)));
            }
            return ret;
        } else {
            return value;
        }
    }

    <T extends Object> void set(String key, T value) {
        this.model.put(key, this.adaptForStorage(value));
    }

    void setAll(Map<String, Object> map) {
        this.model.clear();
        if (map != null) {
            this.model.putAll(map);
        }
    }

    private static final Gson GSON = new GsonBuilder()
        .serializeNulls()
        .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
        .create();

    private static final Gson PRETTY_PRINT_GSON = new GsonBuilder()
        .setPrettyPrinting()
        .serializeNulls()
        .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
        .create();

    public static String toJson(Object value) {
        return GSON.toJson(value);
    }

    public String toJson() {
        return APIObject.toJson(this.model);
    }

    public static Map<String, Object> fromJson(String json, Type t) {
        return GSON.fromJson(json, t);
    }

    @Override
    public String toString() {
        return String.format(
            "<%s@%s> Attributes: %s",
            this.getClass().getName(),
            System.identityHashCode(this),
            this.toJson()
        );
    }

    @Override
    public boolean equals(Object other) {
        return (
            other != null &&
            this.getClass().equals(other.getClass()) &&
            this.model.equals(((APIObject)other).model)
        );
    }

    @Override
    public int hashCode() {
        return this.model.hashCode();
    }

    public static boolean equals(Object a, Object b) {
        return a == null ? b == null : a.equals(b);
    }
}

/**
 * Used for JSON objects with strictly defined fields.
 * Enables users to treat JSON objects as both APIObects and Maps
 */
class StructObject extends APIObject {
    Object get(String key) {
        return this.mapGet(key);
    }
}

/**
 * Used for JSON objects which can arbitrary key/value pairs.
 * Enables users to treat JSON objects as both APIObects and Maps
 */
class MapObject<V> extends APIObject implements Map<String, V> {
    public void clear() {
        this.model.clear();
    }

    boolean containsKey(Object key) {
        return this.model.containsKey(key);
    }

    public boolean containsValue(Object value) {
        return this.model.containsValue(value);
    }

    public Set<Map.Entry<String,V>> entrySet() {
        return this.model.entrySet();
    }

    public boolean equals(Object o) {
        return this.model.equals(o);
    }

    public V get(Object key) {
        return this.model.get(key);
    }

    public int hashCode() {
        return this.model.hashCode();
    }

    public boolean isEmpty() {
        return this.model.isEmpty();
    }

    public Set<String> keySet() {
        return this.model.keySet();
    }

    public V put(String key, V value) {
        return this.model.put(key, value);
    }

    public void putAll(Map<? extends String, ? extends V> m) {
        return this.model.putAll(m);
    }

    public V remove(Object key) {
        return this.model.remove(key);
    }

    public int size() {
        return this.model.size();
    }

    public Collection<V> values() {
        return this.model.values();
    }
}

@@ -443,16 +443,22 @@
  %7D%0A%0A    
+final 
 protecte
@@ -1293,16 +1293,22 @@
  %7D%0A%0A    
+final 
 %3CT exten
@@ -1419,16 +1419,22 @@
  %7D%0A%0A    
+final 
 void set
@@ -3103,16 +3103,32 @@
 t %7B%0A    
+final protected 
 Object g
@@ -3387,24 +3387,30 @@
 g, V%3E %7B%0A    
+final 
 public void 
@@ -3458,16 +3458,22 @@
  %7D%0A%0A    
+final 
 boolean 
@@ -3545,32 +3545,38 @@
 ey);%0A    %7D%0A%0A    
+final 
 public boolean c
@@ -3655,32 +3655,38 @@
 ue);%0A    %7D%0A%0A    
+final 
 public Set%3CMap.E
@@ -3755,32 +3755,38 @@
 t();%0A    %7D%0A%0A    
+final 
 public boolean e
@@ -3843,32 +3843,38 @@
 (o);%0A    %7D%0A%0A    
+final 
 public V get(Obj
@@ -3923,32 +3923,38 @@
 ey);%0A    %7D%0A%0A    
+final 
 public int hashC
@@ -4006,24 +4006,30 @@
 %0A    %7D%0A%0A    
+final 
 public boole
@@ -4083,32 +4083,38 @@
 y();%0A    %7D%0A%0A    
+final 
 public Set%3CStrin
@@ -4166,32 +4166,38 @@
 t();%0A    %7D%0A%0A    
+final 
 public V put(Str
@@ -4266,24 +4266,30 @@
 %0A    %7D%0A%0A    
+final 
 public void 
@@ -4379,24 +4379,30 @@
 %0A    %7D%0A%0A    
+final 
 public V rem
@@ -4465,24 +4465,30 @@
 %0A    %7D%0A%0A    
+final 
 public int s
@@ -4536,24 +4536,30 @@
 %0A    %7D%0A%0A    
+final 
 public Colle

Make some things final