/*
 Code for Life

 Copyright (C) 2019, Ocado Innovation Limited

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

 ADDITIONAL TERMS – Section 7 GNU General Public Licence

 This licence does not grant any right, title or interest in any “Ocado” logos,
 trade names or the trademark “Ocado” or any other trademarks or domain names
 owned by Ocado Innovation Limited or the Ocado group of companies or any other
 distinctive brand features of “Ocado” as may be secured from time to time. You
 must not distribute any modification of this program using the trademark
 “Ocado” or claim any affiliation or association with Ocado or its employees.

 You are not authorised to use the name Ocado (or any of its trade names) or
 the names of any author or contributor in advertising or for publicity purposes
 pertaining to the distribution of this program, without the prior written
 authorisation of Ocado.

 Any propagation, distribution or conveyance of this program must include this
 copyright notice and these terms. You must not misrepresent the origins of this
 program; modified versions of the program must be marked as such and not
 identified as the original program.
 */
'use strict'

var ocargo = ocargo || {}

var GRID_WIDTH = 10
var GRID_HEIGHT = 8
var GRID_SPACE_SIZE = 100
var PAPER_WIDTH = GRID_SPACE_SIZE * GRID_WIDTH
var PAPER_HEIGHT = GRID_SPACE_SIZE * GRID_HEIGHT
var PAPER_PADDING = 30
var EXTENDED_PAPER_WIDTH = PAPER_WIDTH + 2 * PAPER_PADDING
var EXTENDED_PAPER_HEIGHT = PAPER_HEIGHT + 2 * PAPER_PADDING

var DEFAULT_CHARACTER_WIDTH = 40
var DEFAULT_CHARACTER_HEIGHT = 20

var COW_WIDTH = 50
var COW_HEIGHT = 50

var zoom = 15

var gameElement = document.getElementById('paper')

var evCache = new Array()
var prevDiff = -1

var currentWidth = PAPER_WIDTH
var currentHeight = PAPER_HEIGHT
var currentStartX = 0
var currentStartY = 0

ocargo.Drawing = function (startingPosition) {
  /*************/
  /* Constants */
  /*************/

  var characterWidth =
    typeof CHARACTER_WIDTH !== 'undefined'
      ? CHARACTER_WIDTH
      : DEFAULT_CHARACTER_WIDTH
  var characterHeight =
    typeof CHARACTER_HEIGHT !== 'undefined'
      ? CHARACTER_HEIGHT
      : DEFAULT_CHARACTER_HEIGHT

  var TRAFFIC_LIGHT_WIDTH = 60
  var TRAFFIC_LIGHT_HEIGHT = 22

  var INITIAL_CFC_OFFSET_X = -105
  var INITIAL_CFC_OFFSET_Y = -7

  var DESTINATION_NOT_VISITED_COLOUR = 'red'
  var DESTINATION_VISITED_COLOUR = 'green'

  /*********/
  /* State */
  /*********/

  var paper = new Raphael('paper', EXTENDED_PAPER_WIDTH, EXTENDED_PAPER_HEIGHT)
  var roadImages = []

  var lightImages = {}
  var destinationImages = {}

  var character

  if (!ocargo.Drawing.inLevelEditor()) {
    character = new ocargo.Character(
      paper,
      CHARACTER_URL,
      WRECKAGE_URL,
      characterWidth,
      characterHeight,
      startingPosition,
      NIGHT_MODE
    )
  }

  paper.setViewBox(
    currentStartX,
    currentStartY,
    EXTENDED_PAPER_WIDTH,
    EXTENDED_PAPER_HEIGHT
  )

  /**
   * Zooming
   */
  function zoomMap (shouldZoomOut) {
    if (shouldZoomOut) {
      let newX = currentStartX - zoom
      let newY = currentStartY - zoom

      currentHeight = currentHeight + zoom * 2
      currentWidth = currentWidth + zoom * 2

      paper.setViewBox(newX, newY, currentWidth, currentHeight)

      currentStartX = newX
      currentStartY = newY
    } else {
      let newX = currentStartX + zoom
      let newY = currentStartY + zoom

      currentHeight = currentHeight - zoom * 2
      currentWidth = currentWidth - zoom * 2

      paper.setViewBox(newX, newY, currentWidth, currentHeight)

      currentStartX = newX
      currentStartY = newY
    }
  }

  $('#zoomIn').click(function () {
    zoomMap(false)
  })
  $('#zoomOut').click(function () {
    zoomMap(true)
  })

  /**
   * Map moving
   */
  var currentMousePos = { x: -1, y: -1 }

  var isMouseDown = false
  var prevX = 0
  var prevY = 0

  this.enablePanning = function () {
    var $paper = $('#paper')

    $paper.mousedown(function (event) {
      isMouseDown = true
    })

    $paper.mouseup(function (event) {
      isMouseDown = false
    })

    $paper.mousemove(function (event) {
      prevX = currentMousePos.x
      prevY = currentMousePos.y
      currentMousePos.x = event.pageX
      currentMousePos.y = event.pageY

      if (isMouseDown) {
        var deltaX = prevX - currentMousePos.x
        var deltaY = prevY - currentMousePos.y
        currentStartX = currentStartX + deltaX
        currentStartY = currentStartY + deltaY
        paper.setViewBox(
          currentStartX,
          currentStartY,
          currentWidth,
          currentHeight
        )
      }
    })
  }

  this.reset = function () {
    character.reset()
  }

  /*********************/
  /* Preloading images */
  /*********************/
  // Used by level editor to preload road tiles to prevent jittery drawing

  this.preloadRoadTiles = function () {
    var tiles = ['dead_end', 'crossroads', 'straight', 't_junction', 'turn']
    var tileImages = []
    var path = ocargo.Drawing.raphaelImageDir + 'road_tiles/'

    for (var i = 0; i < tiles.length; i++) {
      tileImages.push(
        paper.image(
          path + 'road/' + tiles[i] + '.svg',
          0,
          0,
          GRID_SPACE_SIZE,
          GRID_SPACE_SIZE
        )
      )
      tileImages.push(
        paper.image(
          path + 'path/' + tiles[i] + '.svg',
          0,
          0,
          GRID_SPACE_SIZE,
          GRID_SPACE_SIZE
        )
      )
    }

    for (var i = 0; i < tileImages.length; i++) {
      tileImages[i].remove()
    }
  }

  function calculateCFCInitialPosition (startNode) {
    var coord = ocargo.Drawing.translate(startNode.coordinate)
    return {
      x: coord.x * GRID_SPACE_SIZE + INITIAL_CFC_OFFSET_X + PAPER_PADDING,
      y: coord.y * GRID_SPACE_SIZE + INITIAL_CFC_OFFSET_Y + PAPER_PADDING
    }
  }

  function calculateInitialRotation (previousNode, startNode) {
    var nodeAngleRadians = ocargo.calculateNodeAngle(previousNode, startNode)
    var nodeAngleDegrees = nodeAngleRadians * (180 / Math.PI)
    return -nodeAngleDegrees // Calculation is counterclockwise, transformations are clockwise
  }

  // Return a pair of letters which represent the orientation of the turn
  // Directions are relative to the centre point of the turn
  // E.g. an L-shape turn will be described as 'UR'
  // D: Down, U: Up, L: Left, R:Right
  function getRoadLetters (previous, node1, node2) {
    previous = ocargo.Drawing.translate(previous)
    node1 = ocargo.Drawing.translate(node1)
    node2 = ocargo.Drawing.translate(node2)

    if (
      isHorizontal(node1, node2) &&
      (previous === null || isHorizontal(previous, node1))
    ) {
      return 'H'
    } else if (
      isVertical(node1, node2) &&
      (previous === null || isVertical(previous, node1))
    ) {
      return 'V'
      // Handle turns.
    } else {
      if (isProgressive(previous.x, node1.x)) {
        return nextPointAbove(node1, node2) ? 'DL' : 'UL'
      }
      if (isProgressive(node1.x, previous.x)) {
        return nextPointAbove(node1, node2) ? 'DR' : 'UR'
      }
      if (isProgressive(previous.y, node1.y)) {
        return nextPointFurther(node1, node2) ? 'UR' : 'UL'
      }
      if (isProgressive(node1.y, previous.y)) {
        return nextPointFurther(node1, node2) ? 'DR' : 'DL'
      }
    }
  }

  function isHorizontal (prev, next) {
    return prev.y === next.y
  }

  function isVertical (prev, next) {
    return prev.x === next.x
  }

  function nextPointAbove (curr, next) {
    return curr.y < next.y
  }

  function nextPointFurther (curr, next) {
    return curr.x < next.x
  }

  function isProgressive (coord1, coord2) {
    return coord1 < coord2
  }

  // Returns the direction of the middle branch
  // E.g. T-shaped junction will be described as 'down'
  function tJunctionOrientation (middle, node1, node2, node3) {
    var res1 = getRoadLetters(node1, middle, node2)
    var res2 = getRoadLetters(node2, middle, node3)

    if (res1 === 'H' && res2 === 'DR') {
      return 'down'
    } else if (res1 === 'UR' && res2 === 'DR') {
      return 'right'
    } else if (res1 === 'UL' && res2 === 'V') {
      return 'left'
    } else {
      return 'up'
    }
  }
  /***************/
  /** Rendering **/
  /***************/

  this.renderDestinations = function (destinations) {
    for (var i = 0; i < destinations.length; i++) {
      var destination = destinations[i].node
      var variation = getDestinationPosition(destination)

      var destinationRect = paper
        .rect(
          destination.coordinate.x * GRID_SPACE_SIZE + PAPER_PADDING,
          PAPER_HEIGHT -
            destination.coordinate.y * GRID_SPACE_SIZE -
            100 +
            PAPER_PADDING,
          100,
          100
        )
        .attr({ stroke: DESTINATION_NOT_VISITED_COLOUR })

      var destinationHouse = paper
        .image(
          ocargo.Drawing.raphaelImageDir + HOUSE_URL,
          destination.coordinate.x * GRID_SPACE_SIZE +
            variation[0] +
            PAPER_PADDING,
          PAPER_HEIGHT -
            destination.coordinate.y * GRID_SPACE_SIZE -
            variation[1] +
            PAPER_PADDING,
          50,
          50
        )
        .transform('r' + variation[2])

      destinationImages[destinations[i].id] = {
        rect: destinationRect,
        house: destinationHouse
      }
    }

    //find a side of the road
    function getDestinationPosition (destination) {
      var roadLetters = []

      //might be best to just use the coordinates rather than get road letters and then convert back to directions
      if (destination.connectedNodes.length === 1) {
        var previousNode = destination.connectedNodes[0]
        var nextNode = {}
        nextNode.coordinate = new ocargo.Coordinate(
          destination.coordinate.x +
            (destination.coordinate.x - previousNode.coordinate.x),
          destination.coordinate.y +
            (destination.coordinate.y - previousNode.coordinate.y)
        )
        roadLetters.push(
          getRoadLetters(
            previousNode.coordinate,
            destination.coordinate,
            nextNode.coordinate
          )
        )
      } else {
        for (var i = 0; i < destination.connectedNodes.length; i++) {
          var previousNode = destination.connectedNodes[i]
          for (var j = i + 1; j < destination.connectedNodes.length; j++) {
            roadLetters.push(
              getRoadLetters(
                previousNode.coordinate,
                destination.coordinate,
                destination.connectedNodes[j].coordinate
              )
            )
          }
        }
      }
      var left = true
      var right = true
      var up = true
      var down = true

      //variation specifies x,y,rotation
      var variation = [25, 25, 90]

      // Set "default" variations of the house position
      // based on straight roads and turns
      if (roadLetters.indexOf('H') >= 0) {
        left = false
        right = false
        variation = [25, 25, 90]
      }
      if (roadLetters.indexOf('V') >= 0) {
        up = false
        down = false
        variation = [-25, 75, 180]
      }
      if (roadLetters.indexOf('UL') >= 0) {
        left = false
        up = false
        variation = [45, 55, 45]
      }
      if (roadLetters.indexOf('DL') >= 0) {
        left = false
        down = false
        variation = [45, 95, 315]
      }
      if (roadLetters.indexOf('UR') >= 0) {
        right = false
        up = false
        variation = [5, 55, 135]
      }
      if (roadLetters.indexOf('DR') >= 0) {
        right = false
        down = false
        variation = [5, 95, 225]
      }

      // Adapt for T-junctions and crossroads
      if (!(left || right || up || down)) {
        // 4-way junction, so hang it off to the bottom left
        variation = [-25, 25, 135]
      } else if (!(up || left || right)) {
        // T junction, road at bottom
        variation = [25, 25, 90]
      } else if (!(down || left || right)) {
        // T junction, road at top
        variation = [25, 125, 270]
      } else if (!(up || down || left)) {
        // T junction, road at right
        variation = [75, 75, 0]
      } else if (!(up || down || right)) {
        // T junction, road at left
        variation = [-25, 75, 180]
      }

      return variation
    }
  }

  this.renderOrigin = function (position) {
    var initialPosition = calculateCFCInitialPosition(position.currentNode)
    var cfc = paper.image(
      ocargo.Drawing.raphaelImageDir + CFC_URL,
      initialPosition.x,
      initialPosition.y,
      100,
      107
    )
    var rotation = calculateInitialRotation(
      position.previousNode,
      position.currentNode
    )
    var transformation = ocargo.Drawing.rotationTransformationAroundCentreOfGridSpace(
      rotation,
      position.currentNode.coordinate.x,
      position.currentNode.coordinate.y
    )
    cfc.transform(transformation)
    cfc.transform('... r90')
  }

  this.renderRoad = function (nodes) {
    for (var i = 0; i < roadImages.length; i++) {
      var image = roadImages[i]
      if (image) {
        image.remove()
      }
    }

    var path = ocargo.Drawing.raphaelImageDir + 'road_tiles/'

    path += CHARACTER_NAME === 'Van' ? 'road/' : 'path/'

    roadImages = []
    for (var i = 0; i < nodes.length; i++) {
      var node = nodes[i]
      var roadImage
      switch (node.connectedNodes.length) {
        case 1:
          roadImage = drawDeadEndRoad(node, path)
          break

        case 2:
          roadImage = drawSingleRoadSegment(
            node.connectedNodes[0],
            node,
            node.connectedNodes[1],
            path
          )
          break

        case 3:
          roadImage = drawTJunction(node, path)
          break

        case 4:
          roadImage = drawCrossRoads(node, path)
          break

        default:
          break
      }
      roadImages.push(roadImage)
    }

    function drawDeadEndRoad (node, path) {
      var previousNode = node.connectedNodes[0]

      var nextNode = {}
      nextNode.coordinate = new ocargo.Coordinate(
        node.coordinate.x + (node.coordinate.x - previousNode.coordinate.x),
        node.coordinate.y + (node.coordinate.y - previousNode.coordinate.y)
      )

      var roadLetters = getRoadLetters(
        previousNode.coordinate,
        node.coordinate,
        nextNode.coordinate
      )

      var prevFlipped = ocargo.Drawing.translate(previousNode.coordinate)
      var flipped = ocargo.Drawing.translate(node.coordinate)

      var road = paper.image(
        path + 'dead_end.svg',
        flipped.x * GRID_SPACE_SIZE + PAPER_PADDING,
        flipped.y * GRID_SPACE_SIZE + PAPER_PADDING,
        GRID_SPACE_SIZE,
        GRID_SPACE_SIZE
      )

      if (roadLetters === 'H' && prevFlipped.x < flipped.x) {
        road.rotate(
          90,
          flipped.x * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING,
          flipped.y * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING
        )
      } else if (roadLetters === 'H' && prevFlipped.x > flipped.x) {
        road.rotate(
          270,
          flipped.x * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING,
          flipped.y * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING
        )
      } else if (roadLetters === 'V' && prevFlipped.y < flipped.y) {
        road.rotate(
          180,
          flipped.x * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING,
          flipped.y * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING
        )
      }

      return road
    }

    function drawSingleRoadSegment (previousNode, node, nextNode, path) {
      var roadLetters = getRoadLetters(
        previousNode.coordinate,
        node.coordinate,
        nextNode.coordinate
      )

      var flipped = ocargo.Drawing.translate(node.coordinate)
      var roadSrc =
        path +
        (roadLetters === 'H' || roadLetters === 'V' ? 'straight' : 'turn') +
        '.svg'
      var road = paper.image(
        roadSrc,
        flipped.x * GRID_SPACE_SIZE + PAPER_PADDING,
        flipped.y * GRID_SPACE_SIZE + PAPER_PADDING,
        GRID_SPACE_SIZE,
        GRID_SPACE_SIZE
      )

      if (roadLetters === 'H') {
        road.rotate(90)
      } else if (roadLetters === 'UL') {
        road.rotate(
          90,
          flipped.x * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING,
          flipped.y * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING
        )
      } else if (roadLetters === 'UR') {
        road.rotate(
          180,
          flipped.x * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING,
          flipped.y * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING
        )
      } else if (roadLetters === 'DR') {
        road.rotate(
          270,
          flipped.x * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING,
          flipped.y * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING
        )
      }

      return road
    }

    function drawTJunction (node, path) {
      var node1 = node.connectedNodes[0]
      var node2 = node.connectedNodes[1]
      var node3 = node.connectedNodes[2]

      var flipped = ocargo.Drawing.translate(node.coordinate)

      var letters12 = getRoadLetters(
        node1.coordinate,
        node.coordinate,
        node3.coordinate
      )
      var letters13 = getRoadLetters(
        node1.coordinate,
        node.coordinate,
        node2.coordinate
      )

      var rotation = 0
      if (
        (letters12 === 'V' && (letters13 === 'UL' || letters13 === 'DL')) ||
        (letters12 === 'UL' && (letters13 === 'DL' || letters13 === 'V')) ||
        (letters12 === 'DL' && (letters13 === 'UL' || letters13 === 'V'))
      ) {
        rotation = 0
      } else if (
        (letters12 === 'H' && (letters13 === 'UL' || letters13 === 'UR')) ||
        (letters12 === 'UL' && (letters13 === 'UR' || letters13 === 'H')) ||
        (letters12 === 'UR' && (letters13 === 'UL' || letters13 === 'H'))
      ) {
        rotation = 90
      } else if (
        (letters12 === 'V' && (letters13 === 'UR' || letters13 === 'DR')) ||
        (letters12 === 'UR' && (letters13 === 'DR' || letters13 === 'V')) ||
        (letters12 === 'DR' && (letters13 === 'UR' || letters13 === 'V'))
      ) {
        rotation = 180
      } else if (
        (letters12 === 'H' && (letters13 === 'DL' || letters13 === 'DR')) ||
        (letters12 === 'DL' && (letters13 === 'DR' || letters13 === 'H')) ||
        (letters12 === 'DR' && (letters13 === 'DL' || letters13 === 'H'))
      ) {
        rotation = 270
      }

      var road = paper.image(
        path + 't_junction.svg',
        flipped.x * GRID_SPACE_SIZE + PAPER_PADDING,
        flipped.y * GRID_SPACE_SIZE + PAPER_PADDING,
        GRID_SPACE_SIZE,
        GRID_SPACE_SIZE
      )
      road.rotate(
        rotation,
        flipped.x * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING,
        flipped.y * GRID_SPACE_SIZE + GRID_SPACE_SIZE / 2 + PAPER_PADDING
      )

      return road
    }

    function drawCrossRoads (node, path) {
      var flipped = ocargo.Drawing.translate(node.coordinate)

      return paper.image(
        path + 'crossroads.svg',
        flipped.x * GRID_SPACE_SIZE + PAPER_PADDING,
        flipped.y * GRID_SPACE_SIZE + PAPER_PADDING,
        GRID_SPACE_SIZE,
        GRID_SPACE_SIZE
      )
    }
  }

  this.renderBackground = function () {
    if (!ocargo.Drawing.isMobile()) {
      paper.rect(0, 0, EXTENDED_PAPER_WIDTH, EXTENDED_PAPER_HEIGHT).attr({
        fill: 'url(' + ocargo.Drawing.raphaelImageDir + BACKGROUND_URL + ')',
        stroke: 'none'
      })
    }
  }

  this.renderDecor = function (decors) {
    for (var i = 0; i < decors.length; i++) {
      var decor = decors[i]
      var src = ocargo.Drawing.raphaelImageDir + decor.url
      var x = decor.x + PAPER_PADDING
      var y = PAPER_HEIGHT - decor.y - decor.height + PAPER_PADDING
      var width = decor.width
      var height = decor.height
      paper.image(src, x, y, width, height)
    }
  }

  this.createTrafficLightImage = function (url) {
    return paper.image(url, 0, 0, TRAFFIC_LIGHT_WIDTH, TRAFFIC_LIGHT_HEIGHT)
  }

  this.setTrafficLightImagePosition = function (
    sourceCoordinate,
    controlledCoordinate,
    image
  ) {
    // get position based on nodes
    var x = (controlledCoordinate.x + sourceCoordinate.x) / 2.0
    var y = (controlledCoordinate.y + sourceCoordinate.y) / 2.0

    // get rotation based on nodes (should face source)
    var angle = sourceCoordinate.angleTo(controlledCoordinate) * (180 / Math.PI)
    var rotation = 90 - angle

    // draw red and green lights, keep reference to both
    var drawX = x * GRID_SPACE_SIZE + TRAFFIC_LIGHT_HEIGHT + PAPER_PADDING
    var drawY =
      PAPER_HEIGHT - y * GRID_SPACE_SIZE - TRAFFIC_LIGHT_WIDTH + PAPER_PADDING

    image.transform('t' + drawX + ',' + drawY + ' r' + rotation + 's-1,1')
  }

  this.renderTrafficLights = function (trafficLights) {
    for (var i = 0; i < trafficLights.length; i++) {
      var trafficLight = trafficLights[i]
      var sourceCoordinate = trafficLight.sourceNode.coordinate
      var controlledCoordinate = trafficLight.controlledNode.coordinate

      trafficLight.greenLightEl = this.createTrafficLightImage(
        ocargo.Drawing.raphaelImageDir + 'trafficLight_green.svg'
      )
      trafficLight.redLightEl = this.createTrafficLightImage(
        ocargo.Drawing.raphaelImageDir + 'trafficLight_red.svg'
      )
      trafficLight.greenLightEl.node.id = 'trafficLight_' + i + '_green'
      trafficLight.redLightEl.node.id = 'trafficLight_' + i + '_red'

      this.setTrafficLightImagePosition(
        sourceCoordinate,
        controlledCoordinate,
        trafficLight.greenLightEl
      )
      this.setTrafficLightImagePosition(
        sourceCoordinate,
        controlledCoordinate,
        trafficLight.redLightEl
      )

      // hide light which isn't the starting state
      if (trafficLight.startingState === ocargo.TrafficLight.RED) {
        trafficLight.greenLightEl.attr({ opacity: 0 })
      } else {
        trafficLight.redLightEl.attr({ opacity: 0 })
      }

      lightImages[trafficLight.id] = [
        trafficLight.greenLightEl,
        trafficLight.redLightEl
      ]
    }
  }

  this.determineCowOrientation = function (coordinate, node) {
    var x = coordinate.x
    var y = coordinate.y

    var xOffset = 0
    var yOffset = 0
    var rotation = 0

    if (node.connectedNodes.length === 1) {
      // Deadends
      var previousNode = node.connectedNodes[0]
      var nextNode = {}
      nextNode.coordinate = new ocargo.Coordinate(
        node.coordinate.x + (node.coordinate.x - previousNode.coordinate.x),
        node.coordinate.y + (node.coordinate.y - previousNode.coordinate.y)
      )

      var roadLetters = getRoadLetters(
        previousNode.coordinate,
        node.coordinate,
        nextNode.coordinate
      )

      if (roadLetters === 'V') {
        rotation = 90
      }
    } else if (node.connectedNodes.length === 2) {
      // Turns
      var previousNode = node.connectedNodes[0]
      var nextNode = node.connectedNodes[1]

      var roadLetters = getRoadLetters(
        previousNode.coordinate,
        node.coordinate,
        nextNode.coordinate
      )

      if (roadLetters === 'V') {
        rotation = 90
      } else if (roadLetters === 'UL') {
        xOffset = -0.15 * GRID_SPACE_SIZE
        yOffset = -0.15 * GRID_SPACE_SIZE
        rotation = -45
      } else if (roadLetters === 'UR') {
        xOffset = +0.15 * GRID_SPACE_SIZE
        yOffset = -0.15 * GRID_SPACE_SIZE
        rotation = 45
      } else if (roadLetters === 'DL') {
        xOffset = -0.15 * GRID_SPACE_SIZE
        yOffset = +0.15 * GRID_SPACE_SIZE
        rotation = -135
      } else if (roadLetters === 'DR') {
        xOffset = +0.15 * GRID_SPACE_SIZE
        yOffset = +0.15 * GRID_SPACE_SIZE
        rotation = 135
      }
    } else if (node.connectedNodes.length === 3) {
      // T-junctions
      var previousNode = node.connectedNodes[0]
      var nextNode = node.connectedNodes[1]
      var nextNextNode = node.connectedNodes[2]
      var res = tJunctionOrientation(
        node.coordinate,
        previousNode.coordinate,
        nextNode.coordinate,
        nextNextNode.coordinate
      )
      if (res === 'down') {
        rotation = 180
      } else if (res === 'right') {
        rotation = 90
      } else if (res === 'left') {
        rotation = -90
      } else if (res === 'top') {
      }
    }

    var drawX =
      (x + 0.5) * GRID_SPACE_SIZE - COW_WIDTH / 2 + xOffset + PAPER_PADDING
    var drawY =
      PAPER_HEIGHT -
      (y + 0.5) * GRID_SPACE_SIZE -
      COW_HEIGHT / 2 +
      yOffset +
      PAPER_PADDING

    return { drawX: drawX, drawY: drawY, rotation: rotation }
  }

  this.createCowImage = function (type) {
    return paper.image(
      ocargo.Drawing.raphaelImageDir + ocargo.Drawing.cowUrl(type),
      0,
      0,
      COW_WIDTH,
      COW_HEIGHT
    )
  }

  this.setCowImagePosition = function (coordinate, image, node) {
    var res = this.determineCowOrientation(coordinate, node)

    image.transform('t' + res.drawX + ',' + res.drawY + 'r' + res.rotation)
  }

  this.renderCow = function (id, coordinate, node, animationLength, type) {
    var res = this.determineCowOrientation(coordinate, node)
    var image = paper.image(
      ocargo.Drawing.raphaelImageDir + ocargo.Drawing.cowUrl(type),
      res.drawX,
      res.drawY,
      COW_WIDTH,
      COW_HEIGHT
    )
    var rot = 'r' + res.rotation
    image.transform(rot + 's0.1')
    image.animate({ transform: rot + 's1' }, animationLength, 'linear')

    return {
      coordinate: coordinate,
      image: image
    }
  }

  this.removeCow = function (cow, animationLength) {
    cow.image.animate(
      { transform: 's0.01' },
      animationLength,
      'linear',
      function () {
        cow.image.remove()
      }
    )
  }

  this.renderCharacter = function () {
    character.render()
  }

  this.createGrid = function () {
    var grid = []
    for (var i = 0; i < GRID_WIDTH; i++) {
      var row = []
      for (var j = 0; j < GRID_HEIGHT; j++) {
        var x = i * GRID_SPACE_SIZE + PAPER_PADDING
        var y = j * GRID_SPACE_SIZE + PAPER_PADDING

        row.push(paper.rect(x, y, GRID_SPACE_SIZE, GRID_SPACE_SIZE))
      }
      grid.push(row)
    }
    return grid
  }

  this.renderGrid = function (grid, currentTheme) {
    for (var i = 0; i < GRID_WIDTH; i++) {
      for (var j = 0; j < GRID_HEIGHT; j++) {
        grid[i][j].attr({
          stroke: currentTheme.border,
          fill: currentTheme.background,
          'fill-opacity': 1
        })
      }
    }
  }

  this.clearPaper = function () {
    paper.clear()
  }

  this.renderMap = function (map) {
    this.renderBackground()
    this.renderRoad(map.nodes)
  }

  this.createImage = function (url, x, y, width, height) {
    return paper.image(url, x, y, width, height)
  }

  /****************/
  /** Animations **/
  /****************/

  this.transitionTrafficLight = function (lightID, endState, animationLength) {
    if (endState === ocargo.TrafficLight.GREEN) {
      lightImages[lightID][0].animate(
        { opacity: 1 },
        animationLength / 2,
        'linear'
      )
      lightImages[lightID][1].animate({ opacity: 0 }, animationLength, 'linear')
    } else {
      lightImages[lightID][0].animate(
        { opacity: 0 },
        animationLength / 2,
        'linear'
      )
      lightImages[lightID][1].animate({ opacity: 1 }, animationLength, 'linear')
    }
  }

  this.transitionDestination = function (destinationID, visited, duration) {
    var destinationRect = destinationImages[destinationID].rect
    var colour = visited
      ? DESTINATION_VISITED_COLOUR
      : DESTINATION_NOT_VISITED_COLOUR

    destinationRect.animate({ stroke: colour }, duration, 'linear')
  }

  this.scrollToShowCharacter = function () {
    character.scrollToShow()
  }

  this.moveForward = function (callback, scalingFactor) {
    return character.moveForward(callback, scalingFactor)
  }

  this.turnLeft = function (callback, scalingFactor) {
    return character.turnLeft(callback, scalingFactor)
  }

  this.turnRight = function (callback, scalingFactor) {
    return character.turnRight(callback, scalingFactor)
  }

  this.turnAround = function (direction) {
    return character.turnAround(direction)
  }

  this.wait = function (duration, callback) {
    character.wait(duration, callback)
  }

  this.deliver = function (destinationId, duration) {
    this.transitionDestination(destinationId, true, duration)
  }

  this.collisionWithCow = function (
    previousNode,
    currentNode,
    attemptedAction
  ) {
    return character.collisionWithCow(
      previousNode,
      currentNode,
      attemptedAction
    )
  }

  this.crash = function (previousNode, currentNode, attemptedAction) {
    return character.crash(attemptedAction)
  }

  this.setCharacterManoeuvreDuration = function (speed) {
    character.setManoeuvreDuration(speed)
  }
}

/********************************/
/* Static methods and constants */
/********************************/

// Flips the y coordinate upside down to match the raphael coordinate system
ocargo.Drawing.translate = function (coordinate) {
  return new ocargo.Coordinate(coordinate.x, GRID_HEIGHT - 1 - coordinate.y)
}

/*
 This is the function that starts the pop-up.
 Buttons should be passed in separately to the function instead of concatenating
 to the message so as to keep the layout of the pop-up consistent.
 The following elements will be displayed vertically from top to bottom in this order:
 1. title (bolded)
 2. subtitle (same font size as title)
 3. message (smaller font size than title and subtitle)
 4. buttons (in one row)
 Mascot will be displayed on the right hand side of the popup
 */
ocargo.Drawing.startPopup = function (
  title,
  subtitle,
  message,
  mascot,
  buttons
) {
  $('#myModal-title').html(title)
  $('#myModal-lead').html(subtitle)
  $('#myModal-mainText').html(message)

  if (mascot) {
    $('#modal-mascot').show()
  } else {
    $('#modal-mascot').hide()
  }

  if (buttons) {
    $('#modal-buttons').html(buttons)
  } else {
    $('#modal-buttons').html(
      ocargo.button.dismissButtonHtml('close_button', gettext('Close'))
    )
  }
  // Show popup
  window.location.hash = 'myModal'
}

// This is the function that starts the pop-up with a yes and a no button
ocargo.Drawing.startYesNoPopup = function (
  title,
  subtitle,
  message,
  yesFunction,
  noFunction,
  mascot
) {
  var buttonHtml =
    '<button id="modal-yesBtn" class="navigation_button long_button">Yes</button> <button id="modal-noBtn" class="navigation_button long_button">No</button>'
  ocargo.Drawing.startPopup(title, subtitle, message, mascot, buttonHtml)
  $('#modal-yesBtn').click(yesFunction)
  $('#modal-noBtn').click(noFunction)
}

// This is the function that starts the pop-up when there is no internet connection while playing the game
ocargo.Drawing.startInternetDownPopup = function () {
  ocargo.Drawing.startPopup(
    gettext('Error'),
    '',
    gettext(
      'Could not connect to server. Your internet might not be working properly.'
    )
  )
}

ocargo.Drawing.isMobile = function () {
  var mobileDetect = new MobileDetect(window.navigator.userAgent)
  return !!mobileDetect.mobile()
}

ocargo.Drawing.isChrome = function () {
  return navigator.userAgent.indexOf('Chrome') > -1
}

ocargo.Drawing.renderCoins = function (coins) {
  var html = '<div>'
  var i
  for (i = 0; i < coins.whole; i++) {
    html +=
      "<img src='" +
      ocargo.Drawing.imageDir +
      "coins/coin_gold.svg' width='50'>"
  }
  if (coins.half) {
    html +=
      "<img src='" +
      ocargo.Drawing.imageDir +
      "coins/coin_5050_dots.svg' width='50'>"
  }
  for (i = 0; i < coins.zero; i++) {
    html +=
      "<img src='" +
      ocargo.Drawing.imageDir +
      "coins/coin_empty_dots.svg' width='50'>"
  }

  return html
}

ocargo.Drawing.cowUrl = function (type) {
  switch (type) {
    case ocargo.Cow.WHITE:
      return ocargo.Drawing.whiteCowUrl
    case ocargo.Cow.BROWN:
      return ocargo.Drawing.brownCowUrl
    default:
      return ocargo.Drawing.whiteCowUrl
  }
}

ocargo.Drawing.createAbsoluteRotationTransformation = function (
  degrees,
  rotationPointX,
  rotationPointY
) {
  var transformation = '... R' + degrees
  if (rotationPointX !== undefined && rotationPointY !== undefined) {
    transformation += ',' + rotationPointX
    transformation += ',' + rotationPointY
  }
  return transformation
}

ocargo.Drawing.rotationTransformationAroundCentreOfGridSpace = function (
  degrees,
  x,
  y
) {
  var rotationPointX = (x + 1 / 2) * GRID_SPACE_SIZE + PAPER_PADDING
  var rotationPointY =
    (GRID_HEIGHT - (y + 1 / 2)) * GRID_SPACE_SIZE + PAPER_PADDING //flipping y
  var result = ocargo.Drawing.createAbsoluteRotationTransformation(
    degrees,
    rotationPointX,
    rotationPointY
  )
  return result
}

ocargo.Drawing.inLevelEditor = function () {
  return typeof CHARACTER_URL === 'undefined'
}

ocargo.Drawing.FRONT_VIEW = 'front_view'
ocargo.Drawing.TOP_VIEW = 'top_view'

ocargo.Drawing.whiteCowUrl = 'Clarice.svg'
ocargo.Drawing.brownCowUrl = 'Clarice_Jersey.svg'

ocargo.Drawing.imageDir = '/static/game/image/'
ocargo.Drawing.raphaelImageDir = '/static/game/raphael_image/'
