package ac.essex.gp.problems.examples.noisereduction;

import ac.essex.ooechs.imaging.commons.PixelLoader;
import ac.essex.ooechs.imaging.commons.fast.FastStatistics;
import ac.essex.ooechs.imaging.commons.util.panels.ImageFrame;
import ac.essex.ooechs.imaging.commons.util.panels.ScalingImageFrame;
import ac.essex.ooechs.imaging.commons.util.ImageFilenameFilter;
import ac.essex.gp.problems.ImagingProblem;
import ac.essex.gp.interfaces.graphical.GraphicalListener;

import java.awt.image.BufferedImage;
import java.awt.*;
import java.io.File;
import java.util.Vector;

/**
 * Evolved noise reduction system.
 * Evolved at: Thu Nov 27 13:24:49 GMT 2008 in about 3 minutes.
 */
public class EvolvedNoiseReducer {


    public static void main(String[] args) throws Exception {
        EvolvedNoiseReducer enr = new EvolvedNoiseReducer();
        BufferedImage onePass = enr.process(new PixelLoader("M:\\pc\\desktop\\noise\\noise\\noise5\\keyboard.png"));
        BufferedImage twoPass = enr.process(new PixelLoader(onePass));
        new ScalingImageFrame(onePass, "1 Pass");
        new ScalingImageFrame(twoPass, "2 Passes");

        PixelLoader clean = new PixelLoader("M:\\pc\\desktop\\noise\\noise\\keyboard.png");
        PixelLoader noisy = new PixelLoader("M:\\pc\\desktop\\noise\\noise\\noise5\\keyboard.png");
        PixelLoader median = new PixelLoader("M:\\pc\\desktop\\noise\\noise\\median_filter\\noise5_keyboard.png");
        PixelLoader eps = new PixelLoader("M:\\pc\\desktop\\noise\\noise\\edge_preserving_smooth\\noise5_keyboard.png");

        median = new PixelLoader(enr.toGrayscale(median));
        eps = new PixelLoader(enr.toGrayscale(eps));

        new ScalingImageFrame(enr.toGrayscale(noisy), "Original");
        new ScalingImageFrame(median.getBufferedImage(), "Median");
        new ScalingImageFrame(eps.getBufferedImage(), "EPS");
        new ScalingImageFrame(enr.toMode(noisy), "Truncated Median");
        new ScalingImageFrame(enr.toGaussian(noisy), "Gaussian");

        System.out.println("Clean vs Noisy: " + enr.getDifference(clean, noisy));
        System.out.println("Clean vs 1 Pass: " + enr.getDifference(clean, new PixelLoader(onePass)));
        System.out.println("Clean vs 2 Pass: " + enr.getDifference(clean, new PixelLoader(twoPass)));
        System.out.println("Clean vs Median: " + enr.getDifference(clean, median));
        System.out.println("Clean vs EPS: " + enr.getDifference(clean, eps));
        
    }

    private int[] rgb;

    public EvolvedNoiseReducer() {
        rgb = new int[256];
        for (int i = 0; i < rgb.length; i++) {
            rgb[i] = new Color(i, i, i).getRGB();
        }
    }

    public double getDifference(PixelLoader image1, PixelLoader image2) {
        double rmsError = 0;
        int size = 1;
        for (int ny = size; ny < image1.getHeight() - size; ny++) {
            for (int nx = size; nx < image1.getWidth() - size; nx++) {


                int diff = Math.abs(image1.getGreyValue(nx, ny) - image2.getGreyValue(nx, ny));
                rmsError += diff;

            }
        }
        return rmsError;
    }

    public BufferedImage toMode(PixelLoader noisyImage) {

        BufferedImage out = new BufferedImage(noisyImage.getWidth(), noisyImage.getHeight(), BufferedImage.TYPE_INT_RGB);

        int size = 1;
        for (int ny = size; ny < noisyImage.getHeight() - size; ny++) {
            for (int nx = size; nx < noisyImage.getWidth() - size; nx++) {
                out.setRGB(nx, ny, rgb[(int) noisyImage.get3x3TruncatedMedian(nx, ny, 1)]);
            }
        }

        return out;

    }

    public BufferedImage toGaussian(PixelLoader noisyImage) {

        BufferedImage out = new BufferedImage(noisyImage.getWidth(), noisyImage.getHeight(), BufferedImage.TYPE_INT_RGB);

        int size = 1;
        for (int ny = size; ny < noisyImage.getHeight() - size; ny++) {
            for (int nx = size; nx < noisyImage.getWidth() - size; nx++) {
                out.setRGB(nx, ny, rgb[(int) noisyImage.get3x3Gaussian(nx, ny)]);
            }
        }

        return out;

    }

    public BufferedImage toGrayscale(PixelLoader noisyImage) {

        BufferedImage out = new BufferedImage(noisyImage.getWidth(), noisyImage.getHeight(), BufferedImage.TYPE_INT_RGB);

        int size = 1;
        for (int ny = size; ny < noisyImage.getHeight() - size; ny++) {
            for (int nx = size; nx < noisyImage.getWidth() - size; nx++) {
                out.setRGB(nx, ny, rgb[noisyImage.getGreyValue(nx, ny)]);
            }
        }

        return out;

    }

    public BufferedImage process(PixelLoader noisyImage) {

        BufferedImage out = new BufferedImage(noisyImage.getWidth(), noisyImage.getHeight(), BufferedImage.TYPE_INT_RGB);

        int size = 1;
        for (int ny = size; ny < noisyImage.getHeight() - size; ny++) {
            for (int nx = size; nx < noisyImage.getWidth() - size; nx++) {
                out.setRGB(nx, ny, getGrayValue(noisyImage, nx, ny));
            }
        }

        return out;

    }

    public static final int NUM_FEATURES = 15;

    public static double[] getFeatures(PixelLoader image, int x, int y) {

        double[] features = new double[NUM_FEATURES];

        features[0] = image.getGreyValue(x, y);
        features[1] = image.getGreyValue(x + 1, y);
        features[2] = image.getGreyValue(x + 1, y - 1);
        features[3] = image.getGreyValue(x, y - 1);
        features[4] = image.getGreyValue(x - 1, y - 1);
        features[5] = image.getGreyValue(x - 1, y);
        features[6] = image.getGreyValue(x - 1, y + 1);
        features[7] = image.getGreyValue(x, y + 1);
        features[8] = image.getGreyValue(x + 1, y + 1);

        features[9] = image.get3x3Mean(x, y);


        FastStatistics f = new FastStatistics();

        for (int i = 0; i <= 8; i++) {
            f.addData((float) features[i]);
        }
        features[10] = f.getMin();
        features[11] = f.getMax();
        features[12] = f.getRange();
        features[13] = image.get3x3Gaussian(x, y);
        features[14] = image.get3x3Median(x, y);
        return features;

    }

    public int getGrayValue(PixelLoader image, int x, int y) {
        int v = (int) eval(image, x, y);
        if (v > 255) v = 255;
        if (v < 0) v = 0;
        return rgb[v];
    }

    private double eval(PixelLoader image, int x, int y) {
        double[] features = getFeatures(image, x, y);
        // Fitness: 11407.0
        // Hits: 2343
        // Mistakes: 2986
        // Evolved at: Fri Nov 28 11:47:25 GMT 2008
        double node7 = features[7] * features[7];
        boolean node6 = node7 < 3.0;
        double node10 = Math.abs(features[0]);
        double node5 = node6 ? node10 : 1.0;
        double node4 = Math.abs(node5);
        double node3 = Math.cos(node4);
        double node16 = Math.abs(features[14]);
        boolean node15 = node16 < features[0];
        double node21 = 0.0 != 0 ? 0 : 0;
        double node26 = features[3];
        double node29 = 0.8611785836093115;
        double node25 = Math.hypot(node26, node29);
        double node24 = Math.cos(node25);
        double node20 = node21 * node24;
        double node32 = Math.cos(0.0);
        double node19 = node20 - node32;
        double node35 = Math.cos(202.0);
        double node37 = Math.abs(0.2767050115942894);
        double node34 = node37 != 0 ? node35 / node37 : 0;
        double node14 = node15 ? node19 : node34;
        double node40 = Math.sqrt(features[9]);
        double node39 = Math.hypot(node40, features[0]);
        double node13 = node14 + node39;
        double node2 = node3 - node13;
        boolean node53 = features[1] > features[0];
        double node56 = 0.8231675711392912 != 0 ? features[10] / 0.8231675711392912 : 0;
        double node59 = 0.7092541747579698;
        double node52 = node53 ? node56 : node59;
        double node51 = Math.cos(node52);
        double node65 = Math.abs(0.0);
        double node64 = Math.abs(node65);
        boolean node68 = 0.08334840034338009 > 0.0;
        double node67 = Math.sqrt(toInt(node68));
        double node63 = node64 + node67;
        boolean node73 = 0.9845166155499128 > 0.0;
        double node76 = features[7] - features[14];
        double node72 = toInt(node73) + node76;
        boolean node80 = features[0] < features[0];
        double node83 = 0.0 * features[3];
        boolean node79 = toInt(node80) > node83;
        double node71 = node72 + toInt(node79);
        double node62 = node63 + node71;
        double node50 = Math.hypot(node51, node62);
        double node89 = Math.hypot(features[12], features[7]);
        double node92 = 0.0;
        boolean node88 = node89 > node92;
        double node87 = toInt(node88) * toInt(node88);
        double node97 = (0.8929584825360287) / 2;
        double node100 = features[14] * features[9];
        double node96 = (node97 + node100) / 2;
        double node104 = 0.0 * 0.0;
        double node106 = (features[12] + features[1]) / 2;
        double node103 = node106 != 0 ? node104 / node106 : 0;
        double node95 = node96 * node103;
        double node86 = (node87 + node95) / 2;
        double node49 = Math.hypot(node50, node86);
        double node113 = features[1] * features[4];
        boolean node112 = node113 < 1.0;
        double node111 = node112 ? features[12] : 0.0;
        boolean node119 = 0.0 > 0.0;
        double node110 = node111 * toInt(node119);
        double node109 = Math.sqrt(node110);
        double node48 = node49 - node109;
        boolean node125 = features[3] < features[10];
        double node128 = Math.abs(0.0);
        double node131 = Math.hypot(0.15027217420051675, features[12]);
        double node130 = node131 + features[10];
        double node124 = node125 ? node128 : node130;
        double node123 = Math.sqrt(node124);
        double node122 = Math.abs(node123);
        double node47 = node48 + node122;
        double node141 = 0.0 - 0.0;
        double node144 = 0.6731491763175392 * 0.050870303182722565;
        boolean node140 = node141 < node144;
        double node148 = 1.0;
        double node152 = (0.042941963387035265 + 0.0) / 2;
        boolean node147 = node148 < node152;
        double node139 = toInt(node140) + toInt(node147);
        double node157 = 0.0;
        double node161 = features[10] * features[10];
        double node156 = node157 * node161;
        double node164 = features[8] * 0.4634598592914323;
        double node167 = Math.sqrt(features[2]);
        double node163 = node164 + node167;
        double node155 = (node156 + node163) / 2;
        double node138 = node155 != 0 ? node139 / node155 : 0;
        double node137 = node138 * node138;
        double node136 = Math.sqrt(node137);
        double node173 = 0.0 != 0 ? 0.0 : 0;
        double node172 = node173 * node173;
        boolean node177 = features[12] > features[10];
        double node180 = 0.21403930603184318 * 0.0;
        double node183 = true ? features[4] : 0.8011674742316002;
        double node176 = node177 ? node180 : node183;
        double node171 = Math.hypot(node172, node176);
        double node188 = Math.hypot(0.771657224187505, 0.30166329294275596);
        double node187 = Math.sqrt(node188);
        double node170 = node171 - node187;
        double node193 = (features[8] + 0.0) / 2;
        double node196 = features[2] * features[3];
        double node192 = node196 != 0 ? node193 / node196 : 0;
        double node200 = (features[1] + 0.23663213362551472) / 2;
        double node203 = features[9] != 0 ? 0.0 / features[9] : 0;
        double node199 = node200 * node203;
        double node191 = node192 + node199;
        double node169 = (node170 + node191) / 2;
        double node135 = node136 + node169;
        boolean node46 = node47 < node135;
        double node212 = Math.hypot(features[8], features[2]);
        double node215 = features[1] * features[1];
        double node211 = node212 * node215;
        boolean node218 = features[0] > features[10];
        double node217 = Math.sqrt(toInt(node218));
        boolean node210 = node211 < node217;
        boolean node223 = features[0] < features[9];
        double node226 = (0.0 + features[10]) / 2;
        double node229 = features[13] * features[13];
        double node222 = node223 ? node226 : node229;
        double node232 = (features[14] + features[4]) / 2;
        double node235 = 0.0 * 0.0;
        boolean node231 = node232 < node235;
        boolean node221 = node222 < toInt(node231);
        double node209 = toInt(node221) != 0 ? toInt(node210) / toInt(node221) : 0;
        double node239 = (0.39948512502928646 + features[7]) / 2;
        double node242 = 0.7722177806815654 + 0.42633443177833075;
        boolean node238 = node239 < node242;
        double node246 = features[10];
        double node249 = 0.0;
        double node245 = node246 - node249;
        boolean node237 = toInt(node238) > node245;
        boolean node208 = node209 < toInt(node237);
        boolean node255 = features[8] < 0.3196847781389408;
        double node258 = 0.0 - 0.0;
        double node254 = (toInt(node255) + node258) / 2;
        double node262 = features[1] * features[1];
        double node261 = node262 * node262;
        boolean node253 = node254 > node261;
        double node252 = Math.cos(toInt(node253));
        boolean node207 = toInt(node208) > node252;
        double node206 = toInt(node207);
        double node45 = toInt(node46) - node206;
        double node44 = Math.cos(node45);
        double node266 = Math.sqrt(features[0]);
        double node265 = Math.cos(node266);
        double node268 = Math.sqrt(features[4]);
        double node264 = node265 - node268;
        double node43 = node264 != 0 ? node44 / node264 : 0;
        double node1 = node2 + node43;
        return Math.abs(node1);

    }

    public int toInt(boolean b) {
        return b ? 1 : 0;
    }


}
