/*
 *  Copyright (C) 2010-2015 JPEXS, All rights reserved.
 *
 * 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; either
 * version 3.0 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */
package com.jpexs.decompiler.flash.exporters.commonshape;

import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.modes.FontExportMode;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.RGBA;
import com.jpexs.helpers.Helper;
import java.awt.Color;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 *
 * @author JPEXS
 */
public class SVGExporter {

    protected static final String sNamespace = "http://www.w3.org/2000/svg";

    protected static final String xlinkNamespace = "http://www.w3.org/1999/xlink";

    protected Document _svg;

    protected Element _svgDefs;

    protected CDATASection _svgStyle;

    protected Stack<Element> _svgGs = new Stack<>();

    public List<Element> gradients;

    protected int lastPatternId;

    public Map<Tag, String> exportedTags = new HashMap<>();

    public Map<Tag, Map<Character, String>> exportedChars = new HashMap<>();

    private final Map<String, Integer> lastIds = new HashMap<>();

    private final HashSet<String> fontFaces = new HashSet<>();

    private String clip;

    public boolean useTextTag = Configuration.textExportExportFontFace.get();

    public SVGExporter(ExportRectangle bounds) {

        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
            DOMImplementation impl = docBuilder.getDOMImplementation();
            DocumentType svgDocType = impl.createDocumentType("svg", "-//W3C//DTD SVG 1.0//EN",
                    "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd");
            _svg = impl.createDocument(sNamespace, "svg", svgDocType);
            Element svgRoot = _svg.getDocumentElement();
            svgRoot.setAttribute("xmlns:xlink", xlinkNamespace);
            if (bounds != null) {
                svgRoot.setAttribute("width", (bounds.getWidth() / SWF.unitDivisor) + "px");
                svgRoot.setAttribute("height", (bounds.getHeight() / SWF.unitDivisor) + "px");
                createDefGroup(bounds, null);
            }
        } catch (ParserConfigurationException ex) {
            Logger.getLogger(SVGExporter.class.getName()).log(Level.SEVERE, null, ex);
        }
        gradients = new ArrayList<>();
    }

    private Element getDefs() {
        if (_svgDefs == null) {
            _svgDefs = _svg.createElement("defs");
            _svg.getDocumentElement().appendChild(_svgDefs);
        }
        return _svgDefs;
    }

    private CDATASection getStyle() {
        if (_svgStyle == null) {
            Element style = _svg.createElement("style");
            _svgStyle = _svg.createCDATASection("");
            style.appendChild(_svgStyle);
            getDefs().appendChild(style);
        }
        return _svgStyle;
    }

    public final void createDefGroup(ExportRectangle bounds, String id) {
        Element g = _svg.createElement("g");
        if (bounds != null) {
            g.setAttribute("transform", "matrix(1, 0, 0, 1, "
                    + roundPixels20(-bounds.xMin / (double) SWF.unitDivisor) + ", " + roundPixels20(-bounds.yMin / (double) SWF.unitDivisor) + ")");
        }
        if (id != null) {
            g.setAttribute("id", id);
        }
        if (_svgGs.size() == 0) {
            _svg.getDocumentElement().appendChild(g);
        } else {
            getDefs().appendChild(g);
        }
        _svgGs.add(g);
    }

    public void endGroup() {
        Element g = _svgGs.pop();
        if (g.getChildNodes().getLength() == 0) {
            g.getParentNode().removeChild(g);
        }
    }

    public final Element createSubGroup(Matrix transform, String id) {
        Element group = createSubGroup(id, "g");
        group.setAttribute("transform", transform.getTransformationString(SWF.unitDivisor, 1));
        return group;
    }

    public final Element createClipPath(Matrix transform, String id) {
        Element group = createSubGroup(id, "clipPath");
        Node parent = group.getParentNode();
        if (parent instanceof Element) {
            Element parentElement = (Element) parent;
            group.setAttribute("transform", parentElement.getAttribute("transform"));
        }
        return group;
    }

    private Element createSubGroup(String id, String tagName) {
        Element group = _svg.createElement(tagName);
        if (id != null) {
            group.setAttribute("id", id);
        }
        _svgGs.peek().appendChild(group);
        _svgGs.add(group);
        return group;
    }

    public void addToGroup(Node newChild) {
        _svgGs.peek().appendChild(newChild);
    }

    public void addToDefs(Node newChild) {
        getDefs().appendChild(newChild);
    }

    public Element createElement(String tagName) {
        return _svg.createElement(tagName);
    }

    public String getSVG() {
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        StringWriter writer = new StringWriter();
        try {
            Transformer transformer = transformerFactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            DOMSource source = new DOMSource(_svg);
            StreamResult result = new StreamResult(writer);
            transformer.transform(source, result);
        } catch (TransformerException ex) {
            Logger.getLogger(SVGExporter.class.getName()).log(Level.SEVERE, null, ex);
        }
        return writer.toString();
    }

    public void setBackGroundColor(Color backGroundColor) {
        Attr attr = _svg.createAttribute("style");
        attr.setValue("background: " + new RGBA(backGroundColor).toHexARGB());
    }

    public Element addUse(Matrix transform, RECT boundRect, String href, String instanceName) {
        Element image = _svg.createElement("use");
        if (transform != null) {
            image.setAttribute("transform", transform.getTransformationString(SWF.unitDivisor, 1));
            image.setAttribute("width", Double.toString(boundRect.getWidth() / (double) SWF.unitDivisor));
            image.setAttribute("height", Double.toString(boundRect.getHeight() / (double) SWF.unitDivisor));
        }
        if (instanceName != null) {
            image.setAttribute("id", instanceName);
        }
        image.setAttribute("xlink:href", "#" + href);
        if (clip != null) {
            image.setAttribute("clip-path", "url(#" + clip + ")");
        }
        _svgGs.peek().appendChild(image);
        return image;
    }

    public void addStyle(String fontFace, byte[] data, FontExportMode mode) {
        if (!fontFaces.contains(fontFace)) {
            fontFaces.add(fontFace);
            String base64Data = Helper.byteArrayToBase64String(data);
            String value = getStyle().getTextContent();
            value += Helper.newLine;
            value += "      @font-face {" + Helper.newLine;
            value += "        font-family: \"" + fontFace + "\";" + Helper.newLine;
            switch (mode) {
                case TTF:
                    value += "        src: url('data:font/truetype;base64," + base64Data + "') format(\"truetype\");" + Helper.newLine;
                    break;
                case WOFF:
                    value += "        src: url('data:font/woff;base64," + base64Data + "') format(\"woff\");" + Helper.newLine;
                    break;
            }
            value += "      }" + Helper.newLine;
            getStyle().setTextContent(value);
        }
    }

    public String getUniqueId(String prefix) {
        Integer lastId = lastIds.get(prefix);
        if (lastId == null) {
            lastId = 0;
        } else {
            lastId++;
        }
        lastIds.put(prefix, lastId);
        return prefix + lastId;
    }

    public void setClip(String clip) {
        this.clip = clip;
    }

    public String getClip() {
        return clip;
    }

    protected static double roundPixels20(double pixels) {
        return Math.round(pixels * 100) / 100.0;
    }
}
