/*
 * Copyright (c) 2008, 2021, 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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.
 */

package javafx.scene.text;

import com.sun.javafx.scene.text.FontHelper;
import java.io.FilePermission;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;

import com.sun.javafx.tk.Toolkit;
import javafx.beans.NamedArg;

/**
 * <p>The {@code Font} class represents fonts, which are used to render text on
 * screen.
 * <p>
 * The size of a {@code Font} is described as being specified in points
 * which are a real world measurement of approximately 1/72 inch.
 * <p>
 * Given that fonts scale with the rendering transform as determined
 * by the transform attributes of a {@code Node} using the {@code Font}
 * and its ancestors, the size will actually be relative to the local
 * coordinate space of the node, which should provide coordinates
 * similar to the size of a point if no scaling transforms are present
 * in the environment of the node.
 * Note that the real world distances specified by the default coordinate
 * system only approximate point sizes as a rule of thumb and are typically
 * defaulted to screen pixels for most displays.
 * <p>
 * For more information see {@link javafx.scene.Node} for more information
 * on the default coordinate system
 * @since JavaFX 2.0
 */
public final class Font {

    static {
        // This is used by classes in different packages to get access to
        // private and package private methods.
        FontHelper.setFontAccessor(new FontHelper.FontAccessor() {

            @Override
            public Object getNativeFont(Font font) {
                return font.getNativeFont();
            }

            @Override
            public void setNativeFont(Font font, Object f, String nam, String fam, String styl) {
                font.setNativeFont(f, nam, fam, styl);
            }

            @Override
            public Font nativeFont(Object f, String name, String family, String style, double size) {
                return Font.nativeFont(f, name, family, style, size);
            }

        });
    }

    private static final String DEFAULT_FAMILY = "System";
    private static final String DEFAULT_FULLNAME = "System Regular";

    /**
     * The default font for this platform. This is used whenever a font is not
     * specifically set on a Text node or overridden by CSS.
     */
    private static float defaultSystemFontSize = -1;
    private static float getDefaultSystemFontSize() {
        if (defaultSystemFontSize == -1) {
            defaultSystemFontSize =
                Toolkit.getToolkit().getFontLoader().getSystemFontSize();
        }
        return defaultSystemFontSize;
    }

    private static Font DEFAULT;
    /**
     * Gets the default font which will be from the family "System",
     * and typically the style "Regular", and be of a size consistent
     * with the user's desktop environment, to the extent that can
     * be determined.
     * @return The default font.
     */
    public static synchronized Font getDefault() {
        if (DEFAULT == null) {
            DEFAULT = new Font(DEFAULT_FULLNAME, getDefaultSystemFontSize());
        }
        return DEFAULT;
    }

    /**
     * Gets all the font families installed on the user's system, including any
     * application fonts or SDK fonts. This call has performance considerations
     * as looking up all of the fonts may be an expensive operation the
     * first time.
     * @return The list containing all available font families.
     */
    public static List<String> getFamilies() {
        return Toolkit.getToolkit().getFontLoader().getFamilies();
    }

    /**
     * Gets the names of all fonts that are installed on the users system,
     * including any application fonts and SDK fonts.
     * This call has performance considerations as
     * looking up all of the fonts may be an expensive operation the first time.
     * @return The list containing all available fonts.
     */
    public static List<String> getFontNames() {
        return Toolkit.getToolkit().getFontLoader().getFontNames();
    }

    /**
     * Gets the names of all fonts in the specified font family that are
     * installed on the users system, including any application fonts
     * and SDK fonts.
     * This call has performance considerations as looking up all of the
     * fonts may be an expensive operation the first time.
     * @param family the font family
     * @return The list containing the fonts for the given family.
     */
    public static List<String> getFontNames(String family) {
        return Toolkit.getToolkit().getFontLoader().getFontNames(family);
    }

    /**
     * Searches for an appropriate font based on the font family name and
     * weight and posture style. This method is not guaranteed to return
     * a specific font, but does its best to find one that fits the
     * specified requirements.
     * <p>
     * A null or empty value for family allows the implementation to
     * select any suitable font.
     *
     * @param family The family of the font
     * @param weight The weight of the font
     * @param posture The posture or posture of the font
     * @param size {@literal The point size of the font. This can be a fractional value,
     * but must not be negative. If the size is < 0 the default size will be
     * used.}
     * @return The font that best fits the specified requirements.
     */
    public static Font font(String family, FontWeight weight,
                            FontPosture posture, double size) {

        String fam =
            (family == null || "".equals(family)) ? DEFAULT_FAMILY : family;
        double sz = size < 0 ? getDefaultSystemFontSize() : size;
        return Toolkit.getToolkit().getFontLoader().font(fam, weight, posture, (float)sz);
    }

    /**
     * Searches for an appropriate font based on the font family name and weight
     * style. This method is not guaranteed to return a specific font, but does
     * its best to find one that fits the specified requirements.
     * A null or empty  value for family allows the implementation
     * to select any suitable font.
     *
     * @param family The family of the font
     * @param weight The weight of the font
     * @param size {@literal The point size of the font. This can be a fractional value,
     * but must not be negative. If the size is < 0 the default size will be
     * used.}
     * @return The font that best fits the specified requirements.
     */
    public static Font font(String family, FontWeight weight, double size) {
        return font(family, weight, null, size);
    }

    /**
     * Searches for an appropriate font based on the font family name and posture
     * style. This method is not guaranteed to return a specific font, but does
     * its best to find one that fits the specified requirements. A null or empty
     * value for family allows the implementation to select any suitable font.
     *
     * @param family The family of the font
     * @param posture The posture or posture of the font
     * @param size {@literal The point size of the font. This can be a fractional value,
     * but must not be negative. If the size is < 0 the default size will be
     * used.}
     * @return The font that best fits the specified requirements.
     */
    public static Font font(String family, FontPosture posture, double size) {
        return font(family, null, posture, size);
    }

    /**
     * Searches for an appropriate font based on the font family name and size.
     * This method is not guaranteed to return a specific font, but does
     * its best to find one that fits the specified requirements. A null or empty
     * value for family allows the implementation to select any suitable font.
     *
     * @param family The family of the font
     * @param size {@literal The point size of the font. This can be a fractional value,
     * but must not be negative. If the size is < 0 the default size will be
     * used.}
     * @return The font that best fits the specified requirements.
     */
    public static Font font(String family, double size) {
        return font(family, null, null, size);
    }

    /**
     * Searches for an appropriate font based on the given font family name and
     * default font size.
     * This method is not guaranteed to return a specific font, but does
     * its best to find one that fits the specified requirements. A null or empty
     * value for family allows the implementation to select any suitable font.
     *
     * @param family The family of the font
     * @return The font that best fits the specified requirements.
     */
    public static Font font(String family) {
        return font(family, null, null, -1);
    }

    /**
     * Searches for an appropriate font based on the default font family name and
     * given font size.
     * This method is not guaranteed to return a specific font, but does
     * its best to find one that fits the specified requirements.
     *
     * @param size {@literal The point size of the font. This can be a fractional value,
     * but must not be negative. If the size is < 0 the default size will be
     * used.}
     * @return The font that best fits the specified requirements.
     */
    public static Font font(double size) {
        return font(null, null, null, size);
    }

    /**
     * The full font name. This name includes both the family name
     * and the style variant within that family. For example, for a plain
     * Arial font this would be "Arial Regular" and for a bolded
     * Arial font this would be "Arial Bold". The precise name to use when
     * loading a font is defined within each font file as the full font name.
     * For example, "Proxima Nova ExtraCondensed Bold Italic" would refer to a
     * specific Proxima Nova font.
     * A null or empty name allows the implementation to select any suitable
     * font.
     * <p>
     * There is a single unified way to load all of application supplied
     * (via <code>Font.loadFont()</code>, JavaFX runtime delivered fonts,
     * and system installed fonts. Simply create the font by specifying
     * the full name of the font you want to load.
     * If the specific font cannot be located, then a fallback or default
     * font will be used. The "name" will be updated to reflect the actual name
     * of the font being used. A load failure condition can be discovered by
     * checking the name of the Font with the name you tried to load.
     * <p>
     * Note that if you wish to locate a font by font family and style
     * then you can use one of the {@link #font} factory methods defined in
     * this class.
     *
     * @return the full font name
     * @defaultValue empty string
     */
    public final String getName() { return name; }
    private String name;

    /**
     * Returns the family of this font.
     * @return The family of this font.
     */
    public final String getFamily() { return family; }
    private String family;

    /**
     * The font specified string describing the style within the font family.
     * @return The style name of this font.
     */
    public final String getStyle() { return style; }
    private String style;

    /**
     * The point size for this font. This may be a fractional value such as
     * {@code 11.5}. {@literal If the specified value is < 0 the default size will be
     * used.}
     *
     * @return the point size for this font
     * @defaultValue 12
     */
    public final double getSize() { return size; }
    private double size;

    /**
     * The cached hash code, used to improve performance in situations where
     * we cache fonts, such as in the CSS routines.
     */
    private int hash = 0;

    /**
     * Constructs a font using the default face "System".
     * The underlying font used is determined by the implementation
     * based on the typical UI font for the current UI environment.
     *
     * @param size the font size to use
     */
    public Font(@NamedArg("size") double size) {
        this(null, size);
    }


    /**
     * Constructs a font using the specified full face name and size
     * @param name full name of the font.
     * @param size the font size to use
     */
    public Font(@NamedArg("name") String name, @NamedArg("size") double size) {
        this.name = name;
        this.size = size;

        if (name == null || "".equals(name)) this.name = DEFAULT_FULLNAME;
        if (size < 0f) this.size = getDefaultSystemFontSize();
        // if a search was done based on family + style information, then the
        // native font has already been located and specified. Likewise if the
        // Font was created based on an existing native font. If however a Font
        // was created directly in FX, then we need to find the native font
        // to use. This call will also set the family and style by invoking
        // the setNativeFont callback method.
        Toolkit.getToolkit().getFontLoader().loadFont(this);
    }

    /**
     * Private constructor for internal implementation
     *
     * @param f native font
     * @param family font family name
     * @param name font full name
     * @param style style string
     * @param size font size
     */
    private Font(Object f, String family, String name, String style, double size) {
        this.nativeFont = f;
        this.family = family;
        this.name = name;
        this.style = style;
        this.size = size;
    }

    /**
     * Loads a font resource from the specified URL. If the load is successful
     * such that the location is readable, and it represents a supported
     * font format then a <code>Font</code> object will be returned.
     * <p>
     * If a security manager is present, the application
     * must have both permission to read from the specified URL location
     * and the {@link javafx.util.FXPermission} "loadFont".
     * If the application does not have permission to read from the specified
     * URL location, then null is returned.
     * If the application does not have the "loadFont" permission then this method
     * will return the default system font with the specified font size.
     * <p>
     * Any failure such as a malformed URL being unable to locate or read
     * from the resource, or if it doesn't represent a font, will result in
     * a <code>null</code> return. It is the application's responsibility
     * to check this before use.
     * <p>
     * On a successful (non-null) return the font will be registered
     * with the FX graphics system for creation by available constructors
     * and factory methods, and the application should use it in this
     * manner rather than calling this method again, which would
     * repeat the overhead of downloading and installing the font.
     * <p>
     * The font <code>size</code> parameter is a convenience so that in
     * typical usage the application can directly use the returned (non-null)
     * font rather than needing to create one via a constructor.
     * {@literal Invalid sizes are those <=0 and will result in a default size.}
     * <p>
     * If the URL represents a local disk file, then no copying is performed
     * and the font file is required to persist for the lifetime of the
     * application. Updating the file in any manner will result
     * in unspecified and likely undesired behaviours.
     *
     * @param urlStr from which to load the font, specified as a String.
     * @param size of the returned font.
     * @return the Font, or null if the font cannot be created.
     */
    public static Font loadFont(String urlStr, double size) {
        Font[] fonts = loadFontInternal(urlStr, size, false);
        return (fonts == null) ? null : fonts[0];
    }

    /**
     * Loads font resources from the specified URL. If the load is successful
     * such that the location is readable, and it represents a supported
     * font format then an array of <code>Font</code> will be returned.
     * <p>
     * The use case for this method is for loading all fonts
     * from a TrueType Collection (TTC).
     * <p>
     * If a security manager is present, the application
     * must have both permission to read from the specified URL location
     * and the {@link javafx.util.FXPermission} "loadFont".
     * If the application does not have permission to read from the specified
     * URL location, then null is returned.
     * If the application does not have the "loadFont" permission then this method
     * will return an array of one element which is the default
     *  system font with the specified font size.
     * <p>
     * Any failure such as a malformed URL being unable to locate or read
     * from the resource, or if it doesn't represent a font, will result in
     * a <code>null</code> return. It is the application's responsibility
     * to check this before use.
     * <p>
     * On a successful (non-null) return the fonts will be registered
     * with the FX graphics system for creation by available constructors
     * and factory methods, and the application should use it in this
     * manner rather than calling this method again, which would
     * repeat the overhead of downloading and installing the fonts.
     * <p>
     * The font <code>size</code> parameter is a convenience so that in
     * typical usage the application can directly use the returned (non-null)
     * font rather than needing to create one via a constructor.
     * {@literal Invalid sizes are those <=0 and will result in a default size.}
     * <p>
     * If the URL represents a local disk file, then no copying is performed
     * and the font file is required to persist for the lifetime of the
     * application. Updating the file in any manner will result
     * in unspecified and likely undesired behaviours.
     *
     * @param urlStr from which to load the fonts, specified as a String.
     * @param size of the returned fonts.
     * @return array of Font, or null if the fonts cannot be created.
     * @since 9
     */
    public static Font[] loadFonts(String urlStr, double size) {
        return loadFontInternal(urlStr, size, true);
    }

    private static Font[] loadFontInternal(String urlStr, double size,
                                           boolean loadAll) {
        URL url = null;
        try {
            url = new URL(urlStr); // null string arg. is caught here.
        } catch (Exception e) {
            return null;
        }
        if (size <= 0) {
            size = getDefaultSystemFontSize();
        }
        // Now lets parse the URL and decide if its a file,
        // or a remote URL from which we need to read.
        if (url.getProtocol().equals("file")) {
            String path = url.getFile();
            // The URL path may have a leading "/", when obtained
            // via ClassLoader. This can cause problems on Windows.
            // Getting the path from a File fixes this.
            path = new java.io.File(path).getPath();
            try {
                @SuppressWarnings("removal")
                SecurityManager sm = System.getSecurityManager();
                if (sm != null) {
                    FilePermission filePermission =
                        new FilePermission(path, "read");
                    sm.checkPermission(filePermission);
                }
            } catch (Exception e) {
                return null;
            }
            return
            Toolkit.getToolkit().getFontLoader().loadFont(path, size, loadAll);
        }
        Font[] fonts = null;
        URLConnection connection = null;
        InputStream in = null;
        try {
            connection = url.openConnection();
            in = connection.getInputStream();
            fonts =
               Toolkit.getToolkit().getFontLoader().loadFont(in, size, loadAll);
        } catch (Exception e) {
            return null;
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e) {
            }
        }
        return fonts;
    }

    /**
     * Loads a font resource from the specified input stream.
     * If the load is successful such that the stream can be
     * fully read, and it represents a supported font format then a
     * <code>Font</code> object will be returned.
     * <p>
     * If a security manager is present, the application
     * must have the {@link javafx.util.FXPermission} "loadFont".
     * If the application does not have permission then this method
     * will return the default system font with the specified font size.
     * <p>
     * Any failure such as abbreviated input, or an unsupported font format
     * will result in a <code>null</code> return. It is the application's
     * responsibility to check this before use.
     * <p>
     * On a successful (non-null) return the font will be registered
     * with the FX graphics system for creation by available constructors
     * and factory methods, and the application should use it in this
     * manner rather than calling this method again, which would
     * repeat the overhead of re-reading and installing the font.
     * <p>
     * The font <code>size</code> parameter is a convenience so that in
     * typical usage the application can directly use the returned (non-null)
     * font rather than needing to create one via a constructor.
     * {@literal Invalid sizes are those <=0 and will result in a default size.}
     * <p>
     * This method does not close the input stream.
     * @param in stream from which to load the font.
     * @param size of the returned font.
     * @return the Font, or null if the font cannot be created.
     */
    public static Font loadFont(InputStream in, double size) {
        if (size <= 0) {
            size = getDefaultSystemFontSize();
        }
        Font[] fonts =
           Toolkit.getToolkit().getFontLoader().loadFont(in, size, false);
        return (fonts == null) ? null : fonts[0];
    }

    /**
     * Loads font resources from the specified input stream.
     * If the load is successful such that the stream can be
     * fully read, and it represents a supported font format then
     * an array of <code>Font</code> will be returned.
     * <p>
     * The use case for this method is for loading all fonts
     * from a TrueType Collection (TTC).
     * <p>
     * If a security manager is present, the application
     * must have the {@link javafx.util.FXPermission} "loadFont".
     * If the application does not have permission then this method
     * will return the default system font with the specified font size.
     * <p>
     * Any failure such as abbreviated input, or an unsupported font format
     * will result in a <code>null</code> return. It is the application's
     * responsibility to check this before use.
     * <p>
     * On a successful (non-null) return the fonts will be registered
     * with the FX graphics system for creation by available constructors
     * and factory methods, and the application should use it in this
     * manner rather than calling this method again, which would
     * repeat the overhead of re-reading and installing the fonts.
     * <p>
     * The font <code>size</code> parameter is a convenience so that in
     * typical usage the application can directly use the returned (non-null)
     * fonts rather than needing to re-create via a constructor.
     * {@literal Invalid sizes are those <=0 and will result in a default size.}
     * <p>
     * This method does not close the input stream.
     * @param in stream from which to load the fonts.
     * @param size of the returned fonts.
     * @return array of Font, or null if the fonts cannot be created.
     * @since 9
     */
    public static Font[] loadFonts(InputStream in, double size) {
        if (size <= 0) {
            size = getDefaultSystemFontSize();
        }
        Font[] fonts =
            Toolkit.getToolkit().getFontLoader().loadFont(in, size, true);
        return (fonts == null) ? null : fonts;
    }

    /**
     * Converts this {@code Font} object to a {@code String} representation.
     * The String representation is for informational use only and will change.
     * Do not use this string representation for any programmatic purpose.
     */
    @Override public String toString() {
        StringBuilder builder = new StringBuilder("Font[name=");
        builder = builder.append(name);
        builder = builder.append(", family=").append(family);
        builder = builder.append(", style=").append(style);
        builder = builder.append(", size=").append(size);
        builder = builder.append("]");
        return builder.toString();
    }

    /**
     * Indicates whether some other object is "equal to" this one.
     * @param obj the reference object with which to compare.
     * @return {@code true} if this object is equal to the {@code obj} argument; {@code false} otherwise.
     */
    @Override public boolean equals(Object obj) {
        if (obj == this) return true;
        if (obj instanceof Font) {
            Font other = (Font)obj;
            return (name == null ? other.name == null : name.equals(other.name))
                && size == other.size;
        }
        return false;
    }

    /**
     * Returns a hash code for this {@code Font} object.
     * @return a hash code for this {@code Font} object.
     */
    @Override public int hashCode() {
        if (hash == 0) {
            long bits = 17L;
            bits = 37L * bits + name.hashCode();
            bits = 37L * bits + Double.doubleToLongBits(size);
            hash = (int) (bits ^ (bits >> 32));
        }
        return hash;
    }

    private Object nativeFont;

    Object getNativeFont() { return nativeFont; }

    void setNativeFont(Object f, String nam, String fam, String styl) {
        nativeFont = f;
        name = nam;
        family = fam;
        style = styl;
    }

    static Font nativeFont(Object f, String name, String family,
                                       String style, double size) {
        Font retFont = new Font( f, family, name, style, size);
        return retFont;
    }
}
