package org.forks.jsonrpc;

import java.util.ArrayList;
import java.util.Collection;

import android.util.Pair;

import org.json.JSONException;
import org.json.JSONObject;

@SuppressWarnings("serial")
public final class ManyList extends ArrayList<RpcObject> {
    private final RpcObjectMeta mItemMeta;
    private final ArrayList<RpcObject> mNewItems = new ArrayList<RpcObject>();
    private final ArrayList<RpcObject> mDeletedItems = new ArrayList<RpcObject>();

    public ManyList(RpcObjectMeta itemMeta) {
        mItemMeta = itemMeta;
    }

    public static ManyList create(String itemObjectPath)
            throws JsonRpcException {
        return new ManyList(RpcObjectMeta.get(itemObjectPath));
    }

    @Override
    public void add(int index, RpcObject object) {
        checkMetaType(object);
        addNew(object);
        super.add(index, object);
    }

    @Override
    public boolean add(RpcObject object) {
        checkMetaType(object);
        addNew(object);
        return super.add(object);
    }

    @Override
    public boolean addAll(Collection<? extends RpcObject> collection) {
        checkMetaTypes(collection);
        addNewObjects(collection);
        return super.addAll(collection);
    }

    @Override
    public boolean addAll(int location,
            Collection<? extends RpcObject> collection) {
        checkMetaTypes(collection);
        addNewObjects(collection);
        return super.addAll(location, collection);
    }

    @Override
    public void clear() {
        for (RpcObject item : this) {
            addDeleted(item);
        }
        super.clear();
    }

    @Override
    public RpcObject remove(int index) {
        RpcObject result = super.remove(index);
        addDeleted(result);
        return result;
    }

    @Override
    protected void removeRange(int fromIndex, int toIndex) {
        for (int i = fromIndex; i < toIndex; i++) {
            addDeleted(get(i));
        }
        super.removeRange(fromIndex, toIndex);
    }

    @Override
    public boolean removeAll(Collection<?> collection) {
        // it is too complex to test modify track, and rarely used
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean retainAll(Collection<?> collection) {
        // it is too complex to test modify track, and rarely used
        throw new UnsupportedOperationException();
    }

    @Override
    public RpcObject set(int index, RpcObject object) {
        RpcObject result = super.set(index, object);
        addDeleted(result);
        addNew(object);
        return result;
    }

    private void addNewObjects(Collection<? extends RpcObject> collection) {
        for (RpcObject item : collection) {
            addNew(item);
        }
    }

    private void addNew(RpcObject object) {
        if (!mDeletedItems.remove(object)) {
            mNewItems.add(object);
        }
    }

    private void addDeleted(RpcObject object) {
        if (!mNewItems.remove(object)) {
            mDeletedItems.add(object);
        }
    }

    JSONObject toJson() throws JSONException {
        JSONObject result = new JSONObject();
        Pair<Collection<RpcObject>, Collection<RpcObject>> changedUnChanged = getChangedUnChangedItems();
        putArrayField(result, "_data", changedUnChanged.second);
        putArrayField(result, "_new", getNewItems());
        putArrayField(result, "_deleted", getDeletedItems());
        putArrayField(result, "_modified", changedUnChanged.first);

        return result;
    }

    private static void putArrayField(JSONObject result, String fieldName,
            Collection<RpcObject> list) throws JSONException {
        if (!list.isEmpty()) {
            result.put(fieldName, RpcTypeParser.toJson(list));
        }
    }

    private void checkMetaTypes(Collection<? extends RpcObject> collection) {
        if (!RpcFacade.sDebug) {
            return;
        }

        for (RpcObject rpcObject : collection) {
            checkMetaType(rpcObject);
        }
    }

    private void checkMetaType(RpcObject object) {
        if (!RpcFacade.sDebug) {
            return;
        }

        RpcObjectMeta objMeta = object.getMeta();
        if (objMeta != mItemMeta) {
            throwMetaMisMatchError(objMeta);
        }
    }

    private void throwMetaMisMatchError(RpcObjectMeta objMeta) {
        String msg = "RpcObject %s can not add to %s ManyList";
        msg = String.format(msg, objMeta.getName(), mItemMeta.getName());
        throw new IllegalArgumentException(msg);
    }

    Collection<RpcObject> getNewItems() {
        return mNewItems;
    }

    Collection<RpcObject> getDeletedItems() {
        return mDeletedItems;
    }

    Pair<Collection<RpcObject>, Collection<RpcObject>>
            getChangedUnChangedItems() {
        ArrayList<RpcObject> changed = new ArrayList<RpcObject>();
        ArrayList<RpcObject> unchanged = new ArrayList<RpcObject>();

        for (RpcObject item : this) {
            if (!isNewObject(item)) {
                if (!item.isModified()) {
                    unchanged.add(item);
                } else {
                    changed.add(item);
                }
            }
        }

        return new Pair<Collection<RpcObject>, Collection<RpcObject>>(changed,
                unchanged);
    }

    void acceptChanges() {
        mNewItems.clear();
        mDeletedItems.clear();
        for (RpcObject obj : this) {
            obj.acceptChanges();
        }
    }

    private boolean isNewObject(RpcObject item) {
        return mNewItems.contains(item);
    }
}
