import {
  Application,
  Assets,
  Container,
  EventEmitter,
  Graphics,
  Sprite,
  Text,
  Ticker
} from 'pixi.js'
import { Scale, type ScaleFunction } from '@/game/utilities/scale'
import type { GameState, Movement, TileDto } from '@/common/interfaces'
import { Tile } from '@/game/Tile'
import { DIRECTIONS, createContainer, isTilePair } from '@/common/helpers'
import { createText } from '@/game/utilities/fonts'
import { Dot } from '@/game/Dot'
import { LoggingService } from '@/services/LoggingService'
import { inject } from 'vue'

export class Board extends EventEmitter {
  private _scale: number = 1
  private _canMove: boolean = false
  private logger = inject<LoggingService>('logger')!

  ticker: Ticker
  height: number
  width: number
  grain: number = 25
  scaleY: ScaleFunction
  scaleX: ScaleFunction
  state?: GameState
  container!: Container
  initialContainer!: Container
  tilesContainer!: Container
  leftContainer!: Container
  rightContainer!: Container
  interactionContainer!: Container
  textContainer!: Container
  tiles: Tile[] = []
  boneyard: Tile[] = []
  movements: any[] = []
  freeEnds?: [number, number]
  leftTile?: Tile
  rightTile?: Tile
  nextTile?: Tile
  textWaitForPlayers!: Text
  textYourTurn!: Text
  leftDirection: string = 'west'
  rightDirection: string = 'east'
  playerHand: Tile[] = []
  firstTile?: Tile

  constructor(app: Application) {
    super()
    this.ticker = app.ticker
    this.height = app.canvas.height - 130
    this.width = app.canvas.width
    this.scaleX = Scale([0, this.width], [0, this.width])
    this.scaleY = Scale([0, this.height], [0, this.height])
    this.calculateScale()

    this.container = createContainer({
      width: this.width,
      height: this.height,
      parent: app.stage
    })

    const background = new Sprite(Assets.get('bg-1'))
    // background.width = this.width
    // background.height = this.height
    this.container.addChild(background)

    this.initialContainer = createContainer({
      width: this.width,
      height: this.height,
      // color: 0x1e2f23,
      visible: false,
      parent: this.container
    })

    this.tilesContainer = createContainer({
      width: this.width,
      height: this.height,
      // color: 0x1e2f23,
      parent: this.container
    })

    this.interactionContainer = createContainer({
      width: this.width,
      height: this.height,
      parent: this.container,
      visible: false
    })

    const verticalLine = new Graphics()
      .moveTo(this.scaleX(0), 0)
      .lineTo(this.scaleX(0), this.height)
      .stroke(0xff0000)
    const horizontalLine = new Graphics()
      .moveTo(0, this.scaleY(0))
      .lineTo(this.width, this.scaleY(0))
      .stroke(0xff0000)
    verticalLine.alpha = 0.2
    horizontalLine.alpha = 0.2
    this.tilesContainer.addChild(verticalLine)
    this.tilesContainer.addChild(horizontalLine)

    this.textContainer = createContainer({
      width: this.width,
      height: this.height,
      parent: this.container
    })

    this.showText('Starting game...')
  }

  private calculateScale() {
    const scaleXSteps = Math.floor(this.width / (this.grain * this.scale)) / 2
    const scaleYSteps = Math.floor(this.height / (this.grain * this.scale)) / 2
    this.scaleX = Scale([-scaleXSteps, scaleXSteps], [0, this.width])
    this.scaleY = Scale([-scaleYSteps, scaleYSteps], [0, this.height])
  }

  get count() {
    return this.tiles.length
  }

  get scale() {
    return this._scale
  }

  set scale(value: number) {
    this._scale = value
    this.calculateScale()
  }

  get canMove() {
    return this._canMove
  }

  set canMove(value: boolean) {
    this._canMove = value
    this.updateCanMoveText()
  }

  setPlayerHand(tiles: Tile[]) {
    this.playerHand = tiles
  }

  showText(text: string) {
    this.textContainer.removeChildren()
    this.textContainer.addChild(createText(text, this.scaleX(0), 100))
  }

  private updateCanMoveText() {
    if (this.canMove) {
      this.showText('Your turn!')
    } else {
      this.showText('Waiting for players')
    }
  }

  setState(state: GameState, playerId: string) {
    this.state = state
    const { lastMove } = state

    if (lastMove === null) {
      return
    }
    if (
      lastMove !== null &&
      lastMove.tile !== undefined &&
      lastMove.tile.pips !== undefined &&
      lastMove.playerId !== playerId
    ) {
      const tile = new Tile(lastMove.tile.id, this.ticker, lastMove.tile.pips, this.scale)
      this.nextTile = tile
      lastMove.tile = tile.toPlain()
      this.movements.push(lastMove)
      this.addTile(tile, lastMove)
      this.setFreeEnd(lastMove)
    }
  }

  getOrientationII(tile: TileDto, side: string) {
    let orientation = ''
    const isPair = isTilePair(tile)
    if (side === 'left') {
      if (this.freeEnds !== undefined && tile.pips !== undefined) {
        const isInverted = this.freeEnds[0] === tile.pips[1]
        if (this.leftDirection === 'east') {
          if (!isPair) {
            orientation = isInverted ? 'east' : 'west'
          } else {
            orientation = 'north'
          }
        } else if (this.leftDirection === 'west') {
          if (!isPair) {
            orientation = isInverted ? 'west' : 'east'
          } else {
            orientation = 'north'
          }
        } else if (this.leftDirection === 'north') {
          if (!isPair) {
            orientation = isInverted ? 'north' : 'south'
          } else {
            orientation = 'west'
          }
        } else if (this.leftDirection === 'south') {
          if (!isPair) {
            orientation = isInverted ? 'south' : 'north'
          } else {
            orientation = 'west'
          }
        }
      }
    } else if (side === 'right') {
      if (this.freeEnds !== undefined && tile.pips !== undefined) {
        const isInverted = this.freeEnds[1] === tile.pips[0]
        if (this.rightDirection === 'east') {
          if (!isPair) {
            orientation = isInverted ? 'west' : 'east'
          } else {
            orientation = 'north'
          }
        } else if (this.rightDirection === 'west') {
          if (!isPair) {
            orientation = isInverted ? 'east' : 'west'
          } else {
            orientation = 'north'
          }
        } else if (this.rightDirection === 'north') {
          if (!isPair) {
            orientation = isInverted ? 'south' : 'north'
          } else {
            orientation = 'west'
          }
        } else if (this.rightDirection === 'south') {
          if (!isPair) {
            orientation = isInverted ? 'north' : 'south'
          } else {
            orientation = 'west'
          }
        }
      }
    }
    return orientation
  }

  addTile(tile: Tile, move: Movement) {
    let orientation = ''
    let x: number =
      move.type === 'left'
        ? this.scaleX.inverse(this.leftTile?.x ?? 0)
        : this.scaleX.inverse(this.rightTile?.x ?? 0)
    let y: number =
      move.type === 'left'
        ? this.scaleY.inverse(this.leftTile?.y ?? 0)
        : this.scaleY.inverse(this.rightTile?.y ?? 0)
    const isLeft = move.type === 'left'
    const tileDto = tile.toPlain()
    let direction = move.type === 'left' ? this.leftDirection : this.rightDirection

    if (this.tiles.length === 0) {
      x = 0
      y = 0
      if (tile.isPair()) {
        orientation = 'north'
      } else {
        orientation = 'east'
      }
      this.firstTile = tile
    } else if (move.direction !== undefined) {
      direction = move.direction
      const availableMoves = this.nextTileValidMoves(tileDto, move.type)
      const availablePositions = this.nextTileValidPoints(tileDto, move.type, availableMoves)
      const directionIndex = DIRECTIONS.indexOf(direction)
      isLeft ? (this.leftDirection = direction) : (this.rightDirection = direction)
      const availablePosition: [number, number] | undefined = availablePositions[directionIndex]
      orientation = this.getOrientationII(tileDto, move.type)
      availablePosition && ([x, y] = availablePosition)
    } else {
      if (this.tiles.length === 0) {
        x = 0
        y = 0
        if (tile.isPair()) {
          tile.setOrientation('north')
        } else {
          tile.setOrientation('east')
        }
      } else {
        const availableMoves = this.nextTileValidMoves(tileDto, move.type)
        const availablePositions = this.nextTileValidPoints(tileDto, move.type, availableMoves)

        let directionIndex = DIRECTIONS.indexOf(direction)
        let availablePosition: [number, number] | undefined = availablePositions[directionIndex]
        let endlessLoop: number = 0
        while (endlessLoop < 4 && availablePosition === undefined) {
          directionIndex = (directionIndex + 1) % 4
          availablePosition = availablePositions[directionIndex]
          endlessLoop++
        }
        if (endlessLoop >= 4) {
          throw new Error('No available position')
        }
        direction = DIRECTIONS[directionIndex]
        isLeft ? (this.leftDirection = direction) : (this.rightDirection = direction)
        orientation = this.getOrientationII(tileDto, move.type)
        availablePosition && ([x, y] = availablePosition)
      }
    }
    const endTile = isLeft ? this.leftTile : this.rightTile
    const isEndVertical = endTile?.isVertical() ?? false
    const isNextVertical = orientation === 'north' || orientation === 'south'
    if (this.tiles.length > 0 && endTile !== undefined) {
      //!tile.equals(endTile)
      if (direction === 'east') {
        x += !isEndVertical && isNextVertical ? 0 : 1
      } else if (direction === 'west') {
        x -= !isEndVertical && isNextVertical ? 0 : 1
      } else if (direction === 'north') {
        y -= isEndVertical && !isNextVertical ? 0 : 1
      } else if (direction === 'south') {
        y += isEndVertical && !isNextVertical ? 0 : 1
      }
    }
    tile.setPosition(this.scaleX(x), this.scaleY(y))
    tile.setOrientation(orientation)
    tile.reScale(this.scale)
    this.tiles.push(tile)
    tile.addTo(this.tilesContainer)
  }

  getPlayedTile(id: string): Tile | undefined {
    return this.tiles.find((t) => t.id === id)
  }

  getTileInHand(id: string): Tile | undefined {
    return this.playerHand.find((t) => t.id === id)
  }

  setFreeEnd(move: Movement) {
    const { type, tile: tileDto } = move
    const tile = this.getPlayedTile(tileDto?.id ?? '')

    if (tile === undefined) {
      return
    }

    const { pips } = tile

    if (pips === undefined) {
      return
    }

    if (this.freeEnds === undefined) {
      this.leftTile = this.rightTile = tile
      if (tile.isPair()) {
        this.freeEnds = [pips[0], pips[1]]
      } else {
        this.freeEnds = [pips[1], pips[0]]
      }
    } else if (type === 'left') {
      this.leftTile = tile
      if (tile.isPair()) {
        this.freeEnds[0] = pips[0]
      } else {
        this.freeEnds[0] = pips[0] === this.freeEnds[0] ? pips[1] : pips[0]
      }
    } else if (type === 'right') {
      this.rightTile = tile
      if (tile.isPair()) {
        this.freeEnds[1] = pips[0]
      } else {
        this.freeEnds[1] = pips[0] === this.freeEnds[1] ? pips[1] : pips[0]
      }
    }
  }

  updateBoard(move: Movement) {
    try {
      const { tile: tileDto } = move
      const tile = this.getTileInHand(tileDto?.id ?? '')

      if (tile === undefined) {
        return
      }

      this.movements.push(move)
      this.addTile(tile, move)
      this.setFreeEnd(move)
    } catch (error) {
      this.logger.error(error, 'Error updating board')
    }
  }

  setMovesForTile(values: [boolean, boolean], tile: TileDto) {
    this.setValidEnds(values, tile)
  }

  setValidEnds(values: boolean[], tile: TileDto) {
    if (this.count === 0) {
      this.createInteractionsII('right', [[0, 0], undefined, undefined, undefined])
      return
    }

    if (values[0]) {
      const side = 'left'
      const validInteractions = this.nextTileValidMoves(tile, side)
      const validPoints = this.nextTileValidPoints(tile, side, validInteractions)
      this.createInteractionsII(side, validPoints)
    }
    if (values[1]) {
      const side = 'right'
      const validInteractions = this.nextTileValidMoves(tile, side)
      const validPoints = this.nextTileValidPoints(tile, side, validInteractions)
      this.createInteractionsII(side, validPoints)
    }
  }

  nextTileValidPoints(
    tile: TileDto,
    side: string,
    validMoves: boolean[]
  ): ([number, number] | undefined)[] {
    const isLeft = side === 'left'
    const end = isLeft ? this.leftTile : this.rightTile
    const isEndVertical = end?.isVertical() ?? false
    const direction = isLeft ? this.leftDirection : this.rightDirection
    const signX = direction === 'west' ? -1 : 1
    const signY = direction === 'south' ? 1 : -1
    const isEndPair = end?.isPair() ?? false

    const x = this.scaleX.inverse(end?.x ?? 0)
    const y = this.scaleY.inverse(end?.y ?? 0)

    let pointNorth: [number, number] | undefined = undefined
    let pointSouth: [number, number] | undefined = undefined
    let pointEast: [number, number] | undefined = undefined
    let pointWest: [number, number] | undefined = undefined

    if (this.count !== 0) {
      if (validMoves[0]) {
        // north
        if (isEndVertical) {
          pointNorth = [x, y - 3]
        } else {
          pointNorth = isEndPair ? [x, y - 2] : [x + 1 * signX, y - 2]
        }
      }
      if (validMoves[2]) {
        // south
        if (isEndVertical) {
          pointSouth = [x, y + 3]
        } else {
          pointSouth = isEndPair ? [x, y + 2] : [x + 1 * signX, y + 2]
        }
      }
      if (validMoves[1]) {
        // east
        if (isEndVertical) {
          pointEast = isEndPair ? [x + 2, y] : [x + 2, y + 1 * signY]
        } else {
          pointEast = [x + 3, y]
        }
      }
      if (validMoves[3]) {
        // west
        if (isEndVertical) {
          pointWest = isEndPair ? [x - 2, y] : [x - 2, y + 1 * signY]
        } else {
          pointWest = [x - 3, y]
        }
      }
    }

    return [pointNorth, pointEast, pointSouth, pointWest]
  }

  createInteractionsII(side: string, validInteractions: ([number, number] | undefined)[]) {
    if (validInteractions[0] !== undefined) {
      this.addInteraction(validInteractions[0][0], validInteractions[0][1], side, 'north')
    }
    if (validInteractions[1] !== undefined) {
      this.addInteraction(validInteractions[1][0], validInteractions[1][1], side, 'east')
    }
    if (validInteractions[2] !== undefined) {
      this.addInteraction(validInteractions[2][0], validInteractions[2][1], side, 'south')
    }
    if (validInteractions[3] !== undefined) {
      this.addInteraction(validInteractions[3][0], validInteractions[3][1], side, 'west')
    }
  }

  cleanInteractions() {
    this.interactionContainer.removeChildren()
  }

  addInteraction(x: number, y: number, side: string, direction?: string) {
    const dot = new Dot(this.ticker, this.scale)
    dot.alpha = 0.5
    dot.interactive = true
    dot.on('pointerdown', () => {
      this.emit(`${side}Click`, direction && { direction, x, y })
      this.cleanInteractions()
    })
    dot.on('pointerover', () => {
      dot.alpha = 1
    })
    dot.on('pointerout', () => {
      dot.alpha = 0.5
    })
    dot.setPosition(this.scaleX(x), this.scaleY(y))
    dot.addTo(this.interactionContainer)
  }

  nextTileValidMoves(tile: TileDto, side: string): boolean[] {
    if (tile === undefined || this.freeEnds === undefined) return [false, false, false, false]
    if (this.count === 0) return [false, true, false, true] // depends on game mode

    const isLeft = side === 'left'
    const end = isLeft ? this.freeEnds[0] : this.freeEnds[1]
    const endTile = isLeft ? this.leftTile : this.rightTile
    const direction = isLeft ? this.leftDirection : this.rightDirection
    const signX = isLeft ? -1 : 1
    const signY = direction === 'south' ? 1 : -1
    const validMove = tile.pips?.includes(end) ?? false
    const endX = endTile?.x ?? 0
    const endY = endTile?.y ?? 0
    const tileHeight = endTile?.height ?? 0
    const margin = 20
    const spaceNeeded = tileHeight / 2 + tileHeight + margin
    const isSecond = this.count === 1
    const isPair = isTilePair(tile)

    let canPlayNorth = false
    let canPlaySouth = false
    let canPlayEast = false
    let canPlayWest = false

    if (validMove) {
      canPlayEast = direction !== 'west' && endX + signX * spaceNeeded < this.width
      canPlayWest = direction !== 'east' && endX + signX * spaceNeeded > 0
      canPlayNorth = direction !== 'south' && endY + signY * spaceNeeded > 0
      canPlaySouth = direction !== 'north' && endY + signY * spaceNeeded < this.height
    }

    if (isSecond) {
      canPlayNorth = canPlaySouth = false
    }

    if (isPair && !endTile?.isVertical()) {
      canPlayNorth = canPlaySouth = false
    }

    if (isPair && endTile?.isVertical()) {
      if (direction === 'north') {
        canPlaySouth = false
      } else if (direction === 'south') {
        canPlayNorth = false
      } else if (direction === 'east') {
        canPlayWest = false
      } else if (direction === 'west') {
        canPlayEast = false
      }
    }
    return [canPlayNorth, canPlayEast, canPlaySouth, canPlayWest]
  }

  gameFinished() {
    this.tiles = []
    this.boneyard = []
    this.movements = []
    this.playerHand = []
    this.freeEnds = undefined
    this.leftTile = undefined
    this.rightTile = undefined
    this.nextTile = undefined
    this.leftDirection = 'west'
    this.rightDirection = 'east'
    this.firstTile = undefined
    this.tilesContainer.removeChildren()
    this.interactionContainer.removeChildren()
    this.showText('Game finished')
  }
}
