/*
 * Copyright 2009, 2010 Lari Natri <lari.natri@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package compid.sequences;

import net.aavalla.utils.general.Randomizer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.ListIterator;
import java.util.NoSuchElementException;

/**
 *
 * @author Lari Natri
 */
public abstract class Sequence implements Iterable, Cloneable
{

    private Component[] sequence = null;
    private int preCalcHash;

    protected Sequence() {}
    
    protected final void initializeSequence(Component[] seqArray) throws Exception
    {
        if (this.sequence != null) {
            throw new Exception("Object already initialized!");
        }
        for (int i = 0; i < seqArray.length; ++i) {
            if (this.isCompatible(seqArray[i]) == false) {
                throw new Exception("Not compatible");
            }
        }
        this.sequence = new Component[seqArray.length];
        System.arraycopy(seqArray, 0, this.sequence, 0, seqArray.length);
        this.preCalcHash = this.preCalculateHashCode();

    }

    protected final void initializeSequenceUnsafe(Component[] seqArray)
    {
        this.sequence = seqArray;
        this.preCalcHash = this.preCalculateHashCode();
    }

    /**
     * Returns a *copy* of the  sequence as an array of Components
     * @return components
     */
    public final Component[] getAsArray()
    {
        Component[] result = new Component[sequence.length];
        System.arraycopy(sequence, 0, result, 0, sequence.length);
        return result;
    }

    public final Component getComponentAt(int index) {
        return this.sequence[index];
    }

    public final boolean containsAllComponentsOf(Sequence seq)
    {
        HashSet<Component> set = new HashSet<Component>(sequence.length,1);
        for (int i = 0; i < sequence.length; ++i) {
            set.add(sequence[i]);
        }
        for (int i = 0; i < seq.getLength(); ++i) {
            if (set.contains(seq.sequence[i]) == false) {
                return false;
            }
        }
        return true;
    }

    public final boolean containsComponent(Component sc)
    {
        for (int i = 0; i < sequence.length; ++i) {
            if (sequence[i].equals(sc)) {
                return true;
            }
        }
        return false;
    }

    public final int getLength()
    {
        return sequence.length;
    }

    /**
     * Returns a new Sequence object which has the same component composition
     * as this Sequence, but with the components in randomized order.
     * @return randomized sequence
     */
    public Sequence componentsInRandomOrder() {
        ArrayList<Component> list = new ArrayList<Component>();

        ListIterator<Component> iter = this.iterator();
        while (iter.hasNext()) {
            list.add((Component)iter.next());
        }

        Component[] newSeqArray = new Component[list.size()];
        int newLoc = 0;
        while (!list.isEmpty()) {
            int rand = Randomizer.nextInt(list.size());
            newSeqArray[newLoc++] = list.get(rand);
            list.remove(rand);
        }

        Sequence resultSequence = null;
        try {
            resultSequence = (Sequence)this.clone();
        } catch (CloneNotSupportedException ex) { ex.printStackTrace(); } // never happens

        resultSequence.sequence = newSeqArray;
        
        return resultSequence;
    }

    /**
     * Returns a new Sequence with the components of this Sequence in
     * reversed order
     * @return reversed sequence
     */
    public Sequence componentsInReversedOrder() {
        int seqLength = this.sequence.length;
        Component[] newSeqArray = new Component[seqLength];
        for (int i = 0; i < seqLength; ++i) {
            newSeqArray[i] = this.sequence[seqLength-1-i];
        }
        
        Sequence resultSequence = null;
        try {
            resultSequence = (Sequence)this.clone();
        } catch (CloneNotSupportedException ex) { ex.printStackTrace(); } // never happens

        resultSequence.sequence = newSeqArray;

        return resultSequence;
    }

    public boolean equalsLoosely(Sequence other) {
        if (other==null) return false;
        if (this.getLength()!=other.getLength()) return false;
        for (int i=0; i<this.getLength(); ++i) {
            if (!this.sequence[i].equalsLoosely(other.sequence[i])) return false;
        }
        return true;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }

        if (this.getLength() != ((Sequence)obj).getLength()) {
            return false;
        }
        final Component[] other = ((Sequence) obj).getAsArray();
        for (int i = 0; i < this.sequence.length; ++i) {
            if (this.sequence[i].equals(other[i])==false) {
                return false;
            }
        }
        return true;
    }

    @Override
    public int hashCode() {
        return this.preCalcHash;
    }


    private int preCalculateHashCode()
    {
        int hash = 5;
        for (int i = 0; i < this.sequence.length; ++i) {
            hash = 37 * hash + (this.sequence[i] == null ? 0 : this.sequence[i].hashCode());
        }
        return hash;
    }

    @Override
    public String toString()
    {
        StringBuilder buf = new StringBuilder();

        for (int i = 0; i < sequence.length; ++i) {
            buf.append(sequence[i].toString());
        }
        return buf.toString();
    }

    @Override
    public ListIterator<Component> iterator()
    {
        return new SequenceIterator<Component>();
    }

    public abstract boolean isCompatible(Sequence seq);

    public abstract boolean isCompatible(Component sc);

    public abstract Sequence getCompatibleComponents();

    final class SequenceIterator<SequenceComponent>
            implements ListIterator<SequenceComponent>
    {

        int current = 0;

        @Override
        public boolean hasNext()
        {
            if (current < sequence.length) {
                return true;
            }
            return false;
        }

        @Override
        @SuppressWarnings("unchecked")
        public SequenceComponent next() throws NoSuchElementException
        {
            if (this.hasNext()) {
                return (SequenceComponent) sequence[current++];
            } else {
                throw new NoSuchElementException();
            }
        }

        @Override
        public boolean hasPrevious()
        {
            if (current > 0) {
                return true;
            } else {
                return false;
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public SequenceComponent previous() throws NoSuchElementException
        {
            if (this.hasPrevious()) {
                return (SequenceComponent) sequence[--current];

            } else {
                throw new NoSuchElementException();
            }
        }

        @Override
        public int nextIndex()
        {
            return current;
        }

        @Override
        public int previousIndex()
        {
            return current - 1;
        }

        @Override
        public void set(SequenceComponent arg0)
        {
            throw new UnsupportedOperationException("Not supported ever.");
        }

        @Override
        public void add(SequenceComponent arg0)
        {
            throw new UnsupportedOperationException("Not supported ever.");
        }

        @Override
        public void remove()
        {
            throw new UnsupportedOperationException("Not supported ever.");
        }

    } // end class SequenceIterator

} // end class Sequence
