/*-
 * Copyright (C) 2008 Erik Larsson
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.catacombae.hfs.types.hfs;

import java.io.PrintStream;
import org.catacombae.csjc.DynamicStruct;
import org.catacombae.csjc.PrintableStruct;
import org.catacombae.csjc.StructElements;
import org.catacombae.csjc.structelements.ASCIIStringField;
import org.catacombae.csjc.structelements.Dictionary;
import org.catacombae.util.Util;

/**
 * This class was generated by CStructToJavaClass.
 *
 * @author <a href="https://catacombae.org" target="_top">Erik Larsson</a>
 */
public class CatKeyRec implements DynamicStruct, PrintableStruct,
        StructElements, Comparable<CatKeyRec>
{
    /*
     * struct CatKeyRec
     * size: 38 bytes
     * description:
     *
     * BP  Size  Type      Identifier  Description
     * -------------------------------------------------------------
     * 0   1     SInt8     ckrKeyLen   key length (SignedByte)
     * 1   1     SInt8     ckrResrv1   reserved (SignedByte)
     * 2   4     SInt32    ckrParID    parent directory ID (LongInt)
     * 6   1     UInt8     ckrCNameLen length of catalog node name (part of Str31)
     * 7   1*31  Char[31]  ckrCName    catalog node name (part of Str31)
     */

    private static final int MAX_STRUCTSIZE = 38;

    private final byte[] ckrKeyLen = new byte[1];
    private final byte[] ckrResrv1 = new byte[1];
    private final byte[] ckrParID = new byte[4];
    private final byte[] ckrCNameLen; // = new byte[1];
    private final byte[] ckrCName;// = new byte[1*31];
    private final byte[] ckrPad;

    public CatKeyRec(byte[] data, int offset) {
        System.arraycopy(data, offset + 0, ckrKeyLen, 0, 1);
        System.arraycopy(data, offset + 1, ckrResrv1, 0, 1);
        System.arraycopy(data, offset + 2, ckrParID, 0, 4);

        int structSize = 1 + Util.unsign(getCkrKeyLen());
        if(structSize >= 6 && structSize <= MAX_STRUCTSIZE) {
            if(structSize >= 7) {
                ckrCNameLen = new byte[1];
                System.arraycopy(data, offset + 6, ckrCNameLen, 0, 1);

                final int trailingBytes = structSize - 7;

                final int cNameLen = Util.unsign(getCkrCNameLen());
                if(cNameLen > trailingBytes)
                    throw new RuntimeException("Malformed CatKeyRec: ckrCNameLen=" +
                            cNameLen + " > trailingBytes=" + trailingBytes);

                ckrCName = new byte[cNameLen];
                System.arraycopy(data, offset+7, ckrCName, 0, ckrCName.length);

                final int remainingBytes = trailingBytes-cNameLen;
                ckrPad = new byte[remainingBytes];
                System.arraycopy(data, offset+7+cNameLen, ckrPad, 0, remainingBytes);
            }
            else {
                ckrCNameLen = new byte[0];
                ckrCName = new byte[0];
                ckrPad = new byte[0];
            }
        }
        else
            throw new RuntimeException("Incorrect value for ckrKeyLen: " +
                    Util.unsign(getCkrKeyLen()));
    }

    /**
     * Builds a CatKeyRec from a specified nodeID and a string.
     * @param parID
     * @param cName
     */
    public CatKeyRec(int parID, byte[] cName) {
        if(cName.length < 0 || cName.length > 32)
            throw new IllegalArgumentException("String too large for a catalog" +
                    " file key! (size: " + cName.length + ")");
        byte[] parIDBytes = Util.toByteArrayBE(parID);
        if(parIDBytes.length != 4)
            throw new RuntimeException("Internal error! int array not 4 bytes but " +
                    parIDBytes.length);
        this.ckrCName = new byte[cName.length];
        this.ckrPad = new byte[0];

        this.ckrKeyLen[0] = (byte)(1 + 4 + cName.length); // length of the rest of the key
        this.ckrResrv1[0] = 0; // Reserved
        System.arraycopy(parIDBytes, 0, this.ckrParID, 0, this.ckrParID.length);
        if(cName.length > 0) {
            this.ckrCNameLen = new byte[1];
            this.ckrCNameLen[0] = (byte)cName.length;
            System.arraycopy(cName, 0, this.ckrCName, 0, this.ckrCName.length);
        }
        else {
            this.ckrCNameLen = new byte[0];
        }
    }

    //public int length() { return occupiedSize(); }

    public byte getCkrKeyLen() { return Util.readByteBE(ckrKeyLen); }
    public byte getCkrResrv1() { return Util.readByteBE(ckrResrv1); }
    public int getCkrParID() { return Util.readIntBE(ckrParID); }
    public byte getCkrCNameLen() { return Util.readByteBE(ckrCNameLen); }
    public byte[] getCkrCName() { return Util.createCopy(ckrCName); }
    public byte[] getCkrPad() { return Util.createCopy(ckrPad); }

    public void printFields(PrintStream ps, String prefix) {
        ps.println(prefix + " ckrKeyLen: " + getCkrKeyLen());
        ps.println(prefix + " ckrResrv1: " + getCkrResrv1());
        ps.println(prefix + " ckrParID: " + getCkrParID());
        ps.println(prefix + " ckrCNameLen: " + getCkrCNameLen());
        ps.println(prefix + " ckrCName: \"" + Util.toASCIIString(getCkrCName()) + "\" (0x" + Util.byteArrayToHexString(getCkrCName()) + ")");
        ps.println(prefix + " ckrPad: byte[" + ckrPad.length + "]");
    }

    public void print(PrintStream ps, String prefix) {
        ps.println(prefix + "CatKeyRec:");
        printFields(ps, prefix);
    }

    public byte[] getBytes() {
        byte[] result = new byte[occupiedSize()];
        int offset = 0;

        System.arraycopy(ckrKeyLen, 0, result, offset, ckrKeyLen.length); offset += ckrKeyLen.length;
        System.arraycopy(ckrResrv1, 0, result, offset, ckrResrv1.length); offset += ckrResrv1.length;
        System.arraycopy(ckrParID, 0, result, offset, ckrParID.length); offset += ckrParID.length;
        System.arraycopy(ckrCNameLen, 0, result, offset, ckrCNameLen.length); offset += ckrCNameLen.length;
        System.arraycopy(ckrCName, 0, result, offset, ckrCName.length); offset += ckrCName.length;
        System.arraycopy(ckrPad, 0, result, offset, ckrPad.length); offset += ckrPad.length;
        return result;
    }

    public int maxSize() {
        return MAX_STRUCTSIZE;
    }

    public int occupiedSize() {
        return 1+Util.unsign(getCkrKeyLen());
    }

    /* @Override */
    public Dictionary getStructElements() {
        DictionaryBuilder db = new DictionaryBuilder(CatKeyRec.class.getSimpleName());

        db.addUIntBE("ckrKeyLen", ckrKeyLen, "Key length", "bytes");
        db.addUIntBE("ckrResrv1", ckrResrv1, "Reserved", HEXADECIMAL);
        db.addUIntBE("ckrParID", ckrParID, "Parent ID");
        db.addUIntBE("ckrCNameLen", ckrCNameLen, "Length of record name", "bytes");
        db.add("ckrCName", new ASCIIStringField(ckrCName), "Record name");

        return db.getResult();
    }

    /**
     * Case-folding key-compare table for original HFS.
     *
     * When 'identity mappings' is used below it means a mapping of the sort:
     *   0xXX -> 0xXX00
     * ...so it's not an 'identity mapping' in the true meaning of the word.
     *
     * - The range 0x00-0x5F consists only of identity mappings.
     * - The character 0x60 ('@') sorts after 'A'.
     * - The range 0x61-0x7A consists of lowercase characters mapped to their
     *   uppercase equivalents (the 0x41-0x5A range).
     * - The range 0x7B-0x7F consists only of identity mappings.
     * - The range 0x80-0x9F sorts certain accented characters next to their
     *   non-accented counterparts. For example 'Ä' is sorted after 'A' but
     *   before 'B'. 'Ç' is sorted after 'C' but before 'D', etc.
     * - The range 0xA0-0xA6 consists only of identity mappings.
     * - The range 0xA7-0xA8 sorts the characters 'Æ' and 'Ø' after 'A' and 'O'
     *   respectively.
     * - The range 0xA0-0xA6 consists only of identity mappings.
     * - The character 0xA7 ('ß') sorts after 0x53 ('S').
     * - The range 0xA8-0xAD consists only of identity mappings.
     * - The range 0xAE-0xAF sorts the characters 'Æ' and 'Ø' after 'A' and 'O'
     *   respectively.
     * - The range 0xB0-0xBA consists only of identity mappings.
     * - The range 0xBB-0xBC sorts the characters 'ª' and 'º' after 'A' and 'O'
     *   respectively.
     * - The character 0xBD is an identity mapping.
     * - The range 0xBE-0xBF sorts the characters 'æ' and 'ø' after 'A' and 'O'
     *   respectively.
     * - The range 0xC0-0xC6 consists only of identity mappings.
     * - The range 0xC7-0xC8 sorts the characters '«' and '»' after '"'.
     * - The character 0xC9 is an identity mapping.
     * - The character 0xCA (non-breaking space) is considered equivalent to
     *   regular space (0x20) in a comparison.
     * - The range 0xCB-0xCF sorts the characters 'À', 'Ã', 'Õ', 'Œ', and 'œ'
     *   after 'A', 'A', 'O', 'O' and 'O' respectively.
     * - The range 0xD0-0xD1 consists only of identity mappings.
     * - The range 0xD2-0xD5 sorts the characters '“', '”', '‘' and '’' after
     *   '"', '"', ''' and ''' respectively.
     * - The range 0xD6-0xD7 consists only of identity mappings.
     * - The character 0xD8 ('ÿ') sorts after 'Y'.
     * - The range 0xD9-0xFF consists only of identity mappings.
     */
    private final static char HFS_RELSTRING_KEY_COMPARE_TABLE[] = {
        0x0000, 0x0100, 0x0200, 0x0300, 0x0400, 0x0500, 0x0600, 0x0700,
        0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00, 0x0F00,
        0x1000, 0x1100, 0x1200, 0x1300, 0x1400, 0x1500, 0x1600, 0x1700,
        0x1800, 0x1900, 0x1A00, 0x1B00, 0x1C00, 0x1D00, 0x1E00, 0x1F00,
        0x2000, 0x2100, 0x2200, 0x2300, 0x2400, 0x2500, 0x2600, 0x2700,
        0x2800, 0x2900, 0x2A00, 0x2B00, 0x2C00, 0x2D00, 0x2E00, 0x2F00,
        0x3000, 0x3100, 0x3200, 0x3300, 0x3400, 0x3500, 0x3600, 0x3700,
        0x3800, 0x3900, 0x3A00, 0x3B00, 0x3C00, 0x3D00, 0x3E00, 0x3F00,
        0x4000, 0x4100, 0x4200, 0x4300, 0x4400, 0x4500, 0x4600, 0x4700,
        0x4800, 0x4900, 0x4A00, 0x4B00, 0x4C00, 0x4D00, 0x4E00, 0x4F00,
        0x5000, 0x5100, 0x5200, 0x5300, 0x5400, 0x5500, 0x5600, 0x5700,
        0x5800, 0x5900, 0x5A00, 0x5B00, 0x5C00, 0x5D00, 0x5E00, 0x5F00,
        0x4180, 0x4100, 0x4200, 0x4300, 0x4400, 0x4500, 0x4600, 0x4700,
        0x4800, 0x4900, 0x4A00, 0x4B00, 0x4C00, 0x4D00, 0x4E00, 0x4F00,
        0x5000, 0x5100, 0x5200, 0x5300, 0x5400, 0x5500, 0x5600, 0x5700,
        0x5800, 0x5900, 0x5A00, 0x7B00, 0x7C00, 0x7D00, 0x7E00, 0x7F00,
        0x4108, 0x410C, 0x4310, 0x4502, 0x4E0A, 0x4F08, 0x5508, 0x4182,
        0x4104, 0x4186, 0x4108, 0x410A, 0x410C, 0x4310, 0x4502, 0x4584,
        0x4586, 0x4588, 0x4982, 0x4984, 0x4986, 0x4988, 0x4E0A, 0x4F82,
        0x4F84, 0x4F86, 0x4F08, 0x4F0A, 0x5582, 0x5584, 0x5586, 0x5508,
        0xA000, 0xA100, 0xA200, 0xA300, 0xA400, 0xA500, 0xA600, 0x5382,
        0xA800, 0xA900, 0xAA00, 0xAB00, 0xAC00, 0xAD00, 0x4114, 0x4F0E,
        0xB000, 0xB100, 0xB200, 0xB300, 0xB400, 0xB500, 0xB600, 0xB700,
        0xB800, 0xB900, 0xBA00, 0x4192, 0x4F92, 0xBD00, 0x4114, 0x4F0E,
        0xC000, 0xC100, 0xC200, 0xC300, 0xC400, 0xC500, 0xC600, 0x2206,
        0x2208, 0xC900, 0x2000, 0x4104, 0x410A, 0x4F0A, 0x4F14, 0x4F14,
        0xD000, 0xD100, 0x2202, 0x2204, 0x2702, 0x2704, 0xD600, 0xD700,
        0x5988, 0xD900, 0xDA00, 0xDB00, 0xDC00, 0xDD00, 0xDE00, 0xDF00,
        0xE000, 0xE100, 0xE200, 0xE300, 0xE400, 0xE500, 0xE600, 0xE700,
        0xE800, 0xE900, 0xEA00, 0xEB00, 0xEC00, 0xED00, 0xEE00, 0xEF00,
        0xF000, 0xF100, 0xF200, 0xF300, 0xF400, 0xF500, 0xF600, 0xF700,
        0xF800, 0xF900, 0xFA00, 0xFB00, 0xFC00, 0xFD00, 0xFE00, 0xFF00,
    };

    private static int relstringCompare(byte[] a, int aoff, int alen,
            byte[] b, int boff, int blen)
    {
        final int compareLen = Math.min(alen, blen);

        for(int i = 0; i < compareLen; ++i) {
            final byte aCur = a[aoff + i];
            final byte bCur = b[boff + i];

            if(aCur != bCur) {
                final int aRel = (int)
                        HFS_RELSTRING_KEY_COMPARE_TABLE[aCur & 0xFF] & 0xFFFF;
                final int bRel = (int)
                        HFS_RELSTRING_KEY_COMPARE_TABLE[bCur & 0xFF] & 0xFFFF;

                if(aRel != bRel) {
                    return aRel - bRel;
                }
            }
        }

        // The shortest array gets higher priority
        return alen - blen;
    }

    private static int relstringCompare(byte[] a, byte[] b) {
        return relstringCompare(a, 0, a.length, b, 0, b.length);
    }

    public int compareTo(CatKeyRec o) {
        final long ourParID = Util.unsign(getCkrParID());
        final long theirParID = Util.unsign(o.getCkrParID());

        if(ourParID == theirParID) {
            int res2 = relstringCompare(getCkrCName(), o.getCkrCName());
            if(res2 == 0)
                return 0;
            else if(res2 > 0)
                return 1;
            else
                return -1;
        }
        else if(ourParID > theirParID)
            return 1;
        else
            return -1;
    }
}
