/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _DECAF_LANG_ARRAYPOINTER_H_
#define _DECAF_LANG_ARRAYPOINTER_H_

#include <decaf/util/Config.h>
#include <decaf/lang/System.h>
#include <decaf/lang/exceptions/NullPointerException.h>
#include <decaf/lang/exceptions/IndexOutOfBoundsException.h>
#include <decaf/lang/exceptions/IllegalArgumentException.h>
#include <decaf/util/concurrent/atomic/AtomicInteger.h>
#include <decaf/util/Comparator.h>
#include <decaf/util/Arrays.h>
#include <memory>
#include <typeinfo>
#include <algorithm>

namespace decaf {
namespace lang {

    /**
     * Decaf's implementation of a Smart Pointer that is a template on a Type
     * and is Thread Safe if the default Reference Counter is used.  This Pointer
     * type allows for the substitution of different Reference Counter implementations
     * which provide a means of using invasive reference counting if desired using
     * a custom implementation of <code>ReferenceCounter</code>.
     * <p>
     * The Decaf smart pointer provide comparison operators for comparing Pointer
     * instances in the same manner as normal pointer, except that it does not provide
     * an overload of operators ( <, <=, >, >= ).  To allow use of a Pointer in a STL
     * container that requires it, Pointer provides an implementation of std::less.
     *
     * @since 1.0
     */
    template< typename T >
    class ArrayPointer {
    private:

        struct ArrayData {
        private:

            ArrayData(const ArrayData&);
            ArrayData& operator= (const ArrayData&);

        public:

            T* value;
            int length;
            decaf::util::concurrent::atomic::AtomicInteger refs;

            ArrayData() : value(NULL), length(0), refs(1) {}
            ArrayData(T* value, int length) : value(value), length(length), refs(1) {
                if( value != NULL && length <= 0 ) {
                    throw decaf::lang::exceptions::IllegalArgumentException(
                        __FILE__, __LINE__, "Non-NULL array pointer cannot have a size <= zero" );
                }

                if( value == NULL && length > 0 ) {
                    throw decaf::lang::exceptions::IllegalArgumentException(
                        __FILE__, __LINE__, "NULL array pointer cannot have a size > zero" );
                }
            }

            bool release() {
                if (this->refs.decrementAndGet() < 1) {
                    return true;
                }
                return false;
            }
        };

        typedef void (*deletionFuncPtr)(ArrayData* p);

    private:

        ArrayData* array;

        // Pointer to our internal delete function, allows incompletes.
        deletionFuncPtr onDelete;

    public:

        typedef T* PointerType;              // type returned by operator->
        typedef T& ReferenceType;            // type returned by operator*
        typedef const T& ConstReferenceType; // type returned by const operator*

    public:

        /**
         * Default Constructor
         *
         * Initialized the contained array pointer to NULL, using the subscript operator
         * results in an exception unless reset to contain a real value.
         */
        ArrayPointer() : array(new ArrayData()), onDelete(onDeleteFunc) {}

        /**
         * Create a new ArrayPointer instance and allocates an internal array that is sized
         * using the passed in size value.
         *
         * @param size
         *      The size of the array to allocate for this ArrayPointer instance.
         */
        ArrayPointer(int size) : array(NULL), onDelete(onDeleteFunc) {

            if (size == 0) {
                return;
            }

            try {
                T* value = new T[size];
                this->array = new ArrayData(value, size);
                decaf::util::Arrays::fill(value, size, 0, size, T());
            } catch (std::exception& ex) {
                throw ex;
            } catch (...) {
                throw std::bad_alloc();
            }
        }

        /**
         * Create a new ArrayPointer instance and allocates an internal array that is sized
         * using the passed in size value.  The array elements are initialized with the given
         * value.
         *
         * @param size
         *      The size of the array to allocate for this ArrayPointer instance.
         * @param fillWith
         *      The value to initialize each element of the newly allocated array with.
         */
        ArrayPointer(int size, const T& fillWith) : array(NULL), onDelete(onDeleteFunc) {

            if (size == 0) {
                return;
            }

            try {
                T* value = new T[size];
                decaf::util::Arrays::fill(value, size, 0, size, fillWith);
                this->array = new ArrayData(value, size);
            } catch (std::exception& ex) {
                throw ex;
            } catch (...) {
                throw std::bad_alloc();
            }
        }

        /**
         * Explicit Constructor, creates an ArrayPointer that contains value with a
         * single reference.  This object now has ownership until a call to release.
         *
         * @param value
         *      The pointer to the instance of the array we are taking ownership of.
         * @param size
         *      The size of the array this object is taking ownership of.
         */
        explicit ArrayPointer(const PointerType value, int size) : array(NULL), onDelete(onDeleteFunc) {

            try {
                this->array = new ArrayData(value, size);
            } catch (std::exception& ex) {
                throw ex;
            } catch (...) {
                throw std::bad_alloc();
            }
        }

        /**
         * Copy constructor. Copies the value contained in the ArrayPointer to the new
         * instance and increments the reference counter.
         */
        ArrayPointer(const ArrayPointer& value) : array(value.array), onDelete(onDeleteFunc) {
            this->array->refs.incrementAndGet();
        }

        virtual ~ArrayPointer() {
            if (this->array->release() == true) {
                onDelete(this->array);
            }
        }

        /**
         * Resets the ArrayPointer to hold the new value.  Before the new value is stored
         * reset checks if the old value should be destroyed and if so calls delete.
         * Call reset with a value of NULL is supported and acts to set this Pointer
         * to a NULL pointer.
         *
         * @param value
         *      The new array pointer value to contain.
         * @param size
         *      The size of the new array value this object now contains.
         */
        void reset(T* value, int size = 0) {
            ArrayPointer(value, size).swap(*this);
        }

        /**
         * Releases the Pointer held and resets the internal pointer value to Null.  This method
         * is not guaranteed to be safe if the Pointer is held by more than one object or this
         * method is called from more than one thread.
         *
         * @param value - The new value to contain.
         *
         * @return The pointer instance that was held by this Pointer object, the pointer is
         *          no longer owned by this Pointer and won't be freed when this Pointer goes
         *          out of scope.
         */
        T* release() {
            T* temp = this->array->value;
            this->array->value = NULL;
            this->array->length = 0;
            this->array->refs.set(1);
            return temp;
        }

        /**
         * Gets the real array pointer that is contained within this Pointer.  This
         * is not really safe since the caller could delete or alter the pointer but
         * it mimics the STL auto_ptr and gives access in cases where the caller
         * absolutely needs the real Pointer.  Use at your own risk.
         *
         * @return the contained pointer.
         */
        PointerType get() const {
            return this->array->value;
        }

        /**
         * Returns the current size of the contained array or zero if the array is
         * NULL.
         *
         * @return the size of the array or zero if the array is NULL
         */
        int length() const {
            return this->array->length;
        }

        /**
         * Exception Safe Swap Function
         * @param value - the value to swap with this.
         */
        void swap(ArrayPointer& value) {
            std::swap(this->array, value.array);
        }

        /**
         * Creates a new ArrayPointer instance that is a clone of the value contained in this
         * ArrayPointer.
         *
         * @return an ArrayPointer that contains a copy of the data in this ArrayPointer.
         */
        ArrayPointer clone() const {

            if (this->array->length == 0) {
                return ArrayPointer();
            }

            ArrayPointer copy(this->array->length);
            decaf::lang::System::arraycopy(this->array->value, 0, copy.get(), 0, this->array->length);
            return copy;
        }

        /**
         * Assigns the value of right to this Pointer and increments the reference Count.
         * @param right - Pointer on the right hand side of an operator= call to this.
         */
        ArrayPointer& operator=(const ArrayPointer& right) {
            if (this == (void*) &right) {
                return *this;
            }

            ArrayPointer temp(right);
            temp.swap(*this);
            return *this;
        }
        template< typename T1>
        ArrayPointer& operator=(const ArrayPointer<T1>& right) {
            if (this == (void*) &right) {
                return *this;
            }

            ArrayPointer temp(right);
            temp.swap(*this);
            return *this;
        }

        /**
         * Dereference Operator, returns a reference to the Contained value.  This
         * method throws an NullPointerException if the contained value is NULL.
         *
         * @return reference to the contained pointer.
         *
         * @throws NullPointerException if the contained value is Null
         */
        ReferenceType operator[](int index) {
            if (this->array->value == NULL) {
                throw decaf::lang::exceptions::NullPointerException(
                    __FILE__, __LINE__, "ArrayPointer operator& - Pointee is NULL." );
            }

            if (index < 0 || this->array->length <= index) {
                throw decaf::lang::exceptions::IndexOutOfBoundsException(
                    __FILE__, __LINE__, "Array Index %d is out of bounds for this array.", this->array->length );
            }

            return this->array->value[index];
        }
        ConstReferenceType operator[](int index) const {
            if( this->array->value == NULL ) {
                throw decaf::lang::exceptions::NullPointerException(
                    __FILE__, __LINE__, "ArrayPointer operator& - Pointee is NULL." );
            }

            if( index < 0 || this->array->length <= index ) {
                throw decaf::lang::exceptions::IndexOutOfBoundsException(
                    __FILE__, __LINE__, "Array Index %d is out of bounds for this array.", this->array->length );
            }

            return this->array->value[index];
        }

        bool operator!() const {
            return this->array->value == NULL;
        }

        inline friend bool operator==(const ArrayPointer& left, const T* right) {
            return left.get() == right;
        }

        inline friend bool operator==(const T* left, const ArrayPointer& right) {
            return left == right.get();
        }

        inline friend bool operator!=(const ArrayPointer& left, const T* right) {
            return left.get() != right;
        }

        inline friend bool operator!=(const T* left, const ArrayPointer& right) {
            return left != right.get();
        }

        template<typename T1>
        bool operator==(const ArrayPointer<T1>& right) const {
            return this->array->value == right.get();
        }

        template<typename T1>
        bool operator!=(const ArrayPointer<T1>& right) const {
            return this->array->value != right.get();
        }

    private:

        // Internal Static deletion function.
        static void onDeleteFunc(ArrayData* value) {
            delete [] value->value;
            delete value;
        }

    };

    ////////////////////////////////////////////////////////////////////////////
    template<typename T, typename U>
    inline bool operator==(const ArrayPointer<T>& left, const U* right) {
        return left.get() == right;
    }

    ////////////////////////////////////////////////////////////////////////////
    template<typename T, typename U>
    inline bool operator==(const U* left, const ArrayPointer<T>& right) {
        return right.get() == left;
    }

    ////////////////////////////////////////////////////////////////////////////
    template<typename T, typename U>
    inline bool operator!=(const ArrayPointer<T>& left, const U* right) {
        return !(left.get() == right);
    }

    ////////////////////////////////////////////////////////////////////////////
    template<typename T, typename U>
    inline bool operator!=(const U* left, const ArrayPointer<T>& right) {
        return right.get() != left;
    }

    /**
     * This implementation of Comparator is designed to allows objects in a Collection
     * to be sorted or tested for equality based on the value of the value of the actual
     * pointer to the array being contained in this ArrayPointer.  This allows for a basic
     * ordering to be acheived in Decaf containers.
     *
     * Custom implementations are possible where an array of some type has a logical natural
     * ordering such as array of integers where the sum of all ints in the array is used.
     */
    template< typename T >
    class ArrayPointerComparator : public decaf::util::Comparator< ArrayPointer<T> > {
    public:

        virtual ~ArrayPointerComparator() {}

        // Allows for operator less on types that implement Comparable or provide
        // a workable operator <
        virtual bool operator() ( const ArrayPointer<T>& left, const ArrayPointer<T>& right ) const {
            return left.get() < right.get();
        }

        // Requires that the type in the pointer is an instance of a Comparable.
        virtual int compare(const ArrayPointer<T>& left, const ArrayPointer<T>& right) const {
            return left.get() < right.get() ? -1 : right.get() < left.get() ? 1 : 0;
        }

    };

}}

////////////////////////////////////////////////////////////////////////////////
namespace std{

    /**
     * An override of the less function object so that the Pointer objects
     * can be stored in STL Maps, etc.
     */
    template< typename T >
    struct less< decaf::lang::ArrayPointer<T> > {

        typedef decaf::lang::ArrayPointer<T> first_argument_type;
        typedef decaf::lang::ArrayPointer<T> second_argument_type;
        typedef bool result_type;

        bool operator()(const decaf::lang::ArrayPointer<T>& left,
                        const decaf::lang::ArrayPointer<T>& right) const {
            return less<T*>()(left.get(), right.get());
        }
    };
}

#endif /*_DECAF_LANG_ARRAYPOINTER_H_*/
