package com.bytezone.diskbrowser.applefile;

import com.bytezone.diskbrowser.utilities.HexFormatter;
import com.bytezone.diskbrowser.utilities.Utility;

// -----------------------------------------------------------------------------------//
public class SegmentHeader
// -----------------------------------------------------------------------------------//
{
  int bytecnt;
  int resspc;
  int length;
  int kind1;

  String kindWhereText;
  boolean kindReload;
  boolean kindAbsoluteBank;
  boolean kindSpecialMemory;
  boolean kindPosIndependent;
  boolean kindPrivate;
  boolean kindStatic;

  int lablen;
  int numlen;
  int version;
  int banksize;
  int kind2;
  int unused;
  int org;
  int align;
  int numsex;
  int lcbank;
  int segnum;
  int entry;
  int dispname;
  int dispdata;
  String loadname;
  String segname;

  boolean debug = false;

  // ---------------------------------------------------------------------------------//
  public SegmentHeader (byte[] buffer, int offset)
  // ---------------------------------------------------------------------------------//
  {
    bytecnt = Utility.getLong (buffer, offset);
    resspc = Utility.getLong (buffer, offset + 4);
    length = Utility.getLong (buffer, offset + 8);

    kind1 = buffer[offset + 12] & 0xFF;

    lablen = buffer[offset + 13] & 0xFF;
    numlen = buffer[offset + 14] & 0xFF;
    version = buffer[offset + 15] & 0xFF;

    banksize = Utility.getLong (buffer, offset + 16);
    kind2 = Utility.getShort (buffer, offset + 20);
    unused = Utility.getShort (buffer, offset + 22);
    org = Utility.getLong (buffer, offset + 24);
    align = Utility.getLong (buffer, offset + 28);

    numsex = buffer[offset + 32] & 0xFF;
    lcbank = buffer[offset + 33] & 0xFF;

    segnum = Utility.getShort (buffer, offset + 34);

    entry = Utility.getLong (buffer, offset + 36);

    dispname = Utility.getShort (buffer, offset + 40);
    dispdata = Utility.getShort (buffer, offset + 42);

    decodeKind ();

    if (buffer[offset + dispname] == 0)
      loadname = "";
    else
      loadname = new String (buffer, offset + dispname, 10);

    if (lablen == 0)
      segname = HexFormatter.getPascalString (buffer, offset + dispname + 10);
    else
      segname = "not finished";

    if (debug)
      System.out.println (this);

    int ptr = offset + dispdata;
    while (true)
    {
      int recType = buffer[ptr] & 0xFF;
      //      System.out.printf ("%04X = %02X%n", ptr, recType);

      if (recType > 0 && recType <= 0xDF)
      {
        if (debug)
        {
          System.out.printf ("Const: %02X%n", recType);
          System.out.println (HexFormatter.format (buffer, ptr, recType + 1, ptr));
        }
        ptr += recType + 1;
        continue;
      }

      if (debug)
        System.out.printf ("%02X ", recType);

      switch (recType)
      {
        case 0x00:        // END
          if (debug)
            System.out.println ("END");
          break;

        case 0xE0:        // ALIGN
          if (debug)
            System.out.printf ("ALIGN:%n");
          break;

        case 0xE1:        // ORG
          if (debug)
            System.out.printf ("ORG:%n");
          break;

        case 0xE2:        // RELOC
          int bytesRelocated = buffer[ptr + 1] & 0xFF;
          int bitShift = buffer[ptr + 2] & 0xFF;
          int segmentOffset = Utility.getLong (buffer, ptr + 3);
          int value = Utility.getLong (buffer, ptr + 7);
          if (debug)
            System.out.printf ("RELOC: %02X %02X %08X %08X%n", bytesRelocated, bitShift,
                segmentOffset, value);
          ptr += 11;
          continue;

        case 0xE3:        // INTERSEG
          int count1 = buffer[ptr + 1] & 0xFF;
          int count2 = buffer[ptr + 2] & 0xFF;
          int operandOffset = Utility.getLong (buffer, ptr + 3);
          int fileNo = Utility.getShort (buffer, ptr + 7);
          int segNo = Utility.getShort (buffer, ptr + 9);
          int subroutineOffset = Utility.getLong (buffer, ptr + 11);
          if (debug)
            System.out.printf ("INTERSEG: %02X %02X %08X %04X %04X %08X%n", count1,
                count2, operandOffset, fileNo, segNo, subroutineOffset);
          ptr += 15;
          break;

        case 0xE4:        // USING
          if (debug)
            System.out.printf ("USING:%n");
          break;

        case 0xE5:        // STRONG
          if (debug)
            System.out.printf ("STRONG:%n");
          break;

        case 0xE6:        // GLOBAL
          if (debug)
            System.out.printf ("GLOBAL:%n");
          break;

        case 0xE7:        // GEQU
          if (debug)
            System.out.printf ("GEQU:%n");
          break;

        case 0xE8:        // MEM
          if (debug)
            System.out.printf ("MEM:%n");
          break;

        case 0xEB:        // EXPR
          if (debug)
            System.out.printf ("EXPR:%n");
          break;

        case 0xEC:        // ZEXPR
          if (debug)
            System.out.printf ("ZEXPR:%n");
          break;

        case 0xED:        // BEXPR
          if (debug)
            System.out.printf ("BEXPR:%n");
          break;

        case 0xEE:        // RELEXPR
          if (debug)
            System.out.printf ("RELEXPR:%n");
          break;

        case 0xEF:        // LOCAL
          if (debug)
            System.out.printf ("LOCAL:%n");
          break;

        case 0xF0:        // EQU
          String label = HexFormatter.getPascalString (buffer, ptr + 1);
          if (debug)
            System.out.printf ("EQU: %s%n", label);
          break;

        case 0xF1:        // DS
          if (debug)
            System.out.printf ("DS:%n");
          break;

        case 0xF2:        // LCONST
          int constLength = Utility.getLong (buffer, ptr + 1);
          if (debug)
            System.out.printf ("Const: %04X%n", constLength);
          ptr += constLength + 5;
          continue;

        case 0xF3:        // LEXPR
          if (debug)
            System.out.printf ("LEXPR:%n");
          break;

        case 0xF4:        // ENTRY
          if (debug)
            System.out.printf ("ENTRY:%n");
          break;

        case 0xF5:        // cRELOC
          int cBytesRelocated = buffer[ptr + 1] & 0xFF;
          int cBitShift = buffer[ptr + 2] & 0xFF;
          int cSegmentOffset = Utility.getShort (buffer, ptr + 3);
          int cValue = Utility.getShort (buffer, ptr + 5);
          if (debug)
            System.out.printf ("cRELOC: %02X %02X %08X %08X%n", cBytesRelocated,
                cBitShift, cSegmentOffset, cValue);
          ptr += 7;
          continue;

        case 0xF6:        // cINTERSEG
          int cCount1 = buffer[ptr + 1] & 0xFF;
          int cCount2 = buffer[ptr + 2] & 0xFF;
          int cOperandOffset = Utility.getShort (buffer, ptr + 3);
          int cSegNo = buffer[ptr + 5] & 0xFF;
          int cSubroutineOffset = Utility.getShort (buffer, ptr + 6);
          if (debug)
            System.out.printf ("cINTERSEG: %02X %02X %04X %02X %04X%n", cCount1, cCount2,
                cOperandOffset, cSegNo, cSubroutineOffset);
          ptr += 8;
          continue;

        case 0xF7:        // SUPER
          int superLength = Utility.getLong (buffer, ptr + 1);
          int recordType = buffer[ptr + 5] & 0xFF;
          if (debug)
            System.out.printf ("Super type %02X%n", recordType);
          ptr += superLength + 5;
          continue;

        default:
          System.out.printf ("Unknown record type: %02X%n", recType);
          break;
      }

      if (debug)
        System.out.println ();
      break;
    }
  }

  // ---------------------------------------------------------------------------------//
  private void decodeKind ()
  // ---------------------------------------------------------------------------------//
  {
    int segType;
    int segAttr;

    if (version < 2)              // 8 bits
    {
      segType = kind1 & 0x1F;
      segAttr = kind1 & 0xE0;
    }
    else                          // 16 bits
    {
      segType = kind2 & 0x001F;
      segAttr = kind2 >>> 8;
    }

    kindReload = (segAttr & 0x04) != 0;
    kindAbsoluteBank = (segAttr & 0x08) != 0;
    kindSpecialMemory = (segAttr & 0x10) == 0;
    kindPosIndependent = (segAttr & 0x20) != 0;
    kindPrivate = (segAttr & 0x40) != 0;
    kindStatic = (segAttr & 0x80) == 0;

    kindWhereText = switch (segType)
    {
      case 0x00 -> "Code Segment";
      case 0x01 -> "Data Segment";
      case 0x02 -> "Jump Table Segment";
      case 0x04 -> "Pathname Segment";
      case 0x08 -> "Library Dictionary Segment";
      case 0x10 -> "Initialization Segment";
      case 0x11 -> "Absolute Bank Segment";
      case 0x12 -> "Direct Page / Stack Segment";
      default -> "Unknown";
    };
  }

  // ---------------------------------------------------------------------------------//
  @Override
  public String toString ()
  // ---------------------------------------------------------------------------------//
  {
    StringBuilder text = new StringBuilder ();

    text.append (String.format ("Byte count ......... %08X%n", bytecnt));
    text.append (String.format ("Reserved space ..... %08X%n", resspc));
    text.append (String.format ("Length ............. %08X%n", length));

    text.append (String.format ("Kind v1 ............ %02X%n", kind1));
    if (version < 2)
    {
      text.append (String.format ("  Type ............. %s%n", kindWhereText));
      text.append (
          String.format ("  Position ind ..... %s%n", kindPosIndependent ? "Yes" : "No"));
      text.append (
          String.format ("  Private .......... %s%n", kindPrivate ? "Yes" : "No"));
      text.append (
          String.format ("  Static/Dynamic ... %s%n", kindStatic ? "Static" : "Dynamic"));
    }

    text.append (String.format ("Label length ....... %02X%n", lablen));
    text.append (String.format ("Number length ...... %02X%n", numlen));
    text.append (String.format ("Version ............ %02X%n", version));

    text.append (String.format ("Bank size .......... %08X%n", banksize));
    text.append (String.format ("Kind v2 ............ %04X%n", kind2));

    if (version >= 2)
    {
      text.append (String.format ("  Type ............. %s%n", kindWhereText));
      text.append (
          String.format ("  Reload ........... %s%n", kindReload ? "Yes" : "No"));
      text.append (
          String.format ("  Absolute Bank .... %s%n", kindAbsoluteBank ? "Yes" : "No"));
      text.append (
          String.format ("  Special Memory ... %s%n", kindSpecialMemory ? "Yes" : "No"));
      text.append (
          String.format ("  Position ind ..... %s%n", kindPosIndependent ? "Yes" : "No"));
      text.append (
          String.format ("  Private .......... %s%n", kindPrivate ? "Yes" : "No"));
      text.append (
          String.format ("  Static/Dynamic ... %s%n", kindStatic ? "Static" : "Dynamic"));
    }

    text.append (String.format ("Unused ............. %04X%n", unused));
    text.append (String.format ("Org ................ %08X%n", org));
    text.append (String.format ("Align .............. %08X%n", align));

    text.append (String.format ("Number sex ......... %02X%n", numsex));
    text.append (String.format ("LC Bank ............ %02X%n", lcbank));

    text.append (String.format ("Segment number ..... %04X%n", segnum));

    text.append (String.format ("Entry .............. %08X%n", entry));

    text.append (String.format ("Disp name .......... %04X%n", dispname));
    text.append (String.format ("Disp data .......... %04X%n", dispdata));

    text.append (String.format ("Load name .......... %s%n", loadname));
    text.append (String.format ("Segment name ....... %s%n", segname));

    return text.toString ();
  }
}
