var Backbone, _, Levenshtein, SettingsView, synchronousEach, toast;
var quiz = _.extend({}, Backbone.Events);
var langData;

//refed in index.html
var coverScreen = _.debounce(function(){
  //Cover the screen to let the user know the click was registered
  //the screen lock prevents button clicks on ios, so need to attach below the menu bar
  $.mobile.pageContainer.find('[data-role="content"]').append('<div class="screen-lock">');
  var $sl = $('.screen-lock');
  $sl.delay(700).fadeOut(800, function(){
    $sl.remove();
  });
}, 1500, true);
  
var GameState = Backbone.Model.extend({
  localStorage: new Backbone.LocalStorage("gameState"),
  defaults: {
    id : 1,
    specialEffects : false,
    //Can't deside on the best default
    useTraditional : (Math.random() > 0.5),
    dataVersion : 3,
    debug : false,
    showTutorial : true,
    useZhuyin : false
  }
});
var myGameState = new GameState();
myGameState.fetch();

//The spid is the symbol/phrase id.
//It is the simplified characters with whitespace stripped.
//I've introduced it to avoid implicitly using symbols as ids then
//having things break when switching between simplified and traditional charaters.
var SymbolFlower = Backbone.Model.extend({
  
  defaults: function() {
      return {
          plantedTime : Number(new Date()),
          //finishTime : new Date(),
          rate: Number(), //in seconds
          x: Number(),
          y: Number(),
          spid: String(),
          percentComplete: 0,
          cost: 0
      };
  },
  isBlooming: function(){
    //I'm not sure if having the blooming logic in the css is a good idea.
    //The reason I'm enforing blooming before plucking is that bouquets
    //would be profitable immediatly otherwise.
    if(myGameState.get('debug')) return true;
    return this.get('percentComplete') >= 70;
  }
  
});
var PlantedSymbols = Backbone.Collection.extend({
  localStorage: new Backbone.LocalStorage("plantedSymbols"),
  model: SymbolFlower
});
var myPlantedSymbols = new PlantedSymbols();
var Weed = Backbone.Model.extend({
  
  defaults: function() {
      return {
          plantedTime : Number(new Date()),
          //finishTime : new Date(),
          rate: 100/240, //in seconds
          x: Number(),
          y: Number(),
          percentComplete: 0,
          //weeds have spids so they can use flower logic
          spid: 'weed',
          value: Math.random()
      };
  }
  
});
var Weeds = Backbone.Collection.extend({
  localStorage: new Backbone.LocalStorage("weeds"),
  model: Weed
});
var myWeeds = new Weeds();

var Transactions = Backbone.Collection.extend({
  localStorage: new Backbone.LocalStorage("transactions"),
  currentBalance: function(){
    return this.reduce(function(sofar, transaction){
      return sofar + transaction.get('amount');
    }, 0);
  },
  attempt: function(amount){
    if(this.currentBalance() + amount >= 0) {
      this.create({ amount : amount });
      return true;
    }
    toast("<h3>太贵了!</h3><p>You need more money.</p>");
    return false;
  }
});
var myTransactions = new Transactions();

//This will need to be a reference to the seed data
//so that I can update/add data to symbols
var SymbolSeed = Backbone.Model.extend({
  defaults: function() {
      return {
        //TODO:
          spid: String(),
          numPlanted: Number(),
          numPlucked: Number(),
          transcriptionQuizes: Number(),
          meaningQuizes: Number(),
          failedQuizes: Number(),
          streak: Number(),
          unlockTimestamp : Number(new Date())
      };
  }
});
var SymbolSeeds = Backbone.Collection.extend({
  localStorage: new Backbone.LocalStorage("symbolSeeds"),
  model: SymbolSeed
});
var mySeeds = new SymbolSeeds();
mySeeds.fetch({ success: function(){
  if(mySeeds.length === 0) {
    mySeeds.create({
      spid : "你"
    });
    mySeeds.create({
      spid : "好"
    });
  }
}});


var Phrase = SymbolSeed.extend({});
var PhrasesOwned = Backbone.Collection.extend({
  localStorage: new Backbone.LocalStorage("phrasesOwned"),
  model: Phrase
});
var myPhrasesOwned = new PhrasesOwned();
myPhrasesOwned.fetch();

var ShopItem = Backbone.Model.extend({
  defaults: function() {
      return {
          spid: String(),
          cost: 999999
      };
  }
});
var ShopInventory = Backbone.Collection.extend({
  dirty: true,
  model: ShopItem
});
var myShopInventory = new ShopInventory();

var flowerImages = [
  null,
  "5petal.png",
  "6petal.png",
  "8petal.png",
  "9petal.png",
  "rose.png",
  "5petal2.png",
  "6petal2.png",
  "6petal3.png",
  //repeat
  "5petal.png",
  "6petal.png",
  "8petal.png",
  "9petal.png"
];


var renderSeedSelector = function(){
  //I've desided not to track tier in the seed models.
  //The reason to do so would be to prevent game updates from changing the
  //tier of seeds the user has already purchased.
  //I think I'm alright with that happening though.
  //The seeds purchased are preserved,
  //they just might show up in a different tier.
  //Seeds are rendered by their order in langData.
  //There is the wierd condition where one could buy a seed and see it appear
  //somewhere before the last seed in the row if the order of the seeds changes.
  var mySpids = mySeeds.pluck('spid');
  var seedsByTier = _.chain(langData.symbols)
    .where({ category : $('.seed-type .ui-btn-text').text() })
    .groupBy('tier')
    .values()
    .map(function(seedsInTier){
      return {
        tierNum : seedsInTier[0].tier,
        seeds : seedsInTier
      };
    })
    .sortBy('tierNum')
    .map(function(tier) {
      var potentialSeedsBySpid = _.groupBy(tier.seeds, 'spid');
      var potentialSpids = _.pluck(tier.seeds, 'spid');
      //Because potential seeds comes first it determines their order.
      var available = _.intersection(potentialSpids, mySpids);
      var next = _.difference(potentialSpids, available);
      return {
        tierNum : tier.tierNum,
        seeds : _.map(available, function(spid, idx){
          return potentialSeedsBySpid[spid][0];
        }).concat(_.map(next.slice(0,1), function(spid){
          //TODO: Maybe an unlock button is better for extensions
          return _.extend({}, potentialSeedsBySpid[spid][0], {
            locked : true
          });
        }))
      };
    })
    .takeF(function(tier){
      if(tier.seeds.length > 1) {
        return true;
      }
    })
    .value();
  
  $('.seed-selector-content').html(_.template($('#ss-template').html(), {
    seedsByTier: seedsByTier,
    longestTierLength: _.max(_.map(seedsByTier, function(tier){
      return tier.seeds.length;
    }))
  }));
  $(".seed-scroll-wrap").iscrollview("refresh");
};

var initLangData = function(){
  langData.spHash = {};
  _.each(langData.symbols, function(sp){
    sp.spid = sp.symbols;
    langData.spHash[sp.symbols] = sp;
  });

  if(myGameState.get('useTraditional')){
    _.each(langData.symbols, function(symbol){
      symbol.symbols = symbol.traditional;
    });
  }
  
  
  var toneMarks = {
    1 : '',
    2 : 'ˊ',
    3 : 'ˇ',
    4 : 'ˋ',
    5 : '˙'
  };
  var userZhuyin = myGameState.get('useZhuyin');
  _.each(langData.symbols, function(symbol){
    if(userZhuyin){
      //TODO: Should probably use zhuyin chars in match
      symbol.transcription = symbol.zhuyin.replace(/(.{1,3})(\d)/g, function(match, symbols, tone){
        return symbols + toneMarks[tone];
        //return '<span class="zhuyin' + (symbols.length < 3 ? ' zy-large' : '') + '">' +
        //  symbols + '</span><span class="tone-mark">' + toneMarks[tone] + '</span>';
      });
    } else {
      symbol.transcription = symbol.pinyin;
    }
  });
  
  //Tiers default to the previous tier bc ideally they will increase monotonically.
  var lastTier;
  _.each(langData.symbols, function(sp){
    sp.tier = parseInt(sp.tier || lastTier || 1, 10);
    lastTier = sp.tier;
  });
  
  //Process lang data:
  langData.phrases = _.filter(langData.symbols, function(symbol){
    return symbol.symbols.length > 1;
  });
  langData.symbols = _.filter(langData.symbols, function(symbol){
    return symbol.symbols.length <= 1;
  });
  _.each(langData.symbols, function(symbol, symbolIdx){
    symbol.symbol = symbol.symbols;
  });
  
  //Seed selector order determined by the order of the rows in the spread sheet.
  //prices are all determined on game load
  lastTier = null;
  var lastCategory;
  var tierIdx = 0;
  var categories = [];
  langData.categories = categories;
  var addToCategorySet = function(category){
    if(!_.contains(categories, category)){
      categories.push(category);
    }
  };
  _.each(langData.symbols, function(sp){
    if(lastTier !== sp.tier || lastCategory !== sp.category) {
      tierIdx = 0;
    }
    
    sp.cost = Math.pow(2, sp.tier + 1) * (tierIdx + 1);
    
    lastTier = sp.tier;
    lastCategory = sp.category;
    tierIdx++;
    addToCategorySet(sp.category);
  });

  _.each(langData.phrases, function(sp){
      sp.cost = _.reduce(sp.spid.split(''), function(total, symbol){
        return (total + langData.spHash[symbol].cost);
      }, 0);
  });
};

var init = function() {
  var fieldTemplate = _.template($("#field-template").html());
  var quizTemplate = _.template($("#quiz-template").html());
  var plotTemplate = _.template('<div class="flower-container"><img src="img/<%- flowerImage %>"></div><div class="symbol"><%- symbol %></div>');
  var shopTemplate = _.template($("#shop-template").html());
  var rewardTemplate = _.template($('#reward-template').html());

  initLangData();
  
  //Initialize the field
  $('#fieldPage').html(fieldTemplate({
    rows: 5,
    cols: 5,
    seedTypes: langData.categories,
    seedType: 'basic'
  }));
  
  if(myGameState.get('specialEffects')){
    $('body').addClass('special-effects');
    //This is supposed to happen in mobile init so watch out for bugs
    $.mobile.defaultDialogTransition = 'pop';
    $.mobile.defaultPageTransition = 'slide';
  } else {
    $.mobile.defaultDialogTransition = 'none';
    $.mobile.defaultPageTransition = 'none';
  }
  
  var renderPlot = function(symbol){
    var $plot = $('#field').find('[data-x=' + symbol.get('x') + '][data-y=' + symbol.get('y') + ']');
    
    var isWeed = symbol.collection === myWeeds;
    
    if(isWeed) {
      $plot.html(plotTemplate({
        flowerImage : symbol.get('value') > 0.5 ? 'dandelion.png' : 'garden_weed_2.png',
        symbol : ''
      }));
    } else {
      var symbolData = langData.spHash[symbol.get('spid')];
      if(!symbolData){
        console.error(symbol.toJSON());
        return;
      }
      $plot.html(plotTemplate({
        flowerImage : flowerImages[symbolData.tier],
        symbol : symbolData.symbol
      }));
    }
    
    symbol.on('change:percentComplete', function(evt){
      if(isWeed && symbol.get('percentComplete') > 200) {
        //weeds wither, might want to use a different mechanism though...
        symbol.destroy();
        return;
      }
      
      var tenths = Math.floor(symbol.get('percentComplete') / 10);
      var prevTenths = Math.floor(symbol.previous('percentComplete') / 10);
      //Only trigger class changes on 10% changes
      if(tenths > prevTenths || !symbol.previous('percentComplete')) {
        $plot.removeClass().addClass('tile');
        if(isWeed) {
          $plot.addClass('blooming weed');
        } else if(tenths < 10) {
          $plot.addClass('grown-' + tenths + '-tenths');
        } else if(tenths >= 10) {
          $plot.addClass('blooming');
        }
      }
    });
  };
  myPlantedSymbols.on('add', renderPlot);
  myWeeds.on('add', renderPlot);
  
  myPlantedSymbols.on('remove', function(symbol){
    var selectedSymbol = selectedLocations.findWhere(symbol);
    if(selectedSymbol) selectedLocations.remove(selectedSymbol);
  });
  var removePlot = function(symbol){
    var $plot = $('#field').find('[data-x=' + symbol.get('x') + '][data-y=' + symbol.get('y') + ']');
    $plot.addClass('plucked');
    //wait for image to fade...
    //Do I need to prevent clicking?
    //There could be bugs while the animation happens
    window.setTimeout(function(){
      $plot.removeClass().addClass('tile');
      $plot.empty();
    }, 1000);
  };
  myPlantedSymbols.on('remove', removePlot);
  myWeeds.on('remove', removePlot);
  
  //Fetch is called down here so add events get triggered.
  myPlantedSymbols.fetch();
  myWeeds.fetch();

  myTransactions.on('add', _.debounce(function(transaction){
    console.log("transaction added");
    var sign = transaction.get('amount') > 0 ? '+' : '';
    $('.yuan').append('<div class="transaction">' + sign + transaction.get('amount') + '</div>');
    $('.transaction').delay(1000).fadeOut(800, function(){
      $('.current-balance').text(myTransactions.currentBalance());
    });
  }));
  myTransactions.fetch({
    success : function(){
      if(myTransactions.length === 0) {
        //seed money
        myTransactions.create({ amount : 100 });
      }
    }
  });


  var levNormalize = function(text){
    var toneMap = {
      'ā' : 'a1',
      'á' : 'a2',
      'ǎ' : 'a3',
      'à' : 'a4',
      'ē' : 'e1',
      'é' : 'e2',
      'ě' : 'e3',
      'è' : 'e4',
      'ī' : 'i1',
      'í' : 'i2',
      'ǐ' : 'i3',
      'ì' : 'i4',
      'ō' : 'o1',
      'ó' : 'o2',
      'ǒ' : 'o3',
      'ò' : 'o4',
      'ū' : 'u1',
      'ú' : 'u2',
      'ǔ' : 'u3',
      'ù' : 'u4'
    };
    return _.map(text.split(""), function(char){
      return toneMap[char];
    }).join('');
  };
  //TODO: Add flat to only use current lexicon
  var makeRedHerrings = function(phraseObject, quizType, amount){
    var possibilities;
    if(phraseObject.spid.length > 1){
      possibilities = _.filter(langData.phrases, function(phrase){
        //Only use phrases of the same length
        return phrase.spid.length === phraseObject.spid.length;
      });
    } else {
      possibilities = langData.symbols;
    }
    var answer = phraseObject[quizType];
    var normalizedAnswer = levNormalize(answer);
    return _.chain(possibilities)
      .pluck(quizType)
      .filter(function(val){
        return val && val !== phraseObject[quizType];
      })
      .uniq()
      .sortBy(function(val){
        return (new Levenshtein(normalizedAnswer, levNormalize(val))).distance;
      })
      .first(10)
      .shuffle()
      .first(amount || 4)
      .value();
  };
  
  var renderQuiz = function(ctxt){
    $('.quiz-container').html(quizTemplate(ctxt));
    _.each(ctxt.answers, function(answerText){
      var $answer = $('<a data-role="button" data-theme="a" class="answer">');
      if(ctxt.grid) $answer.addClass('grid');
      $answer.html(answerText);
      if(answerText === ctxt.answer){
        $answer.addClass('correct');
      }
      $('.answers').append($answer);
    });
    $('#quizPage').trigger('create');
  };

  //For efficiency it would be possible to separate getting of data
  var doBouquetQuiz = function(bouquet, doneCB){
    var phrase = _.map(bouquet, function(flower){
      return flower.get('spid');
    }).join('');

    var phraseObject;
    //It would be best to attach this during the parse stage.
    //Parsing needs to happen up front for validation.
    phraseObject = langData.spHash[phrase];

    //I don't think this can actually happen.
    if(!phraseObject) throw new Error("Phrase not found!");
    
    var quizTypes = _.filter(["english", "transcription"], function(quizType){
      return phraseObject[quizType];
    });
    var quizType = _.shuffle(quizTypes)[0];
    var itemType = bouquet.length > 1 ? "word-bouquet" : "character-flower";
    var answer = phraseObject[quizType];
    
    renderQuiz({
      symbols : phraseObject.symbols,
      type : quizType,
      itemType : itemType,
      answers : _.shuffle([answer].concat(makeRedHerrings(phraseObject, quizType, 5))),
      answer : answer
    });
    
    //Delay the answer handler setup to avoid accidental clicks
    window.setTimeout(function(){
      $(document).one('vclick', '.answer', function(evt){
        evt.preventDefault();
        $('.answer').addClass('ui-disabled');
        $(evt.target).closest('.ui-btn').addClass('highlight');
        $('.correct').closest('.ui-btn').addClass('highlight correct');
  
        var reward = function(){
          var nowish = new Date();
          var valuedFlowerObjects = _.map(bouquet, function(flower){
            var flowerObj = flower.toJSON();
            var flowerData = langData.spHash[flowerObj.spid];
            var ageMins = (nowish - flowerObj.plantedTime) / (1000 * 60);
            var peakAge = 100 / (flowerObj.rate * 60);
            return _.extend(flowerObj, {
              //flowers earn N% non-compunding interest per minute
              //If it compounds that fast it explodes
              //The interest rate diminishes with highter tiers so their ROIs don't get too extreme.
              //The percent complete is factored in to account for wilting.
              value : Math.round((1 + (0.05 / flowerData.tier) *
                                  Math.min(ageMins, peakAge)) *
                                  flowerObj.cost),
              symbol : flowerData.symbol
            });
          });
          var flowerSum = _.reduce(valuedFlowerObjects, function(sum, fob){
            return sum + fob.value;
          }, 0);
          var bouquetMultiplier = 1 + (valuedFlowerObjects.length - 1) / 5;
          var totalReward = Math.round(flowerSum * bouquetMultiplier);

          myTransactions.create({ amount : totalReward });
          
          $( "#reward" )
            .html(rewardTemplate({
              totalReward : totalReward,
              valuedFlowerObjects : valuedFlowerObjects,
              flowerSum : flowerSum,
              itemType : itemType,
              bouquetMultiplier : bouquetMultiplier
            }))
            .trigger('create')
            .popup("open")
            .one("popupafterclose", function( event, ui ) {
              console.log('popup closed');
              doneCB(true);
            });
        };
        
        if($(evt.target).closest('.correct').length > 0) {
          _.defer(function(){
            quiz.trigger('pass', bouquet);
          });
          toast({
            theme : 'e',
            html: "<h1>恭喜你!<br /><small>(Congratulations!)</small></h1>"
          });
          window.setTimeout(function(){
            reward();
          }, 1000);
        } else {
          $( "#wrong" )
            .popup("open")
            .one("popupafterclose", function( event, ui ) {
              doneCB(false);
            });
        }
      });
    }, 700);
  };

  //This button only exists incase the quiz freezes.
  $(document).on('vclick', '#cancelQuiz', _.throttle(function(){
    if(confirm("Are you sure you want to end the quiz?")) window.location.reload();
  }, 2000));
  
  //Hack to close popups that is necessair because android chrome doesn't support data-rel=back
  $( document ).on('vclick', '.ok-popup', function(){
    $( this ).closest("[data-role=popup]").popup('close');
  });

  var openQuiz = function(doneCB){
    var startTime = Number(new Date());
    var allBlooming = selectedLocations.every(function(location){
      return 'isBlooming' in location && location.isBlooming();
    });
    if(!allBlooming) {
      doneCB();
      toast("<p>Wait for your flowers to bloom.</p>");
      return;
    }
    var bouquets = selectedLocations.findStreaks();
    var streakSpids = _.map(bouquets, function(streak){
      return _.map(streak, function(flower){
        return flower.get('spid');
      }).join('');
    });
    if(_.uniq(streakSpids).length !== streakSpids.length){
      doneCB();
      toast("<p>Duplicate word-bouquets cannot be plucked together.</p>");
      return;
    }
    var invalidWords = _.filter(streakSpids, function(spid){
      if(spid.length === 1) return false;
      //Filter by available phrases
      return !(myPhrasesOwned.findWhere({
        spid : spid
      }));
    });
    if(invalidWords.length > 0) {
      doneCB();
      //TODO: Show invalid words
      toast("<h3>Unknown word bouquet(s)" + 
        "</h3><p>You need to buy bouquets from the shop before plucking them.</p>");
      return;
    }
    
    //Destroy the flowers (tell noone of this...)
    //Doesn't actually delete the data from memory,
    //only the collection and persistent data is removed,
    //so I can keep using the bouquets in quizes.
    synchronousEach(bouquets, function(bouquet, nextBouquet, idx){
      synchronousEach(bouquet, function(flower, nextFlower){
        if(flower.destroyed) {
          return nextFlower();
        }
        flower.destroyed = true;
        flower.destroy({
          success: nextFlower,
          error: function() {
            alert("Error: Could not remove flower.");
          }
        });
      }, nextBouquet);
    });

    //Delay so users see the plucking graphic
    window.setTimeout(function(){
      $('#quizPage')
        .one('pagebeforeshow', doneCB)
        .one('pageshow', function(){
          quiz.trigger('start');
        });
      
      $.mobile.changePage('#quizPage', {
        transition: $.mobile.defaultDialogTransition,
        //Chaining doesn't work for some reason when this is false
        changeHash: true
      });
      
      var allPass = true;
      synchronousEach(bouquets, function(bouquet, next, idx){
        try {
          doBouquetQuiz(bouquet, function(pass){
            allPass = allPass && pass;
            console.log('pass: ' + pass);
            if(idx + 1 === bouquets.length) {
              next();
              return;
            }
            //Creating a delay to avoid accidental clicks.
            //TODO: This could be more interesting...
            $('.quiz-container').html('<center><h1>下一题 。。。<h1><p>(Next Question...)</p></center>');
            window.setTimeout(next, 2000);
          });
        } catch(e) {
          alert("Error opening quiz: " + e.message);
          //This shouldn't happen.
          window.location.reload();
        }
      }, function(){
        if(allPass) {
          _.defer(function(){
            quiz.trigger('allPass', bouquets);
          });
        }
        console.log('quiz over');
        window.history.back();
      });
      
    }, 2000 - (Number(new Date()) - startTime));
  };

  var openWeedQuiz = function(weed, doneCB) {
    $('#quizPage').one('pageshow', doneCB);
    $.mobile.changePage('#quizPage', {
      transition: $.mobile.defaultDialogTransition,
      //Chaining doesn't work for some reason when this is false
      changeHash: true
    });
    
    var phraseObject = langData.spHash[mySeeds.at(Math.floor(Math.random() * mySeeds.length)).get('spid')];
    
    //Use a different template that makes a grid for symbol quizes?
    renderQuiz({
      symbols : phraseObject.transcription,
      type : 'weed',
      answers : _.shuffle([phraseObject.symbol].concat(makeRedHerrings(phraseObject, 'symbol'))),
      answer : phraseObject.symbol,
      grid : true
    });
    
    $(document).one('vclick', '.answer', function(evt){
      evt.preventDefault();
      $('.answer').addClass('ui-disabled');
      $(evt.target).closest('.ui-btn').addClass('highlight');
      $('.correct').closest('.ui-btn').addClass('highlight correct');

      if($(evt.target).closest('.correct').length > 0) {
        toast({
          theme : 'e',
          html: "<h1>恭喜你!<br /><small>(Congratulations!)</small></h1>"
        });
        window.setTimeout(function(){
          var totalReward = 20;
          myTransactions.create({ amount : totalReward });
          
          $( '#reward' )
            .html(rewardTemplate({
              totalReward: totalReward,
              itemType: 'weed'
            }))
            .trigger('create')
            .popup("open")
            .one("popupafterclose", function( event, ui ) {
              console.log('popup closed');
              try {
                weed.destroy({
                  error: function() {
                    alert("Error: Could not destroy weed.");
                  }
                });
              } catch(e) {
                console.log('Weed previously destroyed...');
              }
              window.history.back();
            });
        }, 1000);
      } else {
        $( "#wrong" )
          .popup("open")
          .one("popupafterclose", function( event, ui ) {
            window.history.back();
          });
      }
    });
  };

  var attemptPluck = (function(){
    var plucking = false;
    return function(pluckAction){
      if(plucking) return;
      plucking = true;
      //Not sure if I should show these graphics...
      var $sl = $('<div class="screen-lock">');
      var $plucker = $('<img src="img/pluck.png" class="plucker" />');
      $.mobile.pageContainer.append($sl).append($plucker);
      $sl.css('z-index', 1200);
      var donePlucking = function(){
        $sl.remove();
        $plucker.remove();
        plucking = false;
      };
      _.defer(function(){
        try {
          pluckAction(donePlucking);
        } catch(e) {
          alert("Error while plucking: " + e.message);
          donePlucking();
          throw e;
        }
      });
    };
  }());
  
  $(document).on("vclick", ".pluck-btn", function(evt) {
    attemptPluck(openQuiz);
  });
  
  $(document).on('vclick', '.click-to-close', _.throttle(function(evt){
    $(evt.target).closest('.click-to-close').popup('close');
  }, 2000));
  
  var neighborCollection = Backbone.Collection.extend({
    addLocation : function(x, y){
      var symbol = myPlantedSymbols.findWhere({
        x:x,
        y:y
      });
      if(!symbol) return;
      if(selectedLocations.findWhere({
        x:x,
        y:y
      })) return;
      if(this.findWhere(symbol)) return;
      this.add(symbol);
    }
  });
  var neighbors = new neighborCollection();
  
  var selectionCollection = Backbone.Collection.extend({
    select : function($tile){
      //Hack to fix this ipad issue:
      //http://stackoverflow.com/questions/17711680/phonegap-taps-not-registering-until-next-tap-on-ipad
      var $refresher = $('<div>');
      $.mobile.pageContainer.append($refresher);
      $refresher.fadeIn().fadeOut();
      
      console.log('select');
      var selectedLocations = this;
      var location = { 
        x : $tile.data("x"),
        y : $tile.data("y")
      };
      var selectedLocation = selectedLocations.findWhere(location);
      if(selectedLocation) {
        selectedLocations.remove(selectedLocation);
        return;
      }
      var selectedSymbol = myPlantedSymbols.findWhere(location);
      if(selectedSymbol) {
        if(!neighbors.contains(selectedSymbol)) {
          selectedLocations.reset();
        }
        selectedLocations.add(selectedSymbol);
      } else {
        selectedLocations.reset();
        selectedLocations.add(location);
      }
    },
    findStreaks: function(){
      var selectedFlowers = selectedLocations.map(function(location){
        return myPlantedSymbols.findWhere(location.toJSON());
      });
      var findLinearStreaks = function(lineAxis) {
        var lines = _.values(_.groupBy(selectedFlowers, function(value){
          return value.get(lineAxis === 'y' ? 'x' : 'y');
        }));
        return _.map(lines, function(line) {
          return _.chain(line)
            .sortBy(function(value){
              return value.get(lineAxis);
            })
            .reduce(function(streaks, value){
              if(streaks.length === 0) return [[value]];
              var lastStreak = _.last(streaks);
              if(_.last(lastStreak).get(lineAxis) + 1 !== value.get(lineAxis)) {
                //Make a new streak
                return streaks.concat([[value]]);
              }
              //FP cheating.
              lastStreak.push(value);
              return streaks;
            }, []).value();
        });
      };
      if(selectedFlowers.length === 1) return [selectedFlowers];
      var rows = findLinearStreaks('x');
      var cols = findLinearStreaks('y');
      return _.filter(_.flatten(rows.concat(cols), true), function(value){
        return value.length > 1;
      });
    }
  });
  var selectedLocations = new selectionCollection();
  window.selectedLocations = selectedLocations;
  
  selectedLocations.on('add remove reset', function(){
    $('.pluck-btn').addClass('ui-disabled');
    var pluckableItemSelected = selectedLocations.some(function(loc){
      return loc.get('spid');
    });
    if(pluckableItemSelected) {
      $('.pluck-btn').removeClass('ui-disabled');
    }
  });
  selectedLocations.on('add', function(location){
    console.log('add l');
    var $tile = $('#field').find('[data-x=' + location.get('x') + '][data-y=' + location.get('y') + ']');
    
    var $selection = $('<div class="selection-item" data-x=' + location.get('x') + ' data-y=' + location.get('y') + ' >');

    $selection.css('top', $tile.position().top - 1);
    $selection.css('left', $tile.position().left - 1);
    $('.selection').append($selection);
    
    neighbors.reset();
    
    window.setTimeout(function(){
      var emptyTileSelected = selectedLocations.length === 1 && !selectedLocations.first().get("spid");
      var $seedSelector = $('#seed-selector');
      if(emptyTileSelected) {
          //Show seeds at bottom if they would appear over the tapped tile.
          if($tile.offset().top < 220) {
            $seedSelector.addClass('bottom');
          } else {
            $seedSelector.removeClass('bottom');
          }
          $seedSelector.addClass('visible-layer');
      } else {
        $seedSelector.removeClass('visible-layer');
      }
    }, 100);//Question: Does a longer delay prevent the delay when making an initial selection on android?
    
  });
  
  selectedLocations.on('remove', function(location){
    //TODO: Check if things get disconnected and discard the smaller component.
    console.log('remove l');
    $('.selection').find('[data-x=' + location.get('x') + '][data-y=' + location.get('y') + ']').remove();
    $('#seed-selector').removeClass('visible-layer');
    neighbors.reset();
  });
  selectedLocations.on('reset', function(){
    console.log('reset l');
    $('#seed-selector').removeClass('visible-layer');
    $('.selection').empty();
    neighbors.reset();
  });
  //TODO: Speed up neighbor highlighting by doing it incrementally
  neighbors.on('reset', _.delayed(function(){ 
    console.log('reset n');
    $('.selection .selection-neighbor').remove();
    selectedLocations.each(function(symbol){
      if(!symbol.get('spid')) return;
      var symbolX = symbol.get('x');
      var symbolY = symbol.get('y');
      neighbors.addLocation((symbolX + 1), symbolY);
      neighbors.addLocation((symbolX - 1), symbolY);
      neighbors.addLocation(symbolX, (symbolY + 1));
      neighbors.addLocation(symbolX, (symbolY - 1));
    });
  }));
  neighbors.on('add', _.delayed(function(location){ 
    console.log('add n');
    var $tile = $('#field').find('[data-x=' + location.get('x') +
      '][data-y=' + location.get('y') + ']');

    var $selection = $('<div class="selection-item selection-neighbor" data-x=' +
      location.get('x') +
      ' data-y=' + location.get('y') +
      ' >');
    $selection.css('top', $tile.position().top - 1);
    $selection.css('left', $tile.position().left - 1);
    $('.selection').append($selection);
  }));

  //selection handler
  $(document).on("vclick", ".tile", _.throttle(function(evt) {
    //What I actually want on this is a throttle until event is emitted function
    var $tile = $(evt.target).closest(".tile");
    evt.preventDefault();
    if($tile.hasClass('weed')) {
      attemptPluck(function(donePlucking){
        var weedModel = myWeeds.findWhere({ 
          x : $tile.data("x"),
          y : $tile.data("y")
        });
        //Delay so the graphic is visible
        window.setTimeout(function(){
          openWeedQuiz(weedModel, donePlucking);
        }, 1000);
        //reset selection out of iOS fud
        selectedLocations.reset();
      });
    } else {
      selectedLocations.select($tile);
    }
  }, 200));

  $(document).on("vclick", "#seed-selector", function(evt) {
    evt.preventDefault();
  });
  
  //Throttled to prevent possibility a vclick triggering it multiple times.
  var showConfirmBtn = _.throttle(function(evt, $item, confirmCB){
    //In theory this isn't necessairy, but I'm seeing multiple confirms on the iphone4S
    $( ".confirm-purchase" ).remove();
    
    var offs = $item.offset();
    evt.preventDefault();
    
    //TODO: Make pre-rendered template for this.
    var $confirmBtn = $('<div class="confirm-purchase"><a data-role="button" data-theme="e" data-mini="true" class="confirm-btn">Tap to buy</a></div>')
      .css('left', offs.left)
      .css('top', evt.pageY - 18)
      .css('width', $item.outerWidth())
      .css('z-index', 1300);
    $('.ui-page-active').append($confirmBtn);
    $confirmBtn.trigger('create');
    
    //Has to be vmousedown for some reason
    $(document).one("vmousedown", function(evt2){
      if($confirmBtn.has(evt2.target).length > 0) {
        console.log("Clicked confirm");
        evt2.preventDefault();
        try {
          confirmCB(evt2);
        } catch(e) {
          alert('Error performing action: ' + e.message);
        }
      }
      $( ".confirm-purchase" ).remove();
    });
  }, 100, {trailing: false});
  
  $(document).on("vclick", ".buyable-item", function(evt) {
    var $item = $(evt.target).closest(".buyable-item");
    showConfirmBtn(evt, $item, function(){
      var spid = $item.data('spid');
      var itemModel = myShopInventory.findWhere({ spid : spid });
      if(!itemModel) throw new Error('Item is not in shop inventory.');
      if(!myTransactions.attempt(-itemModel.get('cost'))) return;
      myPhrasesOwned.create({
        spid : spid
      });
      itemModel.destroy();
      //Avoid repositioning of elements by just hiding it.
      $item.css('visibility', 'hidden');
      //TODO: Show large version to help user learn the phrase first?
    });
  });
  
  $(document).on("vclick", ".seeds", function(evt) {
    var $packet = $(evt.target).closest(".seeds");
    if($packet.length === 0) {
      console.error('No .seeds element found, strange...');
    }
    var spid = $packet.data('spid');
    var cost = parseInt($packet.data('cost'), 10);
    
    showConfirmBtn(evt, $packet, function(){
      if(!myTransactions.attempt(-cost)) return;
      if($packet.hasClass('locked')){
        mySeeds.create({
          spid : spid
        });
      } else {
        if(selectedLocations.length !== 1){
          alert("selectedLocations.length = " + selectedLocations.length);
          return;
        }
        var symbolData = langData.spHash[spid];
        var tileLocation = selectedLocations.first().toJSON();
        myPlantedSymbols.create(_.extend({
          spid: spid,
          rate: 100 / (Math.pow(2, symbolData.tier) * 60),
          cost: cost
        }, tileLocation));
        selectedLocations.reset();
      }
    });

  });
  /*
  $(document).on('vclick', '#seed-selector .arrow-up', function(){
    var $titleEls = $('.seed-tier-title');
    var idx = parseInt($(this).closest('.seed-tier-title').data('idx'), 10);
    $(".seed-scroll-wrap").data("mobileIscrollview").scrollToElement($titleEls[(idx - 1 + $titleEls.length) % $titleEls.length], 1000);
  });
  $(document).on('vclick', '#seed-selector .arrow-down', function(){
    var $titleEls = $('.seed-tier-title');
    var idx = parseInt($(this).closest('.seed-tier-title').data('idx'), 10);
    $(".seed-scroll-wrap").data("mobileIscrollview").scrollToElement($titleEls[(idx + 1 + $titleEls.length) % $titleEls.length], 1000);
  });
  */
  $(document).on('vclick', '.close-seed-selector', function(evt){
    if(selectedLocations.length === 1) {
      //deselect the tile
      selectedLocations.reset();
    }
  });
  $(document).on('vclick', '.seed-type', function(evt){
    $('#seed-type-select').popup('open');
  });
  $(document).on('vclick', '.seed-type-option', function(evt){
    $('.seed-type .ui-btn-text').text($(evt.target).text());
    renderSeedSelector();
  });
  
  $('#catalog').on('pagebeforeshow', function(){
    console.log('rerendering catalog');
    $('.catalog-symbols').empty();
    var catalogItemTemplate = _.template($('#catalog-item-template').html());
    $('.catalog-symbols').append('<li data-role="list-divider">Phrases</li>');
    myPhrasesOwned.each(function(phrase){
      var spObj = langData.spHash[phrase.get('spid')];
      if(!spObj){
        console.log(spObj, phrase);
        return;
      }
      $('.catalog-symbols').append(catalogItemTemplate(_.extend(phrase.toJSON(), spObj)));
    });
    
    $('.catalog-symbols').append('<li data-role="list-divider">Symbols</li>');
    mySeeds.each(function(seed){
      var spObj = langData.spHash[seed.get('spid')];
      if(!spObj){
        console.log(spObj, seed);
        return;
      }
      $('.catalog-symbols').append(catalogItemTemplate(_.extend(seed.toJSON(), spObj)));
    });
    $('.catalog-symbols').listview('refresh');
  });

  $('#shopPage').on('pagebeforeshow', function(){
    try {
      if(myShopInventory.dirty){
        myShopInventory.reset();
        //restock shop
        //No idea whether this has good perf.
        var seedSpidRegex = new RegExp('^(' + mySeeds.pluck('spid').join('|') + ')+$');
        _.chain(langData.phrases)
          .pluck('spid')
          .difference(myPhrasesOwned.pluck('spid'))
          .filter(function(spid) {
            return seedSpidRegex.test(spid);
          })
          .each(function(spid){
            myShopInventory.add(langData.spHash[spid]);
          });
        myShopInventory.dirty = false;
      }
      var items = myShopInventory.map(function(item){
        var itemObj = item.toJSON();
        itemObj.meaning = itemObj.english;
        return itemObj;
      });
      $('.shop-content').html(shopTemplate({
        items : items,
        itemsPerShelf : 2
      }));
    } catch(e) {
      alert(e.message);
    }
  });
  
  var attemptPlantWeedSomewhere = function(){
    var location = {
      x: Math.floor(Math.random()*5),
      y: Math.floor(Math.random()*5)
    };
    if(myPlantedSymbols.findWhere(location)) return;
    if(myWeeds.findWhere(location)) return;
    if(selectedLocations.findWhere(location)) return;
    return myWeeds.create(location);
  };
  
  window.setInterval(function(){
    var updateCompleteness = function(currentSymbol){
      var secondsAlive = ((new Date() - currentSymbol.get('plantedTime')) / 1000);
      currentSymbol.set('percentComplete', secondsAlive * currentSymbol.get('rate'));
    };
    myPlantedSymbols.each(updateCompleteness);
    myWeeds.each(updateCompleteness);
    
    var WEEDS_PER_MINUTE = 0.42;
    if(Math.random() < (WEEDS_PER_MINUTE / 60)) {
      attemptPlantWeedSomewhere();
    }
  }, 999);
  
  //TODO: Use an event for this...
  window.setTimeout(function(){
    $.mobile.changePage('#fieldPage', {
      transition: 'fade'
    });
    
    //Init jQm stuff on rendered field
    $('#fieldPage').trigger("pagecreate");
    //Hack way to avoid having jQm miscompute the page padding
    //so that there is extra scroll space that can cause window scrolling
    //(which causes bugginess when combined with iscrolling)
    $(window).trigger('resize');
  
    //Needs to happen after page create
    renderSeedSelector();
    mySeeds.on('change', renderSeedSelector);
    mySeeds.on('add', function(symbol){
      console.log('shop inventory needs refresh.');
      myShopInventory.dirty = true;
    });
    
    //Not sure this is an intuitive reactor
    myGameState.trigger("ready");
  }, 1000);
};

$(function(){
  var mySettingsView = new SettingsView({
    el: $('#settings-container').get(0)
  });
  $('#settings').on('pagebeforeshow', function(){
    myGameState.fetch();
    $( "#settings" ).dialog( "option", "closeBtn", myGameState.get('initialized') ? "left" : "none" );
    mySettingsView.render();
  });
  
  if(!myGameState.get('initialized')) {
    mySettingsView.settings = [{
      label: "Character Type",
      name: 'useTraditional',
      options: ["Simplified", "Traditional"]
    }, {
      label: "Transcription Type",
      name: 'useZhuyin',
      options: ["Pīnyīn", "Zhuyin"]
    }, {
      label: "Special Effects",
      name: 'specialEffects'
    }, {
      label: "Show Tutorial",
      name: 'showTutorial'
    }];
    mySettingsView.render();
    window.setTimeout(function(){
      $.mobile.changePage('#settings');
    }, 500);
    return;
  }
  
  $.getJSON("languageData.json").success(function(result){
    langData = result;
    init();
  });

  //Transitions seems to cause css styles to indefinately presist after
  //the class is removed on Android.
  //I've seen this happen when:
  //A transition happens without window focus.
  //Flowers are removed after the quiz
  //This prevents the issue by adding then removing the transition styles.
  var fixStyles = function(){
    if(myGameState.get('specialEffects')) {
      $('body').removeClass('special-effects');
      window.setTimeout(function(){
        $('body').addClass('special-effects');
      }, 200);
    }
  };
  $(window).on('focus', fixStyles);
  $('#fieldPage').on('pagebeforeshow', function(){
    window.setTimeout(fixStyles, 1000);
  });

  //Change stuff visually so the user knows something is happening
  $(document).on("vclick", '.phrase-shop-btn', function(){
    coverScreen();
    $.mobile.changePage('#shopPage');
  });
  
  //Disable buttons to prevent double taps from impatience
  $(document).bind("pagebeforechange", _.debounce(function(e, data) {
    //TODO: This still has a bug when closing things that lead to the main page...
    if('toPage' in data && data.toPage.match('html(#shopPage)?$')){
      var $btns = $('.phrase-shop-btn').addClass('ui-disabled');
      window.setTimeout(function(){
        $btns.removeClass('ui-disabled');
      }, 1500);
    }
  }, 100, true));

});
