package yi.iu.processor.feature;

import yi.iu.processor.canny.*;
import yi.iu.processor.channel.*;
import yi.iu.ui.*;
import yi.util.io.OutputResult;

import java.awt.*;

public class ColorOrientation extends yi.iu.processor.canny.CannyGray implements java.io.Serializable
{
    transient boolean VERBOSE= false;
    int[][] colorOrientHist= null;
    transient boolean useHue= true;
    
    public ColorOrientation(int source[], Dimension d) {
        makeFeature(source ,d);
    }
    
    public ColorOrientation(int[][] h) {
        colorOrientHist=h;
    }
    
    public boolean makeFeature(int source[], Dimension d)
    {
        int h=d.height, w=d.width, size=h*w;        // The dimensions of the image
        IntensityMaker im= new IntensityMaker();
        int[] image= im.getProcessed(source, d);
        
        // Perform the edge detection. All of the work takes place here.
        int[] edge= canny(image, d);
        edge= edge_dilations(image, edge, d);
        int[][] ch1= histogram(image, edge, d);
        
        int[][] ch= ch1;
        
        if (useHue) {
            int[] hue= HSB.getHue(source, d);
            saturationCheck(source, edge, d, 60);
            int[][] ch2= histogram(hue, edge, d);
            
            ch= new int[ch1.length+ch2.length][];
            for (int i=0;i<ch1.length;i++) ch[i]=ch1[i];
            for (int i=0;i<ch2.length;i++) ch[i+ch1.length]=ch2[i];
            
            hue=null; ch2=null;
        }
        //OutputResult.outputResult("ch", ch);
        colorOrientHist= ch;
        image= null; edge= null; ch1= null; System.gc();
        
        return true;
    }
    
    public double distance(ColorOrientation co) {
        int[][] h1= colorOrientHist;
        int[][] h2= co.colorOrientHist;
        if (h1==null||h2==null) {
            System.err.println("the feature is not initiazed!");
            return 1;
        }
        int s1= h1.length, s2= h1[0].length;
        int sum_similarity=0, sum_total=0;
        for (int i=0;i<s1;i++) {
            int similarity=0;
            int total1=0;
            int total2=0;
            for (int j=0;j<s2;j++) {
                total1+=h1[i][j];
                total2+=h2[i][j];
                similarity+= (h1[i][j]<h2[i][j])?h1[i][j]:h2[i][j]; //add the smaller one
            }
            sum_similarity+=similarity;
            sum_total+= (total1>total2)?total1:total2;
        }
        if (sum_similarity>sum_total) {
            System.err.println("can't bigger than unit 1");
        }
        if (sum_total==0) return 0;
        return 1-((double)sum_similarity)/sum_total;
    }
    
    transient double[] dir_radians= null;   //direction of every pixel (in radian)
    transient int[] magnitude= null;     //magnitude of every pixel
    
    public ColorOrientation() {
        super();
        sigma= 2;        // Standard deviation of the gaussian kernel.
        tlow= 0.5;       // Fraction of the high threshold in hysteresis.
        thigh= 40;      // High hysteresis threshold control. The actual
    }
    
    public int[] getProcessedPseudoColor(int source[], Dimension d)
    {
        int h=d.height, w=d.width, size=h*w;        // The dimensions of the image
        IntensityMaker im= new IntensityMaker();
        int[] image= im.getProcessed(source, d);
        
        // Perform the edge detection. All of the work takes place here.
        int[] edge= canny(image, d);
        edge= edge_dilations(image, edge, d);
        int[][] ch1= histogram(image, edge, d);
        
        int[][] ch= ch1;
        
        if (useHue) {
            int[] hue= HSB.getHue(source, d);
            saturationCheck(source, edge, d, 60);
            int[][] ch2= histogram(hue, edge, d);
            
            ch= new int[ch1.length+ch2.length][];
            for (int i=0;i<ch1.length;i++) ch[i]=ch1[i];
            for (int i=0;i<ch2.length;i++) ch[i+ch1.length]=ch2[i];
        }
        OutputResult.outputResult("ch", ch);
        
        return null;
    }
    
    public int[] canny(int[] image, Dimension d)
    {
        // Perform gaussian smoothing on the image using the input standard deviation.
        if(VERBOSE) System.err.println("Smoothing the image using a gaussian kernel.");
        int[] smoothedim= gaussian_smooth(image, d);
        
        // Compute the first derivative in the x and y directions.
        if(VERBOSE) System.err.println("Computing the X and Y first derivatives.");
        int[] delta_x= new int[d.width*d.height];
        int[] delta_y= new int[d.width*d.height];
        derrivative_x_y(smoothedim, d, delta_x, delta_y);
        
        // This option to write out the direction of the edge gradient was added
        // to make the information available for computing an edge quality figure
        // of merit.
        // Compute the direction up the gradient, in radians that are
        // specified counteclockwise from the positive x-axis.
        //double[] dir_radians= radian_direction(delta_x, delta_y, d, -1, -1);
        dir_radians= radian_direction(delta_x, delta_y);
        
        // Compute the magnitude of the gradient.
        if(VERBOSE) System.err.println("Computing the magnitude of the gradient.");
        magnitude= magnitude_x_y(delta_x, delta_y, d);
        
        // Perform non-maximal suppression.
        if(VERBOSE) System.err.println("Doing the non-maximal suppression.");
        
        int[] nms= non_max_supp(magnitude, delta_x, delta_y, d);
        
        // Use hysteresis to mark the edge pixels.
        if(VERBOSE) System.err.println("Doing hysteresis thresholding.");
        int[] edge= apply_hysteresis(magnitude, nms, d);
        return edge;
    }
    
    public int[] edge_dilations(int[] image, int[] edge, Dimension d)
    {
        //now the edge is the edge map, magnitude and dir_radians save the magnitude and direction gradient
        //do the dilation (3*3 mask)
        int[] new_edge= edge_dilation(edge, magnitude, dir_radians, d);       
        
        //combine the new edge and old edge
        for (int i=0;i<new_edge.length;i++) {
            if (edge[i]!=0) {
                if (new_edge[i]!=0) System.err.println("both edge!");
                else new_edge[i]= edge[i];
            }
        }
        
        //second time
        new_edge= edge_dilation(new_edge, magnitude, dir_radians, d);
        System.gc();
        return new_edge;
    }
    
    
    public int[][] histogram(int[] image, int[] edge, Dimension d)
    {        
        //make the color-angel histogram
        int[][] ch= new int[256][180];
        for (int i=0;i<edge.length;i++) {
            //if (edge[i]==0&&new_edge[i]!=0) {
            if (edge[i]!=0) {
                int c=image[i];
                if (c<0||c>255) System.err.println("color index out of range");
                int angel= (int)Math.round(dir_radians[i]*180/Math.PI);
                if (angel>=180) angel= 360- angel;
                if (angel==180) angel=0;
                if (angel<0||angel>=180) System.err.println("angel index out of range");
                ch[c][angel]++;
            }
        }
        ch= overlap_quan(ch,16,12,false,true);
        return ch;
    }
    
    transient final static double SQRT2= Math.sqrt(2);
    int[] edge_dilation(int[] edge, int[] mag, double[] dir, Dimension d) {
        int h=d.height, w= d.width, size= h*w;
        int y,x,ptr;
        //new a new edge map
        int[] nedge= new int[size];
        
        //first scan
        ptr=-1;
        for (y=0;y<h;y++) {
            for (x=0;x<w;x++) {
                ptr++;
                //if already be an edge
                if (edge[ptr]!=0) continue;
                
                double xs=0, ys=0;  //x-strength and y-strength
                double wp=0;
                //N, (ptr-w)
                if (y>0&&edge[ptr-w]!=0) {
                    wp+=SQRT2;      //weight+sqrt(2)
                    xs+= SQRT2*mag[ptr-w]*Math.cos(dir[ptr-w]);
                    ys+= SQRT2*mag[ptr-w]*Math.sin(dir[ptr-w]);
                }
                //S, (ptr+w)
                if (y<h-1&&edge[ptr+w]!=0) {
                    wp+=SQRT2;      //weight+sqrt(2)
                    xs+= SQRT2*mag[ptr+w]*Math.cos(dir[ptr+w]);
                    ys+= SQRT2*mag[ptr+w]*Math.sin(dir[ptr+w]);
                }
                //W, (ptr-1)
                if (x>0&&edge[ptr-1]!=0) {
                    wp+=SQRT2;      //weight+sqrt(2)
                    xs+= SQRT2*mag[ptr-1]*Math.cos(dir[ptr-1]);
                    ys+= SQRT2*mag[ptr-1]*Math.sin(dir[ptr-1]);
                }
                //E, (ptr+1)
                if (x<w-1&&edge[ptr+1]!=0) {
                    wp+=SQRT2;      //weight+sqrt(2)
                    xs+= SQRT2*mag[ptr+1]*Math.cos(dir[ptr+1]);
                    ys+= SQRT2*mag[ptr+1]*Math.sin(dir[ptr+1]);
                }
                //NW (ptr-w-1)
                if (y>0&&x>0&&edge[ptr-w-1]!=0) {
                    wp++;           //weight+1
                    xs+= mag[ptr-w-1]*Math.cos(dir[ptr-w-1]);
                    ys+= mag[ptr-w-1]*Math.sin(dir[ptr-w-1]);
                }
                //NE (ptr-w+1)
                if (y>0&&x<w-1&&edge[ptr-w+1]!=0) {
                    wp++;           //weight+1
                    xs+= mag[ptr-w+1]*Math.cos(dir[ptr-w+1]);
                    ys+= mag[ptr-w+1]*Math.sin(dir[ptr-w+1]);
                }
                //SW (ptr+w-1)
                if (y<h-1&&x>0&&edge[ptr+w-1]!=0) {
                    wp++;           //weight+1
                    xs+= mag[ptr+w-1]*Math.cos(dir[ptr+w-1]);
                    ys+= mag[ptr+w-1]*Math.sin(dir[ptr+w-1]);
                }
                //SE (ptr+w+1)
                if (y<h-1&&x<w-1&&edge[ptr+w+1]!=0) {
                    wp++;           //weight+1
                    xs+= mag[ptr+w+1]*Math.cos(dir[ptr+w+1]);
                    ys+= mag[ptr+w+1]*Math.sin(dir[ptr+w+1]);
                }
                
                if (wp>0.1) {
                    //mark the new edge
                    nedge[ptr]=255;
                    xs/=wp; ys/=wp;
                    dir[ptr]= angle_radians(xs, ys);
                    mag[ptr]= (int)Math.round(Math.sqrt(xs*xs+ys*ys));
                }
            }
        }
        return nedge;
    }
    
    //overlap_quantilize the 2-D data
    //r1: the width of the bin on the 1st dimension, r2: the width of the bin on the 2nd dimension
    //l1: whether the data are rotative on 1st dimension, l2: whether the data are rotative on 2nd
    //eg: data[256][256], r1=r2=16, then [0..16], [8..24], .... on the 1st and 2nd dimension
    int[][] overlap_quan(int[][] data, int r1, int r2, boolean l1, boolean l2)
    {
        int s1= data.length, s2= data[0].length;
        //make a intermediate results with the width of half of bin as the final result
        int t_h= s1/(r1/2), t_w= s2/(r2/2);
        int[][] temp= new int[t_h][t_w];
        int q1=s1/t_h, q2= s2/t_w;
        for (int i=0;i<s1;i++) {
            for (int j=0;j<s2;j++) {
                temp[i/q1][j/q2]+=data[i][j];
            }
        }
        
        //intermediate result 2, combining on the 2nd dimension
        int[][] temp2;
        if (l2) temp2= new int[t_h][t_w];       //loop calculate on 2nd-d
        else temp2= new int[t_h][t_w-1];
        for (int i=0;i<t_h;i++) {
            for (int j=0;j<t_w-1;j++) temp2[i][j]= temp[i][j]+temp[i][j+1];
            if (l2) temp2[i][t_w-1]= temp[i][t_w-1]+temp[i][0]; //loop
        }

        //result
        int[][] rst;
        t_w= temp2[0].length;
        if (l1) rst= new int[t_h][t_w];
        else rst= new int[t_h-1][t_w];
        for (int j=0;j<t_w;j++) {
            for (int i=0;i<t_h-1;i++) rst[i][j]= temp2[i][j]+temp2[i+1][j];
            if (l1) rst[t_h-1][j]= temp2[t_h-1][j]+temp2[0][j];
        }
        
        temp= null; temp2=null; System.gc();
        return rst;
    }
    
    public void saturationCheck(int[] source, int[] edge, Dimension d, int threshold)
    {
        int h=d.height, w=d.width, size=h*w;        // The dimensions of the image
        float[] tmp= new float[3];
        for (int i=0;i<size;i++) {
            if (edge[i]<=0) continue;
            int c= source[i];
            int r= c>>16&0xff,g= c>>8&0xff,b= c&0xff;
            Color.RGBtoHSB(r, g, b, tmp);
            int s= (int)Math.round(tmp[1]*255);
            if (s<threshold) edge[i]=0;
        }
    }
    
    public int[][] getHistogram() {return colorOrientHist;}
}