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

namespace ams
{

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

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

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

        imglib4::amsfloatimage_region_set(
            newdata,
            _Nx,_Ny,
            0,0,_Nx,_Ny,
            amsfloatpixel(0,0,0,0)
        );

        if(data!=NULL)
        {
            imglib4::amsfloatimage_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;
    }

    amsfloatimage::amsfloatimage(const amsfloatimage& 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::amsfloatimage_region_copy(
                    data, Nx, Ny,
                    other.data, other.Nx, other.Ny,
                    0,0
                );
            }
        }
    }

    amsfloatimage::amsfloatimage(amsfloatimage&& 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;
    }

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

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

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

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

        ret.resize(_Nx,_Ny);

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

        return ret;
    }

    void amsfloatimage_transpose_tf(
        int threadnum,
        int nthreads,
        float *datato,
        const float *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;
    }

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

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

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

        return ret;
    }

    void amsfloatimage_rotcw_tf(
        int threadnum,
        int nthreads,
        float *datato,
        const float *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;
    }

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

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

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

        return ret;
    }

    void amsfloatimage_rotccw_tf(
        int threadnum,
        int nthreads,
        float *datato,
        const float *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;
    }

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

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

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

        return ret;
    }

    void amsfloatimage_flipx_tf(
        int threadnum,
        int nthreads,
        float *datato,
        const float *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;
    }

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

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

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

        return ret;
    }

    void amsfloatimage_flipy_tf(
        int threadnum,
        int nthreads,
        float *datato,
        const float *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;
    }

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

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

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

        return ret;
    }


    amsfloatpixel amsfloatimage::get_pixel(int I,int J) const
    {
        amsfloatpixel 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 amsfloatimage::set_pixel(int I,int J,const amsfloatpixel 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 amsfloatimage::set_pixel(int I,int J,float R, float G, float B, float A)
    {  
        return this->set_pixel(I,J,amsfloatpixel(R,G,B,A));
    }

    float& amsfloatimage::operator[](int ind)
    {
        return data[ind];
    }

    const float& amsfloatimage::operator[](int ind) const
    {
        return data[ind];
    }

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

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


    int read_image(const char *fname, amsfloatimage* 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::amsfloatimage_region_castcopy(
            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, amsfloatimage* 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::amsfloatimage_region_castcopy(
            im2->data,im2->sizex,im2->sizey,
            image->data,image->Nx,image->Ny,
            0,0
        );

        amscimglib4_writeimage(fname,im2);

        amscimglib4_image_delete(&im2);
        return ret;
    }

    void amsfloatimage::clear()
    {
        this->setall(amsfloatpixel(0,0,0,0));
    }

    void amsfloatimage_setall_tf(
        int threadnum,
        int nthreads,
        amsfloatimage *img,
        const amsfloatpixel color
    )
    {
        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[0 + 4*(Ix + img->Nx*Iy)] = color.R;
            img->data[1 + 4*(Ix + img->Nx*Iy)] = color.G;
            img->data[2 + 4*(Ix + img->Nx*Iy)] = color.B;
            img->data[3 + 4*(Ix + img->Nx*Iy)] = color.A;
        }
    }

    void amsfloatimage::setall(amsfloatpixel color)
    {
        imglib4::threaded_execute(
            amsfloatimage_setall_tf,
            this->Nx*this->Ny,
            this,
            color
        );
        return;
    }

    void amsfloatimage_apply_image_tf(
        int threadnum,
        int nthreads,
        amsfloatimage *imgto,
        const amsfloatimage *imgfrom,
        int x0, int y0
    )
    {
        int dx,dy;
        int64_t N;
        int64_t I,I0,I1,Is,Ix,Iy,Ia,Ib;
        double r1,g1,b1,a1;
        double r2,g2,b2,a2;
        double r3,g3,b3,a3;

        dx = (imgfrom->Nx < (imgto->Nx-x0)) ? imgfrom->Nx : imgto->Nx-x0;
        dx = (dx<0) ? 0 : dx;
        dy = (imgfrom->Ny < (imgto->Ny-y0)) ? imgfrom->Ny : imgto->Ny-y0;
        dy = (dy<0) ? 0 : dy;
        N = dx*dy;

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

        for(I=I0;I<I1;I++)
        {
            Ix = I%dx;
            Iy = I/dx;

            Ia = (Ix + x0) + (Iy + y0)*imgto->Ny;
            Ib = Ix + Iy*imgfrom->Ny;

            r1 = imgto->data[0 + 4*Ia];
            g1 = imgto->data[1 + 4*Ia];
            b1 = imgto->data[2 + 4*Ia];
            a1 = imgto->data[3 + 4*Ia];
            
            r2 = imgfrom->data[0 + 4*Ib];
            g2 = imgfrom->data[1 + 4*Ib];
            b2 = imgfrom->data[2 + 4*Ib];
            a2 = imgfrom->data[3 + 4*Ib];

            r3 = r1 + r2*(1.0-a2);
            g3 = g1 + g2*(1.0-a2);
            b3 = b1 + b2*(1.0-a2);
            a3 = 1.0-(1.0-a1)*(1.0-a2);

            imgto->data[0 + 4*Ia] = r3;
            imgto->data[1 + 4*Ia] = g3;
            imgto->data[2 + 4*Ia] = b3;
            imgto->data[3 + 4*Ia] = a3;
        }

        return;
    }

    int amsfloatimage::apply_image(int x0, int y0, const amsfloatimage *img)
    {
        int ret = amsimage_success;

        int dx,dy;
        int64_t N;

        if(img==NULL)
        {
            ret = amsimage_failure;
            return ret;
        }

        dx = (img->Nx < (this->Nx-x0)) ? img->Nx : this->Nx-x0;
        dx = (dx<0) ? 0 : dx;
        dy = (img->Ny < (this->Ny-y0)) ? img->Ny : this->Ny-y0;
        dy = (dy<0) ? 0 : dy;
        N = dx*dy;

        imglib4::threaded_execute(
            amsfloatimage_apply_image_tf, N,
            this,img,x0,y0
        );

        return ret;
    }

    amsfloatpixel amsfloatimage::interpolate(float x, float y) const
    {
        amsfloatpixel ret = amsfloatpixel(0.0,0.0,0.0,0.0);

        float Nxf,Nyf;
        //just do simple (0,N-1) interpolation for now
        int xi,yi,xip,yip;
        float xif,yif,xr,yr;
        float w00,w01,w10,w11;
        amsfloatpixel p00,p01,p10,p11;

        Nxf = (float)Nx;
        Nyf = (float)Ny;

        if(x>-0.5 && x<Nxf-0.5 && y>-0.5 && y<Nyf-0.5)
        {
            xr = imglib4::mod(x,1.0f);
            yr = imglib4::mod(y,1.0f);
            xif = x - xr;
            yif = y - yr;
            if(xif<0.0f) {xr = 1.0f;}
            if(xif>=(Nxf-1.0f)) {xr = 0.0f;}
            if(yif<0.0f) {yr = 1.0;}
            if(yif>=(Nyf-1.0f)) {yr = 0.0f;}
            xi = (int)xif;
            yi = (int)yif;
            w00 = (1.0-xr)*(1.0-yr);
            w10 = (xr)*(1.0-yr);
            w01 = (1.0-xr)*(yr);
            w11 = (xr)*(yr);

            xip = xi + 1;
            yip = yi + 1;
            if(xi<0) xi = 0;
            if(xi>Nx-1) xi = Nx-1;
            if(yi<0) yi = 0;
            if(yi>Ny-1) yi = Ny-1;
            if(xip>Nx-1) xip = Nx-1;
            if(yip>Ny-1) yip = Ny-1;

            p00 = get_pixel(xi,yi);
            p10 = get_pixel(xip,yi);
            p01 = get_pixel(xi,yip);
            p11 = get_pixel(xip,yip);

            ret = p00*w00 + p10*w10 + p01*w01 + p11*w11;
        }

        return ret;
    }



    void amsfloatimage_rescale_tf(
        int threadnum,
        int nthreads,
        amsfloatimage *imgto,
        const amsfloatimage *imgfrom
    )
    {
        int Nx = imgto->Nx;
        int Ny = imgto->Ny;
        int64_t I,I0,I1,Is,N,Ix,Iy;
        amsfloatpixel p;

        int xs,ys;

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

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

            xs = (float)Ix*((float)(imgfrom->Nx-1))/((float)(imgto->Nx-1));
            ys = (float)Iy*((float)(imgfrom->Ny-1))/((float)(imgto->Ny-1));

            p = imgfrom->interpolate(xs,ys);
            imgto->set_pixel(Ix,Iy,p);
        }
    }

    amsfloatimage amsfloatimage::rescale(int nnx, int nny)
    {
        amsfloatimage ret;
        int res;

        res = ret.resize(nnx,nny);
        if(res!=amsimage_success)
        {
            printf("amsfloatimage::rescale: error, could not allocate return image.\n");
            return ret;
        }

        imglib4::threaded_execute(
            amsfloatimage_rescale_tf, nnx*nny,
            &ret,this
        );

        return ret;
    }

};