package com.github.droidfu.cachefu;

import java.io.IOException;
import java.util.ArrayList;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Superclass of all list objects to be stored in {@link ModelCache}.
 * 
 * Operates just as standard cached object, and contains an array list of objects.
 * 
 * <b>Must</b> be initialized with the class of the objects stored, as this is used in
 * parcelling/unparcelling.
 * 
 * In order to ensure thread-safe use of list (such as iteration), use the {@link #getList()}
 * method, creating a copy of the list in its current state.
 * 
 * @author michaelengland
 * 
 * @param <CO>
 *            Type of cached models to be stored in list
 */
public class CachedList<CO extends CachedModel> extends CachedModel {

    /**
     * Class type of object list
     */
    protected Class<? extends CachedModel> clazz;
    /**
     * List of objects.
     */
    protected ArrayList<CO> list;

    /**
     * Simple parameter-less constructor. <b>Must</b> also have parameter-less constructor in
     * subclasses in order for parceling to work.
     * 
     * <b>Do not use this constructor when creating a list, use one setting class instead.</b>
     */
    public CachedList() {
        list = new ArrayList<CO>();
    }

    /**
     * Constructor setting variables from parcel. Same as using a blank constructor and calling
     * readFromParcel.
     * 
     * @param source
     *            Parcel to be read from.
     * @throws IOException
     */
    public CachedList(Parcel source) throws IOException {
        super(source);
    }

    /**
     * Constructor initializing class of objects stored.
     * 
     * @param clazz
     *            Required for parcelling and unparcelling of list
     */
    public CachedList(Class<? extends CachedModel> clazz) {
        this.clazz = clazz;
        list = new ArrayList<CO>();
    }

    /**
     * Constructor initializing class of objects stored as well as initial length of list.
     * 
     * @param clazz
     *            Required for parcelling and unparcelling of list
     * @param initialLength
     *            Initial length of list
     */
    public CachedList(Class<? extends CachedModel> clazz, int initialLength) {
        this.clazz = clazz;
        list = new ArrayList<CO>(initialLength);
    }

    /**
     * Constructor initializing class of objects stored as well as id used in key generation.
     * 
     * @param clazz
     *            Required for parcelling and unparcelling of list
     * @param id
     *            ID of new list (used when generating cache key).
     */
    public CachedList(Class<? extends CachedModel> clazz, String id) {
        super(id);
        this.clazz = clazz;
        list = new ArrayList<CO>();
    }

    /**
     * Synchronized method to get a copy of the list in its current state. This should be used when
     * iterating over the list in order to avoid thread-unsafe operations.
     * 
     * @return Copy of list in its current state
     */
    public synchronized ArrayList<CO> getList() {
        return new ArrayList<CO>(list);
    }

    /**
     * Synchronized method used to append an object to the list.
     * 
     * @param cachedObject
     *            Object to add to list
     */
    public synchronized void add(CO cachedObject) {
        list.add(cachedObject);
    }

    /**
     * Synchronized method used to set an object at a location in the list.
     * 
     * @param index
     *            Index of item to set
     * @param cachedObject
     *            Object to set in list
     */
    public synchronized void set(int index, CO cachedObject) {
        list.set(index, cachedObject);
    }

    /**
     * Synchronized method used to get an object from the live list.
     * 
     * @param index
     *            Index of item in list
     * @return Item in list
     */
    public synchronized CO get(int index) {
        return list.get(index);
    }

    /**
     * Synchronized method used to return size of list.
     * 
     * @return Size of list
     */
    public synchronized int size() {
        return list.size();
    }

    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public synchronized boolean equals(Object o) {
        if (!(o instanceof CachedList)) {
            return false;
        }
        @SuppressWarnings("rawtypes")
        CachedList that = (CachedList) o;
        return clazz.equals(that.clazz) && list.equals(that.list);
    }

    /**
     * @see com.github.droidfu.cachefu.CachedModel#createKey(java.lang.String)
     */
    @Override
    public synchronized String createKey(String id) {
        return "list_" + id;
    }

    @Override
    public boolean reload(ModelCache modelCache) {
        // First reload list object
        boolean result = super.reload(modelCache);
        // Then reload each item in list. Sometimes a ConcurrentModificationException occurs.
        // Changed implementation so that it doesn't use an Iterator any more.
        // Uglier but hopefully that will solve the issue.
        for (int i = 0; i < list.size(); i++) {
            CachedModel listModel = list.get(i);
            if (listModel.reload(modelCache)) {
                result = true;
            }
        }
        return result;
    }

    /**
     * @see com.github.droidfu.cachefu.CachedModel#reloadFromCachedModel(com.github.droidfu.cachefu.ModelCache,
     *      com.github.droidfu.cachefu.CachedModel)
     */
    @Override
    public synchronized boolean reloadFromCachedModel(ModelCache modelCache, CachedModel cachedModel) {
        @SuppressWarnings("unchecked")
        CachedList<CO> cachedList = (CachedList<CO>) cachedModel;
        clazz = cachedList.clazz;
        list = cachedList.list;
        return false;
    }

    /**
     * Creator object used for parcelling
     */
    public static final Creator<CachedList<CachedModel>> CREATOR = new Parcelable.Creator<CachedList<CachedModel>>() {

        /**
         * @see android.os.Parcelable.Creator#createFromParcel(android.os.Parcel)
         */
        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Override
        public CachedList<CachedModel> createFromParcel(Parcel source) {
            try {
                return new CachedList(source);
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }

        /**
         * @see android.os.Parcelable.Creator#newArray(int)
         */
        @SuppressWarnings("unchecked")
        @Override
        public CachedList<CachedModel>[] newArray(int size) {
            return new CachedList[size];
        }

    };

    /**
     * @see com.github.droidfu.cachefu.CachedModel#readFromParcel(android.os.Parcel)
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public void readFromParcel(Parcel source) throws IOException {
        super.readFromParcel(source);
        // Read class from parcel, then load class and use creator to generate new object from data
        String className = source.readString();
        try {
            clazz = (Class<? extends CachedModel>) Class.forName(className);
            list = source.createTypedArrayList((Creator) clazz.getField("CREATOR").get(this));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @see com.github.droidfu.cachefu.CachedModel#writeToParcel(android.os.Parcel, int)
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        // Write class name to parcel before object, so can be loaded correctly back in
        dest.writeString(clazz.getCanonicalName());
        dest.writeTypedList(list);
    }

}
