/*-
 * Copyright (C) 2012 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.hfsplus;

import java.io.PrintStream;
import java.lang.reflect.Field;
import org.catacombae.csjc.DynamicStruct;
import org.catacombae.csjc.PrintableStruct;
import org.catacombae.csjc.StructElements;
import org.catacombae.csjc.structelements.Dictionary;
import org.catacombae.csjc.structelements.IntegerFieldRepresentation;
import org.catacombae.util.Util;

/**
 * This class was generated by CStructToJavaClass.
 *
 * @author <a href="https://catacombae.org" target="_top">Erik Larsson</a>
 */
public class HFSPlusAttributesKey extends BTKey
        implements PrintableStruct, StructElements, DynamicStruct
{
    /*
     * struct HFSPlusAttributesKey
     * size: <=268 bytes
     * description:
     *
     * BP  Size   Type    Identifier   Description
     * -------------------------------------------------------------------------
     * 0   2      UInt16  keyLength    key length (in bytes)
     * 2   2      UInt16  pad          set to zero
     * 4   4      UInt32  fileID       file associated with attribute
     * 8   4      UInt32  startBlock   first allocation block number for extents
     * 12  2      UInt16  attrNameLen  number of unicode characters
     * 14  <=254  UInt16  attrName     attribute name (Unicode)
     */

    public static final int STATIC_SIZE = 14;

    public static final int kHFSPlusAttrMinNodeSize = 4096;

    private static final int kHFSMaxAttrNameLen = 127;

    private short keyLength;
    private short pad;
    private final HFSCatalogNodeID fileID;
    private int startBlock;
    private short attrNameLen;
    private final byte[] attrName;

    public HFSPlusAttributesKey(byte[] data, int offset) {
        this.keyLength = Util.readShortBE(data, offset+0);
        this.pad = Util.readShortBE(data, offset+2);
        this.fileID = new HFSCatalogNodeID(data, offset+4);
        this.startBlock = Util.readIntBE(data, offset+8);
        this.attrNameLen = Util.readShortBE(data, offset+12);

        if(this.attrNameLen > kHFSMaxAttrNameLen) {
            throw new RuntimeException("Invalid attrNameLen value: " +
                    this.attrNameLen);
        }

        this.attrName =
                Util.createCopy(data, offset+14, 2*this.attrNameLen);
    }

    public HFSPlusAttributesKey(HFSCatalogNodeID fileID, int startBlock,
            char[] attrName)
    {
        int keyLengthInt = STATIC_SIZE + 2 * attrName.length;
        if(keyLengthInt > Short.MAX_VALUE) {
            throw new RuntimeException("Attribute name is too long: " +
                    attrName);
        }

        this.keyLength = (short) keyLengthInt;
        this.pad = 0;
        this.fileID = fileID;
        this.startBlock = startBlock;
        this.attrNameLen = (short) attrName.length;

        if(this.attrNameLen > kHFSMaxAttrNameLen) {
            throw new RuntimeException("Invalid attrNameLen value: " +
                    this.attrNameLen);
        }

        this.attrName = Util.readByteArrayBE(attrName);
    }

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


    public int maxSize() {
        return STATIC_SIZE + 2*kHFSMaxAttrNameLen;
    }

    public int occupiedSize() {
        return STATIC_SIZE + 2*getAttrNameLen();
    }

    /** key length (in bytes) */
    public final short getKeyLength() { return getRawKeyLength(); }
    public final int getKeyLengthUnsigned() {
        return Util.unsign(getRawKeyLength());
    }

    /** set to zero */
    public final int getPad() { return Util.unsign(getRawPad()); }

    /** file associated with attribute */
    public final HFSCatalogNodeID getFileID() { return fileID; }

    /** first allocation block number for extents */
    public final long getStartBlock() {
        return Util.unsign(getRawStartBlock());
    }

    /** number of unicode characters */
    public final int getAttrNameLen() {
        return Util.unsign(getRawAttrNameLen());
    }

    /** attribute name (Unicode) */
    public final char[] getAttrName() {
        return Util.readCharArrayBE(this.attrName);
    }

    /** <b>Note that the return value from this function should be interpreted as an unsigned integer, for instance using Util.unsign(...).</b> */
    public final short getRawKeyLength() { return this.keyLength; }
    /** <b>Note that the return value from this function should be interpreted as an unsigned integer, for instance using Util.unsign(...).</b> */
    public final short getRawPad() { return this.pad; }
    /** <b>Note that the return value from this function should be interpreted as an unsigned integer, for instance using Util.unsign(...).</b> */
    public final int getRawStartBlock() { return this.startBlock; }
    /** <b>Note that the return value from this function should be interpreted as an unsigned integer, for instance using Util.unsign(...).</b> */
    public final short getRawAttrNameLen() { return this.attrNameLen; }

    public void printFields(PrintStream ps, String prefix) {
        ps.println(prefix + " keyLength: " + getKeyLength());
        ps.println(prefix + " pad: " + getPad());
        ps.println(prefix + " fileID:");
        fileID.printFields(ps, prefix + "  ");
        ps.println(prefix + " startBlock: " + getStartBlock());
        ps.println(prefix + " attrNameLen: " + getAttrNameLen());
        ps.println(prefix + " attrName: \"" + new String(getAttrName()) + "\"");
    }

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

    /* @Override */
    public byte[] getBytes() {
        byte[] result = new byte[length()];
        int offset = 0;

        Util.arrayPutBE(result, offset, this.keyLength); offset += 2;
        Util.arrayPutBE(result, offset, this.pad); offset += 2;
        Util.arrayCopy(this.fileID.getBytes(), result, offset); offset += 4;
        Util.arrayPutBE(result, offset, this.startBlock); offset += 4;
        Util.arrayPutBE(result, offset, this.attrNameLen); offset += 2;

        Util.arrayCopy(this.attrName, result, offset);
        offset += 2*kHFSMaxAttrNameLen;

        return result;
    }

    /* @Override */
    public Dictionary getStructElements() {
        final Class thisClass = HFSPlusAttributesKey.class;
        DictionaryBuilder db = new DictionaryBuilder(thisClass.getSimpleName(),
                "HFS+ attributes key");

        try {
            final Field keyLengthField =
                    thisClass.getDeclaredField("keyLength");
            final Field padField =
                    thisClass.getDeclaredField("pad");
            final Field startBlockField =
                    thisClass.getDeclaredField("startBlock");
            final Field attrNameLenField =
                    thisClass.getDeclaredField("attrNameLen");

            keyLengthField.setAccessible(true);
            padField.setAccessible(true);
            startBlockField.setAccessible(true);
            attrNameLenField.setAccessible(true);

            db.addUIntBE("keyLength", keyLengthField, this, "Key length",
                    "bytes");
            db.addUIntBE("pad", padField, this, "Padding",
                    IntegerFieldRepresentation.HEXADECIMAL);
            db.add("fileID", this.fileID.getOpaqueStructElement(), "File ID");
            db.addUIntBE("startBlock", startBlockField, this,
                    "Start block number");
            db.addUIntBE("attrNameLen", attrNameLenField, this,
                    "Attribute name length");
            db.addEncodedString("attrName", this.attrName, "UTF-16BE",
                    "Attribute name");

            return db.getResult();
        }
        catch(NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }
}
