<commit_before>var chessground = require('chessground');
var opposite = chessground.util.opposite;
var tree = require('./tree/tree');
var treePath = require('./tree/path');
var treeOps = require('./tree/ops');
var ground = require('./ground');
var keyboard = require('./keyboard');
var actionMenu = require('./actionMenu').controller;
var autoplay = require('./autoplay');
var control = require('./control');
var promotion = require('./promotion');
var util = require('./util');
var throttle = require('./util').throttle;
var socket = require('./socket');
var forecastCtrl = require('./forecast/forecastCtrl');
var cevalCtrl = require('./ceval/cevalCtrl');
var explorerCtrl = require('./explorer/explorerCtrl');
var router = require('game').router;
var game = require('game').game;
var crazyValid = require('./crazy/crazyValid');
var tour = require('./tour');
var studyCtrl = require('./study/studyCtrl');
var m = require('mithril');

module.exports = function(opts) {

  this.userId = opts.userId;
  this.canStudy = opts.canStudy;

  var initialize = function(data) {
    this.data = data;
    if (!data.game.moveTimes) this.data.game.moveTimes = [];
    this.ongoing = !util.synthetic(this.data) && game.playable(this.data);
    this.tree = tree(this.data.tree);
    this.actionMenu = new actionMenu();
    this.autoplay = new autoplay(this);
    this.socket = new socket(opts.socketSend, this);
    this.explorer = explorerCtrl(this, opts.explorer);
  }.bind(this);

  initialize(opts.data);

  var initialPath = treePath.root;
  if (opts.path) {
    var mainline = treeOps.mainlineNodeList(this.tree.root);
    if (opts.path === 'last') initialPath = treePath.fromNodeList(mainline);
    else {
      var ply = parseInt(opts.path);
      if (ply) initialPath = treeOps.takePathWhile(mainline, function(n) {
        return n.ply <= ply;
      });
    }
  }

  this.vm = {
    initialPath: initialPath,
    cgConfig: null,
    comments: true,
    flip: false,
    showAutoShapes: util.storedProp('show-auto-shapes', true),
    showGauge: util.storedProp('show-gauge', true),
    autoScroll: null,
    element: opts.element,
    redirecting: false,
    contextMenuPath: null
  };

  this.setPath = function(path) {
    this.vm.path = path;
    this.vm.nodeList = this.tree.getNodeList(path);
    this.vm.node = treeOps.last(this.vm.nodeList);
    this.vm.mainline = treeOps.mainlineNodeList(this.tree.root);
  }.bind(this);

  this.setPath(initialPath);

  this.flip = function() {
    this.vm.flip = !this.vm.flip;
    this.chessground.set({
      orientation: this.vm.flip ? this.data.opponent.color : this.data.player.color
    });
  }.bind(this);

  this.togglePlay = function(delay) {
    this.autoplay.toggle(delay);
    this.actionMenu.open = false;
  }.bind(this);

  var uciToLastMove = function(uci) {
    if (!uci) return;
    if (uci[1] === '@') return [uci.substr(2, 2), uci.substr(2, 2)];
    return [uci.substr(0, 2), uci.substr(2, 2)];
  };

  var showGround = function() {
    var node = this.vm.node;
    var color = node.ply % 2 === 0 ? 'white' : 'black';
    var dests = util.readDests(node.dests);
    var drops = util.readDrops(node.drops);
    var config = {
      fen: node.fen,
      turnColor: color,
      movable: {
        color: (dests && Object.keys(dests).length > 0) || drops === null || drops.length ? color : null,
        dests: dests || {}
      },
      check: node.check,
      lastMove: uciToLastMove(node.uci)
    };
    if (!dests && !node.check) {
      // premove while dests are loading from server
      // can't use when in check because it highlights the wrong king
      config.turnColor = opposite(color);
      config.movable.color = color;
    }
    this.vm.cgConfig = config;
    if (!this.chessground)
      this.chessground = ground.make(this.data, config, userMove, userNewPiece);
    this.chessground.set(config);
    onChange();
    if (!dests) getDests();
    this.setAutoShapes();
    if (node.shapes) this.chessground.setShapes(node.shapes);
  }.bind(this);

  var getDests = throttle(800, false, function() {
    if (this.vm.node.dests) return;
    this.socket.sendAnaDests({
      variant: this.data.game.variant.key,
      fen: this.vm.node.fen,
      path: this.vm.path
    });
  }.bind(this));

  var sound = {
    move: throttle(50, false, $.sound.move),
    capture: throttle(50, false, $.sound.capture),
    check: throttle(50, false, $.sound.check)
  };

  var onChange = opts.onChange ? throttle(300, false, function() {
    var mainlinePly = this.tree.pathIsMainline(this.vm.path) ? this.vm.node.ply : false;
    opts.onChange(this.vm.node.fen, this.vm.path, mainlinePly);
  }.bind(this)) : $.noop;

  var updateHref = (!opts.study && window.history.replaceState) ? throttle(750, false, function() {
    window.history.replaceState(null, null, '#' + this.vm.node.ply);
  }.bind(this), false) : $.noop;

  this.autoScroll = function() {
    this.vm.autoScroll && this.vm.autoScroll();
  }.bind(this);

  this.jump = function(path) {
    var tellStudy = this.study && path !== this.vm.path;
    this.setPath(path);
    showGround();
    if (tellStudy) this.study.setPath(path, this.vm.node);
    if (!this.vm.node.uci) sound.move(); // initial position
    else if (this.vm.justPlayed !== this.vm.node.uci) {
      if (this.vm.node.san.indexOf('x') !== -1) sound.capture();
      else sound.move();
      this.vm.justPlayed = null;
    }
    if (/\+|\#/.test(this.vm.node.san)) sound.check();
    this.ceval.stop();
    startCeval();
    this.explorer.setNode();
    updateHref();
    this.autoScroll();
    promotion.cancel(this);
  }.bind(this);

  this.userJump = function(path) {
    this.autoplay.stop();
    this.chessground.selectSquare(null);
    this.jump(path);
  }.bind(this);

  this.mainlinePathToPly = function(ply) {
    return treeOps.takePathWhile(this.vm.mainline, function(n) {
      return n.ply <= ply;
    });
  }.bind(this);

  var jumpToMain = function(ply) {
    this.userJump(this.mainlinePathToPly(ply));
  }.bind(this);

  this.jumpToIndex = function(index) {
    this.jumpToMain(index + 1 + this.data.game.startedAtTurn);
  }.bind(this);

  this.jumpToGlyphSymbol = function(color, symbol) {
    var ply = this.tree.plyOfNextGlyphSymbol(color, symbol, this.vm.mainline, this.vm.node.ply);
    if (ply) jumpToMain(ply);
    m.redraw();
  }.bind(this);

  this.jumpToLast = function() {
    this.userJump(treePath.fromNodeList(this.vm.mainline));
  }.bind(this);

  this.reloadData = function(data) {
    initialize(data);
    this.vm.redirecting = false;
    this.setPath(treePath.root);
  }.bind(this);

  this.changePgn = function(pgn) {
    this.vm.redirecting = true;
    $.ajax({
      url: '/analysis/pgn',
      method: 'post',
      data: {
        pgn: pgn
      },
      success: function(data) {
        this.reloadData(data);
        this.userJump(this.mainlinePathToPly(this.tree.lastPly()));
      }.bind(this),<commit_msg>fix analysis bug<commit_after>var chessground = require('chessground');
var opposite = chessground.util.opposite;
var tree = require('./tree/tree');
var treePath = require('./tree/path');
var treeOps = require('./tree/ops');
var ground = require('./ground');
var keyboard = require('./keyboard');
var actionMenu = require('./actionMenu').controller;
var autoplay = require('./autoplay');
var control = require('./control');
var promotion = require('./promotion');
var util = require('./util');
var throttle = require('./util').throttle;
var socket = require('./socket');
var forecastCtrl = require('./forecast/forecastCtrl');
var cevalCtrl = require('./ceval/cevalCtrl');
var explorerCtrl = require('./explorer/explorerCtrl');
var router = require('game').router;
var game = require('game').game;
var crazyValid = require('./crazy/crazyValid');
var tour = require('./tour');
var studyCtrl = require('./study/studyCtrl');
var m = require('mithril');

module.exports = function(opts) {

  this.userId = opts.userId;
  this.canStudy = opts.canStudy;

  var initialize = function(data) {
    this.data = data;
    if (!data.game.moveTimes) this.data.game.moveTimes = [];
    this.ongoing = !util.synthetic(this.data) && game.playable(this.data);
    this.tree = tree(this.data.tree);
    this.actionMenu = new actionMenu();
    this.autoplay = new autoplay(this);
    this.socket = new socket(opts.socketSend, this);
    this.explorer = explorerCtrl(this, opts.explorer);
  }.bind(this);

  initialize(opts.data);

  var initialPath = treePath.root;
  if (opts.path) {
    var mainline = treeOps.mainlineNodeList(this.tree.root);
    if (opts.path === 'last') initialPath = treePath.fromNodeList(mainline);
    else {
      var ply = parseInt(opts.path);
      if (ply) initialPath = treeOps.takePathWhile(mainline, function(n) {
        return n.ply <= ply;
      });
    }
  }

  this.vm = {
    initialPath: initialPath,
    cgConfig: null,
    comments: true,
    flip: false,
    showAutoShapes: util.storedProp('show-auto-shapes', true),
    showGauge: util.storedProp('show-gauge', true),
    autoScroll: null,
    element: opts.element,
    redirecting: false,
    contextMenuPath: null
  };

  this.setPath = function(path) {
    this.vm.path = path;
    this.vm.nodeList = this.tree.getNodeList(path);
    this.vm.node = treeOps.last(this.vm.nodeList);
    this.vm.mainline = treeOps.mainlineNodeList(this.tree.root);
  }.bind(this);

  this.setPath(initialPath);

  this.flip = function() {
    this.vm.flip = !this.vm.flip;
    this.chessground.set({
      orientation: this.vm.flip ? this.data.opponent.color : this.data.player.color
    });
  }.bind(this);

  this.togglePlay = function(delay) {
    this.autoplay.toggle(delay);
    this.actionMenu.open = false;
  }.bind(this);

  var uciToLastMove = function(uci) {
    if (!uci) return;
    if (uci[1] === '@') return [uci.substr(2, 2), uci.substr(2, 2)];
    return [uci.substr(0, 2), uci.substr(2, 2)];
  };

  var showGround = function() {
    var node = this.vm.node;
    var color = node.ply % 2 === 0 ? 'white' : 'black';
    var dests = util.readDests(node.dests);
    var drops = util.readDrops(node.drops);
    var config = {
      fen: node.fen,
      turnColor: color,
      movable: {
        color: (dests && Object.keys(dests).length > 0) || drops === null || drops.length ? color : null,
        dests: dests || {}
      },
      check: node.check,
      lastMove: uciToLastMove(node.uci)
    };
    if (!dests && !node.check) {
      // premove while dests are loading from server
      // can't use when in check because it highlights the wrong king
      config.turnColor = opposite(color);
      config.movable.color = color;
    }
    this.vm.cgConfig = config;
    if (!this.chessground)
      this.chessground = ground.make(this.data, config, userMove, userNewPiece);
    this.chessground.set(config);
    onChange();
    if (!dests) getDests();
    this.setAutoShapes();
    if (node.shapes) this.chessground.setShapes(node.shapes);
  }.bind(this);

  var getDests = throttle(800, false, function() {
    if (this.vm.node.dests) return;
    this.socket.sendAnaDests({
      variant: this.data.game.variant.key,
      fen: this.vm.node.fen,
      path: this.vm.path
    });
  }.bind(this));

  var sound = {
    move: throttle(50, false, $.sound.move),
    capture: throttle(50, false, $.sound.capture),
    check: throttle(50, false, $.sound.check)
  };

  var onChange = opts.onChange ? throttle(300, false, function() {
    var mainlinePly = this.tree.pathIsMainline(this.vm.path) ? this.vm.node.ply : false;
    opts.onChange(this.vm.node.fen, this.vm.path, mainlinePly);
  }.bind(this)) : $.noop;

  var updateHref = (!opts.study && window.history.replaceState) ? throttle(750, false, function() {
    window.history.replaceState(null, null, '#' + this.vm.node.ply);
  }.bind(this), false) : $.noop;

  this.autoScroll = function() {
    this.vm.autoScroll && this.vm.autoScroll();
  }.bind(this);

  this.jump = function(path) {
    var tellStudy = this.study && path !== this.vm.path;
    this.setPath(path);
    showGround();
    if (tellStudy) this.study.setPath(path, this.vm.node);
    if (!this.vm.node.uci) sound.move(); // initial position
    else if (this.vm.justPlayed !== this.vm.node.uci) {
      if (this.vm.node.san.indexOf('x') !== -1) sound.capture();
      else sound.move();
      this.vm.justPlayed = null;
    }
    if (/\+|\#/.test(this.vm.node.san)) sound.check();
    this.ceval.stop();
    startCeval();
    this.explorer.setNode();
    updateHref();
    this.autoScroll();
    promotion.cancel(this);
  }.bind(this);

  this.userJump = function(path) {
    this.autoplay.stop();
    this.chessground.selectSquare(null);
    this.jump(path);
  }.bind(this);

  this.mainlinePathToPly = function(ply) {
    return treeOps.takePathWhile(this.vm.mainline, function(n) {
      return n.ply <= ply;
    });
  }.bind(this);

  this.jumpToMain = function(ply) {
    this.userJump(this.mainlinePathToPly(ply));
  }.bind(this);

  this.jumpToIndex = function(index) {
    this.jumpToMain(index + 1 + this.data.game.startedAtTurn);
  }.bind(this);

  this.jumpToGlyphSymbol = function(color, symbol) {
    var ply = this.tree.plyOfNextGlyphSymbol(color, symbol, this.vm.mainline, this.vm.node.ply);
    if (ply) this.jumpToMain(ply);
    m.redraw();
  }.bind(this);

  this.jumpToLast = function() {
    this.userJump(treePath.fromNodeList(this.vm.mainline));
  }.bind(this);

  this.reloadData = function(data) {
    initialize(data);
    this.vm.redirecting = false;
    this.setPath(treePath.root);
  }.bind(this);

  this.changePgn = function(pgn) {
    this.vm.redirecting = true;
    $.ajax({
      url: '/analysis/pgn',
      method: 'post',
      data: {
        pgn: pgn
      },
      success: function(data) {
        this.reloadData(data);
        this.userJump(this.mainlinePathToPly(this.tree.lastPly()));
      }.bind(this),
{'fraction_non_alphanumeric': 0.16871983286506737, 'fraction_numerical': 0.0041783733160435125, 'mean_word_length': 3.2168894289185905, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2095144', 'n_tokens_mistral': 4918, 'n_tokens_neox': 4701, 'n_words': 908}