package com.bytezone.diskbrowser.wizardry;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import com.bytezone.diskbrowser.applefile.AbstractFile;
import com.bytezone.diskbrowser.utilities.HexFormatter;

class MazeLevel extends AbstractFile
{
  public final int level;
  private List<Message> messages;
  private List<Monster> monsters;
  private List<Item> items;

  public MazeLevel (byte[] buffer, int level)
  {
    super ("Level " + level, buffer);
    this.level = level;
  }

  @Override
  public String getHexDump ()
  {
    StringBuilder text = new StringBuilder ();

    text.append ("West walls/doors\n\n");
    text.append (HexFormatter.format (buffer, 0, 120));
    addWalls (text, 0);

    text.append ("\nSouth walls/doors\n\n");
    text.append (HexFormatter.format (buffer, 120, 120));
    addWalls (text, 120);

    text.append ("\nEast walls/doors\n\n");
    text.append (HexFormatter.format (buffer, 240, 120));
    addWalls (text, 240);

    text.append ("\nNorth walls/doors\n\n");
    text.append (HexFormatter.format (buffer, 360, 120));
    addWalls (text, 360);

    text.append ("\nEncounters\n\n");
    text.append (HexFormatter.format (buffer, 480, 80));
    addEncounters (text, 480);

    text.append ("\nExtras\n\n");
    text.append (HexFormatter.format (buffer, 560, 200));
    addExtras (text, 560);

    text.append ("\nIndex\n\n");
    text.append (
        String.format ("%04X: %s%n", 760, HexFormatter.getHexString (buffer, 760, 8)));

    text.append ("\nTable\n\n");
    text.append (HexFormatter.format (buffer, 768, 96));

    text.append ("\n\n      0 1  2 3  4 5  6 7  8 9  A B  C D  E F\n");
    text.append (String.format ("%04X: ", 760));
    for (int i = 0; i < 8; i++)
    {
      int val = buffer[760 + i] & 0xFF;
      text.append (String.format ("%X:%X  ", val % 16, val / 16));
    }

    String[] extras =
        { "", "Stairs", "Pit", "Chute", "Spinner", "Darkness", "Teleport", "Ouch",
          "Elevator", "Rock/Water", "Fizzle", "Message/Item", "Monster" };

    List<MazeAddress> messageList = new ArrayList<MazeAddress> ();
    List<MazeAddress> monsterList = new ArrayList<MazeAddress> ();

    text.append ("\n\nValue   Index   Contains          Table\n");
    for (int j = 0; j < 16; j++)
    {
      String extraText = "";
      int val = buffer[760 + j / 2] & 0xFF;
      String extra = (j % 2) == 0 ? extras[val % 16] : extras[val / 16];
      MazeAddress address = getAddress (j);
      int cellFlag = (j % 2) == 0 ? val % 16 : val / 16;
      if (cellFlag == 11)
      {
        extraText = "Msg:" + String.format ("%04X  ", address.row);
        messageList.add (address);          // to print at the end

        int messageType = address.column;
        if (messageType == 2)
        {
          extraText += "Obtained: ";
          if (items != null)
            extraText += items.get (address.level).getName ();
        }

        if (messageType == 5)
        {
          extraText += "Requires: ";
          if (items != null)
            extraText += items.get (address.level).getName ();
        }

        if (messageType == 4)
        {
          extraText += "Unknown";
        }
      }

      if (cellFlag == 12)
      {
        monsterList.add (address);
        extraText = "Encounter: ";
        if (monsters != null)
          extraText += monsters.get (address.column).realName;
      }

      text.append (String.format ("  %X  -->  %X     %-15s   %04X  %04X  %04X  %s%n", j,
          cellFlag, extra, address.level, address.row, address.column, extraText));
    }

    text.append ("\n\nRest\n\n");
    text.append (HexFormatter.format (buffer, 864, buffer.length - 864));

    text.append ("\n");
    for (MazeAddress address : messageList)
    {
      Message message = getMessage (address.row);
      if (message != null)
      {
        text.append (String.format ("%nMessage: %04X  (%d)%n", address.row, address.row));
        text.append (message.getText ());
        text.append ("\n");
      }
    }

    for (MazeAddress address : monsterList)
    {
      Monster monster = getMonster (address.column);
      if (monster != null)
      {
        text.append (String.format ("%nMonster: %04X%n", address.column));
        text.append (monster.getText ());
        text.append ("\n");
      }
    }

    return text.toString ();
  }

  private void addWalls (StringBuilder text, int ptr)
  {
    text.append ("\n\n");
    for (int i = 0; i < 20; i++)
      text.append (String.format ("  Col %2d: %s%n", i,
          HexFormatter.getHexString (buffer, ptr + i * 6, 6)));
  }

  private void addEncounters (StringBuilder text, int ptr)
  {
    text.append ("\n\n");
    for (int i = 0; i < 20; i++)
    {
      text.append (String.format ("  Col %2d: %s  ", i,
          HexFormatter.getHexString (buffer, ptr + i * 4, 4)));
      StringBuilder bitString = new StringBuilder ();
      for (int j = 2; j >= 0; j--)
      {
        byte b = buffer[ptr + i * 4 + j];
        String s = ("0000000" + Integer.toBinaryString (0xFF & b))
            .replaceAll (".*(.{8})$", "$1");
        bitString.append (s);
        //        text.append (s);
        //        text.append ("  ");
      }

      String bitsReversed = bitString.reverse ().toString ();
      bitsReversed = bitsReversed.replace ("1", " 1");
      bitsReversed = bitsReversed.replace ("0", "  ");
      text.append (bitsReversed.substring (0, 40));
      text.append ("  : ");
      text.append (bitsReversed.substring (40));
      text.append ("\n");
    }
  }

  private void addExtras (StringBuilder text, int ptr)
  {
    text.append ("\n\n");
    for (int i = 0; i < 20; i++)
    {
      text.append (String.format ("  Col %2d:  ", i));
      for (int j = 0; j < 10; j++)
      {
        int val = buffer[ptr + i * 10 + j] & 0xFF;
        int left = val / 16;          // 0:F
        int right = val % 16;         // 0:F
        text.append (String.format ("%X:%X  ", right, left));
      }
      text.append ("\n");
    }
  }

  @Override
  public BufferedImage getImage ()
  {
    Dimension cellSize = new Dimension (22, 22);
    image = new BufferedImage (20 * cellSize.width + 1, 20 * cellSize.height + 1,
        BufferedImage.TYPE_USHORT_555_RGB);
    Graphics2D g = image.createGraphics ();
    g.setRenderingHint (RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);

    for (int row = 0; row < 20; row++)
      for (int column = 0; column < 20; column++)
      {
        MazeCell cell = getLocation (row, column);
        int x = column * cellSize.width;
        int y = image.getHeight () - (row + 1) * cellSize.height - 1;
        cell.draw (g, x, y);
      }
    return image;
  }

  public void setMessages (List<Message> messages)
  {
    this.messages = messages;
  }

  public void setMonsters (List<Monster> monsters)
  {
    this.monsters = monsters;
  }

  public void setItems (List<Item> items)
  {
    this.items = items;
  }

  public MazeCell getLocation (int row, int column)
  {
    MazeAddress address = new MazeAddress (level, row, column);
    MazeCell cell = new MazeCell (address);

    // doors and walls
    int BYTES_PER_COL = 6;        // 20 / 4 = 5 bytes + 1 wasted
    int CELLS_PER_BYTE = 4;       // 2 bits each
    int BITS_PER_ROW = 2;

    int offset = column * BYTES_PER_COL + row / CELLS_PER_BYTE;

    int value = buffer[offset] & 0xFF;
    value >>>= (row % CELLS_PER_BYTE) * BITS_PER_ROW;   // push 0, 2, 4, 6 bits
    cell.westWall = ((value & 1) == 1);                 // use rightmost bit
    value >>>= 1;                                       // push 1 bit
    cell.westDoor = ((value & 1) == 1);                 // use rightmost bit

    value = buffer[offset + 120] & 0xFF;
    value >>>= (row % CELLS_PER_BYTE) * BITS_PER_ROW;
    cell.southWall = ((value & 1) == 1);
    value >>>= 1;
    cell.southDoor = ((value & 1) == 1);

    value = buffer[offset + 240] & 0xFF;
    value >>>= (row % CELLS_PER_BYTE) * BITS_PER_ROW;
    cell.eastWall = ((value & 1) == 1);
    value >>>= 1;
    cell.eastDoor = ((value & 1) == 1);

    value = buffer[offset + 360] & 0xFF;
    value >>>= (row % CELLS_PER_BYTE) * BITS_PER_ROW;
    cell.northWall = ((value & 1) == 1);
    value >>>= 1;
    cell.northDoor = ((value & 1) == 1);

    // monster table
    BYTES_PER_COL = 4;        // 3 bytes + 1 wasted
    CELLS_PER_BYTE = 8;       // 1 bit each
    BITS_PER_ROW = 1;

    offset = column * BYTES_PER_COL + row / CELLS_PER_BYTE;  // 4 bytes/column, 1 bit/row
    value = buffer[offset + 480] & 0xFF;
    value >>>= row % 8;
    cell.monsterLair = ((value & 1) == 1);

    // square extra

    offset = column * 10 + row / 2;                       // 10 bytes/column, 4 bits/row
    value = buffer[offset + 560] & 0xFF;
    int b = (row % 2 == 0) ? value % 16 : value / 16;     // 0:F

    int c = buffer[760 + b / 2] & 0xFF;                   // 760:767
    int cellFlag = (b % 2 == 0) ? c % 16 : c / 16;

    switch (cellFlag)
    {
      case 0:         // normal
        break;

      case 1:
        cell.stairs = true;
        cell.addressTo = getAddress (b);
        break;

      case 2:
        cell.pit = true;
        break;

      case 3:
        cell.chute = true;
        cell.addressTo = getAddress (b);
        break;

      case 4:
        cell.spinner = true;
        break;

      case 5:
        cell.darkness = true;
        break;

      case 6:
        cell.teleport = true;
        cell.addressTo = getAddress (b);
        break;

      case 7:       // ouchy
        cell.unknown = cellFlag;
        break;

      case 8:
        cell.elevator = true;
        MazeAddress elevatorAddress = getAddress (b);
        cell.elevatorTo = elevatorAddress.row;
        //            HexFormatter.intValue (buffer[800 + b * 2], buffer[801 + b * 2]);
        cell.elevatorFrom = elevatorAddress.column;
        //            HexFormatter.intValue (buffer[832 + b * 2], buffer[833 + b * 2]);
        break;

      case 9:       // rock/water
        cell.rock = true;
        break;

      case 10:
        cell.spellsBlocked = true;
        break;

      case 11:
        MazeAddress messageAddress = getAddress (b);
        //        int messageNum = HexFormatter.intValue (buffer[800 + b * 2], buffer[801 + b * 2]);

        Message m = getMessage (messageAddress.row);
        if (m != null)
          cell.message = m;

        cell.messageType = messageAddress.column;
        //            HexFormatter.intValue (buffer[832 + b * 2], buffer[833 + b * 2]);

        int itemID = -1;

        if (cell.messageType == 2 && items != null)                 // obtain Item
        {
          //          itemID = HexFormatter.intValue (buffer[768 + b * 2], buffer[769 + b * 2]);
          itemID = messageAddress.level;
          cell.itemObtained = items.get (itemID);
        }

        if (cell.messageType == 5 && items != null)                 // requires Item
        {
          //          itemID = HexFormatter.intValue (buffer[768 + b * 2], buffer[769 + b * 2]);
          itemID = messageAddress.level;
          cell.itemRequired = items.get (itemID);
        }

        if (cell.messageType == 4)
        {
          //          value = HexFormatter.intValue (buffer[768 + b * 2], buffer[769 + b * 2]);
          itemID = messageAddress.level;
          if (value <= 100)
          {
            cell.monsterID = value;
            cell.monsters = monsters;
          }
          else
          {
            int val = (value - 64536) * -1;
            System.out.println ("Value : " + val);
            // this gives Index error: 20410, Size 104 in Wizardry_III/legacy2.dsk
            if (items != null && val < items.size ())
              cell.itemObtained = items.get (val);          // check this
            if (cell.itemObtained == null)
              System.out.printf ("Item %d not found%n", val);
          }
        }
        break;

      case 12:
        MazeAddress monsterAddress = getAddress (b);
        //        cell.monsterID = HexFormatter.intValue (buffer[832 + b * 2], buffer[833 + b * 2]);
        cell.monsterID = monsterAddress.column;
        cell.monsters = monsters;
        break;

      default:
        System.out.println ("Unknown extra: " + cellFlag);
        cell.unknown = cellFlag;
        break;
    }

    return cell;
  }

  private MazeAddress getAddress (int b)      // 0:F
  {
    int x = b * 2;
    return new MazeAddress (HexFormatter.intValue (buffer[768 + x], buffer[769 + x]),
        HexFormatter.intValue (buffer[800 + x], buffer[801 + x]),
        HexFormatter.intValue (buffer[832 + x], buffer[833 + x]));
  }

  private Message getMessage (int messageNo)
  {
    if (messages == null)
      return null;

    for (Message m : messages)
      if (m.match (messageNo))
        return m;

    return null;
  }

  private Monster getMonster (int monsterNo)
  {
    if (monsters == null)
      return null;

    for (Monster m : monsters)
      if (m.match (monsterNo))
        return m;

    return null;
  }

  public int getRows ()
  {
    return 20;
  }

  public int getColumns ()
  {
    return 20;
  }
  /*
   * Pascal code decompiled by Tom Ewers
   * 
   *         TWALL = (OPEN, WALL, DOOR, HIDEDOOR);
             
             TSQUARE = (NORMAL, STAIRS, PIT, CHUTE, SPINNER, DARK, TRANSFER,
                       OUCHY, BUTTONZ, ROCKWATE, FIZZLE, SCNMSG, ENCOUNTE);
              
             TMAZE = RECORD
               W : PACKED ARRAY[ 0..19] OF PACKED ARRAY[ 0..19] OF TWALL;
               S : PACKED ARRAY[ 0..19] OF PACKED ARRAY[ 0..19] OF TWALL;
               E : PACKED ARRAY[ 0..19] OF PACKED ARRAY[ 0..19] OF TWALL;
               N : PACKED ARRAY[ 0..19] OF PACKED ARRAY[ 0..19] OF TWALL;
               
               FIGHTS : PACKED ARRAY[ 0..19] OF PACKED ARRAY[ 0..19] OF 0..1;
               
               SQREXTRA : PACKED ARRAY[ 0..19] OF PACKED ARRAY[ 0..19] OF 0..15;
               
               SQRETYPE : PACKED ARRAY[ 0..15] OF TSQUARE;
               
               AUX0 : PACKED ARRAY[ 0..15] OF INTEGER;
               AUX1 : PACKED ARRAY[ 0..15] OF INTEGER;
               AUX2 : PACKED ARRAY[ 0..15] OF INTEGER;
               
               ENMYCALC : PACKED ARRAY[ 1..3] OF RECORD
                            MINENEMY : INTEGER;
                            MULTWORS : INTEGER;
                            WORSE01  : INTEGER;
                            RANGE0N  : INTEGER;
                            PERCWORS : INTEGER;
                          END;
            END;
   */
}