package yi.iu.processor.building;

import yi.iu.processor.*;
import yi.iu.processor.ort.*;
import yi.iu.processor.filter.GaussianSmooth;
import yi.iu.ui.*;

import java.awt.*;
import java.util.Vector;

public class BuildingRecognition extends yi.iu.processor.ImageProcessor
{
    public static double SMOOTH_SIGMA=4;
    public static int WT=72;       //argument used in peak finding, if the peak is broader than WT, it is not considered to be prominent
                            //a multiplicative weight WB is included in the salience measure
    public static double WB=0.1;   //see comments with WT
    public static double GARMA_THRSH=3; //the threshold for garma (garma is the result of peak salience measure)

    public static int ANGEL_BINS=180;   //the number of bins for angel quantilization

    public static double PIBY2= Math.PI/2;     //1.570796

    //arguments to do the canny edge detection
    public static double sigma=1;
    public static double tlow=0.5;
    public static double thigh=50;
    public static int min_length=4;
    //arguments to do the line extraction
    public static int MaxJumpPixels=1;

    public int[] getProcessed(int source[], Dimension d)
    {
        // This method is derived from class yi.iu.processor.ImageProcessor
        // to do: code goes here
        return null;
    }

    public int[] getProcessedPseudoColor(int source[], Dimension d)
    {
        //1. edge detection and line extraction
        //find all the lines in an image
        Vector[] segs= (new Fex(sigma,tlow,thigh,min_length,MaxJumpPixels)).fex(source, d);
        //sigma=1; tlow=0.5; thigh=50; MINLENGTH=756/100=7; MaxJumpPixels=1;

        //2. calculate the line orientation histogram.
        Vector lines= segs[0];
        double[] h= lineOrientationHistogram(lines);
        //yi.util.io.OutputResult.outputResult("angel_hist",h);

        //3. smooth the histogram (must use periodically smoothing)
        //double[] smooth= (new GaussianSmooth(SMOOTH_SIGMA)).smoothing(h);
        double[] smooth= (new GaussianSmooth(SMOOTH_SIGMA)).smoothing_period(h);
        //yi.util.io.OutputResult.outputResult("angel_hist_s",smooth);

        //4. find the position of peaks (the tip of the peaks)
        //   if there is a peak on smooth[i], then peaks[0][i]=0, [1][i]=left, [2][i]=right
        int[][] peaks= findPeaks(smooth);

        //   convert the peaks format to the range format
        //   the len of ranges is the #peak, and ranges[i][0]=left, ranges[i][1]=right
        int[][] ranges= peaks2Ranges(peaks);    //convert the peaks to the range of peaks
        //yi.util.io.OutputResult.outputResult("peaks_int",peaks);
        //yi.util.io.OutputResult.outputResult("ranges",ranges);

        Vector arcs= segs[1];

        //draw all the lines and arcs using the color index 1
        char[][] label= DrawUtil.ort2image( d, lines, null);
        //change all the lines to color index 1
        for (int y=0;y<d.height;y++)
            for (int x=0;x<d.width;x++)
                if (label[y][x]>0) label[y][x]=1;


        Vector good_lines= new Vector();

        char[][] angel_map= new char[d.height][d.width];

        for (int i=0;i<lines.size();i++) {
            ORTLine line= (ORTLine)lines.elementAt(i);
            int angel= (int)Math.round(line.Theta*ANGEL_BINS/Math.PI);
            DrawUtil.ort2image(angel_map, line, angel, 1);
            int rangeNum=fallInRanges(angel, ranges);
            if (rangeNum>=0) {
                good_lines.addElement(line);
                //draw the line with a color represent the index of angel range
                //color=0 means no line, =1 means unselected line, so use color= rangeNum+2 (rangeNum begins from 0)
                DrawUtil.ort2image(label, line, rangeNum+2, 1);
            }
        }

        if (VERBOSE) System.err.println("line filtered by peaks "+good_lines.size()+"/"+lines.size());

        int[] label_1= yi.util.ImageUtil.twoD2oneD(label);
        int[] img= yi.util.ImageUtil.makeNewImageFromLabel(label_1);
        int[] angel_map_1= yi.util.ImageUtil.twoD2oneD(angel_map);


        //int[] rst= (new CannyGray(sigma,tlow,thigh)).getProcessed(source,d);
        //yi.util.ImageUtil.makeImageFromGray(rst);
        //for (int i=0;i<img.length;i++)
        //    if (img[i]!=0xff000000) rst[i]=img[i];
        //(new DisplayFrame("rst", rst, d.width, d.height)).show();
        //(new DisplayFrame("rst", img, d.width, d.height)).show();
           (new SegFrame("rst", img, d.width, d.height, angel_map_1)).show();

        //make a darker image background
        img= source;
        for (int i=0;i<img.length;i++) {
            int r= img[i]>>16&0xff;
            int g= img[i]>>8&0xff;
            int b= img[i]&0xff;
            img[i]=0xff000000|(r/2)<<16|(g/2)<<8|b/2;
        }


/*        //find the boundary of the building
        //first try
        int[][] have= gridBasedFinding(lines,arcs,ranges,d);
        yi.util.io.OutputResult.outputResult("have",have);
        int[] grid_map= new int[d.width*d.height];
        int qy= d.height/GN, qx=d.width/GN;
        for (int y=0;y<d.height;y++) {
            for (int x=0;x<d.width;x++) {
                int iy=y/qy, ix=x/qx;
                if (iy>=GN) iy=GN-1;
                if (ix>=GN) ix=GN-1;
                grid_map[y*d.width+x]=have[iy][ix];
                if (have[iy][ix]==1) {
                    img[y*d.width+x]|=0x007f7f7f;
                }
            }
        }
        for (int y=1;y<GN;y++) {
            for (int x=0;x<d.width;x++)
                img[y*qy*d.width+x]= 0xffff0000;
        }

        for (int y=0;y<d.height;y++) {
            for (int x=1;x<GN;x++)
                img[y*d.width+x*qx]= 0xffff0000;
        }
        (new SegFrame("rst", img, d.width, d.height, grid_map)).show();
*/

        //seperate the cluster to the vertical cluster (only 1) and other clusters(could more than 1)
        int[][][] _ranges= new int[2][][];      //to get the horizontal range and vertical range
        if (clusterSep(ranges,_ranges)!=3) {
            System.err.println("No Vertical and Other line clusters: NO BUIDINGS");
            return null;
        }
        int[][] hrange= _ranges[0];     //horizontal lines ranges
        int[][] vrange= _ranges[1];     //vertical lines ranges
        System.err.println("having horizontal cluster and vertical cluster!");

        //calculate the angels between all the lines in the vertical cluster and the lines from other cluster
        int[] intersect= new int[d.height*d.width];
        for (int i=0;i<lines.size();i++) {
            ORTLine l1= (ORTLine) lines.elementAt(i);
            int angel= (int)Math.round(l1.Theta*ANGEL_BINS/Math.PI);
            if (fallInRanges(angel,vrange)<0) continue;
            for (int j=0;j<lines.size();j++) {
                ORTLine l2= (ORTLine) lines.elementAt(j);
                angel= (int)Math.round(l2.Theta*ANGEL_BINS/Math.PI);
                int cl_num=fallInRanges(angel,hrange);      //cluster number
                if (cl_num<0) continue;
                ORTLineGroup lg= Lpeg.lineRelation(l1,l2,2,8,0.06,0.8);
                if (lg==null) continue;
                if (lg.groupType==ORTLineGroup.JUNCTION_L||lg.groupType==ORTLineGroup.JUNCTION_T||
                    lg.groupType==ORTLineGroup.JUNCTION_V||lg.groupType==ORTLineGroup.JUNCTION_LAMBDA) {
                        ORTConjLineGroup clg= (ORTConjLineGroup)lg;
                        double cx= clg.JunctionPt.Col, cy= clg.JunctionPt.Row;
                        cx+= d.width/2; cy+= d.height/2;
                        int index=(int)(Math.round(cy)*d.width+Math.round(cx));
                        intersect[index]=1;
                        int color;
                        if (cl_num==0) color=0xffffffff;
                        else if (cl_num==1) color=0xffffffff;
                        else if (cl_num==2) color=0xffffffff;
                        else color= 0xffffffff;
                        drawMark(img,d,(int)Math.round(cx),(int)Math.round(cy),8,color);


                        double deltaTheta =  Math.abs( clg.FirstID.Theta - clg.SecondID.Theta);
                        if (deltaTheta >  PIBY2) deltaTheta = Math.abs(Math.PI - deltaTheta);
                        int delta= (int)Math.round(deltaTheta*ANGEL_BINS/Math.PI);
/*
                        drawMark(angel_map_1,d,(int)Math.round(cx),(int)Math.round(cy),5,delta);
*/
                        /*
                        img[index]=0xffffffff;
                        if (index+1<img.length) img[index+1]=0xffffffff;
                        if (index-1>0)img[index-1]=0xffffffff;
                        if (index+d.width<img.length) img[index+d.width]=0xffffffff;
                        if (index-d.width>0)img[index-d.width]=0xffffffff;
                        */
                }
            }
        }
        //(new SegFrame("rst", img, d.width, d.height, angel_map_1)).show();
        (new DisplayFrame("rst", img, d.width, d.height)).show();

        return img;
    }

    //Seperate the line clusters to two group: one is the vertical line cluster, another contains all the other
    //clusters.  We assume that there is only one vertical-like cluster and one or more than one other clusters
    //(because the perspetive result)
    //return 0: none
    //return 1: only horizontal peak
    //return 2: only vertical peak
    //return 3: both
    //the format of ranges array:
    //  range[i][0]    the left boundary of the i-th peak
    //  range[i][1]    the right boundary of the i-th peak
    //remember the range is rotated, could as [175,5]
    public static int VERRANGE=5;   //determine what degree could be considered as vertical angel
                                    //=5 means [85,95] are considered as vertical angel
    public int clusterSep(int[][] range, int[][][] _ranges)
    {
        int vl=180/2-VERRANGE, vr=180/2+VERRANGE;   //verical angel range
        boolean havever=false;
        boolean havehor=false;
        int best_i=-1;       //the cluster index for the best vertical cluster
        double best_mid=0;  //the middle value of the best vertical cluster
        for (int i=0;i<range.length;i++) {
            int l=range[i][0];
            int r=range[i][1];
            double mid= (((r-l+180)%180)/2.0+l+180)%180;
            System.err.println("mid of range "+i+": "+mid);
            if (vl<=mid&&mid<=vr) {
                if (!havever) {     //first cluster could be considered as vertical cluster
                    havever=true;
                    best_i=i;
                    best_mid=mid;
                }
                else {              //already has a vertical cluster candidate
                    if (Math.abs(mid-180/2)<Math.abs(best_mid-180/2)) { //if this one is better
                        best_i=i;
                        best_mid=mid;
                    }
                }


            }
        }

        int[][] hra= null;  //non-vertical clusters ranges array
        int[][] vra= null;  //vertical cluster ranges array
        if (havever) {
            vra= new int[1][];                  //vertical cluster ranges array
            if (range.length-1>0) {
                hra= new int[range.length-1][];     //horizontal ranges array
                havehor=true;
            }
        }
        else {
            if (range.length>0) {
                hra= new int[range.length][];
                havehor=true;
            }
        }

        int count_nv=0;     //counter of non-vertical cluster
        for (int i=0;i<range.length;i++) {
            if (i==best_i) vra[0]= range[i];
            else hra[count_nv++]= range[i];
        }

        _ranges[0]= hra; _ranges[1]=vra;     //to return the results (horizontal and vertical rangs array)
        System.err.print("sperate the orientation clusters "+range.length+"->");
        System.err.print((havever?vra.length:0)+"/");
        System.err.println(havehor?hra.length:0);

        if (havever&&havehor) return 3;
        else if (havehor) return 1;
        else if (havever) return 2;
        return 0;
    }


    //find the position of the peaks (the tip of the peak)
    //return peak[3][len_of_histogram]
    //if there is a peak on histogram[i], then peak[0][i]=1, peak[1][i]=left_boundary, peak[2][i]=right_boundary
    static boolean TRAINING=false;
    public int[][] findPeaks(double[] data)
    {
        int len= data.length;
        double[] diff= new double[len];
        int i;
        int[][] peaks= new int[3][len];
        double[][] result=null;
        if (TRAINING) {
            result= new double[4][];
            result[0]= data;
            result[1]= new double[len];
            result[2]= new double[len];
            result[3]= new double[len];
        }

        //first difference of orientation histogram
        for (i=0;i<len-1;i++) diff[i]= data[i+1]-data[i];
        diff[len-1]= data[0]-data[len-1];

        //find the "average" of the lower half part of the histogram
        //1. sort the histogram first
        double[] copy= new double[len]; //make a copy of the data
        for (i=0;i<len;i++) copy[i]= data[i];
        double sum=0;
        for (i=0;i<len/2;i++) {
            for (int j=i+1;j<len;j++) {
                if (copy[i]>copy[j]) {
                    double temp= copy[i]; copy[i]= copy[j]; copy[j]=temp;
                }
            }
            sum+= copy[i];
        }
        double average= sum/(len/2);


        //to find the peaks, at that point the difference changes from positive to negative
        //eg data[1 4 5 2] -> diff[3 1 -3 -1]
        //first find a index at which the diff changes from positive to negative
        //then search diff backward to find a index l, satisfying diff[l-1]<diff[l]>diff[l+1];
        //also search diff forward to find a index r, satisfying diff[r-1]>diff[r]<diff[r+1];
        for (i=0;i<len;i++) {
            //REMEMBER (-1)%5=-1 !!!!
            //System.err.println("-1%5="+(-1)%5);
            if (diff[i]>0||diff[(i-1+len)%len]<=0) continue;
            //find a zero-crossing point
            //diff[i] satisfy diff[i]<=0 && diff[i-1]>0

            if (data[i]<=average) continue;
            //now the peak must be higher than the average

            //find the local maxima in the diff histogram
            int l=(i-1+len)%len;
            double max= diff[l];    //remember the maxima
            int max_l=l;            //remember the index of the maxima
            do {
                if (diff[l]>max) {
                    max=diff[l]; max_l=l;
                }
                l=(l-1+len)%len;
            } while (diff[l]>=0);
            //diff[l]<0, data[l+1] is a minima
            //eg data{3,0,3} -> diff{-3,3,?} -> diff[l]=-3, data[l+1]:min
            l=(l+1)%len;
            //now data[l] is the minima
            //data[max_l+1] and data[max_l] are around the inflection point

            //find the local minima in the diff histogram
            int r=i;
            double min= diff[r];
            int min_r=r;
            do {
                if (diff[r]<min) {
                    min=diff[r]; min_r=r;
                }
                r=(r+1)%len;
            } while (diff[r]<=0);
            //diff[r]>0, data[r] is a minima
            //eg data[3,0,3] -> diff[-3,3,?] -> diff[r]=3, data[r]:min

            //find the left boundary of the peak
            //calculate the average of the diff to the left of the peak
            double avrg_d= ((double)(data[i]-data[l]))/((i-l+len)%len);
            int lb=l;
            while (diff[lb]<avrg_d) lb=(lb+1)%len;
            lb=(lb+1)%len;
            //data[lb] is in the range of the peak, and data[lb-1] is not

            //find the right boundary of the peak
            avrg_d= -((double)(data[i]-data[r]))/((r-i+len)%len);
            int rb=r;
            while (diff[rb]>avrg_d) rb=(rb-1+len)%len;
            //data[rb] is in the range of the peak, and data[rb+1] is not

            //calculate the garma
            double hs= data[i];
            //double dv= diff[max_l]-diff[min_r];
            double dv= max-min;
            double dh= (min_r-max_l+len)%len;
            double max_val= data[i];    //maximum point
            double min_val= data[l]>data[r]?data[l]:data[r];    //the largest minimum value in data
            double wm= 1-(min_val/max_val);
            //double wb= ((min_r-max_l+len)%len)>WT?WB:1;
            double board= (rb-lb+len)%len;
            double wb= board>WT?WB:1;
            double garma= hs*dv/dh*wm*wb;
            if (TRAINING) {
                result[1][lb]=max_val;
                result[2][rb]=max_val;
                result[3][i]=garma;
            }
            if (garma>GARMA_THRSH) {
                peaks[0][i]=1;
                peaks[1][i]=lb;
                peaks[2][i]=rb;
            }
            //because the r is already a minimum, so the next maxima must be the right to r
            if (r>i) i=r+1;
            else break;
        }
        //if (TRAINING) yi.util.io.OutputResult.outputResult("peaks",peaks);
        return peaks;

    }

    //calculate the line orientation histogram
    //have all the lines from the image
    //according to their orientation, contribute the length of the lines to the corresponding orientation bins
    //eg.  there is a line with orientation of 145 degree and length of 10
    //it will contribute to the bin 145 10 (histogram[145]+=10)
    public double[] lineOrientationHistogram(Vector lines)
    {
        double[] h= new double[ANGEL_BINS];
        for (int i=0;i<lines.size();i++) {
            ORTLine line= (ORTLine)lines.elementAt(i);
            int angel= (int)Math.round(line.Theta*ANGEL_BINS/Math.PI);
            if (angel<0) angel+=ANGEL_BINS;
            if (angel>=ANGEL_BINS) angel-=ANGEL_BINS;
            h[angel]+=line.Length;
        }
        return h;
    }

    //convert the peaks array to ranges array
    //the format of peaks array:
    //  peaks[0][i]=1  there is a peak at degree i;
    //             =0  else
    //  peaks[1][i]    the left boundary of the range, inclusively
    //  peaks[2][i]    the right boundary of the range, inclusively
    //the format of ranges array:
    //  range[i][0]    the left boundary of the i-th peak
    //  range[i][1]    the right boundary of the i-th peak
    public int[][] peaks2Ranges(int[][] peaks)
    {
        int count=0;
        for (int i=0;i<peaks[0].length;i++) if (peaks[0][i]>0) count++;
        int[][] ranges= new int[count][2];
        System.err.println("found "+count+" ranges");
        count=0;
        for (int i=0;i<peaks[0].length;i++)
            if (peaks[0][i]>0) {
                ranges[count][0]=peaks[1][i];
                ranges[count][1]=peaks[2][i];
                System.err.println("  "+count+": "+ranges[count][0]+"-"+ranges[count][1]);
                count++;
            }
        return ranges;
    }


    //decide which ranges a angel is fallen into
    //if the angel is not fallen into any ranges, return -1
    //remember the index of the ranges begin with 0, NOT 1
    //remember the range is rotated, like 180=0, so the range could be [175, 5] means [175,180)+[0,5]
    //also the ranges is inclusivly on both ends
    public int fallInRanges(int angel, int[][] ranges)
    {
        for (int i=0;i<ranges.length;i++) {
            int l= ranges[i][0];
            int r= ranges[i][1];
            if (l<=r) {
                if (angel>=l&&angel<=r) return i;
            }
            else
                if (angel>=l||angel<=r) return i;
        }
        return -1;
    }


    //draw a rectangel mark on the image
    //(cx,cy) is the coordinate, w is the width of the mark, c is the color
    public void drawMark(int[] img, Dimension d, int cx, int cy, int w, int c)
    {
        for (int y=cy-w/2;y<=cy-w/2+w;y++) {
            if (y<0||y>=d.height) continue;
            for (int x=cx-w/2;x<=cx-w/2+w;x++) {
                if (x<0||x>=d.width) continue;
                img[y*d.width+x]=c;
            }
        }
    }



}