#!/usr/local/bin/python3
import argparse

class DamageCalculator():
	def __init__(self, die, dieCount, step):
		self.die = die
		self.dieCount = dieCount
		self.step = step
		self.maxCombinations = die**dieCount
		self.maxValue = die*dieCount
		self.calculateProbabilities()

	#calculate the probability for each possible result of ndx
	def calculateProbabilities(self):
		possibilities = self.getPossibilities()
		sums = [sum(possibility) for possibility in possibilities]
		probabilities = {}
		for possibility in sums:
			if possibility in probabilities:
				probabilities[possibility] += 100/self.maxCombinations
			else:
				probabilities[possibility] = 100/self.maxCombinations
		self.probabilities = probabilities

	#get the damage that corresponds to a particular percentile
	def getDamage(self, percentile):
		if percentile == self.step:
			return self.maxValue
		elif percentile == 100:
			return self.dieCount

		probability = 100.0
		for result in range(self.dieCount, self.maxValue+1):
			result = self.maxValue + self.dieCount - result
			probability -= self.probabilities[result]
			if probability < percentile:
				break
		return self.maxValue + self.dieCount - result

	#return an array of all possible dice combinations for ndx
	def getPossibilities(self):
		possibility = [1]*self.dieCount
		possibilities = [possibility]

		for index1 in range(1, self.die**self.dieCount):
			possibility = list(possibilities[index1-1])
			if possibility[0] == self.die:
				possibility[0] = 1
				for index2 in range(1, self.dieCount):
					if possibility[index2] < self.die:
						possibility[index2] += 1
						break
					else:
						possibility[index2] = 1
			else:
				possibility[0] += 1
			possibilities.append(possibility)
		return possibilities

#print the table row
def printRow(startDie, endDie, row):
	if endDie:
		if startDie != endDie:
			row[0] = startDie + '-' + endDie
	print('\t'.join(previousRow))

parser = argparse.ArgumentParser(description='Convert damage rolls to d20 or d100 (attack) rolls.')
parser.add_argument('dice', nargs='*', help='dice to convert')
parser.add_argument('--game', choices=['dungeons', 'gods', 'runequest', 'd20', 'd100'], type=str.lower, default='gods')
args = parser.parse_args()

#determine which dice we want data for
dice = args.dice
if not dice:
	dice = ['d2', 'd3', 'd4', 'd6', 'd8', 'd10', 'd12', '2d6', '2d8', '3d6', '4d6']

#runequest uses d100, D&D and Gods & Monsters use d20
if args.game in ['runequest', 'd100']:
	step = 1
else:
	step = 5

#in D&D and Runequest, minimum rolls never hit
if args.game == 'runequest':
	start = 6
elif args.game == 'dungeons':
	start = 10
else:
	start = step

#create a row of attack rolls
rows = []
cells = []
for roll in range(0, 101, step):
	if roll == 0:
		cell = ''
	else:
		if args.game in ['dungeons', 'runequest'] and roll <= 5:
			continue
		cell = str(int(roll/step))
	cells.append(cell)
rows.append(cells)

#create a row corresponding attack roll to damage roll for each damage die
for die in dice:
	#the first column is a header column, what die is this?
	cells = [die]

	#if the die does not start with a number, it is 1dx
	if die.startswith('d'):
		die = '1' + die

	#split ndx into n, x
	dieCount, die = die.split('d')
	dieCount, die = int(dieCount), int(die)

	#create the percentile to damage calculator, and run through each possible attack roll
	damageCalculator = DamageCalculator(die, dieCount, step)
	for percentile in range(start, 101, step):
		#in Gods & Monsters, low rolls are best
		if args.game != 'gods':
			percentile = 100 + step - percentile

		damage = damageCalculator.getDamage(percentile)
		cells.append(str(damage))
	rows.append(cells)

#rotate table so that attack rolls are on the side and dice are across the top
rows = list(zip(*reversed(rows)))

previousDamages = None
startDie = '1'
previousDie = None
for row in rows:
	#due to the rotation, our dice are now in reverse order
	#they're also tuples and we need them to be lists
	row = list(reversed(row))

	#separate the die from the damages, so that we can tell when the damages change, to compress the table
	die, damages = row[0], row[1:]
	if previousDamages and damages != previousDamages:
		printRow(startDie, previousDie, previousRow)
		startDie = die

	#remember the previous row data so that it can be printed when the damages change
	previousDamages = damages
	previousRow = row
	previousDie = die

#print the final row
printRow(startDie, previousDie, previousRow)
