import { Direction, rotateDirection } from "../constants/Direction";
import { TrackType } from "../constants/TrackType";
import { Cell } from "./Cell";
import { InternalNode } from "./InternalNode";
import { PlacedPiece } from "./PlacedPiece";
import { InternalNodeType } from "../constants/InternalNodeType";

export interface PieceProps {
  readonly useInternalTracks: boolean;
  readonly internalNodeType?: InternalNodeType;
  readonly trackDefinitions: {
    readonly startPoint: Direction;
    readonly endPoint?: Direction;
    readonly type: TrackType;
  }[];
}

type Track = {
  readonly joinedPoints: {
    readonly firstPoint: Direction;
    readonly secondPoint: Direction | InternalNode;
  };
  readonly type: TrackType;
};

export class Piece {
  readonly tracks: Set<Track>;
  readonly internalNode?: InternalNode;

  constructor(pieceProps: PieceProps) {
    if (pieceProps.useInternalTracks) {
      if (!pieceProps.internalNodeType) {
        throw Error(
          "Expected to find internal node type when useInternalTracks is set",
        );
      }
      this.internalNode = new InternalNode(pieceProps.internalNodeType);
      this.tracks = new Set(
        pieceProps.trackDefinitions.map((trackDefinition) => {
          return {
            joinedPoints: {
              firstPoint: trackDefinition.startPoint,
              secondPoint: this.internalNode!,
            },
            type: trackDefinition.type,
          };
        }),
      );
    } else {
      this.internalNode = undefined;
      this.tracks = new Set(
        pieceProps.trackDefinitions.map((trackDefinition) => {
          if (!trackDefinition.endPoint) {
            throw Error("Missing end point for non-internal track");
          }
          return {
            joinedPoints: {
              firstPoint: trackDefinition.startPoint,
              secondPoint: trackDefinition.endPoint,
            },
            type: trackDefinition.type,
          };
        }),
      );
    }
  }

  rotate(rotation: 0 | 90 | 180 | 270): Piece {
    return new Piece({
      useInternalTracks: this.internalNode !== undefined,
      internalNodeType: this.internalNode?.type,
      trackDefinitions: Array.from(this.tracks).map((track) => {
        if (track.joinedPoints.secondPoint instanceof InternalNode) {
          return {
            type: track.type,
            startPoint: rotateDirection(
              track.joinedPoints.firstPoint,
              rotation,
            ),
          };
        } else {
          return {
            type: track.type,
            startPoint: rotateDirection(
              track.joinedPoints.firstPoint,
              rotation,
            ),
            endPoint: rotateDirection(track.joinedPoints.secondPoint, rotation),
          };
        }
      }),
    });
  }

  toPlacedPiece(cell: Cell) {
    return new PlacedPiece(
      new Set(
        Array.from(this.tracks).map((track) => {
          return {
            nodes: {
              firstNode: cell.getNodeAt(track.joinedPoints.firstPoint),
              secondNode:
                track.joinedPoints.secondPoint instanceof InternalNode
                  ? (track.joinedPoints.secondPoint as InternalNode)
                  : cell.getNodeAt(track.joinedPoints.secondPoint as Direction),
            },
            type: track.type,
          };
        }),
      ),
      this.internalNode,
      cell,
    );
  }
}
