package yi.iu.processor.graphseg;

import java.awt.*;
import yi.util.io.*;

public class DM_HCI extends DistanceMeasure
{
    //Patrick Lambert, Thierry Carron, Symbolic fusion of luminance-hue-chroma features for region segmentation,
    // Pattern Recognition 32 (1999) 1857-1872
    static int H_GREY=45;
    static int H_PURE=128;
    //int SMALL_SIZE_THRSH=16;
    int SMALL_SIZE_THRSH=1;
    
    public DM_HCI() {super();}
    public DM_HCI(int small_size_thrsh) {this();SMALL_SIZE_THRSH=small_size_thrsh;}
    
    //return the bare data (including the H-C-I information)
    public int[] getBareData(int[] source, Dimension d) {
            
	    int width=d.width, height=d.height;
	    int size=width*height;
	    int[] info= new int[size*3];      //the return information includes 3 planes (H-C-I)
    	int[] hci= new int[3];
	        
	    /*
	    // to test the range is [0..255]
        int maxh=0,maxc=0,maxi=0;
	    for (int r=0;r<256;r++) {
	        System.err.println(r+"/255");
	        for (int g=0;g<256;g++)
	            for (int b=0;b<256;b++) {
            	    rgb2hci(r,g,b,hci);
            	    int h=hci[0]; if(h>maxh) maxh=h;
            	    int c=hci[1]; if(c>maxc) maxc=c;
    	            int i=hci[2]; if(i>maxi) maxi=i;
    	            //if (c>250) System.err.println("c="+c+":"+r+","+g+","+b);
	            }
	    }
	    hci[0]=maxh;hci[1]=maxc;hci[2]=maxi;
	    OutputResult.outputResult("max_hci", hci);
	    System.err.println("max HCI="+maxh+","+maxc+","+maxi);
	    if (true) System.exit(0);
	    */
	        
	    int ptr=0;
	    //int[] c_stat=new int[256];
	    for (int i=0;i<size;i++) {
	        int red=source[i]>>16&255;
	        int green=source[i]>>8&255;
	        int blue=source[i]&255;
    	    rgb2hci(red,green,blue,hci);
    	    //if (hci[1]<35) {hci[0]=255;hci[1]=0;hci[2]=0;}
    	    info[ptr++]=hci[0];
    	    info[ptr++]=hci[1];
    	    info[ptr++]=hci[2];
    	    //c_stat[hci[1]]++;
	    }
	    //OutputResult.outputResult("chroma", c_stat);
	        
	    return info;
    }
        
    //To save calculation
    //the histogram of hue [0..BINS-1], chroma [BINS..BINS2-1], intensity [BINS2, BINS2+BINS-1]
    //the size of hue, chroma, intensity are at [H_SIZE], [C_SIZE], [I_SIZE]
    int BINS2= BINS+BINS;
    int H_SIZE= BINS+BINS2;
    int C_SIZE= H_SIZE+1;
    int I_SIZE= H_SIZE+2;
    //make the region information given the x,y coordinate in term of block
    //the region information including : the histogram of H, C, I, and the total weights of H, C, I, from left to rigth
    public int[] makeRgnInfo(int[] bareData, int w, int h, int blkSize, int x, int y)
    {
        int[] hst= new int[3*(BINS+1)];
        int QUAN=256/BINS;

        //calculate the region information (the histogram)
        //if c>=85, the color is pure, so give weight 2 to hue, give weight 0 to intensity
        //if 85>c>=35, the color is between pure and grey, give 1 to both hue and intensity
        //if 35>c, the color is grey, so give weight 0 to hue, give weight 2 to intensity
        for (int r=y*blkSize;r<(y+1)*blkSize;r++) {
            for (int c=x*blkSize;c<(x+1)*blkSize;c++) {
                int ptr= 3*(r*w+c);
                int hue=bareData[ptr++];
                int chroma=bareData[ptr++];
                int intensity=bareData[ptr];
                if (chroma>=H_PURE) {
                    hst[hue/QUAN]+=2; hst[H_SIZE]+=2;
                }
                else if (chroma>=H_GREY) {
                    hst[hue/QUAN]++; hst[H_SIZE]++;
                    hst[intensity/QUAN+BINS2]++; hst[I_SIZE]++;
                }
                else {
                    hst[intensity/QUAN+BINS2]+=2; hst[I_SIZE]+=2;
                }
                hst[chroma/QUAN+BINS]++; hst[C_SIZE]++;
            }
        }
        return hst;
    }

    public int distance(int h1[], int h2[]) {
        //if the chromas are much different, the two region should be seperated
        //else compare the intensity and hue, the weight of the two need to be decided.
            
        //if the difference between the weights of hue are large enough, return a big number;
        int c_size1=h1[C_SIZE], c_size2=h2[C_SIZE], h_size1=h1[H_SIZE], h_size2=h2[H_SIZE];
        int i_size1=h1[I_SIZE], i_size2=h2[I_SIZE];
        int small_size= c_size1; if (c_size2<small_size) small_size=c_size2;
        double diff_c_r= ((double)h_size2/c_size2*c_size1-h_size1)/c_size1;
        diff_c_r= Math.abs(diff_c_r);
        if (diff_c_r>1) return BIGNUM;
            
        int diff_h= distance(h1,h2,0,BINS,H_SIZE);
        int diff_c= distance(h1,h2,BINS,BINS2,C_SIZE);
        int diff_i= distance(h1,h2,BINS2,BINS2+BINS,I_SIZE);
        
        /*
        //choice 1, if the size is different, treat them as a different
        //if one of the hue or intensity size is 0, adjust the difference
        if (h_size1==0) diff_h=diff_i*h_size2/i_size2;
        if (h_size2==0) diff_h=diff_i*h_size1/i_size1;
        if (i_size1==0) diff_i=diff_h*i_size2/h_size2;
        if (i_size2==0) diff_i=diff_h*i_size1/h_size1;
        */
        //choice 2, if the size is different, treat them as not a different
        double weight_h=Math.sqrt(h_size1*h_size2);
        double weight_i=Math.sqrt(i_size1*i_size2);
        /*
        //make the component corresponding to the big difference takes big weight
        if (diff_h>diff_i) {
            //for example, if diff_h=100, diff_i=10
            //weight_h>=weight_i ==> weight_h=1, weight_i=0
            //weight_h<weight_i ==> weight_h=1/(weight_i/weight_h), weight_i=1-weight_i
            if (weight_h>=weight_i) weight_i=0;
            else weight_i-=weight_h;
        }
        else {
            if (weight_i>=weight_h) weight_h=0;
            else weight_h-=weight_i;
        }
        */
        double diff=(weight_h*diff_h+weight_i*diff_i)/(weight_h+weight_i);
        //adjust the distance according to the size of region
        //if (small_size<SMALL_SIZE_THRSH) diff*=(double)small_size*small_size/(SMALL_SIZE_THRSH*SMALL_SIZE_THRSH);
        if (small_size<SMALL_SIZE_THRSH) diff*=(double)small_size/(SMALL_SIZE_THRSH);
        else {
            //consider the effect of the differce between the sizes
            diff*=(diff_c_r+1);
        }

        //return diff_h+diff_c+diff_i;
        return (int)diff;
    }
        
	//return the distance between two histogram on a single property
	public int distance(int h1[], int h2[], int begin, int end, int size_i) {
        int sum1=0;
        int sum2=0;
        int sum=0;
        int size1=h1[size_i], size2=h2[size_i];
            
        if (size1==0) return 0;
        if (size2==0) return 0;
            
        int adjust1=size1, adjust2=size2;
        double adjust_back=size1*size2;
            
        //if adjusts are too big, the calculation may lead to overflow, so make them smaller
        if (adjust1>1024&&adjust2>1024) {
            adjust2=(int)((double)adjust2/adjust1*1024);
            adjust1=1000;
            //eg. if adjust1=3000, adjust2=2000, after change, the adjust1=1000, adjust2=667
            //for value of number of elements in a bin, the value should be normalized by dividing size
            //say, h1[x] should be normalized to h1[x]/3000, but now to *adjust2/(adjust1*adjust2)=h1[x]/adjust1=/1000,
            //so the adjust_back should be : 
            adjust_back=(adjust1*adjust2)*((double)size1/adjust1);
        }
            
        //choice 1
        for (int i=begin;i<end;i++) {
            sum1+=h1[i]; sum2+=h2[i];
            int d=sum1*adjust2-sum2*adjust1;
            sum+= Math.abs(d);
        }
            
        if (sum<0)  {
            System.err.println("distance is negative");
            return BIGNUM;
        }
        sum=(int)((double)sum*DISSCALE/adjust_back);
        return sum;
    }
        
    //description of the method is in HueChnlMaker.java
    static void rgb2hci(int r, int g, int b, int[] hci)
    {
        /*
        double y= (r+g+b)/3;           //(1/3,1/3,1/3)
        double ch1= r-(g+b)/2;              //(1,-1/2,-1/2)
        double ch2= (b-g)*sqrt3_2;       //(0,-3^1/2/2,3^1/2/2)
        double c=Math.sqrt(ch1*ch1+ch2*ch2);
        hci[1]=(int)c; hci[2]=(int)y;
        if (ch1==0&&ch2==0) hci[0]= 0;      //r=g=b, hue is meaningless
        else if (ch2<=0) hci[0]= (int) (Math.acos(ch1/c)/PI2*256);
        else hci[0]= (int) ((PI2-Math.acos(ch1/c))/PI2*256);
        if (hci[0]>=256||hci[1]>=256||hci[2]>=256)
            System.err.println("Wrong in RGB2HCI:"+"("+r+","+g+","+b+")->("+hci[0]+","+hci[1]+","+hci[2]+")");
        */
            
        int max= r, min=r;
        if (g>max) max=g; if (g<min) min=g;
        if (b>max) max=b; if (b<min) min=b;
            
        int v=max;
        double s=(max!=0)?((double)(max-min)/max):0;
        //extremely black or white
        if (min>235) s=s*(255-min)/20;
        if (max<100) s=s*max/100;
        double h;
        if (s==0) h=0;
        else {
            double delta= max-min;
            if (r==max) h=(g-b)/delta;
            else if (g==max) h=2+(b-r)/delta;
            else h=4+(r-g)/delta;
            h*=(256.0/6);
            if (h<0) h+=256;
        }
        hci[0]=(int)h;hci[1]=(int)(s*255);hci[2]=(r+g+b)/3;;
    }
        
    public boolean VerifyRgnInfo(int[] bareData, int w, int h, int blkSize, int[] segs, int[][] rgnInfo)
    {
        return true;
    }
        
    public void printDiffOfHists(int[][] rgnInfo, int x, int y)
    {
        if (x==y) return ;
        int[] h1=rgnInfo[x], h2=rgnInfo[y];
            
        int c_size1=h1[C_SIZE], c_size2=h2[C_SIZE], h_size1=h1[H_SIZE], h_size2=h2[H_SIZE];
        int i_size1=h1[I_SIZE], i_size2=h2[I_SIZE];
        
        double diff_c_r= ((double)h_size2/c_size2*c_size1-h_size1)/c_size1;
        
        //if (diff_c_r>0.5||diff_c_r<-0.5) return BIGNUM;
            
        int diff_h= distance(h1,h2,0,BINS,H_SIZE);
        int diff_c= distance(h1,h2,BINS,BINS2,C_SIZE);
        int diff_i= distance(h1,h2,BINS2,BINS2+BINS,I_SIZE);
        //return diff_h+diff_c+diff_i;
        
        double weight_h=Math.sqrt(h_size1*h_size2);
        double weight_i=Math.sqrt(i_size1*i_size2);
        double diff=(weight_h*diff_h+weight_i*diff_i)/(weight_h+weight_i);
        
            
        System.err.println("distance "+x+"<->"+y+":"+distance(h1,h2));
        System.err.println("        c_ratio="+diff_c_r+" diff_h="+diff_h+" diff_c="+diff_c+" diff_i="+diff_i);
        System.err.println("        weight_h="+weight_h+" weight_i="+weight_i);
        OutputResult.outputResult("region_"+x,h1);
        OutputResult.outputResult("region_"+y,h2);
    }
        
}
