/*
 *    Geotools2 - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2002, Geotools Project Managment Committee (PMC)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hryope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 */
package org.geotools.renderer.shape;

import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Panel;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.geotools.data.shapefile.Lock;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.shapefile.indexed.IndexedShapefileDataStore;
import org.geotools.data.shapefile.indexed.IndexedShapefileDataStoreFactory;
import org.geotools.data.shapefile.indexed.ShapeFileIndexer;
import org.geotools.filter.AttributeExpression;
import org.geotools.filter.CompareFilter;
import org.geotools.filter.Filter;
import org.geotools.filter.FilterFactory;
import org.geotools.filter.FilterFactoryFinder;
import org.geotools.map.DefaultMapContext;
import org.geotools.map.MapContext;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.resources.TestData;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.Font;
import org.geotools.styling.LineSymbolizer;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.PolygonSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;
import org.geotools.styling.StyleBuilder;
import org.geotools.styling.StyleFactory;
import org.geotools.styling.StyleFactoryFinder;
import org.geotools.styling.Symbolizer;
import org.geotools.styling.TextSymbolizer;

import com.vividsolutions.jts.geom.Envelope;

/**
 * @TODO class description
 * 
 * @author jeichar
 * @since 2.1.x
 * @source $URL:
 *         http://svn.geotools.org/geotools/branches/2.2.x/ext/shaperenderer/perf/org/geotools/renderer/shape/Timing.java $
 */
public class Timing {

    private static final FilterFactory filterFactory = FilterFactoryFinder
            .createFilterFactory();

    private static final int POINTS = 0;

    private static final int LINES = 1;

    private static final int POLYGONS = 2;

    private static boolean ALL_DATA = true;

    private static boolean DISPLAY = false;

    private static boolean ANTI_ALIASING = false;

    private static boolean RUN_SHAPE = true;

    private static boolean RUN_LITE = true;

    private static boolean RUN_TINY = false;

    private static boolean ACCURATE = true;

    private static boolean NO_REPROJECTION = true;

    private static boolean FILTER = false;

    private static boolean CPU_PROFILE = false;

    private static int SHAPE_TYPE = POLYGONS;

    private static boolean LABELING = false;

    private static boolean QUADTREE = false;

    private static int CYCLES = 4;

    private String getTestName() {
        String testName = "";
        if (SHAPE_TYPE == LINES) {
            testName += LINES_TYPE_NAME;
        } else if (SHAPE_TYPE == POLYGONS) {
            testName += POLY_TYPE_NAME;
        } else if (SHAPE_TYPE == POINTS) {
            testName += POINT_TYPE_NAME;
        }
        if (ALL_DATA) {
            testName += "_ALL";
        } else {
            testName += "_ZOOM";
        }
        if (ACCURATE) {
            testName += "_ACCURATE";
        } else {
            testName += "_INACCURATE";
        }
        if (NO_REPROJECTION) {
            testName += "_NO_REPROJECTION";
        } else {
            testName += "_REPROJECTED";
        }
        if (FILTER) {
            testName += "_FILTER";
        } else {
            testName += "_NO_FILTER";
        }
        if (CPU_PROFILE) {
            testName += "_PROFILE";
        }
        if (QUADTREE) {
            testName += "_QUADTREE";
        }
        return testName;
    }

    public final static FileWriter out;

    static {
        FileWriter tmp;
        try {
            String homePath = System.getProperty("user.home");
            File results = new File(homePath, "TimingResults.txt");
            tmp = new FileWriter(results, true);
        } catch (IOException e) {
            tmp = null;
            e.printStackTrace();
        }
        out = tmp;
    }

    static Style createLineStyle() throws Exception {
        return createLineStyle(null);
    }

    static Style createLineStyle(String typeName) throws Exception {
        if (typeName == null)
            typeName = LINES_TYPE_NAME;
        StyleFactory sFac = StyleFactoryFinder.createStyleFactory();
        // The following is complex, and should be built from

        LineSymbolizer linesym = sFac.createLineSymbolizer();
        Stroke myStroke = sFac.getDefaultStroke();
        myStroke.setColor(filterFactory.createLiteralExpression("#0000ff"));
        myStroke
                .setWidth(filterFactory.createLiteralExpression(new Integer(2)));
        linesym.setStroke(myStroke);

        Rule rule2 = sFac.createRule();
        rule2.setSymbolizers(new Symbolizer[] { linesym });
        if (FILTER) {
            AttributeExpression exp = filterFactory
                    .createAttributeExpression("STREETS");
            CompareFilter filter = filterFactory
                    .createCompareFilter(Filter.COMPARE_NOT_EQUALS);
            filter.addLeftValue(exp);
            filter.addRightValue(filterFactory.createLiteralExpression("blah"));
            rule2.setFilter(filter);
        }
        if (LABELING) {
            StyleBuilder builder = new StyleBuilder();
            TextSymbolizer textsym = sFac.createTextSymbolizer();
            textsym.setFill(sFac.getDefaultFill());
            textsym.setGeometryPropertyName("the_geom");
            textsym
                    .setLabel(filterFactory
                            .createLiteralExpression(LINES_LABEL));
            textsym.setFonts(new Font[] { builder.createFont(new java.awt.Font(
                    "Arial", java.awt.Font.PLAIN, 10)) });
            rule2.setSymbolizers(new Symbolizer[] { linesym, textsym });
        }
        FeatureTypeStyle fts2 = sFac.createFeatureTypeStyle();
        fts2.setRules(new Rule[] { rule2 });
        fts2.setFeatureTypeName(typeName);

        Style style = sFac.createStyle();
        style.setFeatureTypeStyles(new FeatureTypeStyle[] { fts2 });

        return style;
    }

    static Style createPolyStyle() throws Exception {
        return createPolyStyle(null);
    }

    static Style createPolyStyle(String typeName) throws Exception {
        if (typeName == null)
            typeName = POLY_TYPE_NAME;
        StyleFactory sFac = StyleFactoryFinder.createStyleFactory();
        // The following is complex, and should be built from
        Stroke myStroke = sFac.getDefaultStroke();
        myStroke.setColor(filterFactory.createLiteralExpression("#0000ff"));
        myStroke
                .setWidth(filterFactory.createLiteralExpression(new Integer(2)));
        Fill myFill = sFac.getDefaultFill();
        PolygonSymbolizer lineSym = sFac.createPolygonSymbolizer(myStroke,
                myFill, "the_geom");

        Rule rule2 = sFac.createRule();
        rule2.setSymbolizers(new Symbolizer[] { lineSym });
        if (FILTER) {
            AttributeExpression exp = filterFactory
                    .createAttributeExpression(POLY_LABEL);
            CompareFilter filter = filterFactory
                    .createCompareFilter(Filter.COMPARE_NOT_EQUALS);
            filter.addLeftValue(exp);
            filter.addRightValue(filterFactory.createLiteralExpression("blah"));
            rule2.setFilter(filter);
        }
        if (LABELING) {
            StyleBuilder builder = new StyleBuilder();

            TextSymbolizer textsym = sFac.createTextSymbolizer();
            textsym.setFill(sFac.createFill(filterFactory
                    .createLiteralExpression("#000000")));
            textsym.setGeometryPropertyName("the_geom");
            textsym.setLabel(filterFactory
                    .createAttributeExpression(POLY_LABEL));
            textsym.setFonts(new Font[] { builder.createFont(new java.awt.Font(
                    "Arial", java.awt.Font.PLAIN, 10)) });
            rule2.setSymbolizers(new Symbolizer[] { lineSym, textsym });
        }

        FeatureTypeStyle fts2 = sFac.createFeatureTypeStyle();
        fts2.setRules(new Rule[] { rule2 });
        fts2.setFeatureTypeName(typeName);

        Style style = sFac.createStyle();
        style.setFeatureTypeStyles(new FeatureTypeStyle[] { fts2 });

        return style;
    }

    static Style createPointStyle() throws Exception {
        return createPointStyle(null);
    }

    static Style createPointStyle(String typeName) throws Exception {
        if (typeName == null)
            typeName = POINT_TYPE_NAME;
        StyleFactory sFac = StyleFactoryFinder.createStyleFactory();
        StyleBuilder builder = new StyleBuilder(sFac);
        // The following is complex, and should be built from
        Stroke myStroke = sFac.getDefaultStroke();
        myStroke.setColor(filterFactory.createLiteralExpression("#0000ff"));
        myStroke
                .setWidth(filterFactory.createLiteralExpression(new Integer(2)));
        PointSymbolizer point = sFac.createPointSymbolizer(builder
                .createGraphic(), "the_geom");

        Rule rule2 = sFac.createRule();
        rule2.setSymbolizers(new Symbolizer[] { point });
        if (FILTER) {
            AttributeExpression exp = filterFactory
                    .createAttributeExpression(POINT_LABEL);
            CompareFilter filter = filterFactory
                    .createCompareFilter(Filter.COMPARE_NOT_EQUALS);
            filter.addLeftValue(exp);
            filter.addRightValue(filterFactory.createLiteralExpression("blah"));
            rule2.setFilter(filter);
        }
        if (LABELING) {

            TextSymbolizer textsym = sFac.createTextSymbolizer();
            textsym.setFill(sFac.createFill(filterFactory
                    .createLiteralExpression("#000000")));
            textsym.setGeometryPropertyName("the_geom");
            textsym.setLabel(filterFactory
                    .createAttributeExpression(POINT_LABEL));
            textsym.setFonts(new Font[] { builder.createFont(new java.awt.Font(
                    "Arial", java.awt.Font.PLAIN, 10)) });
            rule2.setSymbolizers(new Symbolizer[] { point, textsym });
        }

        FeatureTypeStyle fts2 = sFac.createFeatureTypeStyle();
        fts2.setRules(new Rule[] { rule2 });
        fts2.setFeatureTypeName(typeName);

        Style style = sFac.createStyle();
        style.setFeatureTypeStyles(new FeatureTypeStyle[] { fts2 });

        return style;
    }

    public static void main(String[] args) throws Exception {
        try {
            DISPLAY = false;
            RUN_LITE = true;
            RUN_SHAPE = true;
            RUN_TINY = false;
            ACCURATE = true;

            out.write("Battery 1\n");
            ALL_DATA = true;
            ANTI_ALIASING = true;
            FILTER = false;
            LABELING = false;
            NO_REPROJECTION = true;
            QUADTREE = false;
            runLineTest(5);
            runPolygonTest(8);
            runPointTest(20);

            out.write("Battery 2\n");
            
            QUADTREE = true;
            runLineTest(5);
            runPolygonTest(8);
            runPointTest(20);
            out.write("Battery 3\n");

            ALL_DATA = false;
            QUADTREE = false;
            runLineTest(5);
            runPolygonTest(8);
            runPointTest(20);
            out.write("Battery 4\n");

            QUADTREE = true;
            runLineTest(5);
            runPolygonTest(8);
            runPointTest(20);
            out.write("Battery 5\n");

            ALL_DATA = true;
            QUADTREE = true;
            NO_REPROJECTION = false;
            runLineTest(5);
            runPolygonTest(8);
            runPointTest(20);
            out.write("Battery 6\n");

            NO_REPROJECTION = true;
            LABELING=true;
            runLineTest(5);
            runPolygonTest(8);
            runPointTest(20);
            
        }catch (Throwable e) {
        	e.printStackTrace(new PrintWriter(out));
		} finally {
        	
            if (out != null && !DISPLAY && !CPU_PROFILE)
                out.close();

            System.exit(0);
        }
    }

    private static void runLineTest(int cycles) throws Exception, IOException {
        CYCLES = cycles;
        SHAPE_TYPE = LINES;
        runSuite();
    }

    private static void runPolygonTest(int cycles) throws Exception,
            IOException {
        CYCLES = cycles;
        SHAPE_TYPE = POLYGONS;
        runSuite();
    }

    private static void runPointTest(int cycles) throws Exception, IOException {
        CYCLES = cycles;
        SHAPE_TYPE = POINTS;
        runSuite();
    }

    private static void runSuite() throws Exception, IOException {
        Timing t = new Timing();
        if (RUN_SHAPE)
            t.runShapeRendererTest();

        if (RUN_TINY)
            t.runTinyTest();

        if (RUN_LITE)
            t.runLiteRendererTest();

    }

    private void runShapeRendererTest() throws Exception {
        MapContext context = getMapContext();
        ShapefileRenderer renderer = new ShapefileRenderer(context);

        if (ANTI_ALIASING) {
            RenderingHints hints = new RenderingHints(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            renderer.setJava2DHints(hints);
        }

        Image image;
        Graphics2D g;

        image = createImage();
        g = createGraphics(image);

        g.setColor(Color.white);
        g.fillRect(0, 0, w, h);

        Envelope bounds = context.getLayerBounds();
        if (!ALL_DATA)
            bounds = new Envelope(bounds.getMinX() + bounds.getWidth() / 4,
                    bounds.getMaxX() - bounds.getWidth() / 4, bounds.getMinY()
                            + bounds.getHeight() / 4, bounds.getMaxY()
                            - bounds.getHeight() / 4);

        if (ACCURATE)
            renderer.paint(g, new Rectangle(w, h), bounds);
        long start = System.currentTimeMillis();

        renderer.paint(g, new Rectangle(w, h), bounds);
        if (ACCURATE) {
            for (int i = 0; i < CYCLES; i++)
                renderer.paint(g, new Rectangle(w, h), bounds);
        }

        long end = System.currentTimeMillis();
        if (ACCURATE) {
            if (out != null) {
                out.write("shape " + getTestName() + "=" + (end - start) / 3
                        + "\n");
            }
        } else if (out != null) {
            out.write("shape " + getTestName() + "=" + (end - start) + "\n");
        }

        if (DISPLAY) {
            display("shape", image, w, h);
        }
    }

    private Graphics2D createGraphics(Image image) {
        Graphics2D g;
        if (image instanceof VolatileImage) {
            g = ((VolatileImage) image).createGraphics();
        } else {
            g = ((BufferedImage) image).createGraphics();
        }
        return g;
    }

    private Image createImage() {
        Image image;
        image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        return image;
    }

    private MapContext getMapContext() throws Exception {
        URL url;
        if (SHAPE_TYPE == LINES) {
            url = new URL(LINES_FILE);
        } else if (SHAPE_TYPE == POLYGONS) {
            url = new URL(POLY_FILE);
        } else {
            url = new URL(POINT_FILE);
        }

        if (!QUADTREE) {
            String s = url.getPath();
            s = s.substring(0, s.lastIndexOf("."));
            File file = new File(s + ".qix");
            if (file.exists()) {
                file.delete();
            }
        }

        IndexedShapefileDataStoreFactory fac = new IndexedShapefileDataStoreFactory();
        IndexedShapefileDataStore store;
        Map params = new HashMap();
        params.put(IndexedShapefileDataStoreFactory.URLP.key, url);
        params.put(IndexedShapefileDataStoreFactory.CREATE_SPATIAL_INDEX.key,
                new Boolean(false));
        if (QUADTREE) {
            params.put(
                    IndexedShapefileDataStoreFactory.CREATE_SPATIAL_INDEX.key,
                    new Boolean(true));
        }
        store = (IndexedShapefileDataStore) fac.createDataStore(params);
        if (QUADTREE)
        	store.buildQuadTree(0);
        
        DefaultMapContext context = new DefaultMapContext();
        if (SHAPE_TYPE == LINES)
            context.addLayer(store.getFeatureSource(), createLineStyle());
        else if (SHAPE_TYPE == POLYGONS)
            context.addLayer(store.getFeatureSource(), createPolyStyle());
        else
            context.addLayer(store.getFeatureSource(), createPointStyle());

        if (NO_REPROJECTION)
            context.setAreaOfInterest(new Envelope(), store.getSchema()
                    .getDefaultGeometry().getCoordinateSystem());
        return context;
    }

    private void runTinyTest() throws Exception {
        ShapefileDataStoreFactory fac = new ShapefileDataStoreFactory();
        ShapefileDataStore store = (ShapefileDataStore) fac
                .createDataStore(TestData.getResource(Timing.class,
                        "theme1.shp"));
        DefaultMapContext context = new DefaultMapContext();
        context.addLayer(store.getFeatureSource(), createLineStyle("theme1"));
        if (NO_REPROJECTION)
            context.setAreaOfInterest(new Envelope(), store.getSchema()
                    .getDefaultGeometry().getCoordinateSystem());
        ShapefileRenderer renderer = new ShapefileRenderer(context);
        if (ANTI_ALIASING) {
            RenderingHints hints = new RenderingHints(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            renderer.setJava2DHints(hints);
        }
        Image image;
        Graphics2D g;

        image = createImage();
        g = createGraphics(image);

        g.setColor(Color.white);
        g.fillRect(0, 0, w, h);

        Envelope bounds = new Envelope(-7.105552354197932, 8.20555235419793,
                -3.239388966356115, 4.191388966388683);

        if (!ALL_DATA)
            bounds = new Envelope(bounds.getMinX() + bounds.getWidth() / 4,
                    bounds.getMaxX() - bounds.getWidth() / 4, bounds.getMinY()
                            + bounds.getHeight() / 4, bounds.getMaxY()
                            - bounds.getHeight() / 4);

        if (ACCURATE)
            renderer.paint(g, new Rectangle(w, h), bounds);
        long start = System.currentTimeMillis();

        renderer.paint(g, new Rectangle(w, h), bounds);
        if (ACCURATE) {
            for (int i = 0; i < CYCLES; i++)
                renderer.paint(g, new Rectangle(w, h), bounds);
        }

        long end = System.currentTimeMillis();
        if (ACCURATE) {
            out.write("tiny " + getTestName() + "=" + (end - start) / 3 + "\n");
        } else
            out.write("tiny " + getTestName() + "=" + (end - start) + "\n");
        if (DISPLAY)
            display("tiny", image, w, h);

    }

    private void runLiteRendererTest() throws Exception {

        MapContext context = getMapContext();
        StreamingRenderer renderer = new StreamingRenderer();
        renderer.setContext(context);
        Map basichints = new HashMap();
        basichints.put("optimizedDataLoadingEnabled", new Boolean(true));
        renderer.setRendererHints(basichints);
        if (ANTI_ALIASING) {
            RenderingHints hints = new RenderingHints(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            renderer.setJava2DHints(hints);
        }

        Image image;
        Graphics2D g;

        image = createImage();
        g = createGraphics(image);

        g.setColor(Color.white);
        g.fillRect(0, 0, w, h);
        Envelope bounds = context.getLayerBounds();

        if (!ALL_DATA)
            bounds = new Envelope(bounds.getMinX() + bounds.getWidth() / 4,
                    bounds.getMaxX() - bounds.getWidth() / 4, bounds.getMinY()
                            + bounds.getHeight() / 4, bounds.getMaxY()
                            - bounds.getHeight() / 4);

        if (ACCURATE)
            renderer.paint(g, new Rectangle(w, h), bounds);
        long start = System.currentTimeMillis();

        renderer.paint(g, new Rectangle(w, h), bounds);
        if (ACCURATE) {
            for (int i = 0; i < CYCLES; i++)
                renderer.paint(g, new Rectangle(w, h), bounds);
        }
        long end = System.currentTimeMillis();
        if (ACCURATE) {
            if (out != null)
                out.write("lite " + getTestName() + "=" + (end - start) / 3
                        + "\n");
        } else if (out != null)
            out.write("lite " + getTestName() + "=" + (end - start) + "\n");
        if (DISPLAY)
            display("lite", image, w, h);

    }

    public static Frame display(String testName, final Image image, int w, int h) {
        Frame frame = new Frame(testName);
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                e.getWindow().dispose();
            }
        });

        Panel p = new Panel() {
            /** <code>serialVersionUID</code> field */
            private static final long serialVersionUID = 1L;

            public void paint(Graphics g) {
                g.drawImage(image, 0, 0, this);
            }
        };
        frame.add(p);
        frame.setSize(w, h);
        frame.setVisible(true);
        return frame;
    }

    private static String LINES_WORK_FILE = "file:/Users/Jesse/dev/Data/uDigData/bc_roads.shp";

    private static String LINES_WORK_TYPE_NAME = "bc_roads";

    private static String LINES_WORK_LABEL = "STREET";

    private static String LINES_FILE = LINES_WORK_FILE;

    private static String LINES_TYPE_NAME = LINES_WORK_TYPE_NAME;

    private static String LINES_LABEL = LINES_WORK_LABEL;

    private static String POLY_FILE = "file:/Users/Jesse/dev/Data/uDigData/bc_parks_2001.shp";

    private static String POLY_TYPE_NAME = "bc_parks_2001";

    private static String POLY_LABEL = "PA_NAME";

    private static String POINT_FILE = "file:/Users/Jesse/dev/Data/uDigData/bc_pubs.shp";

    private static String POINT_TYPE_NAME = "bc_pubs";

    private static String POINT_LABEL = "NAME";

    int w = 512, h = 512;
}
