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

    void amsbitplane::clear()
    {
        this->setall(0);
    }

    float amsbitplane::getf(int x, int y) const
    {
        float ret = 0.0f;
        int p;
        p = get(x,y);
        ret = (float)p/255.0;
        
        return ret;
    }

    int amsbitplane::setf(int x, int y, float f)
    {
        int ret = amsimage_failure;
        int p;

        if(x>=0 && x<Nx && y>=0 && y<Ny)
        {
            ret = amsimage_success;

            p = (int)(f*255.0);
            if(p<0) p = 0;
            if(p>255) p = 255;
            set(x,y,p);
        }

        return ret;
    }

    float amsbitplane::interpolate(float x, float y) const
    {
        float ret = 0.0f;

        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;
        float 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 = getf(xi,yi);
            p10 = getf(xip,yi);
            p01 = getf(xi,yip);
            p11 = getf(xip,yip);

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

        return ret;
    }

    void amsbitplane_rescale_tf(
        int threadnum,
        int nthreads,
        amsbitplane *imgto,
        const amsbitplane *imgfrom
    )
    {
        int Nx = imgto->Nx;
        int Ny = imgto->Ny;
        int64_t I,I0,I1,Is,N,Ix,Iy;
        float 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->setf(Ix,Iy,p);
        }
    }

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

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

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

        return ret;
    }

    void amsimage_apply_bitplane_tf(
        int threadnum,
        int nthreads,
        amsbitplane *imgto,
        const amsbitplane *imgfrom,
        int x0, int y0
    )
    {
        int dx,dy;
        int64_t N;
        int64_t I,I0,I1,Is,Ix,Iy,Ia,Ib;
        uint8_t v1,v2;

        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; if(Is<1) Is=1;
        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;

            v1 = imgto->data[Ia];
            v2 = imgfrom->data[Ib];
            if(v1<v2)
                imgto->data[Ia] = v2;
        }

        return;
    }

    int amsbitplane::apply_bitplane(int x0, int y0, const amsbitplane *bp)
    {
        int ret = amsimage_success;

        int dx,dy;
        int64_t N;

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

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

        imglib4::threaded_execute(
            amsimage_apply_bitplane_tf, N,
            this,bp,x0,y0
        );

        return ret;
    }

    




}; //end namespace ams