/*
 * Copyright (c) 2004-2022, 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 lc.esp.eo.sdk;

import lc.mecha.log.MechaLogger;
import lc.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 EO parent;
    private String parentKey;

    public EOLoop(List<EO> eos) {
        allocateStorage();
        // We have no edit tracker by definition, so we don't have to call what does not exist
        array.addAll(eos);
    }

    public EOLoop() {
        // Intentionally Blank
    }

    public static MechaLogger getLogger() {
        return logger;
    }

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

    /**
     * Returns the parent, if any, for this loop.
     */
    public EO getParent() {
        return parent;
    }

    /**
     * Set the parent for this loop. This is called automatically by the parent {@link EO} when the loop is set.
     */
    public void setParent(EO parent, String parentKey) {
        assert (parentKey != null);
        assert (parent != null);
        assert (this.parent == null);
        assert (this.parentKey == null);
        this.parent = parent;
        this.parentKey = parentKey;
    }

    /**
     * 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) {
                Object s = eo.getValue(key);
                if (s != null && s.equals(value)) return eo;
            }
        }
        return null;
    }

    public EOLoop clone() {
        EOLoop l = new EOLoop();
        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.
     */
    public Iterator<EO> iterator(String apiType) {
        return new EOFilteringIterator(array.iterator(), apiType);
    }

    public String getParentKey() {
        return parentKey;
    }

    public EOLoop add(final EO value) {
        allocateStorage();
        array.add(value);

        return this;
    }

    public EOLoop addWithoutTracker(final EO value) {
        allocateStorage();
        array.add(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 index, EO eo) {
        array.set(index, eo);
    }

    public EO remove(final int index) {
        if (array == null) return null;
        EO child = array.remove(index);
        logger.debug("Removed EO: {}", child);
        return child;
    }

    public void remove(Collection<EO> toRemove) {
        for (EO eo : toRemove) {
            logger.debug("Removing EO: [index: {}]", array.indexOf(eo));
            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);
    }

}
