/**
 * Fontastic A font file writer to create TTF
 * http://code.andreaskoller.com/libraries/fontastic
 *
 * Copyright (C) 2013 Andreas Koller http://andreaskoller.com
 *
 * Uses: doubletype http://sourceforge.net/projects/doubletype/ for TTF creation
 *
 * 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 2.1 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; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * @author Andreas Koller http://andreaskoller.com
 * @modified 03/16/2014  JPEXS - removed Woff
 * @version 0.4 (4)
 */
package fontastic;

import org.doubletype.ossa.*;
import org.doubletype.ossa.module.*;
import org.doubletype.ossa.adapter.*;

import java.io.*;
import java.util.ArrayList;
import java.util.Map;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

/**
 * Fontastic A font file writer to create TTF and WOFF (Webfonts).
 * http://code.andreaskoller.com/libraries/fontastic
 *
 */
public class Fontastic {

    private org.doubletype.ossa.module.TypefaceFile typeface;
    private org.doubletype.ossa.Engine m_engine;

    private String fontname;

    private List<FGlyph> glyphs;

    private int advanceWidth = 512;

    private File ttffile;
    private File outfile;
    private File tempDir;

    public final static String VERSION = "0.4";

    /**
     * Uppercase alphabet 26 characters *
     */
    public final static char alphabet[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G',
        'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
        'U', 'V', 'W', 'X', 'Y', 'Z'};
    /**
     * Lowercase alphabet 26 characters *
     */
    public final static char alphabetLc[] = {'a', 'b', 'c', 'd', 'e', 'f',
        'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
        't', 'u', 'v', 'w', 'x', 'y', 'z'};

    /**
     * Return the version of the library.
     *
     * @return String
     */
    public static String version() {
        return VERSION;
    }

    /**
     * Constructor
     *
     * @example Fontastic f = new Fontastic(this, "MyFont");
     *
     * @param theParent Your processing sketch (this).
     * @param fontname Font name
     *
     */
    public Fontastic(String fontname, File outfile) throws IOException {
        this.fontname = fontname;
        this.outfile = outfile;
        intitialiseFont();
        this.glyphs = new ArrayList<>();
    }

    /**
     * Returns the font name.
     *
     * @return String
     */
    public String getFontname() {
        return fontname;
    }

    private static File createTempDirectory()
            throws IOException {
        File temp;
        temp = File.createTempFile("temp", Long.toString(System.nanoTime()));

       
        
        if (!(temp.delete())) {
            throw new IOException("Could not delete temp file: " + temp.getAbsolutePath());
        }
   
        if (!(temp.mkdirs())) {
            throw new IOException("Could not create temp directory: " + temp.getAbsolutePath());
        }

        return (temp);
    }

    /**
     * Creates and initialises a new typeface. Font data is put into sketch
     * folder data/fontname.
     */
    private void intitialiseFont() throws IOException {

        tempDir = createTempDirectory();
        /*if (!tempDir.exists()) {
            if(!tempDir.mkdirs()){
                if (!tempDir.exists()) {
                    throw new IOException("Cannot create temp dir");
                }
            }
        } else {
            //deleteFolderContents(tempDir, false);
        }*/
        

        m_engine = Engine.getSingletonInstance();
        m_engine.buildNewTypeface(fontname, tempDir);
        
        this.setFontFamilyName(fontname);
        this.setVersion("CC BY-SA 3.0 http://creativecommons.org/licenses/by-sa/3.0/"); // default
        // license

        String directoryName = tempDir + File.separator + "bin" + File.separator;

        ttffile = new File(directoryName + fontname + ".ttf");
    }

    /**
     * Builds the font and writes the .ttf and the .woff file as well as a HTML
     * template for previewing the WOFF. If debug is set (default is true) then
     * you'll see the .ttf and .woff file name in the console.
     */
    public void buildFont() throws FileNotFoundException {
        // Create TTF file with doubletype
        //m_engine.fireAction();
        //m_engine.addDefaultGlyphs();

        for (FGlyph glyph : glyphs) {

            m_engine.checkUnicodeBlock(glyph.getGlyphChar());
            GlyphFile glyphFile = m_engine.addNewGlyph(glyph.getGlyphChar());
            glyphFile.setAdvanceWidth(glyph.getAdvanceWidth());

            for (FContour contour : glyph.getContours()) {

                EContour econtour = new EContour();
                econtour.setType(EContour.k_quadratic);

                for (FPoint point : contour.points) {

                    EContourPoint e = new EContourPoint(point.x, point.y, true);                    
                    if (point.hasControlPoint1()) {
                        
                        econtour.addContourPoint(new EContourPoint(point.controlPoint.x, point.controlPoint.y, false));
                        /*EControlPoint cp1 = new EControlPoint(true,
                                point.controlPoint1.x, point.controlPoint1.y);
                        e.setControlPoint1(cp1);   
                        */
                    }
/*
                    if (point.hasControlPoint2()) {
                        EControlPoint cp2 = new EControlPoint(false,
                                point.controlPoint2.x, point.controlPoint2.y);    
                        e.setControlPoint2(cp2);  
                    }                      */

                    econtour.addContourPoint(e);
                }

                glyphFile.addContour(econtour);
            }
            glyphFile.saveGlyphFile();
        }
        m_engine.getTypeface().addRequiredGlyphs();        
        m_engine.buildTrueType(false);

        // End TTF creation
        if(outfile.exists()){
            outfile.delete();
        }
        ttffile.renameTo(outfile);
        cleanup();
    }

    /**
     * Deletes all the glyph files created by doubletype in your data/fontname
     * folder.
     */
    private void cleanup() {
        File[] filesToExclude = new File[0];

        deleteFolderContents(tempDir, true, filesToExclude);

    }

    /**
     * Sets the author of the font.
     */
    public void setAuthor(String author) throws FileNotFoundException {
        m_engine.setAuthor(author);
    }

    /**
     * Sets the copyright year of the font.
     */
    public void setCopyrightYear(String copyrightYear) throws FileNotFoundException {
        m_engine.setCopyrightYear(copyrightYear);
    }

    /**
     * Sets the version of the font (default is "0.1").
     */
    public void setVersion(String version) {
        m_engine.getTypeface().getGlyph().getHead().setVersion(version);
    }

    /**
     * Sets the font family name of the font. Also called in the constructor. If
     * changed with setFontFamilyName() it won't affect folder the font is
     * stored in.
     */
    public void setFontFamilyName(String fontFamilyName) throws FileNotFoundException {
        m_engine.setFontFamilyName(fontFamilyName);
    }

    /**
     * Sets the sub family of the font.
     */
    public void setSubFamily(String subFamily) throws FileNotFoundException {
        m_engine.getTypeface().setSubFamily(subFamily);
    }

    /**
     * Sets the license of the font (default is "CC BY-SA 3.0
     * http://creativecommons.org/licenses/by-sa/3.0/")
     */
    public void setTypefaceLicense(String typefaceLicense) throws FileNotFoundException {
        m_engine.setTypefaceLicense(typefaceLicense);
    }

    /**
     * Sets the baseline of the font.
     */
    public void setBaseline(float baseline) throws FileNotFoundException {
        m_engine.setBaseline(baseline);
    }

    /**
     * Sets the meanline of the font.
     */
    public void setMeanline(float meanline) throws FileNotFoundException {
        m_engine.setMeanline(meanline);
    }

    /**
     * Sets the advanceWidth of the font. Can be changed for every glyph
     * individually. Won't affect already created glyphs.
     */
    public void setAdvanceWidth(int advanceWidth) {
        m_engine.setAdvanceWidth(advanceWidth);
        this.advanceWidth = advanceWidth;
    }

    public void setTopSideBearing(float topSideBearing) throws FileNotFoundException {
        try {
            m_engine.getTypeface().setTopSideBearing(topSideBearing);
        } catch (OutOfRangeException e) {
            System.out
                    .println("Error while setting aopSideBearing (must be within range "
                            + m_engine.getTypeface().getEm());
            e.printStackTrace();
        }
    }

    public void setBottomSideBearing(float bottomSideBearing) throws FileNotFoundException {
        try {
            m_engine.getTypeface().setBottomSideBearing(bottomSideBearing);
        } catch (OutOfRangeException e) {
            System.out
                    .println("Error while setting bottomSideBearing (must be within range "
                            + m_engine.getTypeface().getEm());
            e.printStackTrace();
        }
    }

    public void setAscender(float ascender) throws FileNotFoundException {
        try {
            m_engine.getTypeface().setAscender(ascender);
        } catch (OutOfRangeException e) {
            System.out
                    .println("Error while setting ascender (must be within range 0 to "
                            + m_engine.getTypeface().getEm() + ")");
            e.printStackTrace();
        }
    }

    public void setDescender(float descender) throws FileNotFoundException {
        try {
            m_engine.getTypeface().setDescender(descender);
        } catch (OutOfRangeException e) {
            System.out
                    .println("Error while setting descender (must be within range 0 to "
                            + m_engine.getTypeface().getEm() + ")");
            e.printStackTrace();
        }
    }

    public void setXHeight(float xHeight) throws FileNotFoundException {
        try {
            m_engine.getTypeface().setXHeight(xHeight);
        } catch (OutOfRangeException e) {
            System.out
                    .println("Error while setting xHeight (must be within range 0 to "
                            + m_engine.getTypeface().getEm()
                            + " as well as lower than the ascender "
                            + m_engine.getTypeface().getAscender() + ")");
            e.printStackTrace();
        }
    }

    /**
     * Sets the default metrics for the typeface: setTopSideBearing(170); // 2
     * px setAscender(683); // 8 px setXHeight(424); // 5 px setDescender(171);
     * // 2 px setBottomSideBearing(0); // 0px
     *
     */
    public void setDefaultMetrics() throws FileNotFoundException {
        m_engine.getTypeface().setDefaultMetrics();
    }

    /**
     * Add a glyph
     *
     * @param c Character of the glyph.
     *
     * @return FGlyph that has been created.
     *
     */
    public FGlyph addGlyph(char c) {

        FGlyph glyph = new FGlyph(c);       
        glyph.setAdvanceWidth(advanceWidth);
        glyphs.add(glyph);
        return glyph;

    }

    /**
     * Add a glyph and its one contour
     *
     * @param c Character of the glyph.
     *
     * @param FContour Shape of the glyph as FContour.
     *
     * @return The glyph FGlyph that has been created. You can use this to store
     * the glyph and add contours afterwards. Alternatively, you can call
     * getGlyph(char c) to retrieve it.
     */
    public FGlyph addGlyph(char c, FContour contour) {

        FGlyph glyph = new FGlyph(c);
        glyphs.add(glyph);
        glyph.addContour(contour);

        glyph.setAdvanceWidth(advanceWidth);
        return glyph;

    }

    /**
     * Add a glyph and its contours
     *
     * @param c Character of the glyph.
     *
     * @param FContour [] Shape of the glyph in an array of FContour.
     *
     * @return The FGlyph that has been created. You can use this to store the
     * glyph and add contours afterwards. Alternatively, you can call
     * getGlyph(char c) to retrieve it.
     */
    public FGlyph addGlyph(char c, FContour[] contours) {

        FGlyph glyph = new FGlyph(c);
        glyphs.add(glyph);

        for (FContour contour : contours) {
            // if (debug) System.out.println(p.x + " - " + p.y);
            glyph.addContour(contour);
        }
        glyph.setAdvanceWidth(advanceWidth);
        return glyph;

    }

    /**
     * Get glyph by character
     *
     * @param c The character of the glyph
     *
     * @return The glyph
     */
    public FGlyph getGlyph(char c) {

        FGlyph glyph = null;
        for (int i = 0; i < glyphs.size(); i++) {
            if (glyphs.get(i).getGlyphChar() == c) {
                glyph = glyphs.get(i);
                break;
            }
        }
        return glyph;

    }

    /**
     * Engine getter
     *
     * @return The doubletype Engine used for font creation, so that you can
     * access all functions of doubletype in case you need them.
     */
    public Engine getEngine() {
        return m_engine;
    }

    /**
     * Returns the TypefaceFile
     *
     * @return The doubletype TypefaceFile used for font creation, so that you
     * can access functions of doubletype in case you need them.
     */
    public TypefaceFile getTypefaceFile() {
        return m_engine.getTypeface();
    }

    /**
     * Returns the .ttf file name
     *
     * @return The .ttf file name, which is being created when you call build()
     */
    public String getTTFfilename() {
        return ttffile.toString();
    }

    private static void deleteFolderContents(File folder,
            boolean deleteFolderItself) {
        File[] files = folder.listFiles();
        if (files != null) { // some JVMs return null for empty dirs
            for (File f : files) {
                if (f.isDirectory()) {
                    deleteFolderContents(f, true);
                    f.delete();
                } else {
                    f.delete();
                }
            }
        }
        if (deleteFolderItself) {
            folder.delete();
        }
    }

    private static void deleteFolderContents(File folder,
            boolean deleteFolderItself, File[] exceptions) {
        File[] files = folder.listFiles();
        if (files != null) { // some JVMs return null for empty dirs
            for (File f : files) {
                boolean deleteFile = true;
                for (File exceptfile : exceptions) {
                    if (f.equals(exceptfile)) {
                        deleteFile = false;
                    }
                }
                if (deleteFile) {
                    if (f.isDirectory()) {
                        deleteFolderContents(f, true, exceptions);
                        f.delete();
                    } else {
                        f.delete();
                    }
                }
            }
        }
        if (deleteFolderItself) {
            folder.delete();
        }
    }
}
