/*
 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * Copyright (C) 2013-2014 IBM Corporation and Others. All Rights Reserved.
 */

import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.RenderingHints.Key;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.TreeMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * This test runs against a test XML file. It opens the fonts and attempts
 * to shape and layout glyphs.
 * Note that the test is highly environment dependent- you must have
 * the same versions of the same fonts available or the test will fail.
 *
 * It is similar to letest which is part of ICU.
 * For reference, here are some reference items:
 * ICU's test file:
 *  http://source.icu-project.org/repos/icu/icu/trunk/source/test/testdata/letest.xml
 * ICU's readme for the similar test:
 *  http://source.icu-project.org/repos/icu/icu/trunk/source/test/letest/readme.html
 *
 * @bug 8054203
 * @test
 * @summary manual test of layout engine behavior. Takes an XML control file.
 * @compile TestLayoutVsICU.java
 * @author srl
 * @run main/manual
 */
public class TestLayoutVsICU {

    public static boolean OPT_DRAW = false;
    public static boolean OPT_VERBOSE = false;
    public static boolean OPT_FAILMISSING = false;
    public static boolean OPT_NOTHROW= false; // if true - don't stop on failure

    public static int docs = 0; // # docs processed
    public static int skipped = 0; // cases skipped due to bad font
    public static int total = 0; // cases processed
    public static int bad = 0; // cases with errs

    public static final String XML_LAYOUT_TESTS = "layout-tests"; // top level
    public static final String XML_TEST_CASE = "test-case";
    public static final String XML_TEST_FONT = "test-font";
    public static final String XML_TEST_TEXT = "test-text";
    public static final String XML_RESULT_GLYPHS = "result-glyphs";
    public static final String XML_ID = "id";
    public static final String XML_SCRIPT = "script";
    public static final String XML_NAME = "name";
    public static final String XML_VERSION = "version";
    public static final String XML_CHECKSUM = "checksum";
    public static final String XML_RESULT_INDICES = "result-indices";
    public static final String XML_RESULT_POSITIONS = "result-positions";

    /**
     * @param args
     * @throws IOException
     * @throws SAXException
     * @throws ParserConfigurationException
     */
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        System.out.println("Java " + System.getProperty("java.version") + " from " + System.getProperty("java.vendor"));
        TestLayoutVsICU tlvi = null;
        for(String arg : args) {
            if(arg.equals("-d")) {
                OPT_DRAW = true;
            } else if(arg.equals("-n")) {
                OPT_NOTHROW = true;
            } else if(arg.equals("-v")) {
                OPT_VERBOSE = true;
            } else if(arg.equals("-f")) {
                OPT_FAILMISSING = true;
            } else {
                if(tlvi == null) {
                    tlvi = new TestLayoutVsICU();
                }
                try {
                    tlvi.show(arg);
                } finally {
                    if(OPT_VERBOSE) {
                        System.out.println("# done with " + arg);
                    }
                }
            }
        }

        if(tlvi == null) {
            throw new IllegalArgumentException("No XML input. Usage: " + TestLayoutVsICU.class.getSimpleName() + " [-d][-v][-f] letest.xml ...");
        } else {
            System.out.println("\n\nRESULTS:\n");
            System.out.println(skipped+"\tskipped due to missing font");
            System.out.println(total+"\ttested of which:");
            System.out.println(bad+"\twere bad");

            if(bad>0) {
                throw new InternalError("One or more failure(s)");
            }
        }
    }

    String id;

    private void show(String arg) throws ParserConfigurationException, SAXException, IOException {
        id = "<none>";
        File xmlFile = new File(arg);
        if(!xmlFile.exists()) {
            throw new FileNotFoundException("Can't open input XML file " + arg);
        }
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        if(OPT_VERBOSE) {
            System.out.println("# Parsing " + xmlFile.getAbsolutePath());
        }
        Document doc = db.parse(xmlFile);
        Element e = doc.getDocumentElement();
        if(!XML_LAYOUT_TESTS.equals(e.getNodeName())) {
            throw new IllegalArgumentException("Document " + xmlFile.getAbsolutePath() + " does not have <layout-tests> as its base");
        }

        NodeList testCases = e.getElementsByTagName(XML_TEST_CASE);
        for(int caseNo=0;caseNo<testCases.getLength();caseNo++) {
            final Node testCase = testCases.item(caseNo);
            final Map<String,String> testCaseAttrs = attrs(testCase);
            id = testCaseAttrs.get(XML_ID);
            final String script = testCaseAttrs.get(XML_SCRIPT);
            String testText = null;
            Integer[] expectGlyphs = null;
            Integer[] expectIndices = null;
            Map<String,String> fontAttrs = null;
            if(OPT_VERBOSE) {
                System.out.println("#"+caseNo+" id="+id + ", script="+script);
            }
            NodeList children = testCase.getChildNodes();
            for(int sub=0;sub<children.getLength();sub++) {
                Node n = children.item(sub);
                if(n.getNodeType()!=Node.ELEMENT_NODE) continue;
                String nn = n.getNodeName();
                if(nn.equals(XML_TEST_FONT)) {
                    fontAttrs = attrs(n);
                } else if(nn.equals(XML_TEST_TEXT)) {
                    testText = n.getTextContent();
                } else if(nn.equals(XML_RESULT_GLYPHS)) {
                    String hex = n.getTextContent();
                    expectGlyphs = parseHexArray(hex);
                } else if(nn.equals(XML_RESULT_INDICES)) {
                    String hex = n.getTextContent();
                    expectIndices = parseHexArray(hex);
                } else if(OPT_VERBOSE) {
                    System.out.println("Ignoring node " + nn);
                }
            }
            if(fontAttrs == null) {
                throw new IllegalArgumentException(id + " Missing node " + XML_TEST_FONT);
            }
            if(testText == null) {
                throw new IllegalArgumentException(id + " Missing node " + XML_TEST_TEXT);
            }

            String fontName = fontAttrs.get(XML_NAME);
            Font f = getFont(fontName, fontAttrs);
            if(f==null) {
                if(OPT_FAILMISSING) {
                    throw new MissingResourceException("Missing font,  abort test", Font.class.getName(), fontName);
                }
                System.out.println("Skipping " + id + " because font is missing: " + fontName);
                skipped++;
                continue;
            }
            FontRenderContext frc = new FontRenderContext(null, true, true);
            TextLayout tl = new TextLayout(testText,f,frc);
            final List<GlyphVector> glyphs = new ArrayList<GlyphVector>();
            Graphics2D myg2 = new Graphics2D(){

                    @Override
                    public void draw(Shape s) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public boolean drawImage(Image img, AffineTransform xform,
                                             ImageObserver obs) {
                        // TODO Auto-generated method stub
                        return false;
                    }

                    @Override
                    public void drawImage(BufferedImage img,
                                          BufferedImageOp op, int x, int y) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawRenderedImage(RenderedImage img,
                                                  AffineTransform xform) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawRenderableImage(RenderableImage img,
                                                    AffineTransform xform) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawString(String str, int x, int y) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawString(String str, float x, float y) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawString(
                                           AttributedCharacterIterator iterator, int x, int y) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawString(
                                           AttributedCharacterIterator iterator, float x,
                                           float y) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawGlyphVector(GlyphVector g, float x, float y) {
                        if(x!=0.0 || y!=0.0) {
                            throw new InternalError("x,y should be 0 but got " + x+","+y);
                        }
                        //System.err.println("dGV : " + g.toString() + " @ "+x+","+y);
                        glyphs.add(g);
                    }

                    @Override
                    public void fill(Shape s) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
                        // TODO Auto-generated method stub
                        return false;
                    }

                    @Override
                    public GraphicsConfiguration getDeviceConfiguration() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public void setComposite(Composite comp) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void setPaint(Paint paint) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void setStroke(Stroke s) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void setRenderingHint(Key hintKey, Object hintValue) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public Object getRenderingHint(Key hintKey) {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public void setRenderingHints(Map<?, ?> hints) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void addRenderingHints(Map<?, ?> hints) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public RenderingHints getRenderingHints() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public void translate(int x, int y) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void translate(double tx, double ty) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void rotate(double theta) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void rotate(double theta, double x, double y) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void scale(double sx, double sy) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void shear(double shx, double shy) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void transform(AffineTransform Tx) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void setTransform(AffineTransform Tx) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public AffineTransform getTransform() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public Paint getPaint() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public Composite getComposite() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public void setBackground(Color color) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public Color getBackground() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public Stroke getStroke() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public void clip(Shape s) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public FontRenderContext getFontRenderContext() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public Graphics create() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public Color getColor() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public void setColor(Color c) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void setPaintMode() {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void setXORMode(Color c1) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public Font getFont() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public void setFont(Font font) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public FontMetrics getFontMetrics(Font f) {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public Rectangle getClipBounds() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public void clipRect(int x, int y, int width, int height) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void setClip(int x, int y, int width, int height) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public Shape getClip() {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public void setClip(Shape clip) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void copyArea(int x, int y, int width, int height,
                                         int dx, int dy) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawLine(int x1, int y1, int x2, int y2) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void fillRect(int x, int y, int width, int height) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void clearRect(int x, int y, int width, int height) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawRoundRect(int x, int y, int width,
                                              int height, int arcWidth, int arcHeight) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void fillRoundRect(int x, int y, int width,
                                              int height, int arcWidth, int arcHeight) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawOval(int x, int y, int width, int height) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void fillOval(int x, int y, int width, int height) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawArc(int x, int y, int width, int height,
                                        int startAngle, int arcAngle) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void fillArc(int x, int y, int width, int height,
                                        int startAngle, int arcAngle) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawPolyline(int[] xPoints, int[] yPoints,
                                             int nPoints) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void drawPolygon(int[] xPoints, int[] yPoints,
                                            int nPoints) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void fillPolygon(int[] xPoints, int[] yPoints,
                                            int nPoints) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public boolean drawImage(Image img, int x, int y,
                                             ImageObserver observer) {
                        // TODO Auto-generated method stub
                        return false;
                    }

                    @Override
                    public boolean drawImage(Image img, int x, int y,
                                             int width, int height, ImageObserver observer) {
                        // TODO Auto-generated method stub
                        return false;
                    }

                    @Override
                    public boolean drawImage(Image img, int x, int y,
                                             Color bgcolor, ImageObserver observer) {
                        // TODO Auto-generated method stub
                        return false;
                    }

                    @Override
                    public boolean drawImage(Image img, int x, int y,
                                             int width, int height, Color bgcolor,
                                             ImageObserver observer) {
                        // TODO Auto-generated method stub
                        return false;
                    }

                    @Override
                    public boolean drawImage(Image img, int dx1, int dy1,
                                             int dx2, int dy2, int sx1, int sy1, int sx2,
                                             int sy2, ImageObserver observer) {
                        // TODO Auto-generated method stub
                        return false;
                    }

                    @Override
                    public boolean drawImage(Image img, int dx1, int dy1,
                                             int dx2, int dy2, int sx1, int sy1, int sx2,
                                             int sy2, Color bgcolor, ImageObserver observer) {
                        // TODO Auto-generated method stub
                        return false;
                    }

                    @Override
                    public void dispose() {
                        // TODO Auto-generated method stub

                    }

                };
            tl.draw(myg2, 0, 0);
            if(glyphs.size() != 1) {
                err("drew " + glyphs.size() + " times - expected 1");
                total++;
                bad++;
                continue;
            }
            boolean isBad = false;
            GlyphVector gv = glyphs.get(0);

            // GLYPHS
            int gotGlyphs[] = gv.getGlyphCodes(0, gv.getNumGlyphs(), new int[gv.getNumGlyphs()]);

            int count = Math.min(gotGlyphs.length, expectGlyphs.length); // go up to this count

            for(int i=0;i<count;i++) {
                if(gotGlyphs[i]!=expectGlyphs[i]) {
                    err("@"+i+" - got \tglyph 0x" + Integer.toHexString(gotGlyphs[i]) + " wanted 0x" + Integer.toHexString(expectGlyphs[i]));
                    isBad=true;
                    break;
                }
            }

            // INDICES
            int gotIndices[] = gv.getGlyphCharIndices(0, gv.getNumGlyphs(), new int[gv.getNumGlyphs()]);
            for(int i=0;i<count;i++) {
                if(gotIndices[i]!=expectIndices[i]) {
                    err("@"+i+" - got \tindex 0x" + Integer.toHexString(gotGlyphs[i]) + " wanted 0x" + Integer.toHexString(expectGlyphs[i]));
                    isBad=true;
                    break;
                }
            }


            // COUNT
            if(gotGlyphs.length != expectGlyphs.length) {
                System.out.println("Got " + gotGlyphs.length + " wanted " + expectGlyphs.length + " glyphs");
                isBad=true;
            } else {
                if(OPT_VERBOSE) {
                    System.out.println(">> OK: " + gotGlyphs.length + " glyphs");
                }
            }


            if(isBad) {
                bad++;
                System.out.println("* FAIL: " + id + "  /\t" + fontName);
            } else {
                System.out.println("* OK  : " + id + "  /\t" + fontName);
            }
            total++;
        }
    }


    private boolean verifyFont(File f, Map<String, String> fontAttrs) {
        InputStream fis = null;
        String fontName = fontAttrs.get(XML_NAME);
        int count=0;
        try {
            fis = new BufferedInputStream(new FileInputStream(f));

            int i = 0;
            int r;
            try {
                while((r=fis.read())!=-1) {
                    i+=(int)r;
                    count++;
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                return false;
            }
            if(OPT_VERBOSE) {
                System.out.println("for " + f.getAbsolutePath() + " chks = 0x" + Integer.toHexString(i) + " size=" + count);
            }
            String theirStr = fontAttrs.get("rchecksum");

            String ourStr = Integer.toHexString(i).toLowerCase();

            if(theirStr!=null) {
                if(theirStr.startsWith("0x")) {
                    theirStr = theirStr.substring(2).toLowerCase();
                } else {
                    theirStr = theirStr.toLowerCase();
                }
                long theirs = Integer.parseInt(theirStr, 16);
                if(theirs != i) {
                    err("WARNING: rchecksum for " + fontName + " was " + i  + " (0x"+ourStr+") "+ " but file said " + theirs +" (0x"+theirStr+")  - perhaps a different font?");
                    return false;
                } else {
                    if(OPT_VERBOSE) {
                        System.out.println(" rchecksum for " + fontName + " OK");
                    }
                    return true;
                }
            } else {
                //if(OPT_VERBOSE) {
                System.err.println("WARNING: rchecksum for " + fontName + " was " + i  + " (0x"+ourStr+") "+ " but rchecksum was MISSING. Old ICU data?");
                //}
            }
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return false;
        } finally {
            try {
                fis.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return true;
    }


    private Integer[] parseHexArray(String hex) {
        List<Integer> ret = new ArrayList<Integer>();
        String items[] = hex.split("[\\s,]");
        for(String i : items) {
            if(i.isEmpty()) continue;
            if(i.startsWith("0x")) {
                i = i.substring(2);
            }
            ret.add(Integer.parseInt(i, 16));
        }
        return ret.toArray(new Integer[0]);
    }


    private void err(String string) {
        if(OPT_NOTHROW) {
            System.out.println(id+" ERROR: " + string +" (continuing due to -n)");
        } else {
            throw new InternalError(id+ ": " + string);
        }
    }


    private Font getFont(String fontName, Map<String, String> fontAttrs) {
        Font f;
        if(false)
            try {
                f = Font.getFont(fontName);
                if(f!=null)  {
                    if(OPT_VERBOSE) {
                        System.out.println("Loaded default path to " + fontName);
                    }
                    return f;
                }
            } catch(Throwable t) {
                if(OPT_VERBOSE) {
                    t.printStackTrace();
                    System.out.println("problem loading font " + fontName + " - " + t.toString());
                }
            }

        File homeDir = new File(System.getProperty("user.home"));
        File fontDir = new File(homeDir, "fonts");
        File fontFile = new File(fontDir, fontName);
        //System.out.println("## trying " + fontFile.getAbsolutePath());
        if(fontFile.canRead()) {
            try {
                if(!verifyFont(fontFile,fontAttrs)) {
                    System.out.println("Warning: failed to verify " + fontName);
                }
                f = Font.createFont(Font.TRUETYPE_FONT, fontFile);
                if(f!=null & OPT_VERBOSE) {
                    System.out.println("> loaded from " + fontFile.getAbsolutePath() + " - " + f.toString());
                }
                return f;
            } catch (FontFormatException e) {
                if(OPT_VERBOSE) {
                    e.printStackTrace();
                    System.out.println("problem loading font " + fontName + " - " + e.toString());
                }
            } catch (IOException e) {
                if(OPT_VERBOSE) {
                    e.printStackTrace();
                    System.out.println("problem loading font " + fontName + " - " + e.toString());
                }
            }
        }
        return null;
    }


    private static Map<String, String> attrs(Node testCase) {
        Map<String,String> rv = new TreeMap<String,String>();
        NamedNodeMap nnm = testCase.getAttributes();
        for(int i=0;i<nnm.getLength();i++) {
            Node n = nnm.item(i);
            String k = n.getNodeName();
            String v = n.getNodeValue();
            rv.put(k, v);
        }
        return rv;
    }
}