// Johdatus tekoalyyn, syksy 2011
// Esimerkkiratkaisu tehtavaan 5.1
// koodi: Patrik Hoyer, Mika Laitinen, Teemu Roos

import java.awt.*;
import java.awt.image.*;
import javax.imageio.*;
import java.io.*;
import java.util.*;

class Pair implements Comparable<Pair> {
    public int x,y;
    public int compareTo(Pair p) {
        return this.y - p.y;
    }
}

class Image implements Comparable<Image> {
    public Vector<Boolean> vec;
    public int characterClass;   // oikea luokka
    public int distance;         // etaisyys testattavaan kuvaan

    public Image() {
    }

    public int compareTo(Image i1) {
        return this.characterClass - i1.characterClass;
    }

    // laske etaisyys testattavaan kuvaa (image)
    public int evalDistance(Image image) {
        int fails = 0;
        for(int i = 0; i < image.vec.size(); ++i) {
            if(image.vec.elementAt(i) != this.vec.elementAt(i)) {
                ++fails;
            }
        }
	distance = fails;
        return fails;
    }
}

// luokka, jota voi kayttaa kuvien jarjestamiseen etaisyysjarjestykseen
class NeighbourComparator implements Comparator<Image> {
    public NeighbourComparator() {
    }

    // vertailee etaisyyden perusteella
    public int compare(Image i1, Image i2) {
        return i1.distance - i2.distance;
    }
}

public class MakeBMP {
    static Vector<Image> I = new Vector<Image>();

    static void readImages(String xfilename, String yfilename) {
        try {
            Scanner xscanner = new Scanner(new File(xfilename));
            Scanner yscanner = new Scanner(new File(yfilename));
            while(xscanner.hasNextLine()) {
                Image i = new Image();
                String line = xscanner.nextLine();
                int characterClass = yscanner.nextInt();
                String splitarr[] = line.split(",");
                Vector<Boolean> vb = new Vector<Boolean>();
                for(String s : splitarr) {
                    if(s.equals("1")) {
                        vb.addElement(Boolean.TRUE);
                    } else vb.addElement(Boolean.FALSE);
                }
                i.vec = vb;
                i.characterClass = characterClass;
                I.addElement(i);
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    static void writeBMP(Image i, String filename) {
        BufferedImage bi = new BufferedImage(28,28,BufferedImage.TYPE_3BYTE_BGR); 
        for(int y = 0; y < 28; ++y) {
            for(int x = 0; x < 28; ++x) {
                int ind = y*28+x;
                bi.setRGB(x,y,(i.vec.elementAt(ind)?0:0xffffff));
            }
        }
        try {
            ImageIO.write(bi,"BMP",new File(filename));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // k-NN luokittelija
    static void kClassify(int k) {
	NeighbourComparator nc = new NeighbourComparator(); // vertailija
        int failures = 0;            // pidetaan ylla laskuria virheista

	// talletetaan opetusdata uuteen vektoriin, jota voi sorttailla.
	// jos haluat testata koodia nopeasti, voit lisata esim vain 1000
	// ensimmaista vektoria (muuta siis "i < 5000" -> "i < 1000".
        Vector<Image> data = new Vector<Image>();
        for(int i = 0; i < 5000; ++i) data.addElement(I.elementAt(i));
	
	int trials = 0;              // pidetaan ylla laskuria yrityksista

	// kaydaan lapi kaikki testitapaukset 
        for(int test = 5000; test < (int)I.size(); ++test) {
            System.out.print("classifying "+test);

	    // paivitetaan jokaisen opetusvektorin etaisyys testivektoriin
	    for (int train = 0; train < (int)data.size(); ++train)
		data.elementAt(train).evalDistance(I.elementAt(test));

	    // sortataan opetusvektorit etaisyyden mukaisesti
            Collections.sort(data,nc);

	    // alustetaan taulukko, johon lasketaan kunkin luokan
	    // esiintymiskerrat k lahimmassa naapurissa
            Vector<Pair> results = new Vector<Pair>();
            for(int j = 0; j < 10; ++j) {
                Pair p = new Pair();
                p.x = j;
                results.addElement(p);
            }

	    // kaydaan k lahinta naapuria lapi ja paivitetaan
	    // laskurit kunkin luokan esiintymiskerroista
            for(int j = 0; j < k; ++j) {
                results.elementAt(data.elementAt(j).characterClass).y++;
            }

	    // jarjestetaan luokat esiintymiskertojen mukaan laskevaan
	    // jarejstykseen => useimmin esiintynyt luokka ekaksi
            Comparator<Pair> comparator = Collections.reverseOrder();
            Collections.sort(results,comparator);

	    // tulostetaan luokittelun tulos, oikea vastaus, ja virheprosentti
	    System.out.print(": result "+results.elementAt(0).x);
	    System.out.print(" (true "+I.elementAt(test).characterClass+")");
            if(results.elementAt(0).x != I.elementAt(test).characterClass) {
                ++failures;
            }
	    ++trials;
	    System.out.format(" error rate %.1f%%\n",100.*failures/trials);
        }
	// virheprosentti kun kaikki testitapaukset on kayty lapi
        System.out.format("Final error rate with k = "+k+" was %.1f%%\n",100.*failures/trials);
    }

    static void testInput() {
        // otetaan sata ensimmäistä, järjestetään characterClassin
        // mukaan, ja piirretään iso kuva, ja katsotaan että samat
        // kirjaimet tulevat peräkkäin

        Vector<Image> I100 = new Vector<Image>();
        for(int i = 0; i < 100; ++i) I100.addElement(I.elementAt(i));

        Collections.sort(I100);

        BufferedImage bi = new BufferedImage(28*100,28,
                BufferedImage.TYPE_3BYTE_BGR);

        for(int i = 0; i < 100; ++i) {
            for(int y = 0; y < 28; ++y) {
                for(int x = 0; x < 28; ++x) {
                    int ind = y*28+x;
                    bi.setRGB(x+i*28,y,
                            (I100.elementAt(i).vec.elementAt(ind)?
                             0:0xffffff));
                }
            }
        }
        try {
            ImageIO.write(bi,"BMP",new File("test100.bmp"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        readImages(args[0],args[1]);
        testInput();
        for(int k = 1; k <= 10; ++k) {
            kClassify(k);
        }
    }
};
