#include "SynthImg.h"
#include <cmath>

bool SynthImg::in_bounds_synth(int i, int j)
{
    bool inbounds = true;
            
    if( (i < 0) || (i >= synth.get_w()) || 
            ( j < 0) || (j > synth.get_h()) )
        inbounds = false;
    return inbounds;
}

bool SynthImg::in_shared_bounds(int i_off, int j_off, int i_exemp, int j_exemp,
                                                    int i_synth, int j_synth )
{
    bool inbounds = true;
    
    int inet_e = i_exemp+i_off;
    int jnet_e = j_exemp+j_off;
    int inet_s = i_synth+i_off;
    int jnet_s = j_synth+j_off;

    if( (inet_e < 0) || (inet_e >= exemplar.get_w()) ||
            (jnet_e < 0) || (jnet_e > exemplar.get_h()) )
        inbounds = false;
    if( (inet_s < 0) || (inet_s >= synth.get_w()) ||
            (jnet_s < 0) || (jnet_s > j_synth) )
        inbounds = false;
    if( (inet_s >= i_synth) && (jnet_s == j_synth) )
        inbounds = false;

    return inbounds;
}


void SynthImg::copy_pix_exemp_synth(int i_exemp, int j_exemp, 
                                    int i_synth, int j_synth)
{
    for(int d = 0; d < exemplar.get_Bpp(); ++d)
        synth.set_data(i_synth,j_synth,d,exemplar.get_data(i_exemp,j_exemp,d));

    PixPtr ptr;
    ptr.i = i_exemp;
    ptr.j = j_exemp;

    ptrs.set_ptr(i_synth, j_synth, ptr);
}



SynthImg::SynthImg(){
    nradius = -1;
}

void SynthImg::init(Img* img, int ws, int hs, const char* file, int rad)
{
    exemplar.init(img);
    synth.init(ws,hs,img->get_Bpp());
    nns.init(file);

    nradius = rad;

    ptrs.init(&exemplar, &synth);
}
    
double SynthImg::compute_normalized_weight(int i_exemp, int j_exemp,
                                            int i_synth, int j_synth)
{
    int num_pixs = 0;  //number compared; to normalize with.
    double weight = 0;

    for( int joff = -nradius; joff <= 0; ++joff)
    {
        for( int ioff = -nradius; ioff <= nradius; ++ioff)
        {
            if(in_shared_bounds(ioff, joff, i_exemp, j_exemp,i_synth,j_synth))
            {
                for(int d = 0; d < exemplar.get_Bpp(); ++d)
                { 
                    double wtemp=(double)exemplar.get_data(i_exemp+ioff,
                                                        j_exemp+joff,d)
                        -(double)synth.get_data(i_synth+ioff,j_synth+joff,d);
                    if(wtemp < 0)
                        wtemp = -wtemp;
                    weight += wtemp;
                }
                num_pixs++;
            }
        }
    }

    if(num_pixs == 0)
    {
        weight = INFINITY;
    }
    else
        weight = weight/(double)num_pixs;

    return weight;
}

                




PixCand SynthImg::normalized_of_best_nn(int x, int y, int i, int j)
{
    int xoff = i - x;
    int yoff = j - y;

    PixCand best;
    best.i = -1;
    best.j = -1;
    best.weight = INFINITY;
    PixPtr orig = ptrs.get_ptr(x,y);

    double weight=INFINITY;

    if((orig.i+nradius >= exemplar.get_w()) || 
        (orig.j+nradius >= exemplar.get_h()) ||
        (orig.i-nradius < 0) || (orig.j - nradius < 0))
    {
        // Throw this pixel out.
    }
    else
        weight = compute_normalized_weight(orig.i+xoff, orig.j+yoff,i,j);

    best.i = orig.i + xoff;
    best.j = orig.j + yoff;
    best.weight = weight;

    NearestNeighbors* neighbors = nns.getNNs(orig.i,orig.j);

    for(int d = 0; d < neighbors->get_k(); ++d)
    {
        weight = INFINITY;
        PixCand cur;
        int x_nn, y_nn;
        WeightedIndex nn = neighbors->getNeighborInd(d);
        orig.i = nn.i;
        orig.j = nn.j;

        if((orig.i+nradius >= exemplar.get_w()) || 
            (orig.j+nradius >= exemplar.get_h()) ||
            (orig.i-nradius < 0) || (orig.j - nradius < 0))
        {
        }
        else
            weight = compute_normalized_weight(orig.i+xoff, orig.j+yoff,i,j);

        cur.i = orig.i + xoff;
        cur.j = orig.j + yoff;
        cur.weight = weight;

        if(cur.weight <= best.weight)
            best = cur;
    }

    return best;
}

void SynthImg::print_nns(int d)
{
    Img temp;
    temp.init(&exemplar);

    srand(time(NULL));
    int i = rand()%exemplar.get_w();
    int j = rand()%exemplar.get_h();
    temp.set_data(i,j,0,255);
    temp.set_data(i,j,1,0);
    temp.set_data(i,j,2,0);

    NearestNeighbors* neighbors = nns.getNNs(i,j);

    for(int e = 0; e < neighbors->get_k(); ++e)
    { 
        WeightedIndex nn = neighbors->getNeighborInd(e);
        int x = nn.i;
        int y = nn.j;
        temp.set_data(x,y,0,255);
        temp.set_data(x,y,1,0);
        temp.set_data(x,y,2,0);

    }
    temp.print_ppm();
}




void SynthImg::synthesize()
{
    // Initialization; take the first pixel from the exemplar, and copy.
    // Boundary; compare largest possible areas given boundary croppings.
    //  normalize by # pixels compared.
    
    assert(exemplar.is_data());

    // Inititalization.
    // Random first row; from image.
    // srand( time(NULL) );

    for(int i = 0; i < synth.get_w(); ++i)
    {
        for(int j = 1; j < synth.get_h(); ++j)
        {
            PixPtr ptr;

            ptr.i = rand()%(exemplar.get_w()-2*nradius-1) + nradius;
            ptr.j = rand()%(exemplar.get_h()-2*nradius-1) + nradius;

            ptrs.set_ptr(i, j, ptr);
        }
    }

    // Seed with first pixel.
    copy_pix_exemp_synth(0,0,0,0);

    int num_infty=0;
    for(int j = 0; j < synth.get_h(); ++j)
    {
        for(int i = 0; i < synth.get_w(); ++i)
        {
            if( !((i == 0) && (j == 0)) )
            {
                PixCand curbest;
                curbest.weight = INFINITY;
                curbest.i = -1;
                curbest.j = -1;
                for(int y = j-nradius; y < j; ++y)
                {
                    for(int x = i-nradius; x <= i+nradius; ++x)
                    {
                        if(in_bounds_synth(x,y))
                        {
                            PixCand newcand;
                            newcand = normalized_of_best_nn(x,y,i,j);
                            
                            if( newcand.weight <= curbest.weight)
                                curbest = newcand;
                        }
                    }
                }
                for(int x = i-nradius; x < i; ++x)
                {
                    if(in_bounds_synth(x,j))
                    {
                        PixCand newcand;
                        newcand = normalized_of_best_nn(x,j,i,j);
                        
                        if( newcand.weight <= curbest.weight)
                            curbest = newcand;
                    }
                }
                if(curbest.weight == INFINITY)
                {
                    curbest.i = rand()%exemplar.get_w();
                    curbest.j = rand()%exemplar.get_h();
                    num_infty++;
                }

                copy_pix_exemp_synth(curbest.i,curbest.j,i,j);
            }
        }
    }
}

void SynthImg::print_synth_ppm()
{
    synth.print_ppm();
}

