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

namespace ams
{

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

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

    int amsimage::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[4*_Nx*_Ny];
        if(newdata==NULL)
        {
            ret = amsimage_failure;
            return ret;
        }

        imglib4::amsimage_region_set(
            newdata,
            _Nx,_Ny,
            0,0,_Nx,_Ny,
            amspixel(0,0,0,0)
        );

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

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

        return ret;
    }

    amsimage::amsimage(const amsimage& other) : 
        Nx(0), Ny(0),data(NULL),width(Nx),height(Ny)
    {
        int res;
        // Nx = 0;
        // Ny = 0;
        // data = NULL;

        if(this!=&other)
        {
            res = this->resize(other.Nx,other.Ny);
            if(res==amsimage_success)
            {
                imglib4::amsimage_region_copy(
                    data, Nx, Ny,
                    other.data, other.Nx, other.Ny,
                    0,0
                );
            }
        }
    }

    amsimage::amsimage(amsimage&& other) noexcept :
        Nx(0), Ny(0),data(NULL),width(Nx),height(Ny)
    {
        int res;
        // Nx = 0;
        // Ny = 0;
        // data = NULL;

        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;
    }

    amsimage& amsimage::operator=(const amsimage& other)
    {
        int res;

        if(this!=&other)
        {
            res = this->resize(other.Nx,other.Ny);
            if(res==amsimage_success)
            {
                imglib4::amsimage_region_copy(
                    data, Nx, Ny,
                    other.data, other.Nx, other.Ny,
                    0,0
                );
            }
        }

        return *this;
    }
    
    amsimage& amsimage::operator=(amsimage&& 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;
    }

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

        ret.resize(_Nx,_Ny);

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

        return ret;
    }

    void amsimage_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[0 + 4*Ia] = datafrom[0 + 4*Ib];
            datato[1 + 4*Ia] = datafrom[1 + 4*Ib];
            datato[2 + 4*Ia] = datafrom[2 + 4*Ib];
            datato[3 + 4*Ia] = datafrom[3 + 4*Ib];
        }
        return;
    }

    amsimage amsimage::transpose() const
    {
        amsimage ret;
        int64_t N;

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

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

        return ret;
    }

    void amsimage_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[0 + 4*Ia] = datafrom[0 + 4*Ib];
            datato[1 + 4*Ia] = datafrom[1 + 4*Ib];
            datato[2 + 4*Ia] = datafrom[2 + 4*Ib];
            datato[3 + 4*Ia] = datafrom[3 + 4*Ib];
        }
        return;
    }

    amsimage amsimage::rotcw() const
    {
        amsimage ret;
        int64_t N;

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

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

        return ret;
    }

    void amsimage_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[0 + 4*Ia] = datafrom[0 + 4*Ib];
            datato[1 + 4*Ia] = datafrom[1 + 4*Ib];
            datato[2 + 4*Ia] = datafrom[2 + 4*Ib];
            datato[3 + 4*Ia] = datafrom[3 + 4*Ib];
        }
        return;
    }

    amsimage amsimage::rotccw() const
    {
        amsimage ret;
        int64_t N;

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

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

        return ret;
    }

    void amsimage_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[0 + 4*Ia] = datafrom[0 + 4*Ib];
            datato[1 + 4*Ia] = datafrom[1 + 4*Ib];
            datato[2 + 4*Ia] = datafrom[2 + 4*Ib];
            datato[3 + 4*Ia] = datafrom[3 + 4*Ib];
        }
        return;
    }

    amsimage amsimage::flipx() const
    {
        amsimage ret;
        int64_t N;

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

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

        return ret;
    }

    void amsimage_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[0 + 4*Ia] = datafrom[0 + 4*Ib];
            datato[1 + 4*Ia] = datafrom[1 + 4*Ib];
            datato[2 + 4*Ia] = datafrom[2 + 4*Ib];
            datato[3 + 4*Ia] = datafrom[3 + 4*Ib];
        }
        return;
    }

    amsimage amsimage::flipy() const
    {
        amsimage ret;
        int64_t N;

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

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

        return ret;
    }


    amspixel amsimage::get_pixel(int I,int J) const
    {
        amspixel ret;
        if(I>=0 && I<Nx && J>=0 && J<Ny)
        {
            ret.R = data[0 + 4*(I + Nx*J)];
            ret.G = data[1 + 4*(I + Nx*J)];
            ret.B = data[2 + 4*(I + Nx*J)];
            ret.A = data[3 + 4*(I + Nx*J)];
        }
        return ret;
    }

    int amsimage::set_pixel(int I,int J,const amspixel pix)
    {
        int ret = amsimage_failure;
        if(I>=0 && I<Nx && J>=0 && J<Ny)
        {
            ret = amsimage_success;
            data[0 + 4*(I + Nx*J)] = pix.R;
            data[1 + 4*(I + Nx*J)] = pix.G;
            data[2 + 4*(I + Nx*J)] = pix.B;
            data[3 + 4*(I + Nx*J)] = pix.A;
        }
        return ret;
    }

    int amsimage::set_pixel(int I,int J,uint8_t R, uint8_t G, uint8_t B, uint8_t A)
    {  
        return this->set_pixel(I,J,amspixel(R,G,B,A));
    }

    uint8_t& amsimage::operator[](int ind)
    {
        return data[ind];
    }

    const uint8_t& amsimage::operator[](int ind) const
    {
        return data[ind];
    }

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

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


    int read_image(const char *fname, amsimage* image)
    {
        int ret = amsimage_success;
        int res;
        amscimglib4_image *im2 = NULL;

        if(image==NULL)
        {
            ret = amsimage_failure;
            printf("read_image: Error: image pointer is null.\n");
            return ret;
        }

        res = amscimglib4_image_new(&im2,1,1);
        if(res!=amscimglib4_success)
        {
            ret = amsimage_failure;
            printf("read_image: Error: c image struct failed to allocate.\n");
            return ret;
        }

        amscimglib4_readimage(fname,im2);
        //you can NOT move buffers. The im2 buffers are created with malloc, not new. No pseudo-move-semantics for you!

        //copy buffers
        res =  image->resize(im2->sizex,im2->sizey);
        if(res!=amsimage_success)
        {
            ret = amsimage_failure;
            amscimglib4_image_delete(&im2);
            printf("read_image: Error: c++ image failed to allocate.\n");
            return ret;
        }

        //both structures have the same memory layout, so the internal copy function works
        imglib4::amsimage_region_copy(
            image->data,image->Nx,image->Ny,
            im2->data,im2->sizex,im2->sizey,
            0,0
        );


        amscimglib4_image_delete(&im2);

        return ret;
    }

    int write_image(const char *fname, amsimage* image)
    {
        int ret = amsimage_success;
        int res;
        amscimglib4_image *im2 = NULL;

        if(image==NULL)
        {
            ret = amsimage_failure;
            printf("write_image: Error: image pointer is null.\n");
            return ret;
        }

        res = amscimglib4_image_new(&im2,image->Nx,image->Ny);
        if(res!=amscimglib4_success)
        {
            ret = amsimage_failure;
            printf("write_image: Error: c image struct failed to allocate.\n");
            return ret;
        }

        //both structures have the same memory layout, so the internal copy function works
        imglib4::amsimage_region_copy(
            im2->data,im2->sizex,im2->sizey,
            image->data,image->Nx,image->Ny,
            0,0
        );

        amscimglib4_writeimage(fname,im2);

        amscimglib4_image_delete(&im2);
        return ret;
    }

};