/*-
 * 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.Dictionary;
import org.catacombae.csjc.structelements.IntegerFieldRepresentation;
import org.catacombae.hfsexplorer.Util;

/**
 * This class was generated by CStructToJavaClass.
 *
 * @author <a href="https://catacombae.org" target="_top">Erik Larsson</a>
 */
public class HFSPlusAttributesData extends HFSPlusAttributesLeafRecordData
        implements DynamicStruct, PrintableStruct
{
    /*
     * struct HFSPlusAttrData
     * size: 18 bytes
     * description:
     *
     * BP  Size  Type       Identifier  Description
     * ----------------------------------------------------------------
     * 0   4     UInt32     recordType  == kHFSPlusAttrInlineData
     * 4   4*2   UInt32[2]  reserved
     * 12  4     UInt32     attrSize    size of attribute data in bytes
     * 16  1*?   UInt8[?]   attrData    variable length
     */

    public static final int STATIC_SIZE = 16;

    private long reserved;
    private int attrSize;
    private final byte[] attrData1;
    private final byte[] attrData2;

    /* Note: Theoretically a single short[] array would be sufficient for
     * storing all attrData... however retrieving information from a short array
     * might be tricky. Can NIO buffers help? */

    public HFSPlusAttributesData(byte[] data, int offset) {
        super(data, offset);
        this.reserved = Util.readLongBE(data, offset + 4);
        this.attrSize = Util.readIntBE(data, offset + 12);

        long unsignedAttrSize = Util.unsign(this.attrSize);
        if(unsignedAttrSize > Integer.MAX_VALUE) {
            this.attrData1 = new byte[Integer.MAX_VALUE];
            this.attrData2 = new byte[(int) (unsignedAttrSize -
                    Integer.MAX_VALUE)];
        }
        else {
            this.attrData1 = new byte[(int) unsignedAttrSize];
            this.attrData2 = null;
        }

        System.arraycopy(data, offset + 16, this.attrData1, 0,
                this.attrData1.length);
        if(this.attrData2 != null) {
            System.arraycopy(data, offset + 16 + this.attrData1.length,
                    this.attrData2, 0, this.attrData2.length);
        }
    }

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

    public int occupiedSize() {
        final long trueSize = STATIC_SIZE + attrData1.length +
                (attrData2 != null ? attrData2.length : 0);
        return trueSize > Integer.MAX_VALUE ? Integer.MAX_VALUE :
            (int) trueSize;
    }

    public int maxSize() {
        return Integer.MAX_VALUE;
    }

    /**  */
    public final long[] getReserved() { return Util.unsign(getRawReserved()); }
    /** size of attribute data in bytes */
    public final long getAttrSize() { return Util.unsign(getRawAttrSize()); }

    /** variable length */
    public final byte[] getAttrData() {
        if(attrData2 != null) {
            throw new RuntimeException("Attribute data size is too large for " +
                    "Java byte[] address space.");
        }

        return Util.createCopy(attrData1);
    }

    public final int readAttrData(long pos, byte[] b, int offset, int length) {
        final long unsignedAttrSize = getAttrSize();
        final int length1, length2;

        if(pos > unsignedAttrSize) {
            return 0;
        }
        else if(pos + length > unsignedAttrSize) {
            length = (int) (unsignedAttrSize - pos);
        }

        if(pos > Integer.MAX_VALUE) {
            length1 = 0;
            length2 = length;
        }
        else if(pos + length > Integer.MAX_VALUE) {
            length1 = (int) (Integer.MAX_VALUE - pos);
            length2 = (int) ((pos + length) - Integer.MAX_VALUE);
        }
        else {
            length1 = length;
            length2 = 0;
        }

        if(length1 != 0) {
            System.arraycopy(this.attrData1, (int) pos, b, offset, length1);
            pos += length1;
            offset += length1;
        }

        if(length2 != 0) {
            System.arraycopy(this.attrData2, (int) (pos - Integer.MAX_VALUE), b,
                    offset, length2);
            pos += length2;
            offset += length2;
        }

        return length;
    }

    /** <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[] getRawReserved() { return Util.readIntArrayBE(Util.toByteArrayBE(this.reserved)); }
    /** <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 getRawAttrSize() { return this.attrSize; }

    public void printFields(PrintStream ps, String prefix) {
        ps.println(prefix + " recordType: " + getRecordType());
        ps.println(prefix + " reserved: ");
        {
            long[] _array = getReserved();
            for(int _i = 0; _i < 2; ++_i) {
                ps.println(prefix + "  [" + _i + "]:");
                ps.println(prefix + "    " + _array[_i]);
            }
        }
        ps.println(prefix + " attrSize: " + getAttrSize());
        ps.println(prefix + " attrData: ");
        {
            byte[] _array = this.attrData1;
            for(int _i = 0; _i < _array.length; ++_i) {
                ps.println(prefix + "  [" + _i + "]:");
                ps.println(prefix + "    " + _array[_i]);
            }

            if(this.attrData2 != null) {
                _array = this.attrData2;
                for(int _i = 0; _i < this.attrData1.length; ++_i) {
                    ps.println(prefix + "  [" + _i + "]:");
                    ps.println(prefix + "    " + _array[_i]);
                }
            }
        }
    }

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

    public byte[] getBytes() {
        if(this.attrData1.length > Integer.MAX_VALUE - 16) {
            throw new RuntimeException("Struct is too large to fit within a " +
                    "Java byte array (limited by " + Integer.MAX_VALUE + " " +
                    "bytes). Would need " + (16 + this.attrData1.length +
                    (this.attrData2 != null ? this.attrData2.length : 0)) +
                    " bytes.");
        }

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

        offset += super.getBytes(result, offset);
        Util.arrayPutBE(result, offset, this.reserved); offset += 8;
        Util.arrayPutBE(result, offset, this.attrSize); offset += 4;

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

        return result;
    }

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

        try {
            final Field reservedField = thisClass.getDeclaredField("reserved");
            final Field attrSizeField = thisClass.getDeclaredField("attrSize");

            reservedField.setAccessible(true);
            attrSizeField.setAccessible(true);

            db.addAll(super.getStructElements());
            db.addUIntBE("reserved", reservedField, this, "Reserved",
                    IntegerFieldRepresentation.HEXADECIMAL);
            db.addUIntBE("attrSize", attrSizeField, this, "Attribute size");
            if(this.attrData2 == null) {
                db.addByteArray("attrData", this.attrData1, 0,
                        this.attrData1.length, "Attribute data");
            }
            else {
                db.addByteArray("attrData1", this.attrData1, 0,
                        this.attrData1.length, "Attribute data (part 1)");
                db.addByteArray("attrData2", this.attrData2, 0,
                        this.attrData2.length, "Attribute data (part 2)");
            }

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