/*
 *  This file is part of RawTherapee.
 *
 *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
 *
 *  RawTherapee is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  RawTherapee is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with RawTherapee.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <climits>
#include <cstdio>
#include "rawimagesource.h"
#include "rawimagesource_i.h"
#include "../rtgui/options.h"

namespace rtengine {

// computes highlight recovery multipliers. Needs a possibly downscaled image where
// the highlights are indicated by INT_MAX
void hlmultipliers (int** rec[3], int max_3[3], int dh, int dw) {

    // STEP I. recover color with two-color information
    int phase = -1;
    int k=0;
    for (k=0; k<1000; k++) {
        int changed = 0;
        for (int i=1; i<dh-1; i++)
            for (int j=1; j<dw-1; j++) {
                int co, c1, c2;
//                  if (phase==2)
//                    phase++;
//                if (phase>0)// && phase!=4)
//                    continue;
                if (phase==-1 || phase==0 || phase==2) {
                    if (rec[0][i][j] == INT_MAX && rec[1][i][j] != INT_MAX && rec[1][i][j] >=0 && rec[2][i][j] != INT_MAX && rec[2][i][j] >=0) {
                        co = 0;
                        c1 = 1;
                        c2 = 2; 
                    }
                    else if (rec[1][i][j] == INT_MAX && rec[0][i][j] != INT_MAX && rec[0][i][j] >=0 && rec[2][i][j] != INT_MAX && rec[2][i][j] >=0) {
                        co = 1;
                        c1 = 0;
                        c2 = 2; 
                    }
                    else if (rec[2][i][j] == INT_MAX && rec[1][i][j] != INT_MAX && rec[1][i][j] >=0 && rec[0][i][j] != INT_MAX && rec[0][i][j] >=0) {
                        co = 2;
                        c1 = 1;
                        c2 = 0; 
                    }
                    else
                        continue;
                        
                    double ratio[2] = {0.0, 0.0};
                    int count = 0;
                    double rato = (double)rec[c1][i][j] / rec[c2][i][j];
                    double arato = 0.0;
                    if (phase==2) {
                        for (int x=-1; x<=1; x++)
                            for (int y=-1; y<=1; y++) {
                                // average m/c color ratios in the surrounding pixels
                                if (rec[co][i+x][j+y]>=0 && rec[co][i+x][j+y]!=INT_MAX && rec[c1][i+x][j+y]>=0 && rec[c1][i+x][j+y]!=INT_MAX && rec[c2][i+x][j+y]>0 && rec[c2][i+x][j+y]!=INT_MAX) {
                                    double ratt = (double)rec[c1][i+x][j+y] / rec[c2][i+x][j+y];
                                    if (ratt > rato*1.2 || ratt < rato / 1.2 || rec[co][i+x][j+y]<max_3[co]*1/2)
                                        continue;
                                    ratio[0] += (double)rec[c1][i+x][j+y] / rec[co][i+x][j+y];
                                    ratio[1] += (double)rec[c2][i+x][j+y] / rec[co][i+x][j+y];
                                    count++;
                                }
                            }
                    }
                    else if (phase==-1) {
                        for (int x=-1; x<=1; x++)
                            for (int y=-1; y<=1; y++) {
                                // average m/c color ratios in the surrounding pixels
                                if (rec[co][i+x][j+y]>=0 && rec[co][i+x][j+y]!=INT_MAX && rec[c1][i+x][j+y]>=0 && rec[c1][i+x][j+y]!=INT_MAX && rec[c2][i+x][j+y]>0 && rec[c2][i+x][j+y]!=INT_MAX) {
                                    double ratt = (double)rec[c1][i+x][j+y] / rec[c2][i+x][j+y];
                                    if (ratt > rato*1.05 || ratt < rato / 1.05 || rec[co][i+x][j+y]<max_3[co]*4/5)
                                        continue;
                                    arato += ratt;
                                    ratio[0] += (double)rec[c1][i+x][j+y] / rec[co][i+x][j+y];
                                    ratio[1] += (double)rec[c2][i+x][j+y] / rec[co][i+x][j+y];
                                    
                                    count++;
                                }
                            }
                    }
                    else {
                        for (int x=-1; x<=1; x++)
                            for (int y=-1; y<=1; y++) {
                                // average m/c color ratios in the surrounding pixels
                                if (rec[co][i+x][j+y]>=0 && rec[co][i+x][j+y]!=INT_MAX && rec[c1][i+x][j+y]>=0 && rec[c1][i+x][j+y]!=INT_MAX && rec[c2][i+x][j+y]>0 && rec[c2][i+x][j+y]!=INT_MAX) {
                                    double ratt = (double)rec[c1][i+x][j+y] / rec[c2][i+x][j+y];
                                    if (ratt > rato*1.1 || ratt < rato / 1.1 || rec[co][i+x][j+y]<max_3[co]*3/4)
                                        continue;
                                    arato += ratt;
                                    ratio[0] += (double)rec[c1][i+x][j+y] / rec[co][i+x][j+y];
                                    ratio[1] += (double)rec[c2][i+x][j+y] / rec[co][i+x][j+y];
                                    
                                    count++;
                                }
                            }
                    }
                    // compute new pixel values from the surrounding color ratios
                    if (count>1) { //(phase==0 && count>1) || (phase==2 && count>1)) {
                        rec[co][i][j] = -(int)((rec[c1][i][j] / ratio[0] * count + rec[c2][i][j] / ratio[1] * count) / 2);
                        changed++;
                    }
                }
                else if (phase==1 || phase==3) {
                    if (rec[0][i][j] == INT_MAX && rec[1][i][j] == INT_MAX && rec[2][i][j] != INT_MAX && rec[2][i][j] >=0) {
                        co = 2;
                        c1 = 0;
                        c2 = 1; 
                    }
                    else if (rec[0][i][j] == INT_MAX && rec[2][i][j] == INT_MAX && rec[1][i][j] != INT_MAX && rec[1][i][j] >=0) {
                        co = 1;
                        c1 = 0;
                        c2 = 2; 
                    }
                    else if (rec[1][i][j] == INT_MAX && rec[2][i][j] == INT_MAX && rec[0][i][j] != INT_MAX && rec[0][i][j] >=0) {
                        co = 0;
                        c1 = 1;
                        c2 = 2; 
                    }
                    else
                        continue;

                    double ratio[2] = {0.0, 0.0};
                    int count[2] = {0, 0};
                    for (int x=-1; x<=1; x++)
                        for (int y=-1; y<=1; y++) {
                            // average m/c color ratios in the surrounding pixels
                            if (rec[co][i+x][j+y]>=0 && rec[co][i+x][j+y]!=INT_MAX && rec[c1][i+x][j+y]>0 && rec[c1][i+x][j+y]!=INT_MAX) {
                                if ((phase==1 && rec[c1][i+x][j+y]<max_3[c1]*3/4) || (phase==3 && rec[c1][i+x][j+y]<max_3[c1]*1/2))
                                    continue;
                                ratio[0] += (double)rec[co][i+x][j+y] / rec[c1][i+x][j+y];
                                count[0] ++;
                            }
                            if (rec[co][i+x][j+y]>=0 && rec[co][i+x][j+y]!=INT_MAX && rec[c2][i+x][j+y]>0 && rec[c2][i+x][j+y]!=INT_MAX) {
                                if ((phase==1 && rec[c2][i+x][j+y]<max_3[c2]*3/4) || (phase==3 && rec[c2][i+x][j+y]<max_3[c2]*1/2))
//                                if (/*phase!=3 && */rec[c2][i+x][j+y]<max[c2]*3/4)
                                    continue;
                                ratio[1] += (double)rec[co][i+x][j+y] / rec[c2][i+x][j+y];
                                count[1] ++;
                            }
                        }
                    // compute new pixel values from the surrounding color ratios
                    if ((phase==1 && count[0]>2) || (phase==3 && count[0]>1)) {
                        rec[c1][i][j] = - (int) ((double)rec[co][i][j] / ratio[0] * count[0]);
                        changed++;
                    }
                    if ((phase==1 && count[1]>2) || (phase==3 && count[1]>1)) {
                        rec[c2][i][j] = - (int) ((double)rec[co][i][j] / ratio[1] * count[1]);
                        changed++;
                    }
                }
                else {
                    int val = 0;
                    int num = 0;
                    for (int c=0; c<3; c++)
                        if (rec[c][i][j]!=INT_MAX) {
                            val += rec[c][i][j];
                            num++;
                        }
                    if (num<3 && num>0) {
                        for (int c=0; c<3; c++)
                            rec[c][i][j] = val / num;
                    }
                }
            }


        bool change = false;
        for (int i=1; i<dh-1; i++)
            for (int j=1; j<dw-1; j++) 
                for (int c=0; c<3; c++) {
                    if (rec[c][i][j]<0) {
                        rec[c][i][j] = -rec[c][i][j];
                        change = true;
                    }
                }
        if (!change && phase<4) {
            phase++;
            if( options.rtSettings.verbose )
            printf ("phc %d: %d\n", phase, k);
        }
        else if (!change) 
            break;
            
        if (k%20 == 0 && options.rtSettings.verbose )
            printf ("changed %d\n", changed);
    }
    if( options.rtSettings.verbose )
    printf ("Highlight recovery ends in %d iterations\n", k);

    int maxval = max(max_3[0], max_3[1], max_3[2]);
    for (int i=0; i<dh; i++)
        for (int j=0; j<dw; j++) 
            if (rec[0][i][j]==INT_MAX || rec[1][i][j]==INT_MAX || rec[2][i][j]==INT_MAX) {
                rec[0][i][j] = maxval;
                rec[1][i][j] = maxval;
                rec[2][i][j] = maxval;
                
            }
}

void RawImageSource::HLRecovery_ColorPropagation (float* red, float* green, float* blue, int i, int sx1, int width, int skip) {

    int blr = (i+HR_SCALE/2) / HR_SCALE - 1;
    if (blr<0 || blr>=H/HR_SCALE-2)
        return;
    double mr1 = 1.0 - ((double)((i+HR_SCALE/2) % HR_SCALE) / HR_SCALE + 0.5 / HR_SCALE);
    int maxcol = W/HR_SCALE-2;
    for (int j=sx1, jx=0; jx<width; j+=skip, jx++) {
		if (needhr[i][j]) {
            int blc = (j+HR_SCALE/2) / HR_SCALE - 1;
            if (blc<0 || blc>=maxcol)
                continue;
            double mc1 = 1.0 - ((double)((j+HR_SCALE/2) % HR_SCALE) / HR_SCALE + 0.5 / HR_SCALE);
            double mulr = mr1*mc1 * hrmap[0][blr][blc] + mr1*(1.0-mc1) * hrmap[0][blr][blc+1] + (1.0-mr1)*mc1 * hrmap[0][blr+1][blc] + (1.0-mr1)*(1.0-mc1) * hrmap[0][blr+1][blc+1];
            double mulg = mr1*mc1 * hrmap[1][blr][blc] + mr1*(1.0-mc1) * hrmap[1][blr][blc+1] + (1.0-mr1)*mc1 * hrmap[1][blr+1][blc] + (1.0-mr1)*(1.0-mc1) * hrmap[1][blr+1][blc+1];
            double mulb = mr1*mc1 * hrmap[2][blr][blc] + mr1*(1.0-mc1) * hrmap[2][blr][blc+1] + (1.0-mr1)*mc1 * hrmap[2][blr+1][blc] + (1.0-mr1)*(1.0-mc1) * hrmap[2][blr+1][blc+1];
            red[jx] = (red[jx] * mulr);
            green[jx] = (green[jx] * mulg);
            blue[jx] = (blue[jx] * mulb);
        } else {
            red[jx] = (red[jx]);
            green[jx] = (green[jx]);
            blue[jx] = (blue[jx]);
		}
    }
}

void RawImageSource::updateHLRecoveryMap_ColorPropagation () {

    // detect maximal pixel values
    float* red = new float[W];
    float* blue = new float[W];
    int maxr = 0, maxg = 0, maxb = 0;
    for (int i=32; i<H-32; i++) {
        interpolate_row_rb (red, blue, green[i-1], green[i], green[i+1], i);
        for (int j=32; j<W-32; j++) {
            if ((ri->ISRED(i,j)   || !ri->isBayer()) && red[j] > maxr) maxr = red[j];
            if ((ri->ISGREEN(i,j) || !ri->isBayer()) && green[i][j] > maxg) maxg = green[i][j];
            if ((ri->ISBLUE(i,j)  || !ri->isBayer()) && blue[j] > maxb) maxb = blue[j];
		}
    }
    delete [] red;
    delete [] blue;

    maxr = maxr * 19 / 20;
    maxg = maxg * 19 / 20;
    maxb = maxb * 19 / 20;
    max_3[0] = maxr;
    max_3[1] = maxg;
    max_3[2] = maxb;

    // downscale image
    int dw = W/HR_SCALE;
    int dh = H/HR_SCALE;
    Image16* ds = new Image16 (dw, dh);

    // overburnt areas
    int** rec[3];
    for (int i=0; i<3; i++)
        rec[i] = allocArray<int> (dw, dh);

    float* reds[HR_SCALE]; 
    float* blues[HR_SCALE];
    for (int i=0; i<HR_SCALE; i++) {
        reds[i] = new float[W];
        blues[i] = new float[W];
    }

	if (needhr)
		freeArray<char>(needhr, H);
	needhr = allocArray<char> (W, H);

    for (int i=0; i<dh; i++) {
		for (int j=0; j<HR_SCALE; j++) {
            interpolate_row_rb (reds[j], blues[j], green[HR_SCALE*i+j-1], green[HR_SCALE*i+j], green[HR_SCALE*i+j+1], HR_SCALE*i+j);
			for (int k=0; k<W; k++)
				if (reds[j][k]>=max_3[0] || green[HR_SCALE*i+j][k]>=max_3[1] || blues[j][k]>=max_3[2])
					needhr[HR_SCALE*i+j][k] = 1;
				else
					needhr[HR_SCALE*i+j][k] = 0;
		}
		for (int j=0; j<dw; j++) {
            int sumr = 0; int cr = 0;
            int sumg = 0; int cg = 0;
            int sumb = 0; int cb = 0;
            for (int x=0; x<HR_SCALE; x++)
                for (int y=0; y<HR_SCALE; y++) {
                    int ix = HR_SCALE*i+x;
                    int jy = HR_SCALE*j+y;
                    sumr += reds[x][jy];
                    if (reds[x][jy] < maxr) cr++;
                    sumg += green[ix][jy];
                    if (green[ix][jy] < maxg) cg++;
                    sumb += blues[x][jy];
                    if (blues[x][jy] < maxb) cb++;
                }
			if (cr<HR_SCALE*HR_SCALE) rec[0][i][j] = INT_MAX; else rec[0][i][j] = sumr / HR_SCALE/HR_SCALE;
            if (cg<HR_SCALE*HR_SCALE) rec[1][i][j] = INT_MAX; else rec[1][i][j] = sumg / HR_SCALE/HR_SCALE;
            if (cb<HR_SCALE*HR_SCALE) rec[2][i][j] = INT_MAX; else rec[2][i][j] = sumb / HR_SCALE/HR_SCALE;
            ds->r[i][j] = sumr / HR_SCALE/HR_SCALE;
            ds->g[i][j] = sumg / HR_SCALE/HR_SCALE;
            ds->b[i][j] = sumb / HR_SCALE/HR_SCALE;
        }
    }
    for (int i=0; i<HR_SCALE; i++) {
        delete [] reds[i];
        delete [] blues[i];
    }

    hlmultipliers (rec, max_3, dh, dw);

    if (hrmap[0]!=NULL) {
        freeArray<float> (hrmap[0], dh);
        freeArray<float> (hrmap[1], dh);
        freeArray<float> (hrmap[2], dh);
    }

    hrmap[0] = allocArray<float> (dw, dh);
    hrmap[1] = allocArray<float> (dw, dh);
    hrmap[2] = allocArray<float> (dw, dh);

    for (int i=0; i<dh; i++)
        for (int j=0; j<dw; j++) {
            hrmap[0][i][j] = ds->r[i][j]>0 ? (double)rec[0][i][j] / ds->r[i][j] : 1.0;
            hrmap[1][i][j] = ds->g[i][j]>0 ? (double)rec[1][i][j] / ds->g[i][j] : 1.0;
            hrmap[2][i][j] = ds->b[i][j]>0 ? (double)rec[2][i][j] / ds->b[i][j] : 1.0;
        }

    delete ds;
   
    freeArray<int> (rec[0], dh);
    freeArray<int> (rec[1], dh);
    freeArray<int> (rec[2], dh);
}

}

