/*
* Copyright (c) 1997 - 2016
* Actelion Pharmaceuticals Ltd.
* Gewerbestrasse 16
* CH-4123 Allschwil, Switzerland
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
*    list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
*    names of its contributors may be used to endorse or promote products
*    derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

package com.actelion.research.chem;

import com.actelion.research.gui.generic.GenericPolygon;
import com.actelion.research.gui.generic.GenericRectangle;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

public class SVGDepictor extends AbstractDepictor<Void> {
    public static final int DEFAULT_ELEM_WIDTH = 8;

    private static final String FONTNAME = "Helvetica";
    private static int instanceCnt = 0;

    private double lineWidth = 1;
    private int textSize = 10;
    private int width = 400;
    private int height = 400;
    private boolean legacyMode = true;

    private String currentColor = "black";
    private final java.util.List<String> bonds = new ArrayList<String>();
    private final java.util.List<String> atoms = new ArrayList<String>();

    private String id;
    private StringBuilder buffer = new StringBuilder();

    private Font currentFont = new Font(FONTNAME, Font.PLAIN, 12);
    private Graphics2D graphics;

    public SVGDepictor(StereoMolecule mol, String id)
    {
        this(mol, 0, id);
    }

    public SVGDepictor(StereoMolecule mol, int displayMode, String id) {
        super(mol, displayMode);
        this.id = id;
        instanceCnt++;
    }

    /**
     * For legacy reasons the default is to include invisible event atoms and bonds.
     * Call this method after contruction to skip these non-visible contributions to the SVG.
     * @param b
     */
    public void setLegacyMode(boolean b) {
        legacyMode = b;
    }

    public static final String makeColor(int r, int g, int b) {
        return "rgb(" + r + "," + g + "," + b + ")";
    }

    public String getId() {
        return id != null ? id : ("mol" + instanceCnt);
    }

    private void write(String s) {
        buffer.append("\t");
        buffer.append(s);
        buffer.append("\n");
    }

    public static String escapeXML(String s) {
        StringBuilder sb = new StringBuilder();

        int length = s.length();
        for (int i = 0; i < length; i++) {
            int c = s.codePointAt(i);
            char ch = (char) c;
            switch (c) {
                case '&':
                    sb.append("&amp;");
                    break;
                case '<':
                    sb.append("&lt;");
                    break;
                case '>':
                    sb.append("&gt;");
                    break;
                case '\"':
                    sb.append("&quot;");
                    break;
                case '\'':
                    sb.append("&apos;");
                    break;
                default:
                    sb.append(ch);
            }
        }

        return sb.toString();
    }

    @Override
    protected void drawBlackLine(DepictorLine theLine) {
        int x1 = (int) theLine.x1;
        int x2 = (int) theLine.x2;
        int y1 = (int) theLine.y1;
        int y2 = (int) theLine.y2;
        String s = "<line " +
                "x1=\"" + x1 + "\" " +
                "y1=\"" + y1 + "\" " +
                "x2=\"" + x2 + "\" " +
                "y2=\"" + y2 + "\" " +
                "style=\"stroke:" + escapeXML(currentColor) + "; stroke-width:" + lineWidth + "\"/>";
        write(s);
    }

    @Override
    protected void drawDottedLine(DepictorLine theLine) {
        int x1 = (int) theLine.x1;
        int x2 = (int) theLine.x2;
        int y1 = (int) theLine.y1;
        int y2 = (int) theLine.y2;
        int d = (int)(3*lineWidth);
        String s = "<line stroke-dasharray=\""+d+","+d+"\" " +
                "x1=\"" + x1 + "\" " +
                "y1=\"" + y1 + "\" " +
                "x2=\"" + x2 + "\" " +
                "y2=\"" + y2 + "\" " +
                "style=\"stroke:" + escapeXML(currentColor) + "; stroke-width:" + lineWidth + "\"/>";

        write(s);
    }

    @Override
    protected void drawPolygon(GenericPolygon p) {
        StringBuilder s = new StringBuilder("<polygon points=\"");
        for (int i=0; i<p.getSize(); i++) {
            s.append(Math.round(p.getX(i)));
            s.append(",");
            s.append(Math.round(p.getY(i)));
            s.append(" ");
        }
        s.append("\" " +
                "style=\"fill:" + escapeXML(currentColor) + "; stroke:" + escapeXML(currentColor) + "; stroke-width:"+lineWidth+"\"/>");
        write(s.toString());
    }

    @Override
    protected void drawString(String theString, double x, double y) {
        double strWidth = getStringWidth(theString);
        String s = "<text " +
                "x=\"" + (int) (x - strWidth / 2.0) + "\" " +
                "y=\"" + (int) (y + textSize / 3) + "\" " +
                "stroke=\"none\" " +
//                "font-family=\" " + currentFont.getName() + "\" " +
                "font-size=\"" + currentFont.getSize() + "\" " +
                "fill=\"" + escapeXML(currentColor) + "\">" + escapeXML(theString) +
                "</text>";
        write(s);
    }

    @Override
    protected void fillCircle(double x, double y, double d) {
        String s = "<circle " +
                "cx=\"" + (int) (x+d/2) + "\" " +
                "cy=\"" + (int) (y+d/2) + "\" " +
                "r=\"" + (int) (d/2) + "\" " +
                "fill=\"" + escapeXML(currentColor) + "\" />";
        write(s);
    }

    @Override
    protected double getLineWidth()
    {
        return lineWidth;
    }

    @Override
    protected double getStringWidth(String theString) {
        float ret =  (float)currentFont.getStringBounds(theString,graphics.getFontRenderContext()).getWidth();
        return ret;
    }

    @Override
    protected int getTextSize() {
        return textSize;
    }

    @Override
    protected void setTextSize(int theSize) {
        if (textSize != theSize) {
            textSize = theSize;
            currentFont = new Font(FONTNAME, Font.PLAIN, theSize);
        }
    }

    @Override
    protected void setLineWidth(double width) {
        lineWidth = Math.round(100 * Math.max(width, 1.0)) / 100;
    }

    @Override
    protected void setRGB(int rgb) {
        currentColor = makeColor((rgb & 0x00FF0000) >> 16, (rgb & 0x0000FF00) >> 8, rgb & 0x000000FF);
    }

    @Override
    protected void onDrawBond(int bond, double x1, double y1, double x2, double y2) {
        String s = "<line " +
                "id=\"" + escapeXML(getId()) + ":Bond:" + bond + "\" " +
                "class=\"event\" " +	// class to respond to the mouse event
                "x1=\"" + (int) (x1) + "\" " +
                "y1=\"" + (int) (y1) + "\" " +
                "x2=\"" + (int) (x2) + "\" " +
                "y2=\"" + (int) (y2) + "\" " +
                "stroke-width=\"" + DEFAULT_ELEM_WIDTH + "\" " +
                "stroke-opacity=\"0\"" +
                "/>";
        bonds.add(s);
    }

    @Override
    protected void onDrawAtom(int atom, String symbol, double x, double y) {
        int r = DEFAULT_ELEM_WIDTH;
        String s = "<circle " +
                "id=\"" + escapeXML(getId()) + ":Atom:" + atom + "\" " +
                "class=\"event\" " + // class to respond to the mouse event
                "cx=\"" + (int) (x) + "\" " +
                "cy=\"" + (int) (y) + "\" " +
                "r=\"" + r + "\" " +
                "fill-opacity=\"0\"/>";
        atoms.add(s);
    }


    @Override
    public String toString() {
        String header = "<svg " +
                        "id=\"" + escapeXML(getId()) + "\" " +
                        "xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" " +
                        "width=\"" + width + "px\" " +
                        "height=\"" + height + "px\" " +
                        "viewBox=\"0 0 " + width + " " + height + "\">\n";

        String footer = "</svg>";

        String style = legacyMode ?
                "<style>" +
                " #" + escapeXML(getId()) +
                " {pointer-events:none; } " +	// Disable Mouse events on the root element so they get passed to the childs
                " #" + escapeXML(getId()) + " .event " +
                " { pointer-events:all;} " +	// Enable Mouse events for elements possessing the class "event"
                " </style>\n"
              : "<g style=\"font-size:"+getTextSize()+"px; fill-opacity:1; stroke-opacity:1; fill:black; stroke:black;"
              + " font-weight:normal; text-rendering:optimizeLegibility; font-family:sans-serif;"
              + " stroke-linejoin:round; stroke-linecap:round; stroke-dashoffset:0;\">";

//        String rect = "<rect " +
//                      "x=\"" + 0 + "\" " +
//                      "y=\"" + 0 + "\" " +
//                      "width='" + width + "' " +
//                      "height='" + height + "' style='fill-opacity:0;stroke:red;stroke-width:3'/>\n";
        header += "\t";
        header += style;
//        header += rect;

        if (legacyMode) {
            // Append the (invisible) bond lines
            for (String b : bonds)
                write(b);

            // Append the (invisible) atom circles
            for (String a : atoms)
                write(a);
        }

        if (!legacyMode)
            write("</g>");

        return header + buffer.toString() + footer;
    }

    @Override
    public DepictorTransformation simpleValidateView(GenericRectangle viewRect, int mode) {
        width = (int) viewRect.getWidth();
        height = (int) viewRect.getHeight();
        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        graphics = img.createGraphics();

        return super.simpleValidateView(viewRect, mode);
    }
}
