/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/
package javax.microedition.lcdui;

import java.util.Enumeration;
import java.util.Hashtable;

import org.eclipse.ercp.swt.mobile.MobileShell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.internal.qt.graphics.FontUtils;
import org.eclipse.swt.widgets.*;

/**
 * Implementation of LCDUI <code>Font</code> class.
 */
public final class Font
{

    /**
     * System font face.
     *
     * @value for FACE_SYSTEM is 0.
     */
    public static final int FACE_SYSTEM = 0;

    /**
     * Monospace font face.
     *
     * @value for FACE_MONOSPACE is 32.
     */
    public static final int FACE_MONOSPACE = 32;

    /**
     * Proportional font face.
     *
     * @value for FACE_PROPORTIONAL is 64.
     */
    public static final int FACE_PROPORTIONAL = 64;

    /**
     * Plain font style.
     *
     * @value for STYLE_PLAIN is 0.
     */
    public static final int STYLE_PLAIN = 0;

    /**
     * Bold font style.
     *
     * @value for STYLE_BOLD is 1.
     */
    public static final int STYLE_BOLD = 1;

    /**
     * Italic font style.
     *
     * @value for STYLE_ITALIC is 2.
     */
    public static final int STYLE_ITALIC = 2;

    /**
     * Underlined font style.
     *
     * @value for STYLE_UNDERLINED is 4.
     */
    public static final int STYLE_UNDERLINED = 4;

    /**
     * Small font size.
     *
     * @value for SIZE_SMALL is 8.
     */
    public static final int SIZE_SMALL = 8;

    /**
     * Medium font size.
     *
     * @value for SIZE_MEDIUM is 0.
     */
    public static final int SIZE_MEDIUM = 0;

    /**
     * Large font size.
     *
     * @value for SIZE_LARGE is 16.
     */
    public static final int SIZE_LARGE = 16;

    /**
     * Font specifier for default input text drawing.
     *
     * @value for FONT_INPUT_TEXT is 1.
     */
    public static final int FONT_INPUT_TEXT = 1;

    /**
     * Font specifier for default Item and Screen drawing.
     *
     * @value for FONT_STATIC_TEXT is 0.
     */
    public static final int FONT_STATIC_TEXT = 0;

    private static final int STYLE_ALL = STYLE_PLAIN | STYLE_BOLD
                                         | STYLE_ITALIC | STYLE_UNDERLINED;

    private static final String FONTDATA_CONST = "|-1|-1|-1|-1|-1|-1|1|";

    /**
     * Lookup table which stores LCDUI fonts based on FontData keys.
     */
    private static Hashtable fontTable = new Hashtable();
    private static FontUtils fontUtils;
    private static int[] tempFontRect = new int[4];
    private static FontData eswtTempFontData;
    private static FontData eswtSystemFontData;
    private int eswtTempFontAscent;
    private int eswtTempFontHeight;
    private int eswtTempFontWidth;

    // Instance members.

    private org.eclipse.swt.graphics.Font eswtFont;
    private int face;
    private int style;
    private int size;

    /**
     * Disposes all fonts created.
     */
    static void disposeFonts()
    {
        for(Enumeration e = fontTable.elements(); e.hasMoreElements();)
        {
            Font font = (Font) e.nextElement();
            font.eswtFont.dispose();
        }
        fontTable.clear();
    }

    /**
     * LCDUI Font --> eSWT Font
     */
    static org.eclipse.swt.graphics.Font getESWTFont(Font font)
    {
        if(font != null)
        {
            return font.eswtFont;
        }
        return null;
    }

    /**
     * eSWT Font --> LCDUI Font
     */
    static Font getFont(final org.eclipse.swt.graphics.Font font)
    {
        if(font != null)
        {
            FontData fd = getFontData(font);
            return getFont(fd);
        }
        return null;
    }

    /**
     * Returns default font.
     *
     * @return default system font.
     */
    public static Font getDefaultFont()
    {
        return getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
    }

    /**
     * Request specified Font.
     *
     * @param specifier font specifier
     * @return the font
     */
    public static Font getFont(int specifier)
    {
        if(specifier == FONT_INPUT_TEXT || specifier == FONT_STATIC_TEXT)
        {
            return getFont(getFontData(specifier));
        }
        else
        {
            throw new IllegalArgumentException(
                MsgRepository.FONT_EXCEPTION_INVALID_SPECIFIER);
        }
    }

    /**
     * Request Font based on parameters.
     *
     * @param face font face
     * @param style font style
     * @param size font size
     * @return the font
     */
    public static Font getFont(int face, int style, int size)
    {
        if(face != FACE_SYSTEM && face != FACE_MONOSPACE
                && face != FACE_PROPORTIONAL)
        {
            throw new IllegalArgumentException(
                MsgRepository.FONT_EXCEPTION_INVALID_FACE);
        }
        if(style < STYLE_PLAIN || style > STYLE_ALL)
        {
            throw new IllegalArgumentException(
                MsgRepository.FONT_EXCEPTION_INVALID_STYLE);
        }
        if(size != SIZE_SMALL && size != SIZE_MEDIUM && size != SIZE_LARGE)
        {
            throw new IllegalArgumentException(
                MsgRepository.FONT_EXCEPTION_INVALID_SIZE);
        }
        FontData fd = getFontData(face, style, size);
        Font font = getFont(fd);
        // We must handle underlining separately because it is not part of
        // eSWT's style.
        if((style & Font.STYLE_UNDERLINED) == Font.STYLE_UNDERLINED)
        {
            font.style |= Font.STYLE_UNDERLINED;
        }
        return font;
    }

    /**
     * Request freesized Font.
     *
     * @param face font face
     * @param style font style
     * @param height font height
     * @return the font
     */
    static Font getFreeSizedFont(int face, int style, int height)
    {
        if(face != FACE_SYSTEM && face != FACE_MONOSPACE
                && face != FACE_PROPORTIONAL)
        {
            throw new IllegalArgumentException(
                MsgRepository.FONT_EXCEPTION_INVALID_FACE);
        }
        if(style < STYLE_PLAIN || style > STYLE_ALL)
        {
            throw new IllegalArgumentException(
                MsgRepository.FONT_EXCEPTION_INVALID_STYLE);
        }
        if(height < 0)
        {
            throw new IllegalArgumentException(
                MsgRepository.FONT_EXCEPTION_INVALID_SIZE);
        }
        FontData fd = getFontData(face, style, height);
        Font font = getFont(fd);
        // We must handle underlining separately because it is not part of
        // eSWT's style.
        if((style & Font.STYLE_UNDERLINED) == Font.STYLE_UNDERLINED)
        {
            font.style |= Font.STYLE_UNDERLINED;
        }
        return font;
    }

    /**
     * Lookup or create a font based on given FontData.
     *
     * @param fontdata font's data
     * @return a font
     */
    private static Font getFont(FontData fontdata)
    {
        Font ret = null;
        if(fontdata != null)
        {
            if(fontTable.containsKey(fontdata))
            {
                ret = (Font) fontTable.get(fontdata);
            }
            else
            {
                ret = new Font(fontdata);
            }
        }
        return ret;
    }

    /**
     * Constructor (private).<br>
     *
     * @param reqFD the requested eSWT FontData
     */
    private Font(final FontData reqFD)
    {
        ESWTUIThreadRunner.syncExec(new Runnable()
        {
            public void run()
            {
                eswtFont = new org.eclipse.swt.graphics.Font(ESWTUIThreadRunner
                        .getInstance().getDisplay(), reqFD);
            }
        });
        // get FontData info from resulted Font, because resulting FontData
        // might be different than the requested one
        FontData resFD = getFontData(eswtFont);
        this.face = mapFontNameToFace(resFD.getName());
        this.style = mapESWTStyleToStyle(resFD.getStyle());
        this.size = mapHeightToSize(resFD.getHeight());
        fontTable.put(resFD, this);
    }

    /**
     * Return font's style.
     *
     * @return style
     */
    public int getStyle()
    {
        return style;
    }

    /**
     * Return font's size.
     *
     * @return size
     */
    public int getSize()
    {
        return size;
    }

    /**
     * Return font's face.
     *
     * @return face
     */
    public int getFace()
    {
        return face;
    }

    /**
     * Checks if the font is plain styled.
     *
     * @return true if the font is plain, false otherwise
     */
    public boolean isPlain()
    {
        return (style == Font.STYLE_PLAIN);
    }

    /**
     * Checks if the font is bold styled.
     *
     * @return true if the font is bold, false otherwise
     */
    public boolean isBold()
    {
        return ((style & Font.STYLE_BOLD) == Font.STYLE_BOLD);
    }

    /**
     * Checks if the font is italic styled.
     *
     * @return true if the font is italic, false otherwise
     */
    public boolean isItalic()
    {
        return ((style & Font.STYLE_ITALIC) == Font.STYLE_ITALIC);
    }

    /**
     * Checks if the font is underlined styled.
     *
     * @return true if the font is underlined, false otherwise.
     */
    public boolean isUnderlined()
    {
        return ((style & Font.STYLE_UNDERLINED) == Font.STYLE_UNDERLINED);
    }

    /**
     * Return font's height.
     *
     * @return height
     */
    public int getHeight()
    {
        if(eswtTempFontHeight == 0)
        {
            ESWTUIThreadRunner.safeSyncExec(new Runnable()
            {
                public void run()
                {
                    FontUtils fu = eswtGetFontUtils(eswtFont);
                    eswtTempFontHeight = fu.getAscent() + fu.getDescent();
                }
            });
        }
        return eswtTempFontHeight;
    }

    /**
     * The position of the baseline from the top of the font in pixels.
     *
     * @return the baseline position
     */
    public int getBaselinePosition()
    {
        if(eswtTempFontAscent == 0)
        {
            ESWTUIThreadRunner.safeSyncExec(new Runnable()
            {
                public void run()
                {
                    eswtTempFontAscent = eswtGetFontUtils(eswtFont).getAscent();
                }
            });
        }
        return eswtTempFontAscent;
    }

    /**
     * Returns the width of the given character in pixels.
     *
     * @param c the character
     * @return the width
     */
    public int charWidth(char c)
    {
        return stringWidth(String.valueOf(c));
    }

    /**
     * Returns the width of the given character array's region in pixels.
     *
     * @param c characters
     * @param offset starting position
     * @param length the length
     * @return the width
     */
    public int charsWidth(char[] c, int offset, int length)
    {
        try 
        {
           final String string = new String(c, offset, length);
           return stringWidth(string);
        }catch (StringIndexOutOfBoundsException ex) 
        {
            throw new ArrayIndexOutOfBoundsException();
        }
    }

    /**
     * Returns string width in pixels.
     *
     * @param string the string
     * @return the width
     */
    public int stringWidth(String string)
    {
        if(string == null)
        {
            throw new NullPointerException(
                MsgRepository.FONT_EXCEPTION_NULL_STRING);
        }
        final String finalString = string;
        eswtTempFontWidth = 0;
        ESWTUIThreadRunner.safeSyncExec(new Runnable()
        {
            public void run()
            {
                eswtGetFontUtils(eswtFont).getBoundingRect(tempFontRect, finalString);
                eswtTempFontWidth = tempFontRect[2];
            }
        });
        return eswtTempFontWidth;
    }

    /**
     * Get the static FontUtils instance setup with given font.
     */
    private static FontUtils eswtGetFontUtils(org.eclipse.swt.graphics.Font font)
    {
        if(fontUtils == null)
        {
            fontUtils = new FontUtils(font.handle);
        }
        else
        {
            fontUtils.setFont(font.handle);
        }
        return fontUtils;
    }

    /**
     * Returns sub-string width in pixels.
     *
     * @param string the string
     * @param offset starting position
     * @param length the length
     * @return the width of the specified region
     */
    public int substringWidth(String string, int offset, int length)
    {
        return stringWidth(string.substring(offset, offset + length));
    }

    /**
     * Get FontData of a eSWT control.
     *
     * @param control eSWT control
     * @return FontData
     */
    private static FontData getFontData(final int specifier)
    {
        ESWTUIThreadRunner.syncExec(new Runnable()
        {
            public void run()
            {
                Control control = null;
                MobileShell shell = new MobileShell(ESWTUIThreadRunner
                                                    .getInstance().getDisplay(), SWT.NONE);
                if(specifier == FONT_INPUT_TEXT)
                {
                    control = new Text(shell, SWT.NONE);
                }
                else
                {
                    control = new Label(shell, SWT.NONE);
                }
                eswtTempFontData = control.getFont().getFontData()[0];
                control.dispose();
                shell.dispose();
            }
        });
        return eswtTempFontData;
    }

    /**
     * Get FontData of a eSWT font.
     *
     * @param font eSWT font
     * @return FontData
     */
    private static FontData getFontData(
        final org.eclipse.swt.graphics.Font font)
    {
        ESWTUIThreadRunner.syncExec(new Runnable()
        {
            public void run()
            {
                eswtTempFontData = font.getFontData()[0];
            }
        });
        return eswtTempFontData;
    }

    /**
     * Construct FontData with the given specifiers.
     *
     * @return FontData
     */
    private static FontData getFontData(final int face, final int style,
                                        final int size)
    {
        ESWTUIThreadRunner.syncExec(new Runnable()
        {
            public void run()
            {
                int underlined = 0;
                if((style & Font.STYLE_UNDERLINED) == Font.STYLE_UNDERLINED)
                {
                    underlined = 1;
                }
                /* FontData constructor format:
                 *  version|name|height|style|underline|
                 *  overline|strike|stretch|pitch|qt-style|weight|strategy|
                 */
                StringBuffer sb = new StringBuffer();
                sb.append("1|"); // version
                sb.append(mapFaceToFontName(face)); // family name
                sb.append("|");
                sb.append(mapSizeToHeight(size)); // height
                sb.append("|");
                sb.append(mapStyleToESWTStyle(style)); // style
                sb.append("|QT|1|");//Qt version 2 string for extra formats
                sb.append((new Integer(underlined).toString())); // underline
                sb.append(FONTDATA_CONST);
                eswtTempFontData = new FontData(sb.toString());
            }
        });
        return eswtTempFontData;
    }

    /**
     * Returns System default font's FontData instance.
     *
     * @return system FontData
     */
    private static FontData getSystemFontData()
    {
        if(eswtSystemFontData == null)
        {
            ESWTUIThreadRunner.syncExec(new Runnable()
            {
                public void run()
                {
                    eswtSystemFontData = ESWTUIThreadRunner.getInstance()
                                         .getDisplay().getSystemFont().getFontData()[0];
                }
            });
        }
        return eswtSystemFontData;
    }

    /**
     * Get eSWT font name from LCDUI face parameter.
     *
     * @param face font's face type
     * @return font name
     */
    private static String mapFaceToFontName(int face)
    {
        // TODO: how to map face values to names
        switch(face)
        {
        case Font.FACE_MONOSPACE:
        case Font.FACE_PROPORTIONAL:
        case Font.FACE_SYSTEM:
        default:
            return getSystemFontData().getName();
        }
    }

    /**
     * Get face of a given eSWT font name.
     *
     * @param eswtFontName font's name
     * @return font face
     */
    private static int mapFontNameToFace(String eswtFontName)
    {
        // TODO: how to map names to face values
        if(eswtFontName.equals(getSystemFontData().getName()))
        {
            return Font.FACE_SYSTEM;
        }
        else
        {
            // Font.FACE_MONOSPACE:
            // Font.FACE_PROPORTIONAL:
            return 0;
        }
    }

    /**
     * Get eSWT font height from LCDUI size parameter.<br>
     * 0 maps to system's default font size, others map directly.
     *
     * @param size font's size
     * @return font height
     */
    private static int mapSizeToHeight(int size)
    {
        //retreive the system default height
        int defHeight = getSystemFontData().getHeight();

        if(size == Font.SIZE_SMALL)
        {
            //calculate the small size height as a ratio of system default medium size and round off the value to an integer
            return (int)Math.floor((defHeight * (3f/4)) + 0.5f);
        }
        else if(size == Font.SIZE_LARGE)
        {
            //calculate the large height as a ratio of system default medium size and round off the value to an integer
            return (int)Math.floor((defHeight * (4.5f/4)) + 0.5f);
        }
        else
        {
            //return the system default height for medium size which is generally 12 but 7 in symbian for qt
            return defHeight;
        }
    }

    /**
     * Get LCDUI size from eSWT font height
     *
     * @param height font's height
     * @return font size
     */
    private static int mapHeightToSize(int height)
    {
        int defHeight = getSystemFontData().getHeight();
        if(height < defHeight)
        {
            return Font.SIZE_SMALL;
        }
        else if(height > defHeight)
        {
            return Font.SIZE_LARGE;
        }
        else
        {
            return Font.SIZE_MEDIUM;
        }
    }

    /**
     * Return eSWT style.
     *
     * @param style Font's LCDUI style.
     * @return Font's eSWT style.
     */
    private static int mapStyleToESWTStyle(int style)
    {
        int retStyle = SWT.NORMAL;
        if((style & Font.STYLE_BOLD) == Font.STYLE_BOLD)
        {
            retStyle |= SWT.BOLD;
        }
        if((style & Font.STYLE_ITALIC) == Font.STYLE_ITALIC)
        {
            retStyle |= SWT.ITALIC;
        }
        return retStyle;
    }

    /**
     * Return LCDUI style.
     *
     * @param eswtStyle Font's eSWT style.
     * @return Font's LCDUI style.
     */
    private static int mapESWTStyleToStyle(int eswtStyle)
    {
        int retStyle = Font.STYLE_PLAIN;
        if((eswtStyle & SWT.BOLD) != 0)
        {
            retStyle |= Font.STYLE_BOLD;
        }
        if((eswtStyle & SWT.ITALIC) != 0)
        {
            retStyle |= Font.STYLE_ITALIC;
        }
        return retStyle;
    }

}
