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

import java.io.PrintStream;
import org.catacombae.hfs.types.hfsplus.HFSPlusDate;
import org.catacombae.util.Util;

/** This class was generated by CStructToJavaClass. */
public class AliasHeader {
    /*
     * struct AliasHeader
     * size: 150 bytes
     * description:
     *
     * BP   Size    Type      Identifier             Description
     * --------------------------------------------------------------------------------------------------------------------------------------------
     * 0    1 * 4   char[4]   userType
     * 4    2       be16      aliasSize
     * 6    2       be16      version
     * 8    2       be16      flags
     * 10   2       be16      volumeNameLength
     * 11   1 * 27  char[27]  volumeName
     * 38   4       be32      volumeCreateTimestamp
     * 42   1 * 2   char[2]   volumeFsType           'BD' (Big Disk) for HFS and also later for any other filesystem. 'RW' for MFS.
     * 44   2       be16      driveType              0 = HD (fixed), 1 = Network, 2 = 400k FD, 3 = 800k FD, 4 = 1.44M FD, 5 = Other ejectable media
     * 46   4       be32      parentID               Parent directory ID of target item.
     * 50   1       u8        fileNameLength
     * 51   1 * 63  char[63]  fileName
     * 114  4       be32      fileID
     * 118  4       be32      fileCreateTimestamp
     * 122  1 * 4   char[4]   fileType
     * 126  1 * 4   char[4]   fileCreator
     * 130  2       sbe16     nextLevelUpFromAlias
     * 132  2       sbe16     nextLevelDownToTarget
     * 134  4       be32      volumeAttributes
     * 138  2       be16      fileSystemID
     * 140  1 * 10  u8[10]    reserved
     */

    public static final int STRUCTSIZE = 150;

    private int userType;
    private short aliasSize;
    private short version;
    private short flags;
    private byte volumeNameLength;
    private final byte[] volumeName = new byte[1 * 27];
    private int volumeCreateTimestamp;
    private short volumeFsType;
    private short driveType;
    private int parentID;
    private byte fileNameLength;
    private final byte[] fileName = new byte[1 * 63];
    private int fileID;
    private int fileCreateTimestamp;
    private int fileType;
    private int fileCreator;
    private short nextLevelUpFromAlias;
    private short nextLevelDownToTarget;
    private int volumeAttributes;
    private short fileSystemID;
    private final byte[] reserved = new byte[1 * 10];

    public AliasHeader(byte[] data, int offset) {
        this.userType = Util.readIntBE(data, offset + 0);
        this.aliasSize = Util.readShortBE(data, offset + 4);
        this.version = Util.readShortBE(data, offset + 6);
        this.flags = Util.readShortBE(data, offset + 8);
        this.volumeNameLength = Util.readByteBE(data, offset + 10);
        System.arraycopy(data, offset + 11, this.volumeName, 0, 1 * 27);
        this.volumeCreateTimestamp = Util.readIntBE(data, offset + 38);
        this.volumeFsType = Util.readShortBE(data, offset + 42);
        this.driveType = Util.readShortBE(data, offset + 44);
        this.parentID = Util.readIntBE(data, offset + 46);
        this.fileNameLength = Util.readByteBE(data, offset + 50);
        System.arraycopy(data, offset + 51, this.fileName, 0, 1 * 63);
        this.fileID = Util.readIntBE(data, offset + 114);
        this.fileCreateTimestamp = Util.readIntBE(data, offset + 118);
        this.fileType = Util.readIntBE(data, offset + 122);
        this.fileCreator = Util.readIntBE(data, offset + 126);
        this.nextLevelUpFromAlias = Util.readShortBE(data, offset + 130);
        this.nextLevelDownToTarget = Util.readShortBE(data, offset + 132);
        this.volumeAttributes = Util.readIntBE(data, offset + 134);
        this.fileSystemID = Util.readShortBE(data, offset + 138);
        System.arraycopy(data, offset + 140, this.reserved, 0, 1 * 10);
    }

    public static int length() { return STRUCTSIZE; }

    /**  */
    public final byte[] getUserType() {
        return Util.toByteArrayBE(this.userType);
    }

    /**  */
    public final int getAliasSize() {
        return Util.unsign(getRawAliasSize());
    }

    /**  */
    public final int getVersion() {
        return Util.unsign(getRawVersion());
    }

    /**  */
    public final int getFlags() {
        return Util.unsign(getRawFlags());
    }

    /**  */
    public final int getVolumeNameLength() {
        return Util.unsign(getRawVolumeNameLength());
    }

    /**  */
    public final byte[] getVolumeName() {
        return Util.readByteArrayBE(this.volumeName);
    }

    /**  */
    public final long getVolumeCreateTimestamp() {
        return Util.unsign(getRawVolumeCreateTimestamp());
    }

    /**
     * 'H+' for HFS+, 'BD' (Big Disk) for HFS and also later for any other
     * filesystems supported by Mac OS Classic, 'RW' for MFS.
     */
    public final byte[] getVolumeFsType() {
        return Util.toByteArrayBE(this.volumeFsType);
    }

    /**
     * 0 = HD (fixed), 1 = Network, 2 = 400k FD, 3 = 800k FD, 4 = 1.44M FD, 5 =
     * Other ejectable media
     */
    public final int getDriveType() {
        return Util.unsign(getRawDriveType());
    }

    /** Parent directory ID of target item. */
    public final long getParentID() {
        return Util.unsign(getRawParentID());
    }

    /**  */
    public final short getFileNameLength() {
        return Util.unsign(getRawFileNameLength());
    }

    /**  */
    public final byte[] getFileName() {
        return Util.readByteArrayBE(this.fileName);
    }

    /**  */
    public final long getFileID() {
        return Util.unsign(getRawFileID());
    }

    /**  */
    public final long getFileCreateTimestamp() {
        return Util.unsign(getRawFileCreateTimestamp());
    }

    /**  */
    public final byte[] getFileType() {
        return Util.toByteArrayBE(this.fileType);
    }

    /**  */
    public final byte[] getFileCreator() {
        return Util.toByteArrayBE(this.fileCreator);
    }

    /**  */
    public final short getNextLevelUpFromAlias() {
        return this.nextLevelUpFromAlias;
    }

    /**  */
    public final short getNextLevelDownToTarget() {
        return this.nextLevelDownToTarget;
    }

    /**  */
    public final long getVolumeAttributes() {
        return Util.unsign(getRawVolumeAttributes());
    }

    /**  */
    public final int getFileSystemID() {
        return Util.unsign(getRawFileSystemID());
    }

    /**  */
    public final short[] getReserved() {
        return Util.unsign(getRawReserved());
    }

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

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

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

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

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

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

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

    /**
     * <b>Note that the return value from this function should be interpreted as
     * an unsigned integer, for instance using Util.unsign(...).</b>
     */
    public final byte getRawFileNameLength() {
        return this.fileNameLength;
    }

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

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

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

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

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

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

    /**
     * <b>Note that the return value from this function should be interpreted as
     * an unsigned integer, for instance using Util.unsign(...).</b>
     */
    public final byte[] getRawReserved() {
        return Util.readByteArrayBE(this.reserved);
    }

    public static String driveTypeToString(short driveType) {
        switch(driveType & 0xFFFF) {
            case 0: return "HD (fixed)";
            case 1: return "Network";
            case 2: return "400k FD";
            case 3: return "800k FD";
            case 4: return "1.44M FD";
            case 5: return "Ejectable media";
            default: return null;
        }
    }

    public void printFields(PrintStream ps, String prefix) {
        ps.println(prefix + " User type: " +
                "0x" + Util.byteArrayToHexString(getUserType()) + " " +
                "('" + Util.toASCIIString(getUserType()) + "')");
        ps.println(prefix + " Alias size: " + getAliasSize());
        ps.println(prefix + " Version: " + getVersion());
        ps.println(prefix + " Flags: " + getFlags());
        ps.println(prefix + " Volume name length: " + getVolumeNameLength());
        ps.println(prefix + " Volume name: " +
                Util.readString(getVolumeName(), 0, getVolumeNameLength(),
                "MacRoman"));
        ps.println(prefix + " Volume create timestamp: " +
                HFSPlusDate.gmtTimestampToDate(getRawVolumeCreateTimestamp()) +
                " (" + getVolumeCreateTimestamp() + ")");
        ps.println(prefix + " Volume filesystem type: " +
                Util.readString(getVolumeFsType(), "MacRoman"));
        String driveTypeString = driveTypeToString(getRawDriveType());
        ps.println(prefix + " Drive type: " + getDriveType() +
                (driveTypeString != null ? " (" + driveTypeString + ")" : ""));
        ps.println(prefix + " Parent ID: " + getParentID());
        ps.println(prefix + " File name length: " + getFileNameLength());
        ps.println(prefix + " File name: " +
                Util.readString(getFileName(), 0, getFileNameLength(),
                "MacRoman"));
        ps.println(prefix + " File ID: " + getFileID());
        ps.println(prefix + " File create timestamp: " +
                HFSPlusDate.gmtTimestampToDate(getRawVolumeCreateTimestamp()) +
                " (" + getFileCreateTimestamp() + ")");
        ps.println(prefix + " File type: " + Util.toASCIIString(getFileType()));
        ps.println(prefix + " File creator: " +
                Util.toASCIIString(getFileCreator()));
        ps.println(prefix + " Next level up from alias: " +
                getNextLevelUpFromAlias());
        ps.println(prefix + " Next level down to target: " +
                getNextLevelDownToTarget());
        ps.println(prefix + " Volume attributes: " +
                "0x" + Util.toHexStringBE(getVolumeAttributes()));
        ps.println(prefix + " Filesystem ID: " + getFileSystemID());
        ps.println(prefix + " Reserved: " +
                "0x" + Util.byteArrayToHexString(getRawReserved()));
    }

    public void print(PrintStream ps, String prefix) {
        ps.println(prefix + "AliasHeader:");
        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.userType); offset += 4;
        Util.arrayPutBE(result, offset, this.aliasSize); offset += 2;
        Util.arrayPutBE(result, offset, this.version); offset += 2;
        Util.arrayPutBE(result, offset, this.flags); offset += 2;
        Util.arrayPutBE(result, offset, this.volumeNameLength); offset += 1;
        System.arraycopy(this.volumeName, 0, result, offset, this.volumeName.length); offset += this.volumeName.length;
        Util.arrayPutBE(result, offset, this.volumeCreateTimestamp); offset += 4;
        Util.arrayPutBE(result, offset, this.volumeFsType); offset += 2;
        Util.arrayPutBE(result, offset, this.driveType); offset += 2;
        Util.arrayPutBE(result, offset, this.parentID); offset += 4;
        Util.arrayPutBE(result, offset, this.fileNameLength); offset += 1;
        System.arraycopy(this.fileName, 0, result, offset, this.fileName.length); offset += this.fileName.length;
        Util.arrayPutBE(result, offset, this.fileID); offset += 4;
        Util.arrayPutBE(result, offset, this.fileCreateTimestamp); offset += 4;
        Util.arrayPutBE(result, offset, this.fileType); offset += 4;
        Util.arrayPutBE(result, offset, this.fileCreator); offset += 4;
        Util.arrayPutBE(result, offset, this.nextLevelUpFromAlias); offset += 2;
        Util.arrayPutBE(result, offset, this.nextLevelDownToTarget); offset += 2;
        Util.arrayPutBE(result, offset, this.volumeAttributes); offset += 4;
        Util.arrayPutBE(result, offset, this.fileSystemID); offset += 2;
        System.arraycopy(this.reserved, 0, result, offset, this.reserved.length); offset += this.reserved.length;

        return offset - startOffset;
    }
}
