package org.gnu.grub.fonttool;

import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;

/**
 * Load a PFF2 font completely into memory as an instance of Font.
 */
public class PFF2Loader {
    private static final int debug
            = Integer.parseInt(System.getProperty("fonttool.debug", "0"));

    private RandomAccessFile f;
    private Font font;

    private PFF2Loader(String filename) throws FileNotFoundException {
        f = new RandomAccessFile(filename, "r");
        font = new Font();
    }

    public static boolean isPFF2File(String filename) {
        RandomAccessFile f = null;
        boolean isFont = false;
        try {
            f = new RandomAccessFile(filename, "r");
            byte[] sectionNameBytes = new byte[4];
            f.readFully(sectionNameBytes);
            String sectionName = new String(sectionNameBytes, "US-ASCII");
            if (sectionName.equals(PFF2Sections.FILE)) {
                final String expectedType = "PFF2";
                int len = f.readInt();
                if (len == expectedType.length()) {
                    byte[] typeBytes = new byte[len];
                    f.readFully(typeBytes);
                    String type = new String(typeBytes, "US-ASCII");
                    if (type.equals(expectedType)) {
                        isFont = true;
                    }
                }
            }
            f.close();
        } catch (IOException e) {
            if (f != null) {
                try {
                    f.close();
                } catch (IOException e1) {
                    // Ignore.
                }
            }
        }

        return isFont;
    }

    public static Font loadFontFile(String filename) throws IOException {
        PFF2Loader loader = new PFF2Loader(filename);
        loader.loadFont();
        return loader.getFont();
    }

    private void loadFont() throws IOException {
        String s;
        s = openSection();
        if (!PFF2Sections.FILE.equals(s))
            throw new FileFormatException(
                    "First PFF2 section must be FILE but is `" + s + "'.");

        s = readCompleteSectionAsString();
        if (!"PFF2".equals(s))
            throw new FileFormatException("Unsupported file type `" + s + "'.");

        font.setFamily("Unnamed");
        font.setPointSize(Font.UNKNOWN_POINT_SIZE);
        try {
            if (debug >= 1) System.out.println("Loading font information...");
            boolean doneLoadingInfo = false;
            while (!doneLoadingInfo) {
                s = openSection();
                int sectionLength = f.readInt();
                if (debug >= 2)
                    System.out.println("Section: " + s + " length: " + sectionLength);
                if (s.equals(PFF2Sections.MAX_CHAR_WIDTH)) {
                    int maxCharWidth = readSectionContentsAsUShort(sectionLength);
                    font.setMaxCharWidth(maxCharWidth);
                } else if (s.equals(PFF2Sections.MAX_CHAR_HEIGHT)) {
                    int maxCharHeight = readSectionContentsAsUShort(sectionLength);
                    font.setMaxCharHeight(maxCharHeight);
                } else if (s.equals(PFF2Sections.FONT_POINT_SIZE)) {
                    int pointSize = readSectionContentsAsUShort(sectionLength);
                    font.setPointSize(pointSize);
                } else if (s.equals(PFF2Sections.FONT_ASCENT)) {
                    int ascent = readSectionContentsAsUShort(sectionLength);
                    font.setAscent(ascent);
                } else if (s.equals(PFF2Sections.FONT_DESCENT)) {
                    int descent = readSectionContentsAsUShort(sectionLength);
                    font.setDescent(descent);
                } else if (s.equals(PFF2Sections.FONT_NAME)) {
                    String fontName = readSectionContentsAsString(sectionLength);
                    if (debug >= 1)
                        System.out.println("Loaded font: " + fontName);
                } else if (s.equals(PFF2Sections.FONT_FAMILY)) {
                    font.setFamily(readSectionContentsAsString(sectionLength));
                } else if (s.equals(PFF2Sections.FONT_WEIGHT)) {
                    String weightString = readSectionContentsAsString(sectionLength);
                    font.setBold(weightString.equalsIgnoreCase("bold"));
                } else if (s.equals(PFF2Sections.FONT_SLANT)) {
                    String slantString = readSectionContentsAsString(sectionLength);
                    font.setItalic(slantString.equalsIgnoreCase("italic"));
                } else if (s.equals(PFF2Sections.CHAR_INDEX)) {
                    loadCharsFromIndex(sectionLength);
                } else if (s.equals(PFF2Sections.REMAINDER_IS_DATA)) {
                    doneLoadingInfo = true;
                } else {
                    // If the section is not handled, then we just skip it.
                    if (f.skipBytes(sectionLength) != sectionLength) {
                        System.err.println("Warning: Could not skip "
                                           + sectionLength
                                           + " bytes for section `" + s + "'.");
                    }
                }
            }
        } catch (EOFException eofException) {
            // End of file; done reading font.
        }
        f.close();
    }

    private void loadCharsFromIndex(int sectionLength) throws IOException {
        long indexSectionEnd = f.getFilePointer() + sectionLength;
        try {
            while (f.getFilePointer() < indexSectionEnd) {
                int codePoint = f.readInt();
                int storageFlags = f.readUnsignedByte();
                int offset = f.readInt();

                if ((storageFlags & 0x07) == 0) {
                    Glyph g = loadUncompressedGlyph(codePoint, offset);
                    font.putGlyph(codePoint, g);
                }
            }
        } catch (EOFException eofException) {
            // End of file; simple return.
        }
    }

    private Glyph loadUncompressedGlyph(int codePoint, int fileOffset) throws IOException {
        if (debug >= 3)
            System.out.printf("Loading uncompressed glyph U+%04X at %d%n", codePoint, fileOffset);

        long savedPos = f.getFilePointer();

        f.seek(fileOffset);
        int width = f.readUnsignedShort();
        int height = f.readUnsignedShort();
        int offsetX = f.readShort();
        int offsetY = f.readShort();
        int deviceWidth = f.readShort();
        if (debug >= 3) System.out.println("  Size=" + width + "x" + height
                                           + " offset=(" + offsetX + ", " + offsetY
                                           + ") dWidth=" + deviceWidth);
        byte[] bitmap = new byte[(width * height + 7) / 8];
        f.readFully(bitmap);

        f.seek(savedPos);
        return new Glyph(codePoint, width, height,
                         offsetX, offsetY, deviceWidth, bitmap);
    }

    private int readSectionContentsAsUShort(int sectionLength) throws IOException {
        if (sectionLength != 2)
            throw new FileFormatException(
                    "For uint16 section, expected 2 bytes but length is "
                    + sectionLength + ".");
        return f.readUnsignedShort();
    }

    private String openSection() throws IOException {
        byte[] b = new byte[4];
        f.readFully(b);
        return new String(b, Charset.forName("US-ASCII"));
    }

    private String readCompleteSectionAsString() throws IOException {
        int n = f.readInt();
        return readSectionContentsAsString(n);
    }

    private String readSectionContentsAsString(int n) throws IOException {
        StringBuilder buf = new StringBuilder();
        while (n-- != 0) {
            char c = (char) (f.readByte() & 0xFF);
            buf.append(c);
        }
        return buf.toString();
    }

    public Font getFont() {
        return font;
    }
}
