//[Update:[Sun May 30 IST 2021]]
//[[./map2018.png]]
/* History:
   **Wed May 30 2018
   Sort of working version.
 */
// {{{ Imports
 import java.awt.*;
 import java.awt.image.*;
 import javax.swing.*;
 import javax.imageio.*;
 import java.io.*;
 import java.util.*;
         
     // }}}

public class Maja
    implements Callback {
    private BufferedImage img,rough;
    int w,h, cenI, cenJ;
    int left, right, top, bottom;
    Vector<Axar> box;

    private int BACKGROUND = -1;
    FloodFill ff;

    int উপর, নীচ, বাঁ, ডান;
    
    public Maja(JProgressBar pbar) {
        pb = pbar;
        box = new Vector<Axar>();
    }

    int minI, maxI, minJ, maxJ;

    public void work(Pixel p, int id) {
        //dbg("visiting "+p);
        img.setRGB(p.j,p.i,BACKGROUND);
        maxI = (maxI < p.i? p.i : maxI);
        minI = (minI > p.i? p.i : minI);
        maxJ = (maxJ < p.j? p.j : maxJ);
        minJ = (minJ > p.j? p.j : minJ);
    }

    private void count() {
        //dbg("["+minI+", "+maxI+"] x ["+minJ+", "+maxJ+"]");
        int centreI = (minI+maxI)/2;
        int centreJ = (minJ+maxJ)/2;
        rough = ff.getRough();
        Graphics g = rough.getGraphics();
        int localState;

        // {{{ নীচ বার কর 

        নীচ = 0;
        localState = WHITE;
        for(int i=centreI; i<=maxI; i++) {
            switch(localState) {
            case BLACK: 
                if(!isFG(rough,i,centreJ)) localState = WHITE;
                //rough.setRGB(centreJ,i,0xff00ff00);
                break;

            case WHITE: 
                if(isFG(rough,i,centreJ)) {
                    localState = BLACK;
                    নীচ++;
                }
                //rough.setRGB(centreJ,i,0xff0000ff);
                break;
            }
        }

        // }}}

        // {{{ উপর  বার কর 

        উপর = 0;
        localState = WHITE;
        for(int i=centreI; i>=minI; i--) {
            switch(localState) {
            case BLACK: 
                if(!isFG(rough,i,centreJ)) localState = WHITE;
                dbg("isFG is true.");
                //rough.setRGB(centreJ,i,0xff00ff00);
                break;

            case WHITE: 
                if(isFG(rough,i,centreJ)) {
                    localState = BLACK;
                    উপর++;
                }
                //rough.setRGB(centreJ,i,0xff0000ff);
                break;
            }
        }

        // }}}

        // {{{ বাঁ  বার কর 

        বাঁ = 0;
        localState = WHITE;
        for(int j=centreJ; j>=minJ; j--) {
            switch(localState) {
            case BLACK: 
                if(!isFG(rough,centreI,j)) localState = WHITE;
                //rough.setRGB(j,centreI,0xff00ff00);
                break;

            case WHITE: 
                if(isFG(rough,centreI,j)) {
                    localState = BLACK;
                    বাঁ++;
                }
                //rough.setRGB(j,centreI,0xff0000ff);
                break;
            }
        }
        // }}}

        // {{{ ডান  বার কর 

        ডান = 0;
        localState = WHITE;
        for(int j=centreJ; j<=maxJ; j++) {
            switch(localState) {
            case BLACK: 
                if(!isFG(rough,centreI,j)) localState = WHITE;
                break;

            case WHITE: 
                if(isFG(rough,centreI,j)) {
                    localState = BLACK;
                    ডান++;
                }
                break;
            }
        }
        // }}}
    }

    int[] group;

    // {{{ Union and find methods

    private void mergeGroups(int gp1, int gp2) {
        if(gp1 < gp2) 
            group[gp2] = gp1;
        else 
            group[gp1] = gp2;
    }

    private int groupOf(int elt) {
        while(group[elt]!=elt) elt = group[elt];
        return elt;
    }

    // }}}

    public void dumpBox() {
        int nAxar = box.size();
        group = new int[nAxar];

        // {{{ Find the transitive closure
        for(int i=0;i<nAxar;i++) 
            group[i] = i;


        for(int i=0;i<nAxar;i++) {
            for(int j=0;j<i;j++) {
                if(box.elementAt(i).
                   sharesLineWith(box.elementAt(j))) {
                    int gpOfI = groupOf(i);
                    int gpOfJ = groupOf(j);
                    mergeGroups(gpOfI, gpOfJ);

                    dbg("----- "+i+" -> "+j);
                    for(int k=0;k<nAxar;k++)
                        dbg(k+": "+group[k]+", ");

                }
            }
        }
        // }}}

        // {{{ Assign the lines to the axars

        for(int i=0;i<nAxar;i++) {
            int temp = i;
            while(group[temp]!=temp) temp = group[temp];
            group[i] = temp;
        }

        for(int i=0;i<nAxar;i++) 
            box.elementAt(i).setLine(group[i]);

        // }}}

        // {{{ Write the final output

        final int NORMAL = 0, NEED_SWAR = 1, ENG = 2;
        try {
            int outState = NORMAL;
            Collections.sort(box);
            for(Axar a : box) {
                System.err.println(a);
                switch(outState) {
                case NORMAL:
                    if(a.isByanjan()) outState = NEED_SWAR;
                    System.out.print(a.glyph(Axar.BENG));
                    if(a.isShift()) outState = ENG;
                    break;
                case NEED_SWAR:
                    if(a.isByanjan() || a.isYaphala()) {
                        System.out.print("a");
                    }
                    else {
                        outState = NORMAL;
                    }
                    System.out.print(a.glyph(Axar.BENG));
                    if(a.isShift()) outState = ENG;
                    break;
                case ENG: 
                    System.out.print(a.glyph(Axar.ENG));
                    if(a.isShift()) outState = NORMAL;

                }
            
            }
        }
        catch(Exception ex) {
            ex.printStackTrace();
        }

        // }}}
    }

    private boolean isFG(BufferedImage pic, int i, int j) {
        if(i<0 || i>=h || j<0 || j>=w) return false;
        dbg("col at ("+i+", "+j+") is "+(pic.getRGB(j,i) & 0xffffff)+" : ");
        boolean res= ((pic.getRGB(j,i) & 0xffffff) != 0xffffff);
        dbg("result="+res);
        return res;
    }

    Graphics imgG, outG, finG;
    final int BLACK = 0, WHITE = 1;
    public void prepare(String fname) throws Exception {
        img = ImageIO.read(new File(fname));

        System.out.println("\n---"+fname+"---");
        box.clear();
        w = img.getWidth();
        h = img.getHeight();
        pb.setMinimum(0);
        pb.setMaximum(h);
        dbg("w = "+w+", h = "+h);
        //doThresh();
        imgG =  img.getGraphics();

        pageI = pageJ = 0;
        ff = new FloodFill(img,this);
        toI = h; 
        toJ = w;

    }

    
    boolean drama;

    BufferedImage out;

    private void dbg(String msg) {
        if(drama) System.err.println(msg);
    }

    int procCount = 0;
    int pageI, pageJ;

    JProgressBar pb;

    BufferedImage getImage() {
        return rough;
    }

    BufferedImage getOrigImage() {
        return img;
    }

    int fromI, toI, fromJ, toJ;

    private boolean process() {
        dbg("Processing...");
        procCount++;
        pb.setValue(pageI);
        pb.setString(""+procCount);


        BACKGROUND = img.getRGB(0,0) & 0xffffff;

        boolean found = false;
        int i,j=0;
        boolean gotFG = false;
        boolean gotFGinPass;
        minI=h; maxI=0;
        minJ=w; maxJ=0;

        dbg("Starting search for the new glyph");
        find: do {
            gotFGinPass = false;
            // {{{ Find a black pixel (i,j) (gotFG(inPass) = true, if found)
        
            //System.err.println("Looking for a glyph in ["+fromI+", "+toI+"] x ["+fromJ+", "+toJ+"]");
            dbg("Searching for a black pixel...");
            search: for(i=fromI;i<toI;i++) {
                for(j=fromJ;j<toJ;j++) {
                    if(isFG(img,i,j)) {
                        gotFG = gotFGinPass = true;
                        pageI= i;
                        dbg("Found black pixel @ ["+i+", "+j+"]");
                        break search;
                    }
                }
            }
            
            // }}}
        
            if(gotFGinPass) {
                // {{{ Grab its connected component (updating minI/J, maxI/J)
                dbg("box before fill= ["+minI+", "+maxI+"] x ["+minJ+", "+maxJ+"]");                
                try {
                    ff.fill(new Pixel(i,j),0,true);
                }
                catch(Exception ex) {
                    dbg("Exception while finding glyph");
                    ex.printStackTrace();
                    return false;
                }
                dbg("box after fill= ["+minI+", "+maxI+"] x ["+minJ+", "+maxJ+"]");                
                // }}}

                // {{{ Grow the region a bit and narrow down on it

                dbg("Narrowing...");
                fromI = minI; //No need to move up.
                toI = maxI + 5;
                fromJ = minJ - 5;
                toJ = maxJ + 5;

                // }}}
            }

        } while(gotFGinPass);
        
        if(!gotFG) return false;
        fromI = minI; fromJ=0; toI = h; toJ = w; //Region for next search
        dbg("box = ["+minI+", "+maxI+"] x ["+minJ+", "+maxJ+"]");                


        // {{{ Classify what you've got
        int rangeI = maxI-minI;
        int rangeJ = maxJ-minJ;

        if(rangeI < 10 && rangeJ < 10) return true; //Noise blob

        // {{{ Check if dash or bar
        double ratio = ((double)rangeI)/rangeJ;
        if(ratio < 0.4) {
            box.add(new Axar("space",minI,maxI,minJ,maxJ));
            return true;
        }
        if(ratio > 2.5) {
            box.add(new Axar("eng",minI,maxI,minJ,maxJ));
            return true;
        }
        // }}}
        count();
        String id = ""+বাঁ+ডান+উপর+নীচ;
        System.err.println("id = "+id);
        box.add(new Axar(id,minI,maxI,minJ,maxJ));
        // }}}
        dbg("Got a glyph @["+minI+", "+maxI+"] x ["+minJ+", "+maxJ+"]");
        

        return true;
        
    }

    
    public static void main(String args[]) throws Exception {

        JProgressBar pbar = new JProgressBar();
        pbar.setIndeterminate(false);
        pbar.setStringPainted(true);
        pbar.setBorderPainted(true);
        Maja bdry = new Maja(pbar);

        JFrame f = new JFrame("আশ্চর্য লিপি");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JTabbedPane tabs = new JTabbedPane();
        f.add(tabs);
        f.add(BorderLayout.SOUTH,pbar);
        f.pack();
        f.setSize(500,500);
        f.setVisible(true);
        int i=0;
        try {
            for(i=0;i<args.length;i++) {
                System.err.println("Opening "+args[i]);
                bdry.prepare(args[i]);
                while(bdry.process());
                tabs.addTab("Extracted",new JScrollPane(new ImageHolder(bdry.getImage())));
                tabs.addTab("Remaining",new JScrollPane(new ImageHolder(bdry.getOrigImage())));
                bdry.dumpBox();
                System.err.println("\nFinished "+args[i]);
            }
        }
        catch(Exception ex) {
            //tabs.addTab("[x]"+args[i],new JScrollPane(new ImageHolder(bdry.getImage())));
            System.err.println("Undone at "+bdry.procCount+" of "+args[i]);
            ex.printStackTrace();
        }
    }

}
