var gGame = {
  playing: false,
  data: null,
  animating: false,
  _refreshTimeout: null,
  currentRequest: null,

  reset: function() {
    this.data = {};
    this.animating = false;
    this.players.each(function(p) { Element.removeClassName(p, "current"); });
    new Effect.Fade(this.players[1]);
    $$('#opponent .teaser').first().hide();
  },

  cancelPending: function() {
     if(IAFlashes.isFlashing('play-button')) {
       IAFlashes.stopFlash('play-button');
       new Effect.Puff('play-button');
     }
     if(this._refreshTimeout) window.clearTimeout(this._refreshTimeout);
     if(this.currentRequest) this.currentRequest.transport.abort();
  },

  refreshNoCancel: function(action, link) {
    var opts = {
      parameters: 'c=game&a=' + action,
      evalScripts: true
    }
    if(link) {
      var i = this.createIndicator();
      Object.extend(opts,
        {
          onLoading: function() { link._onclick = link.onclick; link.onclick = null; link.appendChild(i); },
          onComplete: function() {  try { link.onclick = link._onclick; i.parentNode.removeChild(i); } catch(e) {} i = null; },
          onError: function() { gGame.refreshNoCancel(action, link); }
        });
    }
    this.currentRequest =  new Ajax.Updater('overlay', location.pathname, opts);
    return false;
  },

  refresh: function(action, link) {
    this.cancelPending();
    this.refreshNoCancel(action, link);
    return false;
  },

  delayRefresh: function(action, delay) {
    this.cancelPending();
    this._refreshTimeout = window.setTimeout(function() {
      this.refresh(action);
    }.bind(this), delay || 1000);
    return false;
  },

  createIndicator: function() {
    var i = document.createElement("img");
    i.className = "indicator";
    i.src = "img/indicator.gif";
    return i;
  },

  checkFreePlayers: function(hash) {
    return this.delayRefresh('free_players&phash='+hash);
  },

  chooseOpponent: function(id, link) {
    return this.refresh('choose_opponent&id=' + id, link);
  },

  sendMove: function(move)
  {
    return this.play(10, "move=" + move.join(","));
  },

  players: ["player", "opponent"],

  play: function(delay, params) {
    this.delayRefresh('play&snapshot=' + (this.data.snapshot || "-1") + (params &&  ("&" + params) || ""), delay);
  },

  playNC: function() {
    if(!(this.data && this.data.current)) {
      this.play(1000);
    }
  },

  forceRefresh: function(delay, params) {
    this.data.snapshot = null;
    this.play(delay || 10, params);
  },

  update: function(data) {
    if(this.animating) {
      window.setTimeout(function() { gGame.update(data); }, 100);
      return;
    }
    if(data && data.move) {
      if(this.data) {
        this.showMove(data);
        return;
      } else data.move = null;
    }

    var oldData = this.data || {};
    this.data = data || oldData || {};

    this.setupPoints();

    if(data && data.gameId) {

      if(!gScopa.cards.showing(40)) {

        $R(1, 40).each(function(o) {
          var c = gScopa.cards[o];
          c.turn("back");
          c.resetDim();
          gScopa.cards.show(o, o * 10 + Math.random() * 5 + 100 , 100 * Math.sin(o * Math.PI / 20), o, o);
        });
      }

      if(this.data.gameId != oldData.gameId) {
        this.newGame();
      } else if(this.data.deckId != oldData.deckId) {
        this.newDeck();
      } else if(this.data.hands &&
        ((!oldData.hands) ||
          (this.data.hands[0].length == this.data.hands[1].length && this.data.hands[1].length == 3)
        )) {
        this.giveCards((this.current ? this.setupPoints : this.update).bind(this));
      } else {
        this.setupCurrent(function() { gGame.update() });
      }
      return;
    }

    if(!this.data.move) {
      this.play(data ? 5000 : 10);
    }
  },

  showMove: function(data) {
    if(!this.data.table) {
      data.move = null;
      gGame.update(data);
      return;
    }

    var move = data.move.split(',');
    var sweep = move[0].charAt(0) == '*';
    if(sweep) move[0] = move[0].substring(1);

    var table = this.data.table;
    data.move = null;
    try {
      move = move.map(function(n) { return parseInt(n) });
      if(table.include(move[0]) || move.without(move[0]).any(function(n) { return !table.include(n) })) {
        gGame.update(data);
        return;
      }
    } catch(e) {}

    data.move = null;
    this.data.table.splice(this.data.table.indexOf(move[1]), 0, move[0]);
    this.layoutTable(function() {
      new Effect.Parallel(move.map(function(o) { return new Effect.Highlight(gScopa.cards[o], gScopa.cards.fxOpts) }),
          { afterFinish: function() {
             move.each(function(o) { with(gScopa.cards[o]) { style.backgroundColor = ""; style.backgroundImage = ""; } });
             var take = move.without(move[0]);
             take.sweep = sweep;
             gGame.animateCapture(1, gScopa.cards[move[0]], take,
               function() { gGame.update(data) });
          } });
    });
  },



  updatePlayer: function(id, p) {
    var root = $(id);
    new Selector("div.name").findElements(root).first().innerHTML = p.name + ' <span style="font-size: 80%">[R<b>' + p.rank + "</b>]</span>";
    new Selector("div.score").findElements(root).first().innerHTML = p.score;
    new Selector("img.avatar").findElements(root).first().src = p.avatar;
    new Selector("img.flag").findElements(root).first().src = '/images/flags/big/' + (p.flag ? p.flag : 'no-country') + '.png';

    if(id == 'opponent') {
      ChatBox.names[1] = p.name;
    } else {
      ChatBox.names[0] = p.name;
    }

    if(!Element.visible(root)) {
      new Effect.Appear(root, { queue: 'players' } );
    }
  },


  ppPoss: [
    { x: 220, y: 180, o: 25 },
    { x: 760, y: 50, o: -25 }
  ],
  setupPoints: function() {
    if(!(this.data && this.data.points)) return;
    var cards = gScopa.cards;
    $A(cards).invoke('resetDim');
    var sweeps = this.data.sweeps;
    var fxOpts = $H({});
    this.data.points.each(function(p, i) {
      var pos = this.ppPoss[i];
      var z = 20;
      p.each(function(o) {
        fxOpts[o] = gGame.fuzzyPos(pos);
        with(cards[o]) {
          resetDim(.5);
          turn("back");
          style.zIndex = z++;
        }
      });
      var dir = Math.abs(pos.o) / pos.o;
      pos = { x: pos.x + pos.o, y: pos.y }

      z = 10;

      sweeps[i].each(function(o) {
        fxOpts[o] = gGame.fuzzyPos(pos);
        pos.x += 15 * dir;
        with(cards[o]) {
          style.zIndex = z++;
          turn("front");
        }
      });
    }.bind(this));

    new Effect.Parallel(fxOpts.map(function(optsEntry)  {
      return new Effect.Move(cards[optsEntry.key], Object.extend(optsEntry.value, cards.fxOpts));
    }), { duration: .3, queue: { position: 'end', scope: 'cards' } } );

  },

  animateCapture: function(player, c, take, callback) {
    if(!take.length) {
      if(callback) callback();
      return;
    }
    var cards = c.deck;
    var pos = this.ppPoss[player];
    var opts = Object.extend({}, cards.fxOpts);
    var scaleOpts = { sync: true, scaleMode: 'contents', queue: cards.fxOpts.queue };
    var takeCards = take.map(function(o) { return cards[o]; }).concat(c);
    dbg("take = " + take.inspect() + ", takeCards = " + takeCards.inspect());
    var sweep = take.sweep;

    new Effect.Parallel(takeCards.map(function(card) {
      opts.x = pos.x + Math.random() * 6 - 3;
      opts.y = pos.y + Math.random() * 6 - 3;
      card.addClassName("outlined");
      return [ new Effect.Move(card, opts), new Effect.Scale(card, 50, scaleOpts), new Effect.Scale(card.image, 50, scaleOpts) ]
    }).flatten(),
      { afterFinish: function() {
        takeCards.each(function(card) {
          card.removeClassName("outlined");
          card.turn("back");
          card.resetDim(.5);
        });
        if(sweep) {
          c.style.zIndex = 19;
          c.turn("front");
          opts.x +=  pos.o;
          opts.sync = false;
          if(callback) opts.afterFinish = callback;
          new Effect.Move(c, opts);
          gScopa.hype("SCOPA!");
        } else {
          if(callback) callback();
        }
    } });

  },

  fuzzyPos: function(pos, obj) {
    obj = obj || {};
    obj.x = pos.x + Math.random() * 6 - 3,
    obj.y = pos.y + Math.random() * 6 - 3
    return obj;
  },

  newGame: function() {
  if(gGame.data['opponent'].avatar.indexOf("/ai") == 0)
  {
  	ChatBox.reset();
  }
    OverlayManager.clear();
    this.newDeck();
  },

  newDeck: function() {
    gScopa.cards.hide(0);
    this.cancelPending();
    gGame.updatePlayersData();


    var d = gScopa.cards.getDimensions();
    var cd = gScopa.cards.cardDimensions;

    gScopa.cards.each(function(c) {
      c.resetDim();
      c.removeClassName("prime");
    });

    gScopa.cards.flock({
      local: { x: d.width - cd.width - 20, y: d.height - cd.height - 20 },
      global: { afterFinish: function() {
        gScopa.cards.each(function(c) { c.turn('back') });
        gGame.giveCards(function() { gGame.layoutTable(function() { if(!gGame.data.current) gGame.forceRefresh(); else gGame.update(); }) });
      } }
    });
    /*anybody can chat in the game time if they play with user, not with bot*/
    if(ChatBox && gGame.data['opponent'].avatar.indexOf("/ai") == 0){ChatBox.cansend(true);}
  },

  layoutTable: function(callback) {

    if(!this.data) return;
    var cd = gScopa.cards.cardDimensions;
    var tableCount = this.data.table.length;
    var spacing = 65 * (1 - (tableCount / 10)) - 5;
    var xStep = cd.width + spacing;
    var x = gScopa.cards.center.x - xStep * (tableCount - 1) / 2;
    var y = gScopa.cards.center.y - cd.height / 2;

    new Effect.Parallel(this.data.table.map(function(o, i) {
      gScopa.cards[o].turn('front');
      return new Effect.Move(gScopa.cards[o], Object.extend({ x: x + xStep * i, y: y} , gScopa.cards.fxOpts ));
    }), {
      duration: 0.5,
      beforeFinish: function() { if(gGame.data.table) gGame.data.table.map(function(o) { gScopa.cards[o].turn('front'); }); },
      afterFinish: callback || function() { gGame.update(); }
    });
  },

  draggables: $A([]),
  droptarget: null,
  giveCards: function(callback) {

    this.draggables.invoke('destroy');
    if(!this.droptarget) Droppables.add(this.droptarget = $("table"));

    new CardsGiving(this.data.hands, this.data.current ? 0 : 1,
        function() { gGame.setupCurrent(callback); });
  },

  setupCurrent: function(callback) {
     var pids;

     if(this.data.movesCount >= 36 && ! $("contenders")) {
       this.players.each(function(p) {
         Element.show(Element.down(p, ".teaser"));
       });
       gScopa.hype(_['msg.computing']);
       this.forceRefresh();
       return;
     } else {
       this.players.each(function(p) {
         Element.hide(Element.down(p, ".teaser"));
       });
     }

     if(this.data.current) {
       pids = this.players;
       var opts = { revert: this.revertCard, onEnd: this.dropCard.bind(this) };
       if(this.draggables) this.draggables.invoke('destroy');
       this.draggables = this.data.hands[0].map(function(n) {
         var c = gScopa.cards[n];
         c.style.cursor = "move";
         return new Draggable(c, opts);
       });

     } else {
       pids = this.players.reverse(false);
     }

     var opponentTeaser = $$('#opponent .teaser').first();
     if(pids[0] == "player") {
       if(!Element.hasClassName(pids[0], "current")) {
         gScopa.hype(_['msg.move'], "32px");
         // new Effect.Shake(pids[0], { duration: 2 });
         opponentTeaser.hide();
       }
     } else {
       opponentTeaser.show();
     }
     Element.addClassName(pids[0], "current");
     Element.removeClassName(pids[1], "current");
     if(callback) callback();
  },
  revertCard: function(c) {
    var revert = c.offsetLeft < 0 || c.offsetLeft > gScopa.table.offsetWidth - c.offsetWidth ||
      c.offsetTop < -60 || c.offsetTop > gScopa.cards.center.y + - c.offsetHeight / 2 + 60;
      return revert;
  },
  dropCard: function(drag) {
    var c = drag.element;
    if(this.revertCard(c)) return false;
    window.setTimeout(function() { this.afterDrop(c) }.bind(this), 10);
    return true;
  },

  afterDrop: function(c) {
    this.draggables.each(function(d) { d.element.style.cursor = ""; d.destroy(); });

    // evaluate move

    var table = this.data.table;
    new CardsMatcher(c, table).evaluate(function(take) {
      var sweep = false;

      var cards = c.deck;
      var tablePos;


      gGame.animating = true;
      gGame.sendMove([c.ordinal, tablePos].concat(take));
      // this transaction is meant to be execute after visual feedback
      var moveTransaction = function() {


        this.data.hands[0].splice(this.data.hands[0].indexOf(c.ordinal), 1);
        // actual move will be sent over the wire after table layout
        this.layoutTable(function() {
          this.data.current = false;
          this.setupCurrent(
            function() { gGame.animating = false; }
          );
        }.bind(this));
      }.bind(this);

      if(take.length) {
        table = table.reject(function(n) { return take.include(n); });
        take.sweep = !table.length;
        this.animateCapture(0, c, take, moveTransaction);
        tablePos = 0;
        this.data.table = table;
      } else if(table.length) {
        table.splice(tablePos = table.inject(0, function(pos, n, index) {
          if(c.offsetLeft <= cards[n].offsetLeft) throw $break;
          return index + 1;
        }), 0, c.ordinal);
        moveTransaction();
      } else {
        table.push(c.ordinal);
        tablePos = 1;
        moveTransaction();
      }
    }.bind(this));
    return true;
  },



  roundOver: function(players) {
    var cards = gScopa.cards;
    var opts = cards.fxOpts;
    var fx = [];
    this.players.each(Element.hide);
    players.each(function(points, j) {
      var prime = points.prime.map(cards.num2ord);
      var x = 0;
      var y = 90;
      var z = 50;
      var xOff = 350 * j + 100;
      points.cards.each(function(o, k) {
        cards.show(o);
        var c = cards[o];
        c.zIndex = z++;
        c.resetDim(.50);
        if(points.prime.include(o)) {
          c.addClassName("prime");
        }
        c.turn("front");
        fx.push(new Effect.Move(c, Object.extend({
          x: xOff + x,
          y: y
        }, opts)));
        x += c.offsetWidth;
        if(x > 300) {
          x = 0;
          y += 45;
        }
      });
    });
    new Effect.Parallel(fx, { duration: 1.5 });
  },

  updatePlayersData: function()
  {
    new Ajax.Request('/index.php?c=game&a=getplayers',
        {
            onComplete: function(req) {
                eval(req.responseText);
                p="player";
                gGame.updatePlayer(p, gGame.data[p]);
                p="opponent";
                gGame.updatePlayer(p, gGame.data[p]);
            }
        });
  }

};



var CardsMatcher = Class.create();
CardsMatcher.prototype = {
  initialize: function(card, set) {
    this.card = card;
    this.set = set;
    Object.extend(this, {
       take: [],
       ord2num: null,
       mustHave: [],
       observer: null,
       alternates: [],
       observed: []
    });
  },

  evaluate: function(callback) {
    if(this.search()) return callback(this.take);
    this.callback = callback;
  },

  search: function() {


    var set = this.set;

    if(!set.length) return true;

    this.ord2num = CardsDeck.Base.ord2num;
    var cnum = this.card.number;
    var card = this.card;
    var cards = card.deck;
    var distance = function(o) { return Math.abs(cards[o].offsetLeft - card.offsetLeft); }

    var tt = this.set.findAll(function(o) { return this.ord2num(o) == cnum; }.bind(this)).sortBy(distance);
    if(tt.length) return this.take.push(tt.first()); // 1 card take


    // find combos
    var lesser = this.set.findAll(function(o) { return this.ord2num(o) < cnum }.bind(this));
    var count = lesser.length;
    dbg("Lesser: " + lesser.inspect());
    if(!count) {
      dbg("this.take: " + this.take.inspect());
      return true;
    }
    var takes = [];
    var take;

    var comboCount = Math.pow(2, count);
    var zeroes = comboCount.toString(2).replace(/1/g, "0");
    var mask, paddif, j, sum;

    for(var c = 1; c < comboCount; c++) {
      mask = c.toString(2);
      if((paddiff = (count - mask.length))) mask = zeroes.substring(0, paddiff) + mask;
      take = [];
      sum = 0;

      for(j = 0; (j = mask.indexOf("1", j)) > -1; j++) {
        if((sum += this.ord2num(t = lesser[j])) > cnum) break;
        take.push(t);

      }

      if(cnum == sum) takes.push(take);
    }



     dbg(takes.inspect());

    if(!takes.length) {
      dbg("Drop");
      return true;
    }

    if(takes.length == 1) {
      dbg("Unique combo take");
      return this.take = takes.first();
    }
    var nearest = takes.flatten().sortBy(distance).first();

    takes = takes.findAll(function(take) { return take.include(nearest); });
    if(takes.length == 1) {
      dbg("Unambiguous nearest combo take");
      return this.take = takes.first();
    }

    gScopa.hype(_['msg.choosecombo']);
    this.observer = this.refine.bind(this);
    this.observed = takes.flatten().uniq().map(function(o) {
      var c = cards[o];
      ['click', 'mouseover', 'mouseout'].each(function(e) {
        Event.observe(c, e, this.observer, true);
      }.bind(this));
      return c;
    }.bind(this));

    this.alternates = takes;
    this.altInterval = window.setInterval(function() {
      var current = this.alternates.current || 0;
      var outlined = this.alternates[current];
      if(outlined) outlined.without(this.mustHave).each(function(o) { cards[o].removeClassName("outlined"); });
      if(++current >= this.alternates.length) current = 0;
      this.alternates[current].each(function(o) { cards[o].addClassName("outlined"); });
      this.alternates.current = current;
    }.bind(this), 1000);

    return false;
  },

  refine: function(ev) {
    var card = Event.findElement(ev, "DIV");
    var mustHave = this.mustHave;

    if(this.mustHave.include(card.number)) {
      // this.mustHave = mustHave.without([card.number]);
      return;
    }

    card.removeClassName("hovered");
    switch(ev.type) {
      case 'mouseover':
        card.addClassName("hovered");
        return;
      case 'mouseout':
        return;
    }

    var cards = card.deck;

    mustHave = mustHave.concat(card.ordinal);
    var takes = this.alternates.findAll(function(t) {
      return mustHave.all(function(mh) { return t.include(mh); });
    }.bind(this));

    if(takes.length == 0) {
      // impossible combo, back out
      return;
    }

    this.mustHave = mustHave;

    this.alternates.flatten().uniq().each(function(o) { cards[o].removeClassName("outlined"); });
    this.alternates = takes;
    if(takes.length != 1) return;

    window.clearInterval(this.altInterval);
    this.observed.each(function(c) {
       ['click', 'mouseover', 'mouseout'].each(function(e) {
         Event.stopObserving(c, e, this.observer, true);
      }.bind(this));
    }.bind(this));
    this.callback(takes.first());
  }

};

var CardsGiving = Class.create();
CardsGiving.prototype = {
  initialize: function(hands, handIdx, callback) {
    this.hands = hands;
    this.total = hands[0].length + hands[1].length;
    if(this.total % 2 == 1) {
      handIdx = hands[0].length > hands[1].length ? 0 : 1;
    }
    this.handIdx = handIdx;
    this.cardIdx = 0;
    this.count = 0;

    this.callback = callback;
    this.run();
  },
  run: function() {
    if(this.count >= this.total) {
      return this.callback && this.callback() && false;
    }
    var c = gScopa.cards[this.hands[this.handIdx][this.cardIdx]];
    if(!c) {
      this.handIdx = this.handIdx ? 0 : 1;
    } else {
      var pos;
      if(this.handIdx) {
        this.handIdx = 0;
        c.turn('back');
        pos = { x: 500 - c.offsetWidth * (this.cardIdx + 1), y: -100 };
      } else {
        this.handIdx = 1;
        c.turn('front');
        pos = { x: 10 + c.offsetWidth * this.cardIdx, y: 150 };
      }
      var opts = Object.extend(pos,  { afterFinish: this.run.bind(this), mode: "absolute", duration: .3 });
      new Effect.Move(c, opts);
    }
    if(this.count++ % 2) {
      this.cardIdx++;
    }
  }


};

window.dbg = function() {};

