/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.collection.lists;

import lombok.experimental.UtilityClass;

import java.io.Serializable;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.List;
import net.ranides.assira.collection.arrays.NativeArrayAllocator;
import net.ranides.assira.collection.arrays.NativeArray;

/**
 * This class converts arrays of primitive values into List view, doing boxing internally.
 *
 * Normally, if you try to wrap primitive array, you won't receive correct result:
 * Arrays.asList(new int[]{1,2,3,4})
 * This will return singleton list with one element of type int[]
 *
 * You can use #wrap methods to create correct list:
 * NativeArrayList.wrap(new char[]{'a','b','c'}) will return {@code List<Character>}
 *
 * Please note, that we can't return correct generic type because of Java limitations: you have to
 * make unchecked cast from {@code List<Object>} on your own, or use unsafe methods like $wrap
 *
 * There is one special ovveride for int[] arrays, because we have specialized IntList interface.
 *
 * Returned lists do not allow insert or remove operations, but support "set"
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
@UtilityClass
public final class NativeArrayList {
    
    private static final int[] EMPTY = new int[0];

    /**
     * Returns list backed by provided array.
     * Changes of array will be reflected by list and vice versa.
     *
     * @param array array
     * @return list
     */
	public static IntList wrap(int[] array) {
        if(null == array) {
            array = EMPTY;
        }
        return new IntegerList(array, 0, array.length);
	}

    /**
     * Returns list backed by provided array subrange.
     * Changes of array will be reflected by list and vice versa.
     *
     * @param array array
     * @param offset offset
     * @param length length
     * @return list
     */
    public static IntList wrap(int[] array, int offset, int length) {
		return new IntegerList(array, offset, offset+length);
		
    }

    /**
     * Returns list backed by provided array.
     * Changes of array will be reflected by list and vice versa.
     *
     * @param array array
     * @return list
     */
    public static List<Object> wrap(Object array) {
        NativeArray na = NativeArray.wrap(array);
        return new NativeList(na, 0, na.size());
	}

    /**
     * Returns list backed by provided array subrange.
     * Changes of array will be reflected by list and vice versa.
     *
     * @param array array
     * @param offset offset
     * @param length length
     * @return list
     */
    public static List<Object> wrap(Object array, int offset, int length) {
        return new NativeList(NativeArray.wrap(array), offset, offset+length);
    }
	
    /**
     * Returns list backed by provided array.
     * Changes of array will be reflected by list and vice versa.
     *
     * It is unsafe version which implicitly infers list type.
     *
     * @param array array
     * @param <Auto> array type
     * @return list
     */
    @SuppressWarnings("unchecked")
    public static <Auto> List<Auto> $wrap(Object array) {
		return (List)wrap(array);
	}

    /**
     * Returns list backed by provided array subrange.
     * Changes of array will be reflected by list and vice versa.
     *
     * It is unsafe version which implicitly infers list type.
     *
     * @param array array
     * @param offset offset
     * @param length length
     * @param <Auto> array type
     * @return list
     */
    @SuppressWarnings("unchecked")
	public static <Auto> List<Auto> $wrap(Object array, int offset, int length) {
		return (List)wrap(array, offset, length);
	}


    /**
     * List implementation which uses NativeArray wrapper
     */
    public static final class NativeList extends AbstractList<Object> implements Serializable {
        
        private static final long serialVersionUID = 1L;
		
		private final int begin;
		private final int end;
		private final NativeArray array;

        /**
         * Creates new list backed by provided NativeArray wrapper
         * @param array array
         * @param begin begin
         * @param end end
         */
        public NativeList(NativeArray array, int begin, int end) {
			this.begin = begin;
			this.end = end;
            this.array = array;
			NativeArrayAllocator.ensureFromTo(array, begin, end);
        }

        @Override
        public Object set(int index, Object element) {
            Object prev = array.get(begin+index);
            array.set(begin+index, element);
            return prev;
        }

        @Override
        public Object get(int index) {
            return array.get(begin+index);
        }

		@Override
		public int size() {
			return end - begin;
		}

		@Override
		public boolean equals(Object object) {
			if(object instanceof NativeList) {
				NativeList list = (NativeList)object;
                int asize = end-begin;
                int bsize = list.end - list.begin;
                if(asize != bsize) {
                    return false;
                }
                return array.equals(list.array, begin, list.begin, asize);
			}
			return super.equals(object);
		}
		
		@Override
		public int hashCode() {
            return array.hashCode(begin, end);
		}
		
    }

    /**
     * List implementation which uses primitive int[] array
     */
    public static final class IntegerList extends AIntList {
        
        private static final long serialVersionUID = 1L;
		
		private final int begin;
		private final int end;
		private final int[] array;

        /**
         * Creates new list backed by provided primitive int[] array
         * @param array array
         * @param begin begin
         * @param end end
         */
        @SuppressWarnings("PMD.ArrayIsStoredDirectly")
        public IntegerList(int[] array, int begin, int end) {
			this.begin = begin;
			this.end = end;
            this.array = array;
			NativeArrayAllocator.ensureFromTo(NativeArray.wrap(array), begin, end);
        }

        @Override
        public int set(int index, int element) {
            int prev = array[begin+index];
            array[begin+index] = element;
            return prev;
        }

        @Override
        public int getInt(int index) {
            return array[begin+index];
        }

		@Override
		public int size() {
			return end - begin;
		}

		@Override
		public boolean equals(Object object) {
			if(object instanceof IntegerList) {
				IntegerList list = (IntegerList)object;
                int asize = end-begin;
                int bsize = list.end - list.begin;
                if(asize != bsize) {
                    return false;
                }
                return NativeArray.wrap(array).equals(NativeArray.wrap(list.array), begin, list.begin, asize);
			}
			return super.equals(object);
		}
		
		@Override
		public int hashCode() {
            return NativeArray.wrap(array).hashCode(begin, end);
		}

        @Override
        public void add(int index, int k) {
            throw new UnsupportedOperationException();
        }

        @Override
        public int removeInt(int index) {
            throw new UnsupportedOperationException();
        }
        
        
        
    }
	
}
