var core = use('core')
var camera = use('camera')
var compositor = use('compositor')
var input = use('input')
var shape = use('shape2d')
var text2d = use('text2d')
var random = use('random')

var COLS = 10, ROWS = 20
var TILE = 8
var GW = COLS * TILE + 80  // extra space for next piece + score
var GH = ROWS * TILE

var game_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
var hud_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})

// Action mapping with possession for discrete inputs
input.configure({
  action_map: {
    left: ['a', 'left'],
    right: ['d', 'right'],
    rotate: ['w', 'up'],
    soft_drop: ['s', 'down'],
    hard_drop: ['space']
  }
})

// Tetrimino definitions
var SHAPES = {
  I: {color: {r: 0, g: 1, b: 1, a: 1}, blocks: [[0, 0], [1, 0], [2, 0], [3, 0]]},
  O: {color: {r: 1, g: 1, b: 0, a: 1}, blocks: [[0, 0], [1, 0], [0, 1], [1, 1]]},
  T: {color: {r: 1, g: 0, b: 1, a: 1}, blocks: [[0, 0], [1, 0], [2, 0], [1, 1]]},
  S: {color: {r: 0, g: 1, b: 0, a: 1}, blocks: [[1, 0], [2, 0], [0, 1], [1, 1]]},
  Z: {color: {r: 1, g: 0, b: 0, a: 1}, blocks: [[0, 0], [1, 0], [1, 1], [2, 1]]},
  J: {color: {r: 0, g: 0, b: 1, a: 1}, blocks: [[0, 0], [0, 1], [1, 1], [2, 1]]},
  L: {color: {r: 1, g: 0.5, b: 0, a: 1}, blocks: [[2, 0], [0, 1], [1, 1], [2, 1]]}
}
var shapeKeys = array(SHAPES)

// Board: 2D array, null or color object
var board = []
// Board shapes: one shape per cell, toggled visible
var board_shapes = []

function init_board() {
  board = []
  board_shapes = []
  var r = 0, c = 0
  for (r = 0; r < ROWS; r++) {
    var row = []
    var srow = []
    for (c = 0; c < COLS; c++) {
      push(row, null)
      push(srow, shape.rect({
        pos: {x: c * TILE + TILE / 2, y: r * TILE + TILE / 2},
        width: TILE - 1, height: TILE - 1,
        fill: {r: 0.5, g: 0.5, b: 0.5, a: 1},
        plane: 'game', layer: 0, visible: false
      }))
    }
    push(board, row)
    push(board_shapes, srow)
  }
}

// Active piece
var piece = null, pieceX = 0, pieceY = 0
var piece_shapes = []
var nextPiece = null

// Score
var score = 0, linesCleared = 0, level = 0
var gameOver = false

// Gravity
var baseGravity = 0.8
var gravityTimer = 0
var softDropping = false

// Horizontal DAS
var hMoveTimer = 0
var hDelay = 0.18
var hRepeat = 0.05
var hDir = 0
var hHeld = false

// Rotate lock
var rotateHeld = false

var score_label = text2d({
  text: "Score: 0", pos: {x: COLS * TILE + 5, y: GH - 15},
  plane: 'hud', size: 8, color: {r: 1, g: 1, b: 1, a: 1}
})
var level_label = text2d({
  text: "Level: 0", pos: {x: COLS * TILE + 5, y: GH - 30},
  plane: 'hud', size: 8, color: {r: 1, g: 1, b: 1, a: 1}
})
var next_label = text2d({
  text: "Next:", pos: {x: COLS * TILE + 5, y: 50},
  plane: 'hud', size: 8, color: {r: 1, g: 1, b: 1, a: 1}
})

// Next piece display shapes
var next_shapes = []
var ns = 0
for (ns = 0; ns < 4; ns++) {
  push(next_shapes, shape.rect({
    pos: {x: 0, y: 0}, width: TILE - 1, height: TILE - 1,
    fill: {r: 1, g: 1, b: 1, a: 1}, plane: 'game', layer: 2, visible: false
  }))
}

function random_shape() {
  var key = shapeKeys[floor(random.random() * length(shapeKeys))]
  return {
    type: key,
    color: SHAPES[key].color,
    blocks: array(SHAPES[key].blocks, function(b) { return [b[0], b[1]] })
  }
}

function collides(px, py, blocks) {
  var i = 0, x = 0, y = 0
  for (i = 0; i < length(blocks); i++) {
    x = px + blocks[i][0]
    y = py + blocks[i][1]
    if (x < 0 || x >= COLS || y < 0 || y >= ROWS) return true
    if (y >= 0 && board[y][x]) return true
  }
  return false
}

function lock_piece() {
  var i = 0, x = 0, y = 0
  for (i = 0; i < length(piece.blocks); i++) {
    x = pieceX + piece.blocks[i][0]
    y = pieceY + piece.blocks[i][1]
    if (y >= 0 && y < ROWS && x >= 0 && x < COLS) {
      board[y][x] = piece.color
      board_shapes[y][x].fill = piece.color
      board_shapes[y][x].visible = true
    }
  }
}

function rotate_blocks(blocks) {
  var i = 0, x = 0, y = 0
  for (i = 0; i < length(blocks); i++) {
    x = blocks[i][0]
    y = blocks[i][1]
    blocks[i][0] = y
    blocks[i][1] = -x
  }
}

function clear_lines() {
  var lines = 0
  var r = ROWS - 1, c = 0, full = false, newRow = null
  while (r >= 0) {
    full = true
    for (c = 0; c < COLS; c++) {
      if (!board[r][c]) { full = false; break }
    }
    if (full) {
      lines++
      // Shift rows down
      var sr = r
      while (sr > 0) {
        for (c = 0; c < COLS; c++) {
          board[sr][c] = board[sr - 1][c]
          if (board[sr][c]) {
            board_shapes[sr][c].fill = board[sr][c]
            board_shapes[sr][c].visible = true
          } else {
            board_shapes[sr][c].visible = false
          }
        }
        sr--
      }
      for (c = 0; c < COLS; c++) {
        board[0][c] = null
        board_shapes[0][c].visible = false
      }
    } else {
      r--
    }
  }
  if (lines == 1) score += 100
  else if (lines == 2) score += 300
  else if (lines == 3) score += 500
  else if (lines == 4) score += 800
  linesCleared += lines
  level = floor(linesCleared / 10)
  score_label.text = "Score: " + score
  level_label.text = "Level: " + level
}

function update_piece_shapes() {
  var i = 0
  for (i = 0; i < 4; i++) {
    var bx = pieceX + piece.blocks[i][0]
    var by = pieceY + piece.blocks[i][1]
    piece_shapes[i].pos.x = bx * TILE + TILE / 2
    piece_shapes[i].pos.y = by * TILE + TILE / 2
    piece_shapes[i].fill = piece.color
    piece_shapes[i].visible = true
  }
}

function update_next_display() {
  var i = 0
  for (i = 0; i < 4; i++) {
    var bx = nextPiece.blocks[i][0]
    var by = nextPiece.blocks[i][1]
    next_shapes[i].pos.x = (COLS + 1) * TILE + bx * TILE + TILE / 2
    next_shapes[i].pos.y = 20 + by * TILE + TILE / 2
    next_shapes[i].fill = nextPiece.color
    next_shapes[i].visible = true
  }
}

function spawn_piece() {
  piece = nextPiece || random_shape()
  nextPiece = random_shape()
  pieceX = 3
  pieceY = 0
  if (collides(pieceX, pieceY, piece.blocks)) {
    gameOver = true
    var i = 0
    for (i = 0; i < 4; i++) piece_shapes[i].visible = false
    return
  }
  update_piece_shapes()
  update_next_display()
}

function place_piece() {
  lock_piece()
  clear_lines()
  spawn_piece()
}

// Create 4 shapes for active piece (layer 1 = on top of board)
var pi = 0
for (pi = 0; pi < 4; pi++) {
  push(piece_shapes, shape.rect({
    pos: {x: 0, y: 0}, width: TILE - 1, height: TILE - 1,
    fill: {r: 1, g: 1, b: 1, a: 1}, plane: 'game', layer: 1, visible: false
  }))
}

init_board()
spawn_piece()

// Compositor
var comp_config = {
  clear: {r: 0, g: 0, b: 0, a: 1},
  planes: [
    {name: 'game', camera: game_cam, resolution: {width: GW, height: GH}, presentation: 'letterbox'},
    {name: 'hud', camera: hud_cam, resolution: {width: GW, height: GH}, presentation: 'stretch'}
  ]
}

core.start({
  width: 640, height: 480, title: "Tetris",

  update: function(dt) {
    if (gameOver) return

    var down = input.player1().down()
    var test = null

    // Horizontal movement with DAS
    var wantLeft = down.left
    var wantRight = down.right
    var newDir = 0
    if (wantLeft && !wantRight) newDir = -1
    else if (wantRight && !wantLeft) newDir = 1

    if (newDir != 0) {
      if (newDir != hDir || !hHeld) {
        // First press
        if (!collides(pieceX + newDir, pieceY, piece.blocks)) pieceX += newDir
        hMoveTimer = hDelay
        hDir = newDir
        hHeld = true
      } else {
        hMoveTimer -= dt
        if (hMoveTimer <= 0) {
          if (!collides(pieceX + newDir, pieceY, piece.blocks)) pieceX += newDir
          hMoveTimer = hRepeat
        }
      }
    } else {
      hDir = 0
      hHeld = false
      hMoveTimer = 0
    }

    // Rotate (once per press)
    if (down.rotate) {
      if (!rotateHeld) {
        rotateHeld = true
        test = array(piece.blocks, function(b) { return [b[0], b[1]] })
        rotate_blocks(test)
        if (!collides(pieceX, pieceY, test)) piece.blocks = test
      }
    } else {
      rotateHeld = false
    }

    // Soft drop
    softDropping = down.soft_drop

    // Hard drop (once per press)
    if (down.hard_drop) {
      while (!collides(pieceX, pieceY + 1, piece.blocks)) pieceY++
      place_piece()
      update_piece_shapes()
      return
    }

    // Gravity
    var fallSpeed = softDropping ? 10 : 1
    gravityTimer += dt * fallSpeed
    var dropInterval = max(0.05, baseGravity - level * 0.05)
    if (gravityTimer >= dropInterval) {
      gravityTimer = 0
      if (!collides(pieceX, pieceY + 1, piece.blocks)) {
        pieceY++
      } else {
        place_piece()
      }
    }

    update_piece_shapes()
  },

  render: function() {
    return compositor.execute(compositor.compile(comp_config))
  }
})
