/*-
 * Copyright (C) 2015 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.hfsexplorer.types.applesingle;

import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.Arrays;
import org.catacombae.csjc.PrintableStruct;
import org.catacombae.csjc.StaticStruct;
import org.catacombae.csjc.structelements.Dictionary;
import org.catacombae.csjc.structelements.DictionaryBuilder;
import org.catacombae.util.Util;

/**
 * This class was generated by CStructToJavaClass.
 *
 * @author <a href="https://catacombae.org" target="_top">Erik Larsson</a>
 */
public class AttributeHeader implements StaticStruct, PrintableStruct {
    /*
     * struct AttributeHeader
     * size: 36 bytes
     * description:
     *
     * BP  Size    Type      Identifier  Description
     * ---------------------------------------------
     * 0   4       be32      magic
     * 4   4       be32      debugTag
     * 8   4       ube32     totalEnd
     * 12  4       ube32     dataStart
     * 16  4       ube32     dataLength
     * 20  1 * 12  char[12]  reserved
     * 32  2       be16      flags
     * 34  2       ube16     numAttrs
     */

    public static final int STRUCTSIZE = 36;

    /** The appropriate value for the 'magic' field ("ATTR" in ASCII). */
    public static final int MAGIC = 0x41545452;

    private int magic;
    private int debugTag;
    private int totalEnd;
    private int dataStart;
    private int dataLength;
    private final byte[] reserved = new byte[1 * 12];
    private short flags;
    private short numAttrs;

    public AttributeHeader(byte[] data, int offset) {
        this.magic = Util.readIntBE(data, offset + 0);
        this.debugTag = Util.readIntBE(data, offset + 4);
        this.totalEnd = Util.readIntBE(data, offset + 8);
        this.dataStart = Util.readIntBE(data, offset + 12);
        this.dataLength = Util.readIntBE(data, offset + 16);
        System.arraycopy(data, offset + 20, this.reserved, 0, 1 * 12);
        this.flags = Util.readShortBE(data, offset + 32);
        this.numAttrs = Util.readShortBE(data, offset + 34);
    }

    public AttributeHeader(int debugTag, int totalEnd, int dataStart,
            int dataLength, short flags, short numAttrs)
    {
        this.magic = MAGIC;
        this.debugTag = debugTag;
        this.totalEnd = totalEnd;
        this.dataStart = dataStart;
        this.dataLength = dataLength;
        Arrays.fill(reserved, (byte) 0);
        this.flags = flags;
        this.numAttrs = numAttrs;
    }

    public static int length() { return STRUCTSIZE; }

    public int size() { return STRUCTSIZE; }


    /**  */
    public final int getMagic() { return this.magic; }

    /**  */
    public final int getDebugTag() { return this.debugTag; }

    /**  */
    public final long getTotalEnd() { return Util.unsign(getRawTotalEnd()); }

    /**  */
    public final long getDataStart() { return Util.unsign(getRawDataStart()); }

    /**  */
    public final long getDataLength() {
        return Util.unsign(getRawDataLength());
    }

    /**  */
    public final byte[] getReserved() { return Util.createCopy(this.reserved); }

    /**  */
    public final short getFlags() { return this.flags; }

    /**  */
    public final int getNumAttrs() { return Util.unsign(getRawNumAttrs()); }

    /**
     * <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 getRawTotalEnd() { return this.totalEnd; }

    /**
     * <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 getRawDataStart() { return this.dataStart; }

    /**
     * <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 getRawDataLength() { return this.dataLength; }

    /**
     * <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 getRawNumAttrs() { return this.numAttrs; }

    public void printFields(PrintStream ps, String prefix) {
        ps.println(prefix + " magic: " + getMagic());
        ps.println(prefix + " debugTag: " + getDebugTag());
        ps.println(prefix + " totalEnd: " + getTotalEnd());
        ps.println(prefix + " dataStart: " + getDataStart());
        ps.println(prefix + " dataLength: " + getDataLength());
        ps.print(prefix + " reserved: {");
        {
            byte[] _array = getReserved();
            for(int _i = 0; _i < 12; ++_i) {
                ps.print((_i > 0 ? "," : "") + " 0x" +
                        Util.toHexStringBE(_array[_i]));
            }
        }
        ps.println(" }");
        ps.println(prefix + " flags: " + getFlags());
        ps.println(prefix + " numAttrs: " + getNumAttrs());
    }

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

    public byte[] getBytes() {
        byte[] result = new byte[length()];
        getBytes(result, 0);
        return result;
    }

    public int getBytes(byte[] result, int offset) {
        final int startOffset = offset;

        Util.arrayPutBE(result, offset, this.magic);
        offset += 4;

        Util.arrayPutBE(result, offset, this.debugTag);
        offset += 4;

        Util.arrayPutBE(result, offset, this.totalEnd);
        offset += 4;

        Util.arrayPutBE(result, offset, this.dataStart);
        offset += 4;

        Util.arrayPutBE(result, offset, this.dataLength);
        offset += 4;

        System.arraycopy(this.reserved, 0, result, offset,
                this.reserved.length);
        offset += this.reserved.length;

        Util.arrayPutBE(result, offset, this.flags);
        offset += 2;

        Util.arrayPutBE(result, offset, this.numAttrs);
        offset += 2;

        return offset - startOffset;
    }

    private Field getPrivateField(String name) throws NoSuchFieldException {
        Field f = getClass().getDeclaredField(name);
        f.setAccessible(true);
        return f;
    }

    public Dictionary getStructElements() {
        DictionaryBuilder db =
                new DictionaryBuilder(AttributeHeader.class.getSimpleName());

        try {
            db.addUIntBE("magic", getPrivateField("magic"), this);
            db.addUIntBE("debugTag", getPrivateField("debugTag"), this);
            db.addUIntBE("totalEnd", getPrivateField("totalEnd"), this);
            db.addUIntBE("dataStart", getPrivateField("dataStart"), this);
            db.addUIntBE("dataLength", getPrivateField("dataLength"), this);
            db.addUIntBE("flags", getPrivateField("flags"), this);
            db.addUIntBE("numAttrs", getPrivateField("numAttrs"), this);
        } catch(NoSuchFieldException e) {
            throw new RuntimeException(e);
        }

        return db.getResult();
    }
}
