/*
 * Copyright (c) 2004-2017, by Alex Leigh.
 * All rights reserved.
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE
 * The copyright notice above does not evidence any
 * actual or intended publication of such source code.
 */

package leigh.eo;

import leigh.eo.util.EOFilteringIterator;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;

import java.io.Serializable;
import java.util.*;

/**
 * <code>EOLoop</code> acts as an ordered array containing <code>EO</code> objects.
 *
 * @author Alex Leigh
 */
public class EOLoop implements Serializable, Iterable<EO> {
    private static final long serialVersionUID = 1960993024123977523L;
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(EOLoop.class);
    private ArrayList<EO> array;
    private transient EditTracker editTracker;

    public EOLoop(List<EO> eos) {
        allocateStorage();
        array.addAll(eos);
    }

    public EOLoop() {
        // Intentionally Blank
    }

    public static MechaLogger getLogger() {
        return logger;
    }

    public EOLoop clear() {
        if (array != null) array.clear();
        return this;
    }

    public EditTracker getEditTracker() {
        return editTracker;
    }

    /**
     * Set the edit tracker for this loop. This will have the side-effect of also setting the edit tracker
     * for all EOs contained within the loop.
     */
    public void setEditTracker(EditTracker editTracker) {
        this.editTracker = editTracker;

        // We may not have storage yet, no big deal.
        if (array != null) {
            for (EO eo : array) {
                eo.setEditTracker(editTracker);
            }
        }
    }

    /**
     * Find the first EO from this loop which contains a key matching value.
     *
     * @since mk14 mod10 (CRYSTAL)
     */
    public EO findFirst(String key, Serializable value) {
        if (array != null) {
            for (EO eo : array) {
                Serializable s = eo.getValue(key);
                if (s != null && s.equals(value)) return eo;
            }
        }
        return null;
    }

    public EOLoop clone() {
        EOLoop l = new EOLoop();
        l.setEditTracker(editTracker);
        l.addAll(array);
        return l;
    }

    /**
     * Sort the loop using the given comparator.
     */
    public void sort(Comparator<EO> comparator) {
        array.sort(comparator);
    }

    private void allocateStorage() {
        if (array == null) array = new ArrayList<>();
    }

    /**
     * Return the index of the given object in this loop, or -1 if it does not exist.
     */
    public int indexOf(Object o) {
        return array.indexOf(o);
    }

    /**
     * Returns <code>true</code> if the loop only contains EOs of the given type, otherwise, returns
     * <code>false</code>. If the loop is empty, <code>true</code> is returned.
     */
    public boolean onlyContains(String apiName) {
        for (EO eo : array) {
            if (!eo.isType(apiName)) return false;
        }
        return true;
    }

    /**
     * Return an iterator which will only return EOs compatible with the given type.
     *
     * @param apiType
     * @return
     */
    public Iterator<EO> iterator(String apiType) {
        return new EOFilteringIterator(array.iterator(), apiType);
    }

    public EOLoop add(final EO value) {
        allocateStorage();
        array.add(value);
        if (editTracker != null) {
            value.setEditTracker(editTracker);
            editTracker.objAdded(this, value);
        }

        return this;
    }

    public void addAll(final Collection<EO> values) {
        allocateStorage();
        // We do this instead of addAll() to ensure that the event receiver is called
        for (EO eo : values) {
            add(eo);
        }
    }

    public void addAll(EOLoop loop) {
        for (EO eo : loop) add(eo);
    }

    public void set(int idx, EO eo) {
        array.set(idx, eo);
    }

    public EO remove(final int index) {
        if (array == null) return null;
        EO child = array.remove(index);
        if (editTracker != null) editTracker.objRemoved(this, child, index);
        return child;
    }

    public void remove(Collection<EO> toRemove) {
        for (EO eo : toRemove) {
            remove(array.indexOf(eo));
        }
    }

    public void remove(EO eo) {
        remove(array.indexOf(eo));
    }

    // It's 2145h. She probably thinks I'm looking at midget porn instead of working.

    public int size() {
        if (array == null) return 0;
        return array.size();
    }

    public EO get(final int idx) {
        if (array == null) return null;
        return array.get(idx);
    }

    public ArrayList<EO> getArray() {
        return array;
    }

    public void setArray(ArrayList<EO> array) {
        this.array = array;
    }

    @Override
    public String toString() {
        return "EOLoop{" +
                "array=" + array +
                "} ";
    }

    @Override
    public Iterator<EO> iterator() {
        allocateStorage();
        return array.iterator();
    }

    /**
     * Remove all the objects from this loop after the given index position. For example, given 0,1,2,3 calling
     * with 1 will result in 2,3 being removed.
     */
    public void removeAfter(int idx) {
        if (idx >= array.size()) return;

        for (int i = array.size() - 1; i > idx; i--) {
            remove(i);
        }
    }

    /**
     * Move the given EO up in the list position. If the EO is already at the top of the list, no change will occur.
     * If the EO is not in the list, a runtime exception is thrown.
     *
     * @param obj
     */
    public void moveUp(EO obj) {
        int i = array.indexOf(obj);
        if (i == -1) throw new RuntimeException("EO not found in loop.");
        if (i == 0) return;
        Collections.swap(array, i, i - 1);
    }

    /**
     * Move the given EO down in the list position. If the EO is already at the bottom of the list, no change will
     * occur.
     * <p>
     * If the EO is not in the list, a runtime exception is thrown.
     *
     * @param obj
     */
    public void moveDown(EO obj) {
        int i = array.indexOf(obj);
        if (i == -1) throw new RuntimeException("EO not found in loop.");
        if (i == array.size() - 1) return;
        Collections.swap(array, i, i + 1);
    }

}
