/*
 *  Copyright (C) 2010-2021 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;

import com.google.typography.font.sfntly.Font;
import com.google.typography.font.sfntly.FontFactory;
import com.google.typography.font.sfntly.data.WritableFontData;
import com.google.typography.font.tools.conversion.woff.WoffWriter;
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
import com.jpexs.decompiler.flash.ApplicationInfo;
import com.jpexs.decompiler.flash.EventListener;
import com.jpexs.decompiler.flash.ReadOnlyTagList;
import com.jpexs.decompiler.flash.RetryTask;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.modes.FontExportMode;
import com.jpexs.decompiler.flash.exporters.settings.FontExportSettings;
import com.jpexs.decompiler.flash.exporters.shape.PathExporter;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.base.FontTag;
import com.jpexs.decompiler.flash.types.SHAPE;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.Path;
import fontastic.FGlyph;
import fontastic.FPoint;
import fontastic.Fontastic;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

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

    public List<File> exportFonts(AbortRetryIgnoreHandler handler, String outdir, ReadOnlyTagList tags, final FontExportSettings settings, EventListener evl) throws IOException, InterruptedException {
        List<File> ret = new ArrayList<>();
        if (tags.isEmpty()) {
            return ret;
        }

        File foutdir = new File(outdir);
        Path.createDirectorySafe(foutdir);

        int count = 0;
        for (Tag t : tags) {
            if (t instanceof FontTag) {
                count++;
            }
        }

        if (count == 0) {
            return ret;
        }

        int currentIndex = 1;
        for (Tag t : tags) {
            if (t instanceof FontTag) {
                if (evl != null) {
                    evl.handleExportingEvent("font", currentIndex, count, t.getName());
                }

                final FontTag st = (FontTag) t;
                String ext = ".ttf";
                if (settings.mode == FontExportMode.WOFF) {
                    ext = ".woff";
                }
                final File file = new File(outdir + File.separator + Helper.makeFileName(st.getCharacterExportFileName() + ext));
                new RetryTask(() -> {
                    exportFont(st, settings.mode, file);
                }, handler).run();

                ret.add(file);

                if (evl != null) {
                    evl.handleExportedEvent("font", currentIndex, count, t.getName());
                }

                currentIndex++;
            }
        }

        return ret;
    }

    public byte[] exportFont(final FontTag t, FontExportMode mode) {
        try {
            String ext = null;
            switch (mode) {
                case TTF:
                    ext = ".ttf";
                    break;
                case WOFF:
                    ext = ".woff";
            }
            File f = File.createTempFile("temp", ext);
            exportFont(t, mode, f);
            return Helper.readFile(f.getPath());
        } catch (IOException ex) {
            Logger.getLogger(FontExporter.class.getName()).log(Level.SEVERE, null, ex);
        }
        return SWFInputStream.BYTE_ARRAY_EMPTY;
    }

    public void exportFont(FontTag ft, FontExportMode mode, File file) throws IOException {
        final FontTag t = ft.toClassicFont();
        List<SHAPE> shapes = t.getGlyphShapeTable();

        final double divider = t.getDivider();

        File ttfFile = file;

        if (mode == FontExportMode.WOFF) {
            ttfFile = File.createTempFile("ffdec_export", ".ttf");
        }

        String fontName = Helper.makeFileName(t.getFontNameIntag());
        if (fontName.length() == 0) {
            fontName = "noname";
        }

        Fontastic f = new Fontastic(fontName, ttfFile);
        String cop = t.getCopyright();

        f.getEngine().setCopyrightYear(cop == null ? "" : cop);
        if (Configuration.setFFDecVersionInExportedFont.get()) {
            f.setAuthor(ApplicationInfo.shortApplicationVerName);
        } else {
            f.setAuthor(ApplicationInfo.SHORT_APPLICATION_NAME);
        }

        f.setVersion("1.0");

        SWF swf = t.getSwf();
        if (swf != null) {
            Date date = swf.getFileModificationDate();
            f.setCreationDate(date);
            f.setModificationDate(date);
        }

        int ascent = t.getAscent();
        if (ascent != -1) {
            float value = Math.round(ascent / divider);
            value = Math.min(value, 1024);
            f.setAscender(value);
        }

        int descent = t.getDescent();
        if (descent != -1) {
            float value = Math.round(descent / divider);
            value = Math.min(value, 1024);
            f.setDescender(value);
        }

        List<Integer> reallyExportedGlyphs = new ArrayList<>();
        Set<Character> processedCharacters = new HashSet<>();
        //if there are more glyphs for one char (in some weird fonts), use the last glyph            
        for (int i = shapes.size() - 1; i >= 0; i--) {
            char c = t.glyphToChar(i);
            if (!processedCharacters.contains((Character) c)) {
                reallyExportedGlyphs.add(0, (Integer) i);
                processedCharacters.add((Character) c);
            }
        }
        int glyphCount = 0;
        for (Integer ii : reallyExportedGlyphs) {
            int i = (int) ii;
            char c = t.glyphToChar(i);
            SHAPE s = shapes.get(i);
            final List<FPoint[]> contours = new ArrayList<>();
            PathExporter seb = new PathExporter(swf, s, null) {

                private double transformX(double x) {
                    return Math.ceil((double) (x / divider));
                }

                private double transformY(double y) {
                    return -Math.ceil((double) (y / divider));
                }

                List<FPoint> path = new ArrayList<>();

                @Override
                protected void finalizePath() {
                    FPoint[] points = path.toArray(new FPoint[path.size()]);
                    if (points.length > 0) {
                        contours.add(points);
                    }
                    path.clear();
                }

                @Override
                public void moveTo(double x, double y) {
                    finalizePath();
                    path.add(new FPoint(transformX(x), transformY(y)));
                }

                @Override
                public void lineTo(double x, double y) {
                    path.add(new FPoint(transformX(x), transformY(y)));
                }

                @Override
                public void curveTo(double controlX, double controlY, double anchorX, double anchorY) {
                    path.add(new FPoint(
                            new FPoint(transformX(anchorX), transformY(anchorY)),
                            new FPoint(transformX(controlX), transformY(controlY))
                    ));

                }
            };
            seb.export();

            FGlyph g = f.addGlyph(c);
            glyphCount++;
            double adv = t.getGlyphAdvance(i);
            if (adv != -1) {
                g.setAdvanceWidth((int) Math.round(adv / divider));
            } else {
                g.setAdvanceWidth((int) Math.round(t.getGlyphWidth(i) / divider + 100));
            }
            for (FPoint[] cnt : contours) {
                if (cnt.length == 0) {
                    continue;
                }
                g.addContour(cnt);
            }

        }

        if (glyphCount == 0) {
            return;
        }

        f.buildFont();

        if (mode == FontExportMode.WOFF) {
            FontFactory fontFactory = FontFactory.getInstance();
            byte[] fontBytes;
            try (FileInputStream fis = new FileInputStream(ttfFile)) {
                fontBytes = new byte[(int) ttfFile.length()];
                fis.read(fontBytes);
            }

            Font[] fontArray = null;
            fontArray = fontFactory.loadFonts(fontBytes);

            Font font = fontArray[0];

            try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
                WoffWriter w = new WoffWriter();
                WritableFontData woffData = w.convert(font);
                woffData.copyTo(fos);
            }

            ttfFile.delete();
        }
    }
}
