// Copyright (C) 2006  Davis E. King (davisking@users.sourceforge.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_ARRAY2D_KERNEl_C_
#define DLIB_ARRAY2D_KERNEl_C_

#include "array2d_kernel_abstract.h"
#include "../algs.h"
#include "../assert.h"
#include "../interfaces/enumerable.h"

namespace dlib
{


    template <
        typename array2d_base // is an implementation of array2d_kernel_abstract.h
        >
    class array2d_kernel_c : public enumerable<typename array2d_base::type> 
    {

        /*!
            CONVENTION
                - if (obj.size() > 0) then
                    - rows == an array of size obj.nr() row objects and
                      each row object in the array has a valid pointer to its
                      associated row in obj.
                  - else
                    - rows == 0

        !*/

        typedef typename array2d_base::type T;
    public:
        typedef typename array2d_base::type type;
        typedef typename array2d_base::mem_manager_type mem_manager_type;


        // -----------------------------------

        class row 
        {

            friend class array2d_kernel_c;
        public:
            long nc (
            ) const { return data->nc(); }

            const T& operator[] (
                long column
            ) const;

            T& operator[] (
                long column
            );

        private:

            typename array2d_base::row* data; 

            // restricted functions
            row(){}
            row(row&);
            row& operator=(row&);
        };

        // -----------------------------------

        array2d_kernel_c (
        ) : 
            rows(0)
        {
        }

        virtual ~array2d_kernel_c (
        ) { clear(); }

        long nc (
        ) const { return obj.nc(); }

        long nr (
        ) const { return obj.nr(); }

        row& operator[] (
            long row
        );

        const row& operator[] (
            long row
        ) const; 

        void swap (
            array2d_kernel_c& item
        )
        {
            exchange(obj,item.obj);
            exchange(rows,item.rows);
        }

        void clear (
        )
        {
            obj.clear();
            if (rows != 0)
            {
                delete [] rows;
                rows = 0;
            }
        }

        void set_size (
            long nr__,
            long nc__
        );

        bool at_start (
        ) const { return obj.at_start();; }

        void reset (
        ) const { obj.reset(); }

        bool current_element_valid (
        ) const { return obj.current_element_valid(); }

        const T& element (
        ) const;

        T& element (
        ); 

        bool move_next (
        ) const { return obj.move_next(); }

        unsigned long size (
        ) const { return obj.size(); }

    private:

        array2d_base obj;
        row* rows;

    };

    template <
        typename array2d_base
        >
    inline void swap (
        array2d_kernel_c<array2d_base>& a, 
        array2d_kernel_c<array2d_base>& b 
    ) { a.swap(b); }


    template <
        typename array2d_base
        >
    void serialize (
        const array2d_kernel_c<array2d_base>& item, 
        std::ostream& out 
    )   
    {
        try
        {
            serialize(item.nc(),out);
            serialize(item.nr(),out);

            item.reset();
            while (item.move_next())
                serialize(item.element(),out);
            item.reset();
        }
        catch (serialization_error e)
        { 
            throw serialization_error(e.info + "\n   while serializing object of type array2d_kernel_c"); 
        }
    }

    template <
        typename array2d_base
        >
    void deserialize (
        array2d_kernel_c<array2d_base>& item, 
        std::istream& in
    )   
    {
        try
        {
            long nc, nr;
            deserialize(nc,in);
            deserialize(nr,in);

            item.set_size(nr,nc);

            while (item.move_next())
                deserialize(item.element(),in); 
            item.reset();
        }
        catch (serialization_error e)
        { 
            item.clear();
            throw serialization_error(e.info + "\n   while deserializing object of type array2d_kernel_c"); 
        }
    }

    template < typename array2d_base >
    const rectangle get_rect (
        const array2d_kernel_c<array2d_base>& item
    ) { return rectangle(0, 0, item.nc()-1, item.nr()-1); }

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // member function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    template <
        typename array2d_base
        >
    const typename array2d_base::type& array2d_kernel_c<array2d_base>::
    element (
    ) const
    {
        // make sure requires clause is not broken
        DLIB_CASSERT(current_element_valid() == true,
               "\tT& array2d::element()()"
               << "\n\tYou can only call element() when you are at a valid one."
               << "\n\tthis:    " << this
        );

        return obj.element();
    }

// ----------------------------------------------------------------------------------------

    template <
        typename array2d_base
        >
    typename array2d_base::type& array2d_kernel_c<array2d_base>::
    element (
    ) 
    {
        // make sure requires clause is not broken
        DLIB_CASSERT(current_element_valid() == true,
               "\tT& array2d::element()()"
               << "\n\tYou can only call element() when you are at a valid one."
               << "\n\tthis:    " << this
        );

        return obj.element();
    }

// ----------------------------------------------------------------------------------------

    template <
        typename array2d_base
        >
    void array2d_kernel_c<array2d_base>::
    set_size (
        long nr_,
        long nc_
    )
    {
        // make sure requires clause is not broken
        DLIB_CASSERT((nc_ > 0 && nr_ > 0) ||
                (nc_ == 0 && nr_ == 0),
               "\tvoid array2d::set_size(long nr_, long nc_)"
               << "\n\tYou have to give a non zero nc and nr or just make both zero."
               << "\n\tthis:    " << this
               << "\n\tnc_:  " << nc_ 
               << "\n\tnr_: " << nr_ 
        );

        obj.set_size(nr_,nc_);

        // set up the rows array
        if (rows != 0)
            delete [] rows;

        try
        {
            rows = new row[obj.nr()];
        }
        catch (...)
        {
            rows = 0;
            obj.clear();
            throw;
        }

        for (long i = 0; i < obj.nr(); ++i)
        {
            rows[i].data = &obj[i];
        }
    }

// ----------------------------------------------------------------------------------------

    template <
        typename array2d_base
        >
    typename array2d_kernel_c<array2d_base>::row& array2d_kernel_c<array2d_base>::
    operator[] (
        long row
    )
    {
        // make sure requires clause is not broken
        DLIB_CASSERT(row < nr() && row >= 0,
               "\trow& array2d::operator[](long row)"
               << "\n\tThe row index given must be less than the number of rows."
               << "\n\tthis:     " << this
               << "\n\trow:      " << row 
               << "\n\tnr(): " << nr()
        );

        return rows[row];
    }

// ----------------------------------------------------------------------------------------

    template <
        typename array2d_base
        >
    const typename array2d_kernel_c<array2d_base>::row& array2d_kernel_c<array2d_base>::
    operator[] (
        long row
    ) const
    {
        // make sure requires clause is not broken
        DLIB_CASSERT(row < nr() && row >= 0,
               "\tconst row& array2d::operator[](long row) const"
               << "\n\tThe row index given must be less than the number of rows."
               << "\n\tthis:     " << this
               << "\n\trow:      " << row 
               << "\n\tnr(): " << nr()
        );

        return rows[row];
    }

// ----------------------------------------------------------------------------------------

    template <
        typename array2d_base
        >
    const typename array2d_base::type& array2d_kernel_c<array2d_base>::row::
    operator[] (
        long column
    ) const
    {
        // make sure requires clause is not broken
        DLIB_CASSERT(column < nc() && column >= 0,
               "\tconst T& array2d::operator[](long column) const"
               << "\n\tThe column index given must be less than the number of columns."
               << "\n\tthis:    " << this
               << "\n\tcolumn:  " << column 
               << "\n\tnc(): " << nc()
        );

        return (*data)[column];
    }

// ----------------------------------------------------------------------------------------

    template <
        typename array2d_base
        >
    typename array2d_base::type& array2d_kernel_c<array2d_base>::row::
    operator[] (
        long column
    )
    {
        // make sure requires clause is not broken
        DLIB_CASSERT(column < nc() && column >= 0,
               "\tT& array2d::operator[](long column)"
               << "\n\tThe column index given must be less than the number of columns."
               << "\n\tthis:    " << this
               << "\n\tcolumn:  " << column 
               << "\n\tnc(): " << nc()
        );

        return (*data)[column];
    }

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_ARRAY2D_KERNEl_C_

