#include <iostream>
#include <opencv2/opencv.hpp> // all opencv header
#include "hdrplus/finish.h"
#include "hdrplus/utility.h"
#include <cmath>

#ifdef __ANDROID__
#define DBG_OUTPUT_ROOT "/sdcard/com.xypower.mpapp/tmp/"
#else
#define DBG_OUTPUT_ROOT ""
#endif
// #include <type_traits>

namespace hdrplus
{
    

    cv::Mat convert16bit2_8bit_(cv::Mat ans){
        if(ans.type()==CV_16UC3){
            cv::MatIterator_<cv::Vec3w> it, end;
            for( it = ans.begin<cv::Vec3w>(), end = ans.end<cv::Vec3w>(); it != end; ++it)
            {
                // std::cout<<sizeof (*it)[0] <<std::endl;
                (*it)[0] *=(255.0/USHRT_MAX);
                (*it)[1] *=(255.0/USHRT_MAX);
                (*it)[2] *=(255.0/USHRT_MAX);
            }
            ans.convertTo(ans, CV_8UC3);
        }else if(ans.type()==CV_16UC1){
            u_int16_t* ptr = (u_int16_t*)ans.data;
            int end = ans.rows*ans.cols;
            for(int i=0;i<end;i++){
                *(ptr+i) *=(255.0/USHRT_MAX);
            }
            ans.convertTo(ans, CV_8UC1);
        }else{
            std::cout<<"Unsupported Data Type"<<std::endl;
        }
        return ans;
    }

    cv::Mat convert8bit2_16bit_(cv::Mat ans){
        if(ans.type()==CV_8UC3){
            ans.convertTo(ans, CV_16UC3);
            cv::MatIterator_<cv::Vec3w> it, end;
            for( it = ans.begin<cv::Vec3w>(), end = ans.end<cv::Vec3w>(); it != end; ++it)
            {
                // std::cout<<sizeof (*it)[0] <<std::endl;
                (*it)[0] *=(65535.0/255.0);
                (*it)[1] *=(65535.0/255.0);
                (*it)[2] *=(65535.0/255.0);
            }
            
        }else if(ans.type()==CV_8UC1){
            ans.convertTo(ans, CV_16UC1);
            u_int16_t* ptr = (u_int16_t*)ans.data;
            int end = ans.rows*ans.cols;
            for(int i=0;i<end;i++){
                *(ptr+i) *=(65535.0/255.0);
            }
            
        }else{
            std::cout<<"Unsupported Data Type"<<std::endl;
        }
        return ans;
    }

    cv::Mat convert8bit2_12bit_(cv::Mat ans){
        // cv::Mat ans(I);
        cv::MatIterator_<cv::Vec3w> it, end;
        for( it = ans.begin<cv::Vec3w>(), end = ans.end<cv::Vec3w>(); it != end; ++it)
        {
            // std::cout<<sizeof (*it)[0] <<std::endl;
            (*it)[0] *=(2048.0/255.0);
            (*it)[1] *=(2048.0/255.0);
            (*it)[2] *=(2048.0/255.0);
        }
        ans.convertTo(ans, CV_16UC3);
        return ans;
    }

    uint16_t uGammaCompress_1pix(float x, float threshold,float gainMin,float gainMax,float exponent){
        // Normalize pixel val
        x/=USHRT_MAX;
        // check the val against the threshold
        if(x<=threshold){
            x =gainMin*x;
        }else{
            x = gainMax* pow(x,exponent)-gainMax+1;
        }
        // clip
        if(x<0){
            x=0;
        }else{
            if(x>1){
                x = 1;
            }
        }

        x*=USHRT_MAX;
        
        return (uint16_t)x;
    }

    uint16_t uGammaDecompress_1pix(float x, float threshold,float gainMin,float gainMax,float exponent){
        // Normalize pixel val
        x/=65535.0;
        // check the val against the threshold
        if(x<=threshold){
            x = x/gainMin;
        }else{
            x = pow((x+gainMax-1)/gainMax,exponent);
        }
        // clip
        if(x<0){
            x=0;
        }else{
            if(x>1){
                x = 1;
            }
        }
        x*=65535;
        
        return (uint16_t)x;
    }

    cv::Mat uGammaCompress_(cv::Mat m,float threshold,float gainMin,float gainMax,float exponent){
        if(m.type()==CV_16UC3){
            cv::MatIterator_<cv::Vec3w> it, end;
            for( it = m.begin<cv::Vec3w>(), end = m.end<cv::Vec3w>(); it != end; ++it)
            {
                (*it)[0] =uGammaCompress_1pix((*it)[0],threshold,gainMin,gainMax,exponent);
                (*it)[1] =uGammaCompress_1pix((*it)[1],threshold,gainMin,gainMax,exponent);
                (*it)[2] =uGammaCompress_1pix((*it)[2],threshold,gainMin,gainMax,exponent);
            }
        }else if(m.type()==CV_16UC1){
            u_int16_t* ptr = (u_int16_t*)m.data;
            int end = m.rows*m.cols;
            for(int i=0;i<end;i++){
                *(ptr+i) = uGammaCompress_1pix(*(ptr+i),threshold,gainMin,gainMax,exponent);
            }

        }else{
            std::cout<<"Unsupported Data Type"<<std::endl;
        }
        return m;
    }

    cv::Mat uGammaDecompress_(cv::Mat m,float threshold,float gainMin,float gainMax,float exponent){
        if(m.type()==CV_16UC3){
            cv::MatIterator_<cv::Vec3w> it, end;
            for( it = m.begin<cv::Vec3w>(), end = m.end<cv::Vec3w>(); it != end; ++it)
            {
                (*it)[0] =uGammaDecompress_1pix((*it)[0],threshold,gainMin,gainMax,exponent);
                (*it)[1] =uGammaDecompress_1pix((*it)[1],threshold,gainMin,gainMax,exponent);
                (*it)[2] =uGammaDecompress_1pix((*it)[2],threshold,gainMin,gainMax,exponent);
            }
        }else if(m.type()==CV_16UC1){
            u_int16_t* ptr = (u_int16_t*)m.data;
            int end = m.rows*m.cols;
            for(int i=0;i<end;i++){
                *(ptr+i) = uGammaDecompress_1pix(*(ptr+i),threshold,gainMin,gainMax,exponent);
            }

        }else{
            std::cout<<"Unsupported Data Type"<<std::endl;
        }
        
        return m;
    }

    cv::Mat gammasRGB(cv::Mat img, bool mode){
        if(mode){// compress
            return uGammaCompress_(img,0.0031308, 12.92, 1.055, 1. / 2.4);
        }else{ // decompress
            return uGammaDecompress_(img, 0.04045, 12.92, 1.055, 2.4);
        }
    }

    void copy_mat_16U_2(u_int16_t* ptr_A, cv::Mat B){
        // u_int16_t* ptr_A = (u_int16_t*)A.data;
        u_int16_t* ptr_B = (u_int16_t*)B.data;
        for(int r = 0; r < B.rows; r++) {
            for(int c = 0; c < B.cols; c++) {
                *(ptr_A+r*B.cols+c) = *(ptr_B+r*B.cols+c);
            }
        }
    }

    cv::Mat mean_(cv::Mat img){
        // initialize processedImg
        int H = img.rows;
        int W = img.cols;
        cv::Mat processedImg = cv::Mat(H,W,CV_16UC1);
        u_int16_t* ptr = (u_int16_t*)processedImg.data;
        
        // traverse img
        int idx = 0;
        cv::MatIterator_<cv::Vec3w> it, end;
        for( it = img.begin<cv::Vec3w>(), end = img.end<cv::Vec3w>(); it != end; ++it)
        {
            uint32_t tmp = (*it)[0]+(*it)[1]+(*it)[2];
            uint16_t avg_val = tmp/3;
            *(ptr+idx) = avg_val;
            idx++;
        }

        return processedImg;
    }

    double getMean(cv::Mat img){
        u_int16_t* ptr = (u_int16_t*)img.data;
        int max_idx = img.rows*img.cols*img.channels();
        double sum=0;
        for(int i=0;i<max_idx;i++){
            sum += *(ptr+i);
        }
        sum/=max_idx;
        sum/=USHRT_MAX;
        return sum;
    }

    cv::Mat matMultiply_scalar(cv::Mat img,float gain){
        u_int16_t* ptr = (u_int16_t*)img.data;
        int max_idx = img.rows*img.cols*img.channels();
        for(int i=0;i<max_idx;i++){
            double tmp = *(ptr+i)*gain;
            if(tmp<0){
                *(ptr+i)=0;
            }else if(tmp>USHRT_MAX){
                *(ptr+i) = USHRT_MAX;
            }else{
                *(ptr+i)=(u_int16_t)tmp;
            }
        }
        return img;
    }

    double getSaturated(cv::Mat img, double threshold){
        threshold *= USHRT_MAX;
        double count=0;
        u_int16_t* ptr = (u_int16_t*)img.data;
        int max_idx = img.rows*img.cols*img.channels();
        for(int i=0;i<max_idx;i++){
            if(*(ptr+i)>threshold){
                count++;
            }
        }
        return count/(double)max_idx;

    }

    cv::Mat meanGain_(cv::Mat img,int gain){
        if(img.channels()!=3){
            std::cout<<"unsupport img type in meanGain_()"<<std::endl;
            return cv::Mat();
        }else{ // RGB img
            int H = img.rows;
            int W = img.cols;
            cv::Mat processedImg = cv::Mat(H,W,CV_16UC1);
            u_int16_t* ptr = (u_int16_t*)processedImg.data;
            int idx=0;

            cv::MatIterator_<cv::Vec3w> it, end;
            for( it = img.begin<cv::Vec3w>(), end = img.end<cv::Vec3w>(); it != end; ++it)
            {
                double sum = 0;
                // R
                double tmp = (*it)[0]*gain;
                if(tmp<0) tmp=0;
                if(tmp>USHRT_MAX) tmp = USHRT_MAX;
                sum+=tmp;

                // G
                tmp = (*it)[1]*gain;
                if(tmp<0) tmp=0;
                if(tmp>USHRT_MAX) tmp = USHRT_MAX;
                sum+=tmp;

                // B
                tmp = (*it)[2]*gain;
                if(tmp<0) tmp=0;
                if(tmp>USHRT_MAX) tmp = USHRT_MAX;
                sum+=tmp;

                // put into processedImg
                uint16_t avg_val = sum/3;
                *(ptr+idx) = avg_val;
                idx++;
            }
            return processedImg;
        }

    }

    cv::Mat applyScaling_(cv::Mat mergedImage, cv::Mat shortGray, cv::Mat fusedGray){
        cv::Mat result = mergedImage.clone();
        u_int16_t* ptr_shortg = (u_int16_t*)shortGray.data;
        u_int16_t* ptr_fusedg = (u_int16_t*)fusedGray.data;
        int count = 0;
        cv::MatIterator_<cv::Vec3w> it, end;
        for( it = result.begin<cv::Vec3w>(), end = result.end<cv::Vec3w>(); it != end; ++it)
        {
            double s = 1;
            if(*(ptr_shortg+count)!=0){
                s = *(ptr_fusedg+count);
                s/=*(ptr_shortg+count);
            }
            for(int c=0;c<mergedImage.channels();c++){
                double tmp = (*it)[c]*s;
                if(tmp<0){
                    (*it)[c] = 0;
                }else if(tmp>USHRT_MAX){
                    (*it)[c] = USHRT_MAX;
                }else{
                    (*it)[c] = tmp;
                }
            }
        }
        return result;
    }

    void localToneMap(cv::Mat& mergedImage, Options options, cv::Mat& shortg,
         cv::Mat& longg, cv::Mat& fusedg, int& gain){
        std::cout<<"HDR Tone Mapping..."<<std::endl;
        // # Work with grayscale images
        cv::Mat shortGray = rgb_2_gray<uint16_t, uint16_t, CV_16U>(mergedImage); //mean_(mergedImage);
        std::cout<<"--- Compute grayscale image"<<std::endl;

        // compute gain
        gain = 0;
        if(options.ltmGain==-1){
            double dsFactor = 25;
            int down_height = round(shortGray.rows/dsFactor);
            int down_width = round(shortGray.cols/dsFactor);
            cv::Mat shortS;
            cv::resize(shortGray,shortS,cv::Size(down_height,down_width),cv::INTER_LINEAR);
            shortS = shortS.reshape(1,1);

            bool bestGain = false;
            double compression = 1.0;
            double saturated = 0.0;
            cv::Mat shortSg = gammasRGB(shortS.clone(), true);
            double sSMean = getMean(shortSg);

            while((compression < 1.9 && saturated < .95)||((!bestGain) && (compression < 6) && (gain < 30) && (saturated < 0.33))){
                gain += 2;
                cv::Mat longSg = gammasRGB(shortS.clone()*gain, true);
                double lSMean = getMean(longSg);
                compression = lSMean / sSMean;
                bestGain = lSMean > (1 - sSMean) / 2;  // only works if burst underexposed
                saturated = getSaturated(longSg,0.95);
                if(options.verbose==4){

                }
            }    

        }else{
            if(options.ltmGain>0){
                gain = options.ltmGain;
            }
        }
        std::cout<<"--- Compute gain"<<std::endl;
        // create a synthetic long exposure
        cv::Mat longGray = meanGain_(mergedImage.clone(),gain);
        std::cout<<"--- Synthetic long expo"<<std::endl;
        // apply gamma correction to both
        longg = gammasRGB(longGray.clone(), true);
        shortg = gammasRGB(shortGray.clone(),true);
        std::cout<<"--- Apply Gamma correction"<<std::endl;
        // perform tone mapping by exposure fusion in grayscale
        cv::Ptr<cv::MergeMertens> mergeMertens = cv::createMergeMertens();
        std::cout<<"--- Create Mertens"<<std::endl;
        // hack: cv2 mergeMertens expects inputs between 0 and 255
	    // but the result is scaled between 0 and 1 (some values can actually be greater than 1!)
        std::vector<cv::Mat> src_expos;
        src_expos.push_back(convert16bit2_8bit_(shortg.clone()));
        src_expos.push_back(convert16bit2_8bit_(longg.clone()));
        mergeMertens->process(src_expos, fusedg);
        fusedg = fusedg*USHRT_MAX;
        fusedg.convertTo(fusedg, CV_16UC1);
        std::cout<<"--- Apply Mertens"<<std::endl;
        // undo gamma correction
        cv::Mat fusedGray = gammasRGB(fusedg.clone(), false);
        // cv::imwrite("fusedg_degamma.png", fusedGray);
        std::cout<<"--- Un-apply Gamma correction"<<std::endl;
        // scale each RGB channel of the short exposure accordingly
        mergedImage = applyScaling_(mergedImage, shortGray, fusedGray);
        std::cout<<"--- Scale channels"<<std::endl;
    }

    u_int16_t enhanceContrast_1pix(u_int16_t pix_val,double gain){
        double x = pix_val;
        x/=USHRT_MAX;
        x = x - gain*sin(2*M_PI*x);
        if(x<0){
            x = 0;
        }else if(x>1){
            x = 1;
        }
        u_int16_t result = x*USHRT_MAX;
        return result;
    }

    cv::Mat enhanceContrast(cv::Mat image, Options options){
        if(options.gtmContrast>=0 && options.gtmContrast<=1){
            u_int16_t* ptr = (u_int16_t*)image.data;
            int end = image.rows*image.cols*image.channels();
            for(int idx = 0;idx<end;idx++){
                *(ptr+idx) = enhanceContrast_1pix(*(ptr+idx),options.gtmContrast);
            }
        }else{
            std::cout<<"GTM ignored, expected a contrast enhancement ratio between 0 and 1"<<std::endl;
        }
        return image;
    }

    cv::Mat distL1_(cv::Mat X, cv::Mat Y){
        int end_x = X.rows*X.cols*X.channels();
        int end_y = Y.rows*Y.cols*Y.channels();
        cv::Mat result = cv::Mat(X.rows,X.cols,X.type());
        if(end_x==end_y){
            u_int16_t* ptr_x = (u_int16_t*)X.data;
            u_int16_t* ptr_y = (u_int16_t*)Y.data;
            u_int16_t* ptr_r = (u_int16_t*)result.data;
            for(int i=0;i<end_x;i++){
                if(*(ptr_x+i)<*(ptr_y+i)){
                    *(ptr_r+i) = *(ptr_y+i) - *(ptr_x+i);
                }else{
                    *(ptr_r+i) = *(ptr_x+i) - *(ptr_y+i);
                }
            }
        }else{
            std::cout<<"Mat size not match. distL1_ failed!"<<std::endl;
        }
        return result;
    }

    cv::Mat sharpenTriple_(cv::Mat image,
        cv::Mat blur0, cv::Mat low0, float th0, float k0,
        cv::Mat blur1, cv::Mat low1, float th1, float k1,
        cv::Mat blur2, cv::Mat low2, float th2, float k2){
            // create result mat
            cv::Mat result = cv::Mat(image.rows,image.cols,image.type());
            // initialize iteraters
            u_int16_t* ptr_r = (u_int16_t*)result.data;
            u_int16_t* ptr_img = (u_int16_t*)image.data;
            u_int16_t* ptr_blur0 = (u_int16_t*)blur0.data;
            u_int16_t* ptr_low0 = (u_int16_t*)low0.data;
            u_int16_t* ptr_blur1 = (u_int16_t*)blur1.data;
            u_int16_t* ptr_low1 = (u_int16_t*)low1.data;
            u_int16_t* ptr_blur2 = (u_int16_t*)blur2.data;
            u_int16_t* ptr_low2 = (u_int16_t*)low2.data;
            int n_channels = image.channels();
            int end = image.rows*image.cols*n_channels;
            // traverse Image
            for(int idx = 0;idx<end;idx++){
                double r, r0, r1, r2;
                double x = *(ptr_img+idx);
                double l0 = *(ptr_low0+idx)/(double)USHRT_MAX;
                double l1 = *(ptr_low1+idx)/(double)USHRT_MAX;
                double l2 = *(ptr_low2+idx)/(double)USHRT_MAX;
                double b0 = *(ptr_blur0+idx);
                double b1 = *(ptr_blur1+idx);
                double b2 = *(ptr_blur2+idx);
                r0 = l0<th0? x:x+k0*(x-b0);
                r1 = l1<th1? x:x+k1*(x-b1);
                r2 = l2<th2? x:x+k2*(x-b2);
                r = (r0+r1+r2)/3.0;
                if(r<0) r=0;
                if(r>USHRT_MAX) r = USHRT_MAX;
                *(ptr_r+idx) = (u_int16_t)r;
            }
            return result;
        }

    cv::Mat sharpenTriple(cv::Mat image, Tuning tuning, Options options){
        // sharpen the image using unsharp masking
        std::vector<float> amounts = tuning.sharpenAmount;
        std::vector<float> sigmas = tuning.sharpenSigma;
        std::vector<float> thresholds = tuning.sharpenThreshold;
        // Compute all Gaussian blur
        cv::Mat blur0,blur1,blur2;
        cv::GaussianBlur(image,blur0,cv::Size(0,0),sigmas[0]);
        cv::GaussianBlur(image,blur1,cv::Size(0,0),sigmas[1]);
        cv::GaussianBlur(image,blur2,cv::Size(0,0),sigmas[2]);
        std::cout<<" --- gaussian blur"<<std::endl;
        // cv::imwrite("blur2.png", blur2);
        // Compute all low contrast images
        cv::Mat low0 = distL1_(blur0, image);
        cv::Mat low1 = distL1_(blur1, image);
        cv::Mat low2 = distL1_(blur2, image);
        std::cout<<" --- low contrast"<<std::endl;
        // cv::imwrite("low2.png", low2);
        // Compute the triple sharpen
        cv::Mat sharpImage = sharpenTriple_(image,
         blur0, low0, thresholds[0], amounts[0],
         blur1, low1, thresholds[1], amounts[1],
         blur2, low2, thresholds[2], amounts[2]);
        std::cout<<" --- sharpen"<<std::endl;
        return sharpImage;
    }

    void copy_mat_16U_3(u_int16_t* ptr_A, cv::Mat B){
        // u_int16_t* ptr_A = (u_int16_t*)A.data;
        u_int16_t* ptr_B = (u_int16_t*)B.data;
        int H = B.rows;
        int W = B.cols;
        int end = H*W;
        for(int i=0;i<end;i++){
            *(ptr_A+i) = *(ptr_B+i);
        }
    }

    // void copy_mat_16U_3(u_int16_t* ptr_A, cv::Mat B){
    //     // u_int16_t* ptr_A = (u_int16_t*)A.data;
    //     u_int16_t* ptr_B = (u_int16_t*)B.data;
    //     for(int r = 0; r < B.rows; r++) {
    //         for(int c = 0; c < B.cols; c++) {
    //             *(ptr_A+r*B.cols+c) = *(ptr_B+r*B.cols+c);
    //         }
    //     }
    // }
    cv::Mat processMergedMat(cv::Mat mergedImg, int opencv_type){
        cv::Mat m;
#if 0
        uint16_t* ptr = (uint16_t*)mergedImg.data;
        for(int r = 0; r < mergedImg.rows; r++) {
            std::vector<int> dvals;
            for(int c = 0; c < mergedImg.cols; c++) {
                dvals.push_back(*(ptr+r*mergedImg.cols+c));
            }
            cv::Mat mline(dvals, true);
            cv::transpose(mline, mline);
            m.push_back(mline);
        }
#endif
        int ch = CV_MAT_CN(opencv_type);

        m = mergedImg.clone();
        m = m.reshape(ch);
        m.convertTo(m, opencv_type);
        
        return m;
    }

    void show20_20(cv::Mat m){
        u_int16_t* ptr = (u_int16_t*)m.data;
        for(int i=0;i<20;i++){
            for(int j=0;j<20;j++){
                std::cout<<*(ptr+i*m.cols+j)<<", ";
            }
            std::cout<<std::endl;
        }
    }

    void writeCSV(std::string filename, cv::Mat m)
    {
        std::ofstream myfile;
        myfile.open(filename.c_str());
        myfile<< cv::format(m, cv::Formatter::FMT_CSV) << std::endl;
        myfile.close();
    }

    void finish::process(const hdrplus::burst& burst_images, cv::Mat& finalOutputImage){
        // copy mergedBayer to rawReference
        std::cout<<"finish pipeline start ..."<<std::endl;

        // save merged Image value
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
        writeCSV(DBG_OUTPUT_ROOT "merged.csv",burst_images.merged_bayer_image);
#endif

        this->refIdx = burst_images.reference_image_idx;
        // this->burstPath = burstPath;
        // std::cout<<"processMerged:"<<std::endl;
        // show20_20(mergedB);

#ifndef HDRPLUS_NO_DETAILED_OUTPUT
        this->mergedBayer = loadFromCSV(DBG_OUTPUT_ROOT "merged.csv", CV_16UC1);
        // this->mergedBayer = processMergedMat(mergedB,CV_16UC1);//loadFromCSV("merged.csv", CV_16UC1);
        // std::cout<<"processMerged:"<<std::endl;
        // show20_20(this->mergedBayer);
        // this->mergedBayer = loadFromCSV(DBG_OUTPUT_ROOT "merged.csv", CV_16UC1);
        // this->mergedBayer = processMergedMat(burst_images.merged_bayer_image, CV_16UC1);
#else
        // this->mergedBayer = loadFromCSV(DBG_OUTPUT_ROOT "merged.csv", CV_16UC1);
        this->mergedBayer = processMergedMat(burst_images.merged_bayer_image, CV_16UC1);
		// std::cout<<"processMerged:"<<std::endl;
#endif
        // std::cout<<"csv:"<<std::endl;
        // show20_20(this->mergedBayer);
        // load_rawPathList(burstPath);

// read in ref img
        // bayer_image* ref = new bayer_image(rawPathList[refIdx]);
        bayer_image* ref = new bayer_image(burst_images.bayer_images[burst_images.reference_image_idx]);
        cv::Mat processedRefImage = postprocess(ref->libraw_processor,params.rawpyArgs);

        std::cout<<"size ref: "<<processedRefImage.rows<<"*"<<processedRefImage.cols<<std::endl;

// write reference image
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
		if(params.flags["writeReferenceImage"]){
            std::cout<<"writing reference img ..."<<std::endl;
            cv::Mat outputImg = convert16bit2_8bit_(processedRefImage.clone());
            cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
            // cv::imshow("test",processedImage);
            cv::imwrite(DBG_OUTPUT_ROOT "processedRef.jpg", outputImg);
            // cv::waitKey(0);
        }
#endif

// write gamma reference
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
        if(params.flags["writeGammaReference"]){
            std::cout<<"writing Gamma reference img ..."<<std::endl;
            cv::Mat outputImg = gammasRGB(processedRefImage.clone(),true);
            outputImg = convert16bit2_8bit_(outputImg);
            cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
            cv::imwrite(DBG_OUTPUT_ROOT "processedRefGamma.jpg", outputImg);
        }
#endif

// get the bayer_image of the merged image
        // bayer_image* mergedImg = new bayer_image(rawPathList[refIdx]);
        bayer_image* mergedImg  = new bayer_image(burst_images.bayer_images[this->refIdx]);
        mergedImg->libraw_processor->imgdata.rawdata.raw_image = (uint16_t*)this->mergedBayer.data;
        // copy_mat_16U_3(mergedImg->libraw_processor->imgdata.rawdata.raw_image,this->mergedBayer);
        cv::Mat processedMerge = postprocess(mergedImg->libraw_processor,params.rawpyArgs);

// write merged image
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
        if(params.flags["writeMergedImage"]){
            std::cout<<"writing Merged img ..."<<std::endl;
            cv::Mat outputImg = convert16bit2_8bit_(processedMerge.clone());
            cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
            cv::imwrite(DBG_OUTPUT_ROOT "mergedImg.jpg", outputImg);
        }
#endif

// write gamma merged image
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
        if(params.flags["writeMergedImage"]){
            std::cout<<"writing Gamma Merged img ..."<<std::endl;
            cv::Mat outputImg = gammasRGB(processedMerge.clone(),true);
            outputImg = convert16bit2_8bit_(outputImg);
            cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
            cv::imwrite(DBG_OUTPUT_ROOT "mergedImgGamma.jpg", outputImg);
        }
#endif

// step 5. HDR tone mapping
// processedImage, gain, shortExposure, longExposure, fusedExposure = localToneMap(burstPath, processedImage, options)
        int gain;
        if(params.options.ltmGain){
            cv::Mat shortExposure, longExposure, fusedExposure;
            
            localToneMap(processedMerge, params.options,shortExposure,longExposure,fusedExposure,gain);
            std::cout<<"gain="<< gain<<std::endl;
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
            if(params.flags["writeShortExposure"]){
                std::cout<<"writing ShortExposure img ..."<<std::endl;
                cv::Mat outputImg = convert16bit2_8bit_(shortExposure);
                cv::imwrite(DBG_OUTPUT_ROOT "shortg.jpg", outputImg);
            }
#endif

#ifndef HDRPLUS_NO_DETAILED_OUTPUT
            if(params.flags["writeLongExposure"]){
                std::cout<<"writing LongExposure img ..."<<std::endl;
                cv::Mat outputImg = convert16bit2_8bit_(longExposure);
                cv::imwrite(DBG_OUTPUT_ROOT "longg.jpg", outputImg);
            }
#endif

#ifndef HDRPLUS_NO_DETAILED_OUTPUT
            if(params.flags["writeFusedExposure"]){
                std::cout<<"writing FusedExposure img ..."<<std::endl;
                cv::Mat outputImg = convert16bit2_8bit_(fusedExposure);
                cv::imwrite(DBG_OUTPUT_ROOT "fusedg.jpg", outputImg);
            }
#endif

#ifndef HDRPLUS_NO_DETAILED_OUTPUT
            if(params.flags["writeLTMImage"]){
                std::cout<<"writing LTMImage ..."<<std::endl;
                cv::Mat outputImg = convert16bit2_8bit_(processedMerge.clone());
                cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
                cv::imwrite(DBG_OUTPUT_ROOT "ltmGain.jpg", outputImg);
            }
#endif

#ifndef HDRPLUS_NO_DETAILED_OUTPUT
            if(params.flags["writeLTMGamma"]){
                std::cout<<"writing LTMImage Gamma ..."<<std::endl;
                cv::Mat outputImg = gammasRGB(processedMerge.clone(),true);
                outputImg = convert16bit2_8bit_(outputImg);
                cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);
                cv::imwrite(DBG_OUTPUT_ROOT "ltmGain_gamma.jpg", outputImg);
            }
#endif
        }

// step 6 GTM: contrast enhancement / global tone mapping
        if(params.options.gtmContrast){
            processedMerge = enhanceContrast(processedMerge, params.options);
            std::cout<<"STEP 6 -- Apply GTM"<<std::endl;
        }

        // apply the final sRGB gamma curve
        processedMerge = gammasRGB(processedMerge.clone(),true);
        std::cout<<"-- Apply Gamma"<<std::endl;

#ifndef HDRPLUS_NO_DETAILED_OUTPUT
        if(params.flags["writeGTMImage"]) {
            std::cout<<"writing GTMImage ..."<<std::endl;
            cv::Mat outputImg = convert16bit2_8bit_(processedMerge.clone());
            cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);

            cv::imwrite(DBG_OUTPUT_ROOT "GTM_gamma.jpg", outputImg);
        }
#endif

// Step 7: sharpen
		finalOutputImage = sharpenTriple(processedMerge.clone(), params.tuning, params.options);
        cv::Mat& processedImage = finalOutputImage;
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
        if(params.flags["writeFinalImage"]){
            std::cout<<"writing FinalImage ..."<<std::endl;
            cv::Mat outputImg = convert16bit2_8bit_(processedImage.clone());
            cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);

            cv::imwrite(DBG_OUTPUT_ROOT "FinalImage.jpg", outputImg);
        }
#endif

// write final ref
#ifndef HDRPLUS_NO_DETAILED_OUTPUT
        if(params.flags["writeReferenceFinal"]){
            std::cout<<"writing Final Ref Image ..."<<std::endl;
            if(params.options.ltmGain){
                params.options.ltmGain = gain;
            }
            cv::Mat shortExposureRef, longExposureRef, fusedExposureRef;
            localToneMap(processedRefImage, params.options,shortExposureRef,longExposureRef,fusedExposureRef,gain);
            if(params.options.gtmContrast){ // contrast enhancement / global tone mapping
                processedRefImage = enhanceContrast(processedRefImage, params.options);
            }
            processedRefImage = gammasRGB(processedRefImage.clone(),true);
            // sharpen
            processedRefImage = sharpenTriple(processedRefImage.clone(), params.tuning, params.options);
            cv::Mat outputImg = convert16bit2_8bit_(processedRefImage.clone());
            cv::cvtColor(outputImg, outputImg, cv::COLOR_RGB2BGR);

            cv::imwrite(DBG_OUTPUT_ROOT "FinalReference.jpg", outputImg);
        }
#endif
// End of finishing
    }

    void finish::copy_mat_16U(cv::Mat& A, cv::Mat B){
        u_int16_t* ptr_A = (u_int16_t*)A.data;
        u_int16_t* ptr_B = (u_int16_t*)B.data;
        for(int r = 0; r < A.rows; r++) {
            for(int c = 0; c < A.cols; c++) {
                *(ptr_A+r*A.cols+c) = *(ptr_B+r*B.cols+c);
            }
        }
    }

    

    void finish::copy_rawImg2libraw(std::shared_ptr<LibRaw>& libraw_ptr, cv::Mat B){
        u_int16_t* ptr_A = (u_int16_t*)libraw_ptr->imgdata.rawdata.raw_image;
        u_int16_t* ptr_B = (u_int16_t*)B.data;
        for(int r = 0; r < B.rows; r++) {
            for(int c = 0; c < B.cols; c++) {
                *(ptr_A+r*B.cols+c) = *(ptr_B+r*B.cols+c);
            }
        }

    }
    
    
} // namespace hdrplus
