function ScoreTracker(scoreOutput, linesOutput, levelOutput, tickerOutput) {
    this.level = 1;
    this.score = 0;
    this.linesRemaining = ScoreTracker.levelLines(this.level);

    this.scoreOutput = scoreOutput;
    this.linesOutput = linesOutput;
    this.levelOutput = levelOutput;
    this.tickerOutput = tickerOutput;
    
    this.curCombo = -1;
    this.lastWasBonus = false;
    this.backToBackCount = 0;

    this.isGameWon = false;

    this.outputScore();
    this.outputLines();
    this.outputLevel();
}

ScoreTracker.levelLines = function (level) {
    return level*5;
};

ScoreTracker.prototype.updateScore = function(config) {
    var linesCleared = 0,
    isBonus = false,
    scoreDiff = 0,
    tickerLines = [],
    i;

    if (config.miniT) {
	// mini t spin, 1 for no lines, 2 for 1 line
	tickerLines.push("T Spin Mini");
	linesCleared += 1;
	scoreDiff += 100 * this.level;
	if (config.lines === 1) {
	    linesCleared += 1;
	    scoreDiff += 100 * this.level;
	}
    } else if (config.normalT) {
	// normal t spin, bonus for eveything but 0 lines
	switch (config.lines) {
	case 0:
	    tickerLines.push("T Spin");
	    linesCleared += 4;
	    scoreDiff += 400 * this.level;
	    break;
	case 1:
	    tickerLines.push("T Spin Single");
	    linesCleared += 8;
	    isBonus = true;
	    scoreDiff += 800 * this.level;
	    break;
	case 2:
	    tickerLines.push("T Spin Double");
	    linesCleared += 12;
	    isBonus = true;
	    scoreDiff += 1200 * this.level;
	    break;
	case 3:
	    tickerLines.push("T SPIN TRIPLE");
	    linesCleared += 16;
	    isBonus = true;
	    scoreDiff += 1600 * this.level;
	    break;
	}
    } else if (config.lines > 0) {
	// plain old line clears
	switch (config.lines) {
	case 1:
	    tickerLines.push("Single");
	    linesCleared += 1;
	    scoreDiff += 100 * this.level;
	    break;
	case 2:
	    tickerLines.push("Double");
	    linesCleared += 3;
	    scoreDiff += 300 * this.level;
	    break;
	case 3:
	    tickerLines.push("Triple");
	    linesCleared += 5;
	    scoreDiff += 500 * this.level;
	    break;
	case 4:
	    tickerLines.push("TETRIS");
	    linesCleared += 8;
	    isBonus = true;
	    scoreDiff += 800 * this.level;
	    break;
	}
    }

    // apply the combo
    if (linesCleared > 0) {
	this.curCombo += 1;
	linesCleared += Math.floor(this.curCombo * 0.5);
	scoreDiff += 50 * this.curCombo * this.level;
	if (this.curCombo >= 1) {
	    tickerLines.push("Combo x" + this.curCombo);
	}
    } else {
	this.curCombo = -1;
    }

    // apply back-to-back bonus
    if (this.lastWasBonus && isBonus) {
	tickerLines.push("Back-to-Back");
	this.backToBackCount += 1;
	linesCleared = Math.floor(linesCleared * 1.5);	
	scoreDiff += this.backToBackCount * 0.5 * scoreDiff;
    } else {
	this.backToBackCount = 0;
    }
    // only update the last bonus state if a single through triple was gotten
    if (config.lines > 0) {
	this.lastWasBonus = isBonus;
    }
    
    // apply the lines cleared
    this.linesRemaining -= linesCleared;    
    if (this.linesRemaining <= 0) {
	if (this.level < 15) {
	    this.level += 1;
	    this.linesRemaining = ScoreTracker.levelLines(this.level);
	} else {
	    this.isGameWon = true;
	}
	this.outputLevel();
    }

    if (linesCleared > 0) {
	this.outputLines();
    }


    this.score += scoreDiff;
    this.outputScore();

    if (tickerLines.length === 0) {
	this.tickerOutput.addLine("");
    } else {
	for (i = 0; i < tickerLines.length; i += 1) {
	    this.tickerOutput.addLine(tickerLines[i]);
	}
    }
};

ScoreTracker.prototype.softDrop = function() {
    this.score += 1;
};

ScoreTracker.prototype.hardDrop = function(dist) {
    this.score += 2 * dist;
};

ScoreTracker.prototype.getLinesRemaining = function() { return this.linesRemaining; };
ScoreTracker.prototype.getScore = function() { return this.score; };
ScoreTracker.prototype.getLevel = function() { return this.level; };

ScoreTracker.prototype.getLevelPeriod = function() {
    var periods = [
	1000,
	800,
	600,
	470,
	380,
	250,
	200,
	160,
	130,
	90,
	50,
	27,
	20,
	15,
	10
    ],
    res = periods[(this.level < periods.length) ? this.level : periods.length - 1];
    return res;
};

ScoreTracker.prototype.gameWon = function() {
    return this.isGameWon;
};

ScoreTracker.prototype.getResults = function() {
    return {
	score: this.score,
	level: this.level,
	won: this.isGameWon
    };
};

ScoreTracker.prototype.outputScore = function() {
    this.scoreOutput.addLine("Score:");
    this.scoreOutput.addLine("" + this.score);
    this.scoreOutput.addLine("");
};

ScoreTracker.prototype.outputLines = function() {
    this.linesOutput.addLine("Lines:");
    this.linesOutput.addLine("" + this.linesRemaining);
    this.linesOutput.addLine("");
};

ScoreTracker.prototype.outputLevel = function() {
    this.levelOutput.addLine("Level:");
    this.levelOutput.addLine("" + this.level);
    this.levelOutput.addLine("");
};