/*
 * Copyright (c) 2002-2009 "Neo Technology,"
 *     Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 * 
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.neo4j.kernel.impl.util;

import java.util.HashSet;
import java.util.Set;

public class IntArray
{
    private int[] rels;
    private int arrayCount = 0;

    public IntArray() 
    {
        rels = new int[2];
    }
    
    public IntArray( int initialCapacity )
    {
        rels = new int[ initialCapacity ];
    }
    
    public IntArray( int[] array )
    {
        rels = array;
        arrayCount = array.length;
    }
    
    public void add( int id )
    {
        if ( arrayCount == rels.length )
        {
            int newRels[] = new int[rels.length * 2];
            System.arraycopy( rels, 0, newRels, 0, rels.length );
            rels = newRels;
        }
        rels[arrayCount++] = id;
    }
    
    public void addAll( IntArray array )
    {
        if ( array == null )
        {
            return;
        }
        if ( array.length() + arrayCount > rels.length )
        {
            int newSize = rels.length * 2;
            while ( array.length() + arrayCount > newSize )
            {
                newSize = newSize * 2;
            }
            int newRels[] = new int[newSize];
            System.arraycopy( rels, 0, newRels, 0, arrayCount );
            rels = newRels;
        }
        System.arraycopy( array.getArray(), 0, rels, arrayCount, 
            array.length() );
        arrayCount += array.length();
    }
    
    public int length()
    {
        return arrayCount;
    }
    
    public int[] getArray()
    {
        return rels;
    }
    
    public int get( int i )
    {
        assert i >= 0 && i < arrayCount;
        return rels[i];
    }
    
    public static IntArray composeNew( IntArray src, IntArray add, 
        IntArray remove )
    {
        if ( remove == null )
        {
            if ( src == null )
            {
                return add;
            }
            if ( add != null )
            {
                IntArray newArray = new IntArray( add.length() + src.length() );
                newArray.addAll( src );
                newArray.addAll( add );
                return newArray;
            }
            return src;
        }
        else
        {
            if ( src == null && add == null )
            {
                return null;
            }
            int newLength = 0;
            if ( add != null )
            {
                newLength += add.length();
            }
            if ( src != null )
            {
                newLength += src.length();
            }
            IntArray newArray = new IntArray( newLength );
            Set<Integer> set = new HashSet<Integer>( remove.length() + 1, 
                1.0f );
            for ( int i = 0; i < remove.length(); i++ )
            {
                set.add( remove.get( i ) );
            }
            newArray.addAll( src );
            for ( int i = 0; i < newArray.length(); i++ )
            {
                int value = newArray.get( i );
                if ( set.contains( value ) )
                {
                    boolean swapSuccessful = false;
                    for ( int j = newArray.length() - 1; j >= i + 1; j--)
                    {
                        int backValue = newArray.get( j );
                        newArray.arrayCount--;
                        if ( !set.contains( backValue) )
                        {
                            newArray.getArray()[i] = backValue;
                            swapSuccessful = true;
                            break;
                        }
                    }
                    if ( !swapSuccessful ) // all elements from pos in remove
                    {
                        newArray.arrayCount--;
                    }
                }
            }
            if ( add != null )
            {
                for ( int i = 0; i < add.length(); i++ )
                {
                    int value = add.get( i );
                    if ( !set.contains( value ) )
                    {
                        newArray.add( value );
                    }
                }
            }
           return newArray;
        }
    }

/*    public static IntArray composeNew( IntArray src, IntArray add, 
        IntArray remove )
    {
        if ( remove == null )
        {
            if ( src == null )
            {
                return add;
            }
            if ( add != null )
            {
                IntArray newArray = new IntArray( add.length() + src.length() );
                newArray.addAll( src );
                newArray.addAll( add );
                return newArray;
            }
            return src;
        }
        else
        {
            if ( src == null )
            {
                return null;
            }
            // TODO: merge add and remove array then append add array to result
            int[] newArray = new int[src.length()];
            int[] srcArray = src.getArray();
            int[] removeArray = remove.getArray();
            assert removeArray.length <= srcArray.length;
            System.arraycopy( srcArray, 0, newArray, 0, src.length() );
            Arrays.sort( newArray );
            Arrays.sort( removeArray );
            int newArraySize = newArray.length;
            int startSearchFrom = 0;
            int checkTo = removeArray.length; // can decrease if we swap
            for ( int i = 0; i < checkTo; i++ )
            {
                int index = binarySearch( newArray, startSearchFrom, 
                    newArraySize, removeArray[i] );
                if ( index >= 0 )
                {
                    // next search can start from here
                    startSearchFrom = index + 1;
                    
                    // find element we can swap with
                    for ( int j = newArraySize - 1; j >= startSearchFrom; j-- )
                    {
                        int swapValue = newArray[j];
                        int rIndex = binarySearch( removeArray, i, checkTo, 
                            swapValue );

                        newArraySize--;
                        
                        // ok last element in newArray == 
                        // last element in removeArray
                        if ( rIndex > 0 ) 
                        {
                            checkTo--;
                            // continue with second last to see if that is 
                            // swapable
                        }
                        else // we swap with this element
                        {
                            newArray[index] = newArray[j];
                            break;
                        }
                    }
                }
            }
            IntArray newIntArray = new IntArray( newArray );
            newIntArray.arrayCount = newArraySize;
            return newIntArray;
        }
    }
    
    private static int binarySearch( int[] array, int startOffset, 
        int endOffset, int value )
    {
        int pIndex = startOffset + ( endOffset - startOffset ) / 2;

        int valueFound = array[pIndex];
        if ( valueFound == value )
        {
            return pIndex;
        }
        if ( pIndex == startOffset ) // search exhausted
        {
            return -1;
        }
        
        if ( value < valueFound )
        {
            return binarySearch( array, startOffset, pIndex, value );
        }
        else
        {
            return binarySearch( array, pIndex, endOffset, value );
        }
    }*/
}