import random
import math
from fractions import Fraction
from datetime import datetime
from jinja2 import Template

# empty class for passing to template engine 
class Recipe:
	def __init__(self):
		return

# returns flour percent using flour type
def get_special_flour_percent(flourType: str, breadFlourPercent:int) -> int:
	if flourType == 'Hard Red Whole Wheat' or flourType == 'Hard White Wheat':
		percentages = [0,25,30,35,40,45,50]
		percentages = list(filter(lambda x: 100-breadFlourPercent >= x, percentages))
		return random.choice(percentages)
	elif flourType == 'Rye' and breadFlourPercent >= 75:
		percentages = [0,10,15,20]
		percentages = list(filter(lambda x: 100-breadFlourPercent >= x, percentages))
		return random.choice(percentages)
	else:
		percentages = [0,10,15,20,25.30]
		percentages = list(filter(lambda x: 100-breadFlourPercent >= x, percentages))
		return random.choice(percentages)

# returns multiplied spoon units from teaspoon fraction input, 3 tsp = 1 tbsp
def spoon_mult(tsp: Fraction(), multiplier: float) -> str:
	tsp *= Fraction(multiplier)
	spoonString = ""
	if tsp >= 3: # use tablespoons
		tablespoons = int(tsp // 3)
		remainder = (tsp % 3) / 3
		if tablespoons != 0:
			spoonString += f"{tablespoons} "
		if remainder.numerator != 0:
			spoonString += f"{remainder.numerator}/{remainder.denominator} "
		return f"{spoonString}tbsp"
	else:
		teaspoons = int(tsp // 1)
		remainder = tsp % 1
		if teaspoons != 0:
			spoonString += f"{teaspoons} "
		if remainder.numerator != 0:
			spoonString += f"{remainder.numerator}/{remainder.denominator} "
		return f"{spoonString}tsp"

# returns amount given the type of flavoring(spices) 
def get_flavor_amount(flavor: str, flourAmount: int) -> str:
	colorsDict = {}
	scale = 4 # floors to the 500g/scale for clean fractional multiplication
	multiplier = math.floor(flourAmount/500*scale) / scale
	# flavors in category
	red = ('Cardamom', 'Nutmeg','Hazelnut','Almond','Lemon Extract','Peppermint')
	blue = ('Cinnamon', 'Allspice')
	green = ('Vanilla', 'Instant Coffee')
	purple = ('Orange Zest', 'Lime Zest', 'Lemon Zest', 'Ginger')
	orange = ('Lavender', 'Hojicha', 'Matcha', 'Earl Grey', 'Oolong')
	# default possible teaspoon values list for flour = 500, 3 tsp = 1 tbsp
	redAmt = list(map(Fraction, [1/4, 1/2]))
	blueAmt = list(map(Fraction, [1/4, 1/2, 1]))
	greenAmt = list(map(Fraction, [1/2, 1, 3/2]))
	purpleAmt = list(map(Fraction, [2, 3, 9/2]))
	orangeAmt = list(map(Fraction, [9]))
	# random tablespoons
	colorsDict[red] = list(map(lambda x: spoon_mult(x, multiplier), redAmt))
	colorsDict[blue] = list(map(lambda x: spoon_mult(x, multiplier), blueAmt))
	colorsDict[green] = list(map(lambda x: spoon_mult(x, multiplier), greenAmt))
	colorsDict[purple] = list(map(lambda x: spoon_mult(x, multiplier), purpleAmt))
	colorsDict[orange] = list(map(lambda x: spoon_mult(x, multiplier), orangeAmt))

	for color in colorsDict.keys():
		if flavor in color:
			return random.choice(colorsDict[color])

	# print("Error in Flavor Input: " + flavor)
	return "get_flavor_amount wrong input"

# returns list of spices using number of spices
def get_spices(spicesNum: int) -> [str]:
	spicesList = ['Cinnamon', 'Allspice', 'Cardamom', 'Nutmeg']
	if spicesNum > len(spicesList):
		print("WARNING: spicesNum exceeds spices of num")
		return spicesList
	if spicesNum == 1:
		return random.sample(['Cinnamon', 'Cardamom'], 1)
	return random.sample(spicesList, spicesNum)


# check if extract is nut
def is_nut(extract: str) -> bool:
	nuts = ['Hazelnut','Almond']
	return extract in nuts

# checks if extract1 and extract2 are both allowed based on zest/extract same flavor
def zest_extract_same_flavor(extract1: str, extract2: str) -> bool:
	if extract1 == extract2:
		return False
	e1 = extract1.split(" ") # may need to change if new types are added
	e2 = extract2.split(" ")
	if len(e1) != 2 or len(e2) != 2:
		return False
	
	if e1[0]==e2[0] and 'Zest' in [e1[1],e2[1]] and 'Extract' in [e1[1],e2[1]]:
		return True
	return False

# return list of extracts using number of extracts
def get_extracts(extractsNum: int) -> [str]:
	if extractsNum == 0:
		return []

	allowedExtracts = ['Vanilla', 'Hazelnut', 'Almond', 'Lemon Extract', 'Peppermint', 
		'Orange Zest', 'Lime Zest', 'Lemon Zest', 'Ginger']
	# if more than one, vanilla must be included
	currentExtracts = ['Vanilla']
	allowedExtracts.remove('Vanilla')
	extractsLeft = extractsNum-1
	
	while extractsLeft > 0:
		if len(allowedExtracts) <= 0:
			print("Incorrecnt number of extracts")
			return "Incorrecnt number of extracts"

		newExtract = random.choice(allowedExtracts)
		# one nut at a time
		if True in map(is_nut, currentExtracts) and is_nut(newExtract):
			allowedExtracts.remove(newExtract)
			continue # skips decrement, try again
		# no zest + extract comibination of the same flavor
		for currentExtract in currentExtracts:
			exit = False
			if zest_extract_same_flavor(currentExtract, newExtract):
				allowedExtracts.remove(newExtract)
				exit = True # skips decrement, try again
			if exit:
				continue

		# passed restraints, remove it from allowed
		currentExtracts.append(newExtract)
		if newExtract in allowedExtracts:
			allowedExtracts.remove(newExtract)
		extractsLeft -= 1
	return currentExtracts

# return percentage of enrichment
def get_enrichment_percent(enrichment: str) -> int:
	if enrichment == 'Cream Cheese':
		return 10
	return 5

# return liquid percent from liquid tpye
def get_liquid_percent(liquidType: str) -> int:
	if liquidType in ['Heavy Cream', 'Coconut Milk']:
		return 13
	elif liquidType in ['Cow Milk']:
		return 63
	# print("Error in liquidType input.")
	return -1

# return fruit puree fruit choice(s), omitted fruit chance weighting for now
def get_fruit_purees() -> [str]:
	fruitPureesNum = random.randint(1,2)
	fruitPureesChoices = ['Banana','Apple','Cherry','Strawberry','Fig','Mango']
	return random.sample(fruitPureesChoices, fruitPureesNum)

# retrun fruit puree percent from 0-2 fruitPurees using random generation 
def get_fruit_purees_percent(fruitPurees) -> [float]:
	totalFruitPureePercent = random.choice([25,30,35,40,45,50])
	fruitPureeNum = len(fruitPurees)
	if fruitPureeNum == 1:
		return [totalFruitPureePercent]
	elif fruitPureeNum == 2:
		firstPercent = random.randint(0,totalFruitPureePercent)
		return [firstPercent, totalFruitPureePercent - firstPercent]
	return [0]

# returns rounded ml conversion from percent, used in template
def to_g(flourMl, percent) -> int:
	return round(flourMl * percent/100)

# takes filename and writes an html recipe file
def generate_recipe(breadname: str, filename: str, flourGramInput: int) -> str:
	# ALL NUMBERICAL VALUES REPRESENT PERCENTAGES
	r = Recipe()
	r.breadname = breadname
	r.totalFlourGrams = flourGramInput
	r.totalLiquidPercent = 63

	r.preferment = random.choice(['Poolish', 'None'])
	r.breadFlourPercent = random.choice([75, 50])
	# FLOUR STYLE 
	r.breadShape = random.choice(['Pullman', 'Regular'])
	# FLOUR TYPES
	r.specialFlour = random.choice([
		'Einkorn',
		'Khorasan',
		'Spelt',
		'Emmer',
		'Semolina (Durum)',
		'Hard Red Whole Wheat',
		'Regular Whole Wheat',
		'Hard White Wheat',
		'Rye'
	])
	r.specialFlourPercent = get_special_flour_percent(r.specialFlour, r.breadFlourPercent)
	r.whiteFlourPercent = 100 - r.breadFlourPercent - r.specialFlourPercent

	# SPICES/FLAVORING
	spicesNum = random.randint(0,4)
	r.spices = get_spices(spicesNum)
	extractsNum = random.randint(0,3)
	r.extracts = get_extracts(extractsNum)
	teaList = ['Lavender', 'Hojicha', 'Matcha', 'Earl Grey', 'Oolong', 'Instant Coffee']
	r.tea = random.choice(teaList)
	# illegal with fruit purees and all extracts but ginger, almond, and hazelnut

	# BASIC INGREDIENTS
	r.sugar = random.choice(['Brown Sugar','White Sugar','Honey','Molasses'])
	r.sugarPercent = random.choice([5,10,15])
	r.salt = 'Table Salt'
	r.saltPercent = random.choice([1,1.5,2])
	r.yeast = random.choice(['Instant Yeast','Active Yeast'])
	r.yeastPercent = 0.62

	# ENRICHMENTS – All 5% , only one chosen
	enrichmentList = ['Olive Oil','Butter','Cream Cheese','Coconut oil']
	if r.tea == 'Instant Coffee':
		enrichmentList.remove('Olive Oil')
	r.enrichment = random.choice(enrichmentList)
	r.enrichmentPercent = get_enrichment_percent(r.enrichment)
	if r.enrichment == 'Cream Cheese':
		r.totalLiquidPercent -= 5
		
	# LIQUIDS
	# cap total liquid at 60% when these sugars are used
	if r.sugar in ['Honey', 'Molasses']:
		r.totalLiquidPercent = 60
	# cow milk only if there is no preferemnt
	viableLiquids = ['Heavy Cream', 'Coconut Milk', 'Cow Milk']
	if r.preferment != 'None':
		viableLiquids.remove('Cow Milk')
	r.liquid = random.choice(viableLiquids)
	r.liquidPercent = get_liquid_percent(r.liquid)

	## LIQUIDS - FRUIT PUREE
	r.fruitPurees = []
	r.fruitPureesPercent = []
	if r.preferment != 'Poolish':
		# 50 percent chance to include
			# sugar reduction by 5 percent
			r.sugarPercent -= 5
			r.fruitPurees = get_fruit_purees()
			r.fruitPureesPercent = get_fruit_purees_percent(r.fruitPurees)

	# account for cow milk
	r.liquidPercent = min(r.liquidPercent, r.totalLiquidPercent - sum(r.fruitPureesPercent))
	r.waterPercent = max(0, r.totalLiquidPercent - sum(r.fruitPureesPercent) - r.liquidPercent)

	# BICOLOR ROLL
	r.isBicolorRoll = False
	if len(r.fruitPureesPercent) > 0 or r.tea in ['Lavender', 'Hojicha', 'Matcha', 'Earl Grey', 'Oolong']:
		r.isBicolorRoll = random.choice([True,False])

	# COCOA POWDER
	r.cocoaPowderPercent = 0
	cocoaPowderAllowedExtracts = ['Ginger', 'Almond', 'Hazelnut']
	if r.fruitPurees == [] and any(not x in cocoaPowderAllowedExtracts for x in r.extracts): # allowed
		if random.randint(0,2) == 0:
			r.tea = '' # removes tea
			r.cocoaPowderPercent = round(random.choice([5,10])/100 * r.whiteFlourPercent,1)
			r.whiteFlourPercent = round(r.whiteFlourPercent - r.cocoaPowderPercent,1)

	# WRITE FORMAT
	time = datetime.now()
	r.datetime = time.strftime('%A, %b %d %Y')
	templateFile = open("./template.html")
	templateString = templateFile.read()

	## Conversion to ml for percentages
	r.totalLiquidGrams = to_g(r.totalFlourGrams, r.totalLiquidPercent)
	r.breadFlourGrams = to_g(r.totalFlourGrams, r.breadFlourPercent)
	r.specialFlourGrams = to_g(r.totalFlourGrams, r.specialFlourPercent)
	r.whiteFlourGrams = to_g(r.totalFlourGrams, r.whiteFlourPercent)
	r.sugarGrams = to_g(r.totalFlourGrams, r.sugarPercent)
	r.saltGrams = to_g(r.totalFlourGrams, r.saltPercent)
	r.yeastGrams = to_g(r.totalFlourGrams, r.yeastPercent)
	r.spicesAmt = list(map(lambda x: get_flavor_amount(x, r.totalFlourGrams), r.spices))
	r.extractsAmt = list(map(lambda x: get_flavor_amount(x, r.totalFlourGrams), r.extracts))
	r.teaAmt = get_flavor_amount(r.tea, r.totalFlourGrams)
	r.enrichmentGrams = to_g(r.totalFlourGrams, r.enrichmentPercent)
	r.waterGrams = to_g(r.totalFlourGrams, r.waterPercent)
	r.liquidGrams = to_g(r.totalFlourGrams, r.liquidPercent)
	r.fruitPureesGrams = list(map(lambda x: to_g(r.totalFlourGrams,x), r.fruitPureesPercent))
	r.cocoaPowderGrams = round(r.cocoaPowderPercent/100 * r.totalFlourGrams)

	template = Template(templateString)
	htmlString = template.render(r = r)
	outfile = open(f'{filename}', 'w')
	outfile.write(htmlString)
	outfile.close()
	templateFile.close()

	return htmlString
[((29, 8, 29, 28), 'fractions.Fraction', 'Fraction', ({(29, 17, 29, 27): 'multiplier'}, {}), '(multiplier)', False, 'from fractions import Fraction\n'), ((87, 8, 87, 44), 'random.sample', 'random.sample', ({(87, 22, 87, 32): 'spicesList', (87, 34, 87, 43): 'spicesNum'}, {}), '(spicesList, spicesNum)', False, 'import random\n'), ((163, 18, 163, 37), 'random.randint', 'random.randint', ({(163, 33, 163, 34): '1', (163, 35, 163, 36): '2'}, {}), '(1, 2)', False, 'import random\n'), ((165, 8, 165, 57), 'random.sample', 'random.sample', ({(165, 22, 165, 40): 'fruitPureesChoices', (165, 42, 165, 56): 'fruitPureesNum'}, {}), '(fruitPureesChoices, fruitPureesNum)', False, 'import random\n'), ((169, 26, 169, 60), 'random.choice', 'random.choice', ({(169, 40, 169, 59): '[25, 30, 35, 40, 45, 50]'}, {}), '([25, 30, 35, 40, 45, 50])', False, 'import random\n'), ((190, 16, 190, 50), 'random.choice', 'random.choice', ({(190, 30, 190, 49): "['Poolish', 'None']"}, {}), "(['Poolish', 'None'])", False, 'import random\n'), ((191, 23, 191, 46), 'random.choice', 'random.choice', ({(191, 37, 191, 45): '[75, 50]'}, {}), '([75, 50])', False, 'import random\n'), ((193, 16, 193, 53), 'random.choice', 'random.choice', ({(193, 30, 193, 52): "['Pullman', 'Regular']"}, {}), "(['Pullman', 'Regular'])", False, 'import random\n'), ((195, 18, 205, 3), 'random.choice', 'random.choice', ({(195, 32, 205, 2): "['Einkorn', 'Khorasan', 'Spelt', 'Emmer', 'Semolina (Durum)',\n    'Hard Red Whole Wheat', 'Regular Whole Wheat', 'Hard White Wheat', 'Rye']"}, {}), "(['Einkorn', 'Khorasan', 'Spelt', 'Emmer', 'Semolina (Durum)',\n    'Hard Red Whole Wheat', 'Regular Whole Wheat', 'Hard White Wheat', 'Rye'])", False, 'import random\n'), ((210, 13, 210, 32), 'random.randint', 'random.randint', ({(210, 28, 210, 29): '0', (210, 30, 210, 31): '4'}, {}), '(0, 4)', False, 'import random\n'), ((212, 15, 212, 34), 'random.randint', 'random.randint', ({(212, 30, 212, 31): '0', (212, 32, 212, 33): '3'}, {}), '(0, 3)', False, 'import random\n'), ((215, 9, 215, 31), 'random.choice', 'random.choice', ({(215, 23, 215, 30): 'teaList'}, {}), '(teaList)', False, 'import random\n'), ((219, 11, 219, 74), 'random.choice', 'random.choice', ({(219, 25, 219, 73): "['Brown Sugar', 'White Sugar', 'Honey', 'Molasses']"}, {}), "(['Brown Sugar', 'White Sugar', 'Honey', 'Molasses'])", False, 'import random\n'), ((220, 18, 220, 42), 'random.choice', 'random.choice', ({(220, 32, 220, 41): '[5, 10, 15]'}, {}), '([5, 10, 15])', False, 'import random\n'), ((222, 17, 222, 41), 'random.choice', 'random.choice', ({(222, 31, 222, 40): '[1, 1.5, 2]'}, {}), '([1, 1.5, 2])', False, 'import random\n'), ((223, 11, 223, 58), 'random.choice', 'random.choice', ({(223, 25, 223, 57): "['Instant Yeast', 'Active Yeast']"}, {}), "(['Instant Yeast', 'Active Yeast'])", False, 'import random\n'), ((230, 16, 230, 45), 'random.choice', 'random.choice', ({(230, 30, 230, 44): 'enrichmentList'}, {}), '(enrichmentList)', False, 'import random\n'), ((243, 12, 243, 40), 'random.choice', 'random.choice', ({(243, 26, 243, 39): 'viableLiquids'}, {}), '(viableLiquids)', False, 'import random\n'), ((275, 8, 275, 22), 'datetime.datetime.now', 'datetime.now', ({}, {}), '()', False, 'from datetime import datetime\n'), ((297, 12, 297, 36), 'jinja2.Template', 'Template', ({(297, 21, 297, 35): 'templateString'}, {}), '(templateString)', False, 'from jinja2 import Template\n'), ((17, 9, 17, 35), 'random.choice', 'random.choice', ({(17, 23, 17, 34): 'percentages'}, {}), '(percentages)', False, 'import random\n'), ((28, 20, 28, 30), 'fractions.Fraction', 'Fraction', ({}, {}), '()', False, 'from fractions import Fraction\n'), ((52, 14, 52, 47), 'math.floor', 'math.floor', ({(52, 25, 52, 46): '(flourAmount / 500 * scale)'}, {}), '(flourAmount / 500 * scale)', False, 'import math\n'), ((86, 9, 86, 51), 'random.sample', 'random.sample', ({(86, 23, 86, 47): "['Cinnamon', 'Cardamom']", (86, 49, 86, 50): '(1)'}, {}), "(['Cinnamon', 'Cardamom'], 1)", False, 'import random\n'), ((125, 15, 125, 45), 'random.choice', 'random.choice', ({(125, 29, 125, 44): 'allowedExtracts'}, {}), '(allowedExtracts)', False, 'import random\n'), ((263, 20, 263, 47), 'random.choice', 'random.choice', ({(263, 34, 263, 46): '[True, False]'}, {}), '([True, False])', False, 'import random\n'), ((21, 9, 21, 35), 'random.choice', 'random.choice', ({(21, 23, 21, 34): 'percentages'}, {}), '(percentages)', False, 'import random\n'), ((25, 9, 25, 35), 'random.choice', 'random.choice', ({(25, 23, 25, 34): 'percentages'}, {}), '(percentages)', False, 'import random\n'), ((74, 10, 74, 42), 'random.choice', 'random.choice', ({(74, 24, 74, 41): 'colorsDict[color]'}, {}), '(colorsDict[color])', False, 'import random\n'), ((174, 17, 174, 57), 'random.randint', 'random.randint', ({(174, 32, 174, 33): '0', (174, 34, 174, 56): 'totalFruitPureePercent'}, {}), '(0, totalFruitPureePercent)', False, 'import random\n'), ((269, 5, 269, 24), 'random.randint', 'random.randint', ({(269, 20, 269, 21): '(0)', (269, 22, 269, 23): '(2)'}, {}), '(0, 2)', False, 'import random\n'), ((271, 32, 271, 53), 'random.choice', 'random.choice', ({(271, 46, 271, 52): '[5, 10]'}, {}), '([5, 10])', False, 'import random\n')]