#include <amscppimglib4/amscppimglib4.hpp>
#include <amscppimglib4/amscppimglib4_intlutil.hpp>

namespace ams
{

    amsbitplane::amsbitplane() : Nx(0), Ny(0), data(NULL), width(Nx), height(Ny)
    {
        return;
    }

    amsbitplane::~amsbitplane()
    {
        if(data!=NULL) {delete[] data; data=NULL;}
        Nx = 0;
        Ny = 0;
        return;
    }

    amsbitplane::amsbitplane(const amsbitplane& other) : Nx(0), Ny(0), data(NULL), width(Nx), height(Ny)
    {
        int res;
        if(this!=&other)
        {
            res = this->resize(Nx,Ny);
            if(res==amsimage_success)
            {
                imglib4::amsimage_plane_copy(
                    this->data, 0, 1, Nx, Ny,
                    other.data, 0, 1, other.Nx, other.Ny,
                    0,0
                );
            }
        }
        return;
    }

    amsbitplane::amsbitplane(amsbitplane&& other) noexcept : Nx(0), Ny(0), data(NULL), width(Nx), height(Ny)
    {
        if(this!=&other)
        {
            this->Nx = other.Nx;
            this->Ny = other.Ny;
            this->data = other.data;

            other.Nx = 0;
            other.Ny = 0;
            other.data = NULL;
        }
        return;
    }
    
    amsbitplane& amsbitplane::operator=(const amsbitplane& other)
    {
        int res;
        if(this!=&other)
        {
            res = this->resize(Nx,Ny);
            if(res==amsimage_success)
            {
                imglib4::amsimage_plane_copy(
                    this->data, 0, 1, Nx, Ny,
                    other.data, 0, 1, other.Nx, other.Ny,
                    0,0
                );
            }
        }
        return *this;
    }
    
    amsbitplane& amsbitplane::operator=(amsbitplane&& other) noexcept
    {
        if(this!=&other)
        {
            if(this->data!=NULL) {delete[] this->data; this->data=NULL;}
            this->Nx = 0; this->Ny = 0;

            this->Nx = other.Nx;
            this->Ny = other.Ny;
            this->data = other.data;

            other.Nx = 0;
            other.Ny = 0;
            other.data = NULL;
        }
        return *this;
    }

    int amsbitplane::resize(int _Nx, int _Ny)
    {
        int ret = amsimage_success;
        
        uint8_t *newdata = NULL;

        _Nx = (_Nx<0) ? 0 : _Nx;
        _Ny = (_Ny<0) ? 0 : _Ny;

        if(_Nx == Nx && _Ny == Ny)
        {
            return ret; //no resize necessary
        }

        if(_Nx==0 || _Ny == 0)
        {
            //zero size image
            if(data!=NULL) {delete[] data; data=NULL;}
            Nx = 0;
            Ny = 0;
            return ret;
        }
        
        newdata = new(std::nothrow) uint8_t[_Nx*_Ny];
        if(newdata==NULL)
        {
            ret = amsimage_failure;
            return ret;
        }

        imglib4::amsimage_planeregion_set(
            newdata,
            1,_Nx,_Ny,0,
            0,0,_Nx,_Ny,
            0
        );

        if(data!=NULL)
        {
            imglib4::amsimage_plane_copy(
                newdata,0,1,_Nx,_Ny,
                data,0,1,Nx,Ny,
                0,0
            );
        }

        if(data!=NULL) {delete[] data; data=NULL;}
        data = newdata;
        Nx = _Nx;
        Ny = _Ny;

        return ret;
    }


        uint8_t amsbitplane::get(int I, int J) const
        {
            uint8_t ret = 0;
            if(I>=0 && I<Nx && J>=0 && J<Ny)
            {
                ret = data[I+Nx*J];
            }
            return ret;
        }

        int amsbitplane::set(int I, int J, uint8_t val)
        {
            int ret = amsimage_success;
            if(I>=0 && I<Nx && J>=0 && J<Ny)
            {
                data[I+Nx*J] = val;
            }
            else
                ret = amsimage_failure;
            return ret;
        }

        uint8_t& amsbitplane::at(int I, int J)
        {
            return data[I + Nx*J];
        }

        const uint8_t& amsbitplane::at(int I, int J) const
        {
            return data[I + Nx*J];
        }

        uint8_t& amsbitplane::operator()(int I, int J)
        {
            return data[I + Nx*J];
        }

        const uint8_t& amsbitplane::operator()(int I, int J) const
        {
            return data[I + Nx*J];
        }

        uint8_t& amsbitplane::operator[](long I)
        {
            return data[I];
        }

        const uint8_t& amsbitplane::operator[](long I) const
        {
            return data[I];
        }

    // Lots of Thread Functions //

    void amsbitplane_transpose_tf(
        int threadnum,
        int nthreads,
        uint8_t *datato,
        const uint8_t *datafrom,
        int Nx, int Ny
    )
    {
        int64_t I,I0,I1,Is,N,Ia,Ib,Ix,Iy;
        N = Nx*Ny;
        Is = N/nthreads;
        I0 = (threadnum)*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%Nx;
            Iy = I/Nx;
            Ia = Iy + Ny*Ix;
            Ib = Ix+Nx*Iy;

            datato[Ia] = datafrom[Ib];

        }
        return;
    }

    void amsbitplane_rotcw_tf(
        int threadnum,
        int nthreads,
        uint8_t *datato,
        const uint8_t *datafrom,
        int Nx, int Ny
    )
    {
        int64_t I,I0,I1,Is,N,Ia,Ib,Ix,Iy;
        N = Nx*Ny;
        Is = N/nthreads;
        I0 = (threadnum)*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%Nx;
            Iy = I/Nx;
            Ia = Iy + Ny*Ix;
            Ib = Ix+Nx*(Ny-Iy-1);

            datato[Ia] = datafrom[Ib];
        }
        return;
    }

    void amsbitplane_rotccw_tf(
        int threadnum,
        int nthreads,
        uint8_t *datato,
        const uint8_t *datafrom,
        int Nx, int Ny
    )
    {
        int64_t I,I0,I1,Is,N,Ia,Ib,Ix,Iy;
        N = Nx*Ny;
        Is = N/nthreads;
        I0 = (threadnum)*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%Nx;
            Iy = I/Nx;
            Ia = Iy + Ny*Ix;
            Ib = (Nx-Ix-1)+Nx*Iy;

            datato[Ia] = datafrom[Ib];
        }
        return;
    }

    void amsbitplane_flipx_tf(
        int threadnum,
        int nthreads,
        uint8_t *datato,
        const uint8_t *datafrom,
        int Nx, int Ny
    )
    {
        int64_t I,I0,I1,Is,N,Ia,Ib,Ix,Iy;
        N = Nx*Ny;
        Is = N/nthreads;
        I0 = (threadnum)*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%Nx;
            Iy = I/Nx;
            Ia = Ix + Nx*Iy;
            Ib = (Nx-Ix-1)+Nx*Iy;

            datato[Ia] = datafrom[Ib];
        }
        return;
    }

    void amsbitplane_flipy_tf(
        int threadnum,
        int nthreads,
        uint8_t *datato,
        const uint8_t *datafrom,
        int Nx, int Ny
    )
    {
        int64_t I,I0,I1,Is,N,Ia,Ib,Ix,Iy;
        N = Nx*Ny;
        Is = N/nthreads;
        I0 = (threadnum)*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%Nx;
            Iy = I/Nx;
            Ia = Ix + Nx*Iy;
            Ib = Ix+Nx*(Ny-Iy-1);

            datato[Ia] = datafrom[Ib];
        }
        return;
    }

    void amsbitplane_setall_tf(
        int threadnum,
        int nthreads,
        amsbitplane *img,
        const uint8_t val
    )
    {
        int64_t I,I0,I1,Is,N,Ix,Iy;

        N = img->Nx*img->Ny;
        Is = N/nthreads; Is = (Is<1) ? 1 : Is;
        I0 = Is*threadnum;
        I1 = (threadnum<(nthreads-1)) ? Is*(threadnum+1) : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%img->Nx;
            Iy = I/img->Nx;

            img->data[Ix + img->Nx*Iy] = val;
        }
    }

    // Methods again //

    amsbitplane amsbitplane::subimage(int I0, int J0, int I1, int J1) const
    {
        amsbitplane ret;
        int _Nx,_Ny;
        _Nx = ((I1-I0) < 0) ? 0 : I1-I0;
        _Ny = ((J1-J0) < 0) ? 0 : J1-J0;

        ret.resize(_Nx,_Ny);

        imglib4::amsimage_plane_copy(
            ret.data, 0, 1, ret.Nx, ret.Ny,
            this->data, 0, 1, Nx,Ny,
            I0,J0
        );

        return ret;
    }

    amsbitplane amsbitplane::transpose()
    {
        amsbitplane ret;
        int64_t N;

        ret.resize(Ny,Nx);
        N = Nx*Ny;

        imglib4::threaded_execute(
            amsbitplane_transpose_tf,
            N,
            ret.data,this->data,Nx,Ny
        );

        return ret;
    }

    amsbitplane amsbitplane::rotcw()
    {
        amsbitplane ret;
        int64_t N;

        ret.resize(Ny,Nx);
        N = Nx*Ny;

        imglib4::threaded_execute(
            amsbitplane_rotcw_tf,
            N,
            ret.data,this->data,Nx,Ny
        );

        return ret;
    }

    amsbitplane amsbitplane::rotccw()
    {
        amsbitplane ret;
        int64_t N;

        ret.resize(Ny,Nx);
        N = Nx*Ny;

        imglib4::threaded_execute(
            amsbitplane_rotccw_tf,
            N,
            ret.data,this->data,Nx,Ny
        );

        return ret;
    }

    amsbitplane amsbitplane::flipx()
    {
        amsbitplane ret;
        int64_t N;

        ret.resize(Nx,Ny);
        N = Nx*Ny;

        imglib4::threaded_execute(
            amsbitplane_flipx_tf,
            N,
            ret.data,this->data,Nx,Ny
        );

        return ret;
    }

    amsbitplane amsbitplane::flipy()
    {
        amsbitplane ret;
        int64_t N;

        ret.resize(Nx,Ny);
        N = Nx*Ny;

        imglib4::threaded_execute(
            amsbitplane_flipy_tf,
            N,
            ret.data,this->data,Nx,Ny
        );

        return ret;
    }

    void amsbitplane::setall(uint8_t val)
    {
        imglib4::threaded_execute(
            amsbitplane_setall_tf,
            this->Nx*this->Ny,
            this,
            val
        );
        return;
    }



};