(function () {
  "use strict";
  function Game(dictionary, json) {
    this.dictionary = dictionary;
    this.id = Math.floor(Math.random() * 100000);
    this.state = 'checkIn';
    this.Player = require('./Player');
    this.deck = new (require('./Deck')).Deck();
    this.players = [];
    this.round = -1;
    this.dealer = -1;
    this.turn = -1;
    this.log = [];
    this.created = new Date().getTime();
    this.lastPlayed = new Date().getTime();
    if (json) {
      this.fromJSON(json);
    }
  }
  Game.prototype.toJSON = function () {
    var game = {};
    game.id = this.id;
    game.state = this.state;
    game.deck = {};
    game.deck.pile = this.deck.pile;
    game.deck.discards = this.deck.discards;
    game.deck.played = this.deck.played;
    game.players = this.players;
    game.round = this.round;
    game.dealer = this.dealer;
    game.turn = this.turn;
    game.log = this.log;
    game.created = this.created;
    game.lastPlayed = this.lastPlayed;
    return JSON.stringify(game);
  };
  Game.prototype.fromJSON = function (json) {
    var game = JSON.parse(json);
    this.id = game.id;
    this.state = game.state;
    this.deck.pile = game.deck.pile;
    this.deck.discards = game.deck.discards;
    this.deck.played = game.deck.played;
    this.players = game.players;
    this.round = game.round;
    this.dealer = game.dealer;
    this.turn = game.turn;
    this.log = game.log;
    this.created = game.created;
    this.lastPlayed = game.lastPlayed;
  };
  Game.prototype.getPlayer = function (playerId, name) {
    var i;
    for (i = 0; i < this.players.length; i += 1) {
      if (this.players[i].id === playerId && this.players[i].name === name) {
        return this.players[i];
      }
    }
    return null;
  };
  Game.prototype.getLog = function (logStart) {
    var data = {
        okay: true,
        msg: '',
        logSize: this.log.length,
        log: []
      },
      i;
    if (typeof logStart !== 'number' || logStart < 0) {
      data.okay = false;
      data.msg = 'Invalid log entry requested';
      return data;
    }
    for (i = logStart; i < this.log.length; i += 1) {
      data.log.push(this.log[i]);
    }
    return data;
  };
  Game.prototype.getState = function (playerId, name, okay, msg) {
    var data = {
        okay: typeof okay === 'undefined' ? true : okay,
        msg: msg || '',
        log: [], //this.log,
        game: {
          id: this.id,
          state: this.state,
          round: this.round,
          dealer: this.dealer,
          turn: this.turn,
          discards: this.deck.discards.length > 0 ? this.deck.discards[0] : ''
        },
        players: [],
        player: this.getPlayer(playerId, name)
      },
      i;
    if (!data.player) {
      data.okay = false;
      data.err = 'Invalid player';
    } else {
      for (i = 0; i < this.players.length; i += 1) {
        data.players.push({});
        data.players[i].name = this.players[i].name;
        data.players[i].state = this.players[i].state;
        data.players[i].lastDraw = this.players[i].lastDraw;
        data.players[i].lastDiscard = this.players[i].lastDiscard;
        data.players[i].points = this.players[i].points;
        if (typeof this.players[i].words === 'object') {
          data.players[i].words = this.players[i].words;
        }
      }
    }
    for (i = data.player.logStart; i < this.log.length; i += 1) {
      data.log.push(this.log[i]);
    }
    data.player.logStart = this.log.length;
    if (!data.okay) {
      //this.log.push(name + ' error: ' + data.msg);
    }
    this.lastPlayed = new Date().getTime();
    return JSON.parse(JSON.stringify(data));
  };
  Game.prototype.addPlayer = function (gameId, name) {
    var i;
    if (this.round === -1 && this.id === gameId) {
      for (i = 0; i < this.players.length; i += 1) {
        if (this.players[i].name.toLowerCase() === name.toLowerCase()) {
          return false; // don't allow duplicate names
        }
      }
      this.players.push(new this.Player.Player(name));
      this.players[this.players.length - 1].logStart = 0;
      return true;
    }
    return false;
  };
  Game.prototype.addPlayers = function (names) {
    var i;
    for (i = 0; i < names.length; i += 1) {
      if (!this.addPlayer(this.id, names[i])) {
        return -1;
      }
    }
    return this.id; // return gameId
  };
  Game.prototype.updateScore = function () {
    var i,
      j,
      k,
      c,
      w,
      p,
      log = {},
      mxCnt = 0,
      mxCntIdx = -1,
      mxLen = 0,
      mxLenIdx = -1;
    log.round = this.round;
    log.turn = this.turn;
    log.action = 'updateScore';
    log.oldPoints = [];
    log.newPoints = [];
    for (i = 0; i < this.players.length; i += 1) {
      log.oldPoints[i] = this.players[i].points;
    }
    for (i = 0; i < this.players.length; i += 1) {
      p = this.players[i];
      p.words = p.words || [[]];
      p.tmp = {hand: [], cnt: 0, len: 0, points: 0};
      for (j = 0; j < p.hand.length; j += 1) {
        p.tmp.hand[j] = p.hand[j];
      }
      for (j = 0; j < p.words.length; j += 1) {
        w = '';
        for (k = 0; k < p.words[j].length; k += 1) {
          c = p.words[j][k];
          w += c;
          p.tmp.points += this.deck.points[c];
          p.tmp.hand.splice(p.tmp.hand.indexOf(c), 1);
        }
        p.tmp.cnt += w.length ? 1 : 0;
        p.tmp.len = w.length > p.tmp.len ? w.length : p.tmp.len;
      }
      while (p.tmp.hand.length) {
        p.tmp.points -= this.deck.points[p.tmp.hand.pop()];
      }
      p.points += p.tmp.points > 0 ? p.tmp.points : 0;
      if (p.tmp.cnt > mxCnt) {
        mxCnt = p.tmp.cnt;
        mxCntIdx = i;
      } else if (p.tmp.cnt === mxCnt) {
        mxCntIdx = -1;
      }
      if (p.tmp.len > mxLen) {
        mxLen = p.tmp.len;
        mxLenIdx = i;
      } else if (p.tmp.len === mxLen) {
        mxLenIdx = -1;
      }
      delete p.tmp;
    }
    if (mxCntIdx > -1) {
      this.players[mxCntIdx].points += 10;
    }
    if (mxLenIdx > -1) {
      this.players[mxLenIdx].points += 10;
    }
    for (i = 0; i < this.players.length; i += 1) {
      log.newPoints[i] = this.players[i].points;
    }
    this.log.push(log);
  };
  Game.prototype.nextRound = function () {
    var i;
    this.updateScore();
    this.round = this.round < 3 ? 3 : this.round + 1;
    this.dealer = (this.round - 3) % this.players.length;
    this.turn = this.dealer;
    this.state = 'deal';
    this.deck.returnDiscards();
    for (i = 0; i < this.players.length; i += 1) {
      this.deck.returnPlayed(this.players[i].hand);
      this.players[i].hand = [];
      this.players[i].lastDiscard = '';
      delete this.players[i].words;
      this.players[i].state = i === this.dealer ? 'deal' : 'wait';
    }
    if (this.round > 10) {
      this.state = 'gameOver';
      this.players[this.dealer].state = 'wait';
    }
  };
  Game.prototype.nextTurn = function () {
    this.turn = (this.turn + 1) % this.players.length;
  };
  Game.prototype.validState = function (f, player) {
    switch (f) {
    case 'checkIn':
      return this.state === 'checkIn' && player.state === 'checkIn' ? true : false;
    case 'deal':
      return this.state === 'deal' && player.state === 'deal' ? true : false;
    case 'draw':
      return ['play', 'goOut'].indexOf(this.state) !== -1 && player.state === 'draw' ? true : false;
    case 'discard':
      return ['play'].indexOf(this.state) !== -1 && player.state === 'discard' ? true : false;
    case 'goOut':
      return ['play', 'goOut'].indexOf(this.state) !== -1 && ['discard', 'goOut'].indexOf(player.state) != -1 ? true : false;
    case 'challenge':
      return false;
    default:
      return false;
    }
  };
  Game.prototype.checkIn = function (gameId, name) {
    var player = null,
      data = {},
      cnt = 0,
      log = {},
      i;
    if (gameId !== this.id) {
      return player;
    }
    for (i = 0; i < this.players.length; i += 1) {
      if (this.players[i].name === name) {
        player = this.getPlayer(this.players[i].id, name);
        if (!player) {
          return null;
        }
        log.round = this.round;
        log.turn = i;
        log.action = 'checkIn';
        log.name = name;
        break;
      }
    }
    if (!player || !this.validState('checkIn', player)) {
      return null;
    }
    player.state = 'wait';
    this.log.push(log);
    for (i = 0; i < this.players.length; i += 1) {
      cnt = this.players[i].state === 'checkIn' ? cnt + 1 : cnt;
    }
    if (cnt === 0) {
      this.nextRound();
    }
    data = this.getState(player.id, player.name);
    data.points = this.deck.points;
    return data;
  };
  Game.prototype.deal = function (playerId, name) {
    var player = this.getPlayer(playerId, name),
      tmp = [],
      log = {},
      i,
      j,
      k,
      dealer = this.dealer;
    if (!player) {
      return null;
    }
    if (!this.validState('deal', player)) {
      return this.getState(playerId, name, false, 'invalid deal request');
    }
    log.round = this.round;
    log.turn = this.turn;
    log.action = 'deal';
    for (i = 0; i < this.round; i += 1) {
      for (j = 0; j < this.players.length; j += 1) {
        k = (dealer + 1 + j) % this.players.length;
        this.deck.drawPile(this.players[k].hand);
      }
    }
    this.deck.drawPile(tmp);
    this.deck.discard(tmp[0], tmp);
    this.state = 'play';
    player.state = 'wait';
    this.nextTurn();
    this.players[this.turn].state = 'draw';
    this.log.push(log);
    return this.getState(playerId, name);
  };
  Game.prototype.draw = function (playerId, name, from) {
    var player = this.getPlayer(playerId, name),
      log = {};
    if (!player) {
      return null;
    }
    if (!this.validState('draw', player)) {
      return this.getState(playerId, name, false, 'invalid draw request');
    }
    if (['pile', 'discards'].indexOf(from) < 0) {
      return this.getState(playerId, name, false, 'invalid draw type');
    }
    if (!this.deck.draw(from, player.hand)) {
      return this.getState(playerId, name, false, 'draw failed');
    }
    log.round = this.round;
    log.turn = this.turn;
    log.action = 'draw';
    log.from = from;
    log.card = from === 'discards' ? player.hand[player.hand.length - 1] : '';
    log.discards = this.deck.discards[0];
    player.lastDraw = from;
    player.lastDiscard = '';
    player.state = this.state === 'goOut' ? 'goOut' : 'discard';
    this.log.push(log);
    return this.getState(playerId, name);
  };
  Game.prototype.discard = function (playerId, name, card) {
    var player = this.getPlayer(playerId, name),
      log = {};
    if (!player) {
      return null;
    }
    if (!this.validState('discard', player)) {
      return this.getState(playerId, name, false, 'invalid discard request');
    }
    if (!this.deck.discard(card, player.hand)) {
      return this.getState(playerId, name, false, 'invalid discard card');
    }
    log.round = this.round;
    log.turn = this.turn;
    log.action = 'discard';
    log.card = card;
    log.discards = card;
    player.lastDiscard = card;
    player.state = 'wait';
    this.nextTurn();
    this.players[this.turn].state = 'draw';
    this.log.push(log);
    return this.getState(playerId, name);
  };
  Game.prototype.goOut = function (playerId, name, card, words) {
    var player = this.getPlayer(playerId, name),
      log = {},
      i,
      j,
      k,
      l,
      goodWords = [],
      hand = [],
      arr = [];
    if (!player) {
      return null;
    }
    if (!this.validState('goOut', player)) {
      return this.getState(playerId, name, false, 'invalid goOut request');
    }
    for (i = 0; i < words.length; i += 1) {
      if (words[i].length > 1 && this.dictionary.checkWord(words[i])) {
        goodWords.push(words[i]);
      }
    }
    for (i = 0; i < player.hand.length; i += 1) {
      hand[i] = player.hand[i];
    }
    arr.push(card);
    for (i = 0; i < goodWords.length; i += 1) {
      for (j = 0; j < goodWords[i].length; j += 1) {
        arr.push(goodWords[i][j]);
      }
    }
    for (i = 0; i < arr.length; i += 1) {
      j = hand.indexOf(arr[i]);
      if (j < 0) {
        return this.getState(playerId, name, false, 'discard or word letter not in hand');
      }
      hand.splice(j, 1);
    }
    if (player.state !== 'goOut' && hand.length > 0) {
      return this.getState(playerId, name, false, 'invalid word(s)');
    }
    this.deck.discard(card, player.hand);
    log.round = this.round;
    log.turn = this.turn;
    log.action = 'goOut';
    log.card = card;
    log.words = goodWords;
    log.hand = hand;
    log.discards = card;
    this.log.push(log);
    player.lastDiscard = card;
    player.state = 'out';
    player.words = goodWords;
    this.state = 'goOut';
    this.nextTurn();
    if (this.players[this.turn].state === 'out') {
      this.nextRound();
    } else {
      this.players[this.turn].state = 'draw';
    }
    return this.getState(playerId, name);
  };
  exports.Game = Game;
}());
