package org.regalib.map;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import org.regalib.collection.ArraySet;
import org.regalib.reference.FieldProperty;

public class FieldMap implements Map<String,Object> {
   private static final Map<Class, Map<String, Field>> fieldsMap = new LinkedHashMap<>();

   private Object source;
   private ArraySet<String> keys;
   private Map.Entry<String,Object>[] entries;

   public FieldMap() {
      this(null);
   }

   public FieldMap(Object source) {
      this.source = source;
      buildEntries();
   }

   public String toString() {
      return String.valueOf(source);
   }

   public Object getSource() {
      return source;
   }

   public void setSource(Object src) {
      source = src;
      buildEntries();
   }

   public Map.Entry getEntry(String key) {
      if (key != null) {
         int index = keys.indexOf(key);
         if (index >= 0)
            return entries[index];
      }
      return null;
   }

   public Object get(Object key) {
      if (key != null) {
         int index = keys.indexOf(key.toString());
         if (index >= 0)
            return entries[index].getValue();
      }
      return null;
   }

   public Object put(String key, Object value) {
      if (key != null) {
         int index = keys.indexOf(key);
         if (index >= 0)
            return entries[index].setValue(value);
      }
      return null;
   }

   public void putAll(Map map) {
      if (map != null) {
         for (Object key : map.keySet()) {
            if (key != null) {
               put(key.toString(), map.get(key));
            }
         }
      }
   }

   public boolean containsKey(Object key) {
      return (key == null) ? false : (keys.indexOf(key.toString()) >= 0);
   }

   public boolean containsValue(Object value) {
      for (Map.Entry entry : entries) {
         Object v = entry.getValue();
         if ((v == null && value == null) || (v != null && v.equals(value)))
            return true;
      }
      return false;
   }

   public Set<Map.Entry<String, Object>> entrySet() {
      return new ArraySet<Map.Entry<String, Object>>(entries);
   }

   public boolean isEmpty() {
      return keys.isEmpty();
   }

   public Set<String> keySet() {
      return keys;
   }

   public int size() {
      return keys.size();
   }

   public Collection values() {
      List values = new ArrayList();
      for (Map.Entry entry : entries)
         values.add(entry.getValue());
      return values;
   }

   public void clear() {
      throw new UnsupportedOperationException("Cannot modify the property list of a FieldMap");
   }

   public Object remove(Object key) {
      throw new UnsupportedOperationException("Cannot modify the property list of a FieldMap");
   }

   public boolean equals(Object that) {
      if (source == null) {
         return that == null || (that instanceof FieldMap && ((FieldMap) that).source == null);
      }
      return (that instanceof FieldMap && source.equals(((FieldMap) that).source))
              || (that instanceof Map && ((Map) that).equals(this))
              || source.equals(that);
   }

   public int hashCode() {
      return (source == null) ? 0 : source.hashCode();
   }

   private void buildEntries() {
      Class type = (source == null) ? null : source.getClass();
      Map<String,Field> map = fieldsMap.get(type);
      if (map == null) {
         map = new LinkedHashMap<>();
         fieldsMap.put(type, map);
         for (Field f : type.getFields()) {
            int mods = f.getModifiers();
            if (!Modifier.isStatic(mods) && Modifier.isPublic(mods))
               map.put(f.getName(), f);
         }
      }
      String[] keys = map.keySet().toArray(new String[map.size()]);
      this.keys = new ArraySet(keys);
      entries = new Map.Entry[keys.length];
      Iterator<Field> it = map.values().iterator();
      for (int i = 0; i < keys.length; i++)
         entries[i] = new FieldProperty(it.next(), source);
   }
}
