import random

# Constants that Scout uses to compute domains for element sizes. 
CANVAS_HEIGHT = 640
CANVAS_WIDTH = 360
CANVAS_ALIGNMENT = ["left", "center", "right", "other"]
MAX_WIDTH = 360 # Largest while subtracting the smallest amount of padding
MAX_HEIGHT = 640 # Largest while subtracting the smallest amount of padding
MIN_WIDTH = 12 # sort of arbitrary now, but could 
MIN_HEIGHT = 12
MIN_WIDTH_TOUCH_TARGET = 120
MIN_WIDTH_SEPARATOR = 24
MIN_HEIGHT_ASPECT_RATIO = 16
GRID_CONSTANT = 4
SNAP_GRID_CONSTANT = 16
MAGNIFICATION_VALUES = [1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2]
MINIFICATION_VALUES = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]
LAYOUT_COLUMNS = [2,3,4,6]
GUTTERS = [4,8,16] 
COLUMNS = [1,2,3,4,5,6,7,8,9,10,11,12]
BASELINE_GRIDS = [4,8,16]
MARGINS = [0,4,8,12,16,20,24,28,32,36,40]
PADDINGS = [4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100]
LAYOUT_GRID_PROPERTIES = ["margin", "columns", "column_width", "gutter_width"]
SIZE_PROPERTIES = ["width", "height", "size_factor"]

def compute_y_domain(): 
	y_values = range(0, CANVAS_HEIGHT)
	y_values = [y for y in y_values if y > 0 and (y % GRID_CONSTANT) == 0]
	return y_values

def compute_layout_grid_domains(): 
	# We precompute these values all ahead of time to avoid
	# needing to use multiplication or division in the solver as 
	# the performance generally tends to be slow for these operators
	domain = []
	for margin_value in MARGINS: 
		for column_value in LAYOUT_COLUMNS:
			for gutter_value in GUTTERS: 
				column_width = float((CANVAS_WIDTH - (2*margin_value) - ((column_value-1)*gutter_value)))/float(column_value)
				int_column_width = int(round(column_width,0))
				if column_width - int_column_width == 0: 
					domain.append([margin_value, column_value, gutter_value, int_column_width])

	return domain

def baseline_grid_consistent_with_prevents(baseline_grid, element, at_root=False):
	""" Checks whether the baseline grid is not prevented by the locks an on element """
	if "prevents" in element and "baseline_grid" in element["prevents"]: 
		bg_value = element["prevented_values"]["baseline_grid"]
		if baseline_grid in bg_value: 
			return False
	return True

def is_consistent_with_prevents(layout_grid, element, at_root=False):
	""" Checks whether the layout grid is not prevented by the locks on an element """
	if "prevents" in element: 
		for prevent in element["prevents"]: 
			if prevent in LAYOUT_GRID_PROPERTIES: 
				if prevent == "margin": 
					margin_value = element["prevented_values"]["margin"]
					for marg_value in margin_value: 
						if layout_grid[0] == marg_value: 
							return False

				if prevent == "columns": 
					columns_value = element["prevented_values"]["columns"]
					for col_value in columns_value: 
						if layout_grid[1] == col_value: 
							return False

				if prevent == "gutter_width": 
					gutter_width_value = element["prevented_values"]["gutter_width"]
					for gut_value in gutter_width_value: 
						if layout_grid[2] == gut_value: 
							return False

				if prevent == "column_width": 
					column_width_value = element["prevented_values"]["column_width"]
					for col_width_value in column_width_value: 
						if layout_grid[3] == col_width_value: 
							return False

	return True

def baseline_grid_consistent_with_locks(baseline_grid, element, at_root=False): 
	# Check if the baseline grid is consistent with the locks on the element
	if "locks" in element: 
		for lock in element["locks"]: 
			if lock == "size":
				print(element["locked_values"]["size"])
				# Consistency of the height values with the baseline grid value 
				size = element["locked_values"]["size"]
				for size_val in size: 
					height_val = size_val[1]
					if height_val % baseline_grid != 0: 
						return False	

			if lock == "baseline_grid": 
				bg_value = element["locked_values"]["baseline_grid"]
				if baseline_grid not in bg_value: 
					return False
	return True

def is_consistent_with_locks(layout_grid, element, at_root=False): 
	""" Checks whether a layout grid is consistesnt with locks on an element. """
	margin = layout_grid[0]
	columns = layout_grid[1]
	gutter_width = layout_grid[2]
	column_width = layout_grid[3] 

	# Check if the layout grid combination is consistent with the locks on the element
	if "locks" in element: 
		for lock in element["locks"]: 
			if lock == "size" and at_root:
				# Consistency of the width values with the margins 
				size_values = element["locked_values"]["size"]
				cons = False
				width_values = []
				for size_value in size_values: 
					width_value = size_value[0]
					width_values.append(width_value)
					if width_value <= (CANVAS_WIDTH  -  margin * 2): 
						cons = True

				if not cons: 
					return False

				cons = False
				cols = 1
				while cols <= columns: 
					width = column_width * cols + (gutter_width * (cols-1))
					if width in width_values: 
						cons = True
					cols += 1

				if not cons: 
					return False

			# TODO - check consistency of height values wrt width values in columns
			if lock in LAYOUT_GRID_PROPERTIES: 
				if lock == "margin": 
					margin_value = element["locked_values"]["margin"]
					matches = False
					for marg_value in margin_value: 
						if layout_grid[0] == marg_value:
							matches = True
					if not matches: 
						return False 

				if lock == "columns": 
					columns_value = element["locked_values"]["columns"]
					matches = False
					for col_value in columns_value: 
						if layout_grid[1] == col_value: 
							matches = True
					if not matches: 
						return False 

				if lock == "gutter_width": 
					gutter_width_value = element["locked_values"]["gutter_width"]
					matches = False
					for gut_value in gutter_width_value: 
						if layout_grid[2] == gut_value: 
							matches = True
					if not matches: 
						return False 

				if lock == "column_width": 
					column_width_value = element["locked_values"]["column_width"]
					matches = False
					for col_width_value in column_width_value: 
						if layout_grid[3] == col_width_value: 
							matches = True
					if not matches: 
						return False 
	return True

def grid_consistent_with_element_locks(layout_grid, element_tree, at_root=False): 
	""" Checks whether a layout grid value is consistent with locks on elements in the element tree """
	cons = is_consistent_with_locks(layout_grid, element_tree, at_root)
	if not cons: 
		return False

	cons = is_consistent_with_prevents(layout_grid, element_tree, at_root)
	if not cons: 
		return False

	if "children" in element_tree: 
		for child in element_tree["children"]: 
			at_root = element_tree["type"] == "canvas"
			cons = grid_consistent_with_element_locks(layout_grid, child, at_root)
			if not cons:
				return False

	return True

def baseline_grid_consistent_with_element_locks(baseline_grid, element_tree, at_root=False): 
	""" Checks whether a baseline grid value is consistent with locks on elements in the element tree """
	cons = baseline_grid_consistent_with_locks(baseline_grid, element_tree, at_root)
	if not cons: 
		return False

	cons = baseline_grid_consistent_with_prevents(baseline_grid, element_tree, at_root)
	if not cons: 
		return False

	if "children" in element_tree: 
		for child in element_tree["children"]: 
			at_root = element_tree["type"] == "canvas"
			cons = baseline_grid_consistent_with_element_locks(baseline_grid, child, at_root)
			if not cons:
				return False

	return True

def select_consistent_layout_grid(element_tree): 
	""" Selects a layout grid value to use based on the current set of element locks """
	layout_grids = compute_layout_grid_domains()
	filtered_grids = []
	for grid in layout_grids:
		if grid_consistent_with_element_locks(grid, element_tree):
			filtered_grids.append(grid)

	# Now, randomly sample one to use
	if len(filtered_grids):
		selected_grid = random.sample(filtered_grids, 1)
		return selected_grid
	else:
		# Return any grid if we could not select a consistent one
		selected_grid = random.sample(layout_grids, 1)
		return selected_grid

def select_consistent_baseline_grid(element_tree): 
	"""Select a consistent baseline grid with the values that are locked"""
	filtered_grids = []
	for grid in BASELINE_GRIDS:
		if baseline_grid_consistent_with_element_locks(grid, element_tree):
			filtered_grids.append(grid)

	# Now, randomly sample one to use
	if len(filtered_grids):
		selected_grid = random.sample(filtered_grids, 1)
		return selected_grid
	else:
		# Return any grid if we could not select a consistent one
		selected_grid = random.sample(BASELINE_GRIDS, 1)
		return selected_grid

def get_layout_grids():
	"""Return the full set of layout grids"""
	layout_grids = compute_layout_grid_domains()
	return layout_grids

def compute_size_domain_change_width_only_root(importance, width, height, layout_grids, is_separator=False): 
	""" Computes the size domain a priori for elements that will change their width only and are 
		also located on the root of the tree. 

		Attributes: 
			importance: Current emphasis value 
			width: original width 
			height: original height 
			layout_grids: possible set of layout grids that we will compute the sizes from. Root elements have to be placed on a column. 
	"""
	# For touch targets, the calcuated sizes should only 
	# increase/decrease the width (buttons, fields) 
	domain = []
	factor_id = 0

	# First, round the values down to a mult of the grid constant
	orig_height = height
	orig_width = width 
	if not is_separator: 
		height_diff = height % GRID_CONSTANT
		orig_height = height -  height_diff
		orig_width = width 

	for grid in layout_grids: 
		margin = grid[0]
		columns = grid[1]
		gutter_width = grid[2]
		column_width = grid[3] 

		num_columns = 1
		while num_columns <= columns: 
			width_value = (column_width * num_columns) + (gutter_width * (num_columns-1))
			if width_value >= MIN_WIDTH_TOUCH_TARGET and width_value <= MAX_WIDTH:
				# if (width_value > width and importance != "low") or (width_value <= width and importance != "high"):
				hw_values = [width_value, orig_height]
				if hw_values not in domain:
					domain.append([width_value, orig_height])
					factor_id += 1
			num_columns += 1

	domain_with_factor = []
	for i in range(0, len(domain)): 
		domain_with_factor.append([domain[i][0], domain[i][1], i])
	return domain_with_factor

def compute_size_domain_maintain_aspect_ratio_root(importance, width, height, layout_grids):
	""" Computes the size domain a priori for elements that will change width and height to maintain their aspect ratio. 
		also located on the root of the tree. 

		Attributes: 
			importance: Current emphasis value 
			width: original width 
			height: original height 
			layout_grids: possible set of layout grids that we will compute the sizes from. Root elements have to be placed on a column. 
	"""
	# For touch targets, the calcuated sizes should only
	# increase/decrease the width (buttons, fields)
	domain = []
	factor_id = 0

	# First, round the values down to a mult of the grid constant
	aspect_ratio = height/width
	for grid in layout_grids:
		margin = grid[0]
		columns = grid[1]
		gutter_width = grid[2]
		column_width = grid[3]

		num_columns = 1
		while num_columns <= columns:
			width_value = (column_width * num_columns) + (gutter_width * (num_columns-1))
			height_value = int(width_value * aspect_ratio)
			if width_value <= MAX_WIDTH \
					and height_value >= MIN_HEIGHT_ASPECT_RATIO and height_value <= MAX_HEIGHT:
				# if (width_value >= width and importance != "low") or (width_value <= width and importance != "high"):
				hw_values = [width_value, height_value]
				if hw_values not in domain:
					domain.append(hw_values)
					factor_id += 1
			num_columns += 1

	domain_with_factor = []
	for i in range(0, len(domain)):
		domain_with_factor.append([domain[i][0], domain[i][1], i])
	return domain_with_factor

def compute_size_domain_change_width_only(importance, width, height, baseline_grid, is_separator=False): 
	""" Computes the size domain a priori for elements that will change their width only, and are 
		not located on the root of the tree. 

		Attributes: 
			importance: Current emphasis value 
			width: original width 
			height: original height 
			layout_grids: possible set of layout grids that we will compute the sizes from. Root elements have to be placed on a column. 
	"""
	# For touch targets, the calcuated sizes should only 
	# increase/decrease the width (buttons, fields) 
	domain = []
	factor_id = 0

	# First, round the values down to a mult of the grid constant
	grid = baseline_grid[0] if len(baseline_grid) > 0 else GRID_CONSTANT
	orig_height = height
	orig_width = width 
	if not is_separator: 
		height_diff = height % grid
		orig_height = height -  height_diff
		orig_height = orig_height if orig_height > SNAP_GRID_CONSTANT else SNAP_GRID_CONSTANT
	
	width_diff = width % GRID_CONSTANT
	orig_width = width - width_diff

	domain.append([orig_width, orig_height, factor_id])

	computed_width = orig_width
	minimum_element_width = MIN_WIDTH_SEPARATOR if is_separator else MIN_WIDTH_TOUCH_TARGET
	shrink_factor_id = 0

	if importance != "high": 
		while computed_width > minimum_element_width: 
				shrink_factor_id -= 1

				computed_width -= GRID_CONSTANT
				if computed_width >= minimum_element_width: 
					domain.append([computed_width, orig_height, shrink_factor_id])

	computed_width = orig_width
	increase_factor_id = 0
	maximum_element_width = MAX_WIDTH
	if importance != "low": 
		while computed_width < maximum_element_width: 
				increase_factor_id += 1

				computed_width += GRID_CONSTANT
				if computed_width <= maximum_element_width: 
					domain.append([computed_width, orig_height, increase_factor_id])

	return domain	

def get_nearest_grid_size(size, grid): 

	floor_grid = (size // grid) * grid
	ceil_grid = floor_grid + grid
	return (ceil_grid if size - floor_grid >= ceil_grid - size else floor_grid)

def compute_size_domain_maintain_aspect_ratio(importance, width, height, baseline_grid):
	""" Computes the size domain a priori for elements that will change their width and height in order
		to maintain their aspect ratio, and are not located on the root of the tree. 

		Attributes: 
			importance: Current emphasis value 
			width: original width 
			height: original height 
			layout_grids: possible set of layout grids that we will compute the sizes from. Root elements have to be placed on a column. 
	""" 
	domain = []
	factor_id = 0
	aspect_ratio = width/height

	# First, round the values down to a mult of the grid constant
	# height_diff = height % SNAP_GRID_CONSTANT
	grid = baseline_grid[0] if len(baseline_grid) == 1 else GRID_CONSTANT
	orig_height = get_nearest_grid_size(height, SNAP_GRID_CONSTANT)
	orig_height = orig_height if orig_height > SNAP_GRID_CONSTANT else SNAP_GRID_CONSTANT

	orig_width = orig_height * aspect_ratio
	orig_width = int(round(orig_width, 0))
	width_diff = orig_width % 2
	orig_width = orig_width - width_diff

	domain.append([orig_width, orig_height, factor_id])

	computed_height = orig_height
	computed_width = orig_width

	# Don't reduce height greater than half from the original 
	# minimum_element_height = MIN_HEIGHT if MIN_HEIGHT > (orig_height/2) else (orig_height/2)
	# minimum_element_width = MIN_WIDTH if MIN_WIDTH > (orig_width/2)  else (orig_width/2)
	minimum_element_height = MIN_HEIGHT_ASPECT_RATIO
	minimum_element_width = MIN_WIDTH
	shrink_factor_id = 0

	if importance != "high": 
		while computed_height > minimum_element_height and computed_width > minimum_element_width: 
				shrink_factor_id -= 1

				computed_height -= grid
				computed_width = computed_height * aspect_ratio
				computed_width = int(round(computed_width, 0))
				width_diff = computed_width % 2
				computed_width = computed_width - width_diff

				if computed_height >= minimum_element_height and computed_width >= minimum_element_width: 
					domain.append([computed_width, computed_height, shrink_factor_id])

	computed_width = orig_width
	computed_height = orig_height
	increase_factor_id = 0

	# maximum_element_height = MAX_HEIGHT if MAX_HEIGHT < (orig_height * 2) else (orig_height * 2)
	# maximum_element_width = MAX_WIDTH if MAX_WIDTH < (orig_width * 2) else (orig_width * 2)
	maximum_element_height = MAX_HEIGHT
	maximum_element_width = MAX_WIDTH
	if importance != "low": 
		while computed_width < maximum_element_width and computed_height < maximum_element_height: 
				increase_factor_id += 1

				computed_height += grid
				computed_width = computed_height * aspect_ratio
				computed_width = int(round(computed_width, 0))
				width_diff = computed_width % 2
				computed_width = computed_width - width_diff

				if computed_width <= maximum_element_width and computed_height <= maximum_element_height: 
					domain.append([computed_width, computed_height, increase_factor_id])

	return domain
import random

# Constants that Scout uses to compute domains for element sizes. 
CANVAS_HEIGHT = 640
CANVAS_WIDTH = 360
CANVAS_ALIGNMENT = ["left", "center", "right", "other"]
MAX_WIDTH = 360 # Largest while subtracting the smallest amount of padding
MAX_HEIGHT = 640 # Largest while subtracting the smallest amount of padding
MIN_WIDTH = 12 # sort of arbitrary now, but could 
MIN_HEIGHT = 12
MIN_WIDTH_TOUCH_TARGET = 120
MIN_WIDTH_SEPARATOR = 24
MIN_HEIGHT_ASPECT_RATIO = 16
GRID_CONSTANT = 4
SNAP_GRID_CONSTANT = 16
MAGNIFICATION_VALUES = [1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2]
MINIFICATION_VALUES = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]
LAYOUT_COLUMNS = [2,3,4,6]
GUTTERS = [4,8,16] 
COLUMNS = [1,2,3,4,5,6,7,8,9,10,11,12]
BASELINE_GRIDS = [4,8,16]
MARGINS = [0,4,8,12,16,20,24,28,32,36,40]
PADDINGS = [4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100]
LAYOUT_GRID_PROPERTIES = ["margin", "columns", "column_width", "gutter_width"]
SIZE_PROPERTIES = ["width", "height", "size_factor"]

def compute_y_domain(): 
	y_values = range(0, CANVAS_HEIGHT)
	y_values = [y for y in y_values if y > 0 and (y % GRID_CONSTANT) == 0]
	return y_values

def compute_layout_grid_domains(): 
	# We precompute these values all ahead of time to avoid
	# needing to use multiplication or division in the solver as 
	# the performance generally tends to be slow for these operators
	domain = []
	for margin_value in MARGINS: 
		for column_value in LAYOUT_COLUMNS:
			for gutter_value in GUTTERS: 
				column_width = float((CANVAS_WIDTH - (2*margin_value) - ((column_value-1)*gutter_value)))/float(column_value)
				int_column_width = int(round(column_width,0))
				if column_width - int_column_width == 0: 
					domain.append([margin_value, column_value, gutter_value, int_column_width])

	return domain

def baseline_grid_consistent_with_prevents(baseline_grid, element, at_root=False):
	""" Checks whether the baseline grid is not prevented by the locks an on element """
	if "prevents" in element and "baseline_grid" in element["prevents"]: 
		bg_value = element["prevented_values"]["baseline_grid"]
		if baseline_grid in bg_value: 
			return False
	return True

def is_consistent_with_prevents(layout_grid, element, at_root=False):
	""" Checks whether the layout grid is not prevented by the locks on an element """
	if "prevents" in element: 
		for prevent in element["prevents"]: 
			if prevent in LAYOUT_GRID_PROPERTIES: 
				if prevent == "margin": 
					margin_value = element["prevented_values"]["margin"]
					for marg_value in margin_value: 
						if layout_grid[0] == marg_value: 
							return False

				if prevent == "columns": 
					columns_value = element["prevented_values"]["columns"]
					for col_value in columns_value: 
						if layout_grid[1] == col_value: 
							return False

				if prevent == "gutter_width": 
					gutter_width_value = element["prevented_values"]["gutter_width"]
					for gut_value in gutter_width_value: 
						if layout_grid[2] == gut_value: 
							return False

				if prevent == "column_width": 
					column_width_value = element["prevented_values"]["column_width"]
					for col_width_value in column_width_value: 
						if layout_grid[3] == col_width_value: 
							return False

	return True

def baseline_grid_consistent_with_locks(baseline_grid, element, at_root=False): 
	# Check if the baseline grid is consistent with the locks on the element
	if "locks" in element: 
		for lock in element["locks"]: 
			if lock == "size":
				print(element["locked_values"]["size"])
				# Consistency of the height values with the baseline grid value 
				size = element["locked_values"]["size"]
				for size_val in size: 
					height_val = size_val[1]
					if height_val % baseline_grid != 0: 
						return False	

			if lock == "baseline_grid": 
				bg_value = element["locked_values"]["baseline_grid"]
				if baseline_grid not in bg_value: 
					return False
	return True

def is_consistent_with_locks(layout_grid, element, at_root=False): 
	""" Checks whether a layout grid is consistesnt with locks on an element. """
	margin = layout_grid[0]
	columns = layout_grid[1]
	gutter_width = layout_grid[2]
	column_width = layout_grid[3] 

	# Check if the layout grid combination is consistent with the locks on the element
	if "locks" in element: 
		for lock in element["locks"]: 
			if lock == "size" and at_root:
				# Consistency of the width values with the margins 
				size_values = element["locked_values"]["size"]
				cons = False
				width_values = []
				for size_value in size_values: 
					width_value = size_value[0]
					width_values.append(width_value)
					if width_value <= (CANVAS_WIDTH  -  margin * 2): 
						cons = True

				if not cons: 
					return False

				cons = False
				cols = 1
				while cols <= columns: 
					width = column_width * cols + (gutter_width * (cols-1))
					if width in width_values: 
						cons = True
					cols += 1

				if not cons: 
					return False

			# TODO - check consistency of height values wrt width values in columns
			if lock in LAYOUT_GRID_PROPERTIES: 
				if lock == "margin": 
					margin_value = element["locked_values"]["margin"]
					matches = False
					for marg_value in margin_value: 
						if layout_grid[0] == marg_value:
							matches = True
					if not matches: 
						return False 

				if lock == "columns": 
					columns_value = element["locked_values"]["columns"]
					matches = False
					for col_value in columns_value: 
						if layout_grid[1] == col_value: 
							matches = True
					if not matches: 
						return False 

				if lock == "gutter_width": 
					gutter_width_value = element["locked_values"]["gutter_width"]
					matches = False
					for gut_value in gutter_width_value: 
						if layout_grid[2] == gut_value: 
							matches = True
					if not matches: 
						return False 

				if lock == "column_width": 
					column_width_value = element["locked_values"]["column_width"]
					matches = False
					for col_width_value in column_width_value: 
						if layout_grid[3] == col_width_value: 
							matches = True
					if not matches: 
						return False 
	return True

def grid_consistent_with_element_locks(layout_grid, element_tree, at_root=False): 
	""" Checks whether a layout grid value is consistent with locks on elements in the element tree """
	cons = is_consistent_with_locks(layout_grid, element_tree, at_root)
	if not cons: 
		return False

	cons = is_consistent_with_prevents(layout_grid, element_tree, at_root)
	if not cons: 
		return False

	if "children" in element_tree: 
		for child in element_tree["children"]: 
			at_root = element_tree["type"] == "canvas"
			cons = grid_consistent_with_element_locks(layout_grid, child, at_root)
			if not cons:
				return False

	return True

def baseline_grid_consistent_with_element_locks(baseline_grid, element_tree, at_root=False): 
	""" Checks whether a baseline grid value is consistent with locks on elements in the element tree """
	cons = baseline_grid_consistent_with_locks(baseline_grid, element_tree, at_root)
	if not cons: 
		return False

	cons = baseline_grid_consistent_with_prevents(baseline_grid, element_tree, at_root)
	if not cons: 
		return False

	if "children" in element_tree: 
		for child in element_tree["children"]: 
			at_root = element_tree["type"] == "canvas"
			cons = baseline_grid_consistent_with_element_locks(baseline_grid, child, at_root)
			if not cons:
				return False

	return True

def select_consistent_layout_grid(element_tree): 
	""" Selects a layout grid value to use based on the current set of element locks """
	layout_grids = compute_layout_grid_domains()
	filtered_grids = []
	for grid in layout_grids:
		if grid_consistent_with_element_locks(grid, element_tree):
			filtered_grids.append(grid)

	# Now, randomly sample one to use
	if len(filtered_grids):
		selected_grid = random.sample(filtered_grids, 1)
		return selected_grid
	else:
		# Return any grid if we could not select a consistent one
		selected_grid = random.sample(layout_grids, 1)
		return selected_grid

def select_consistent_baseline_grid(element_tree): 
	"""Select a consistent baseline grid with the values that are locked"""
	filtered_grids = []
	for grid in BASELINE_GRIDS:
		if baseline_grid_consistent_with_element_locks(grid, element_tree):
			filtered_grids.append(grid)

	# Now, randomly sample one to use
	if len(filtered_grids):
		selected_grid = random.sample(filtered_grids, 1)
		return selected_grid
	else:
		# Return any grid if we could not select a consistent one
		selected_grid = random.sample(BASELINE_GRIDS, 1)
		return selected_grid

def get_layout_grids():
	"""Return the full set of layout grids"""
	layout_grids = compute_layout_grid_domains()
	return layout_grids

def compute_size_domain_change_width_only_root(importance, width, height, layout_grids, is_separator=False): 
	""" Computes the size domain a priori for elements that will change their width only and are 
		also located on the root of the tree. 

		Attributes: 
			importance: Current emphasis value 
			width: original width 
			height: original height 
			layout_grids: possible set of layout grids that we will compute the sizes from. Root elements have to be placed on a column. 
	"""
	# For touch targets, the calcuated sizes should only 
	# increase/decrease the width (buttons, fields) 
	domain = []
	factor_id = 0

	# First, round the values down to a mult of the grid constant
	orig_height = height
	orig_width = width 
	if not is_separator: 
		height_diff = height % GRID_CONSTANT
		orig_height = height -  height_diff
		orig_width = width 

	for grid in layout_grids: 
		margin = grid[0]
		columns = grid[1]
		gutter_width = grid[2]
		column_width = grid[3] 

		num_columns = 1
		while num_columns <= columns: 
			width_value = (column_width * num_columns) + (gutter_width * (num_columns-1))
			if width_value >= MIN_WIDTH_TOUCH_TARGET and width_value <= MAX_WIDTH:
				# if (width_value > width and importance != "low") or (width_value <= width and importance != "high"):
				hw_values = [width_value, orig_height]
				if hw_values not in domain:
					domain.append([width_value, orig_height])
					factor_id += 1
			num_columns += 1

	domain_with_factor = []
	for i in range(0, len(domain)): 
		domain_with_factor.append([domain[i][0], domain[i][1], i])
	return domain_with_factor

def compute_size_domain_maintain_aspect_ratio_root(importance, width, height, layout_grids):
	""" Computes the size domain a priori for elements that will change width and height to maintain their aspect ratio. 
		also located on the root of the tree. 

		Attributes: 
			importance: Current emphasis value 
			width: original width 
			height: original height 
			layout_grids: possible set of layout grids that we will compute the sizes from. Root elements have to be placed on a column. 
	"""
	# For touch targets, the calcuated sizes should only
	# increase/decrease the width (buttons, fields)
	domain = []
	factor_id = 0

	# First, round the values down to a mult of the grid constant
	aspect_ratio = height/width
	for grid in layout_grids:
		margin = grid[0]
		columns = grid[1]
		gutter_width = grid[2]
		column_width = grid[3]

		num_columns = 1
		while num_columns <= columns:
			width_value = (column_width * num_columns) + (gutter_width * (num_columns-1))
			height_value = int(width_value * aspect_ratio)
			if width_value <= MAX_WIDTH \
					and height_value >= MIN_HEIGHT_ASPECT_RATIO and height_value <= MAX_HEIGHT:
				# if (width_value >= width and importance != "low") or (width_value <= width and importance != "high"):
				hw_values = [width_value, height_value]
				if hw_values not in domain:
					domain.append(hw_values)
					factor_id += 1
			num_columns += 1

	domain_with_factor = []
	for i in range(0, len(domain)):
		domain_with_factor.append([domain[i][0], domain[i][1], i])
	return domain_with_factor

def compute_size_domain_change_width_only(importance, width, height, baseline_grid, is_separator=False): 
	""" Computes the size domain a priori for elements that will change their width only, and are 
		not located on the root of the tree. 

		Attributes: 
			importance: Current emphasis value 
			width: original width 
			height: original height 
			layout_grids: possible set of layout grids that we will compute the sizes from. Root elements have to be placed on a column. 
	"""
	# For touch targets, the calcuated sizes should only 
	# increase/decrease the width (buttons, fields) 
	domain = []
	factor_id = 0

	# First, round the values down to a mult of the grid constant
	grid = baseline_grid[0] if len(baseline_grid) > 0 else GRID_CONSTANT
	orig_height = height
	orig_width = width 
	if not is_separator: 
		height_diff = height % grid
		orig_height = height -  height_diff
		orig_height = orig_height if orig_height > SNAP_GRID_CONSTANT else SNAP_GRID_CONSTANT
	
	width_diff = width % GRID_CONSTANT
	orig_width = width - width_diff

	domain.append([orig_width, orig_height, factor_id])

	computed_width = orig_width
	minimum_element_width = MIN_WIDTH_SEPARATOR if is_separator else MIN_WIDTH_TOUCH_TARGET
	shrink_factor_id = 0

	if importance != "high": 
		while computed_width > minimum_element_width: 
				shrink_factor_id -= 1

				computed_width -= GRID_CONSTANT
				if computed_width >= minimum_element_width: 
					domain.append([computed_width, orig_height, shrink_factor_id])

	computed_width = orig_width
	increase_factor_id = 0
	maximum_element_width = MAX_WIDTH
	if importance != "low": 
		while computed_width < maximum_element_width: 
				increase_factor_id += 1

				computed_width += GRID_CONSTANT
				if computed_width <= maximum_element_width: 
					domain.append([computed_width, orig_height, increase_factor_id])

	return domain	

def get_nearest_grid_size(size, grid): 

	floor_grid = (size // grid) * grid
	ceil_grid = floor_grid + grid
	return (ceil_grid if size - floor_grid >= ceil_grid - size else floor_grid)

def compute_size_domain_maintain_aspect_ratio(importance, width, height, baseline_grid):
	""" Computes the size domain a priori for elements that will change their width and height in order
		to maintain their aspect ratio, and are not located on the root of the tree. 

		Attributes: 
			importance: Current emphasis value 
			width: original width 
			height: original height 
			layout_grids: possible set of layout grids that we will compute the sizes from. Root elements have to be placed on a column. 
	""" 
	domain = []
	factor_id = 0
	aspect_ratio = width/height

	# First, round the values down to a mult of the grid constant
	# height_diff = height % SNAP_GRID_CONSTANT
	grid = baseline_grid[0] if len(baseline_grid) == 1 else GRID_CONSTANT
	orig_height = get_nearest_grid_size(height, SNAP_GRID_CONSTANT)
	orig_height = orig_height if orig_height > SNAP_GRID_CONSTANT else SNAP_GRID_CONSTANT

	orig_width = orig_height * aspect_ratio
	orig_width = int(round(orig_width, 0))
	width_diff = orig_width % 2
	orig_width = orig_width - width_diff

	domain.append([orig_width, orig_height, factor_id])

	computed_height = orig_height
	computed_width = orig_width

	# Don't reduce height greater than half from the original 
	# minimum_element_height = MIN_HEIGHT if MIN_HEIGHT > (orig_height/2) else (orig_height/2)
	# minimum_element_width = MIN_WIDTH if MIN_WIDTH > (orig_width/2)  else (orig_width/2)
	minimum_element_height = MIN_HEIGHT_ASPECT_RATIO
	minimum_element_width = MIN_WIDTH
	shrink_factor_id = 0

	if importance != "high": 
		while computed_height > minimum_element_height and computed_width > minimum_element_width: 
				shrink_factor_id -= 1

				computed_height -= grid
				computed_width = computed_height * aspect_ratio
				computed_width = int(round(computed_width, 0))
				width_diff = computed_width % 2
				computed_width = computed_width - width_diff

				if computed_height >= minimum_element_height and computed_width >= minimum_element_width: 
					domain.append([computed_width, computed_height, shrink_factor_id])

	computed_width = orig_width
	computed_height = orig_height
	increase_factor_id = 0

	# maximum_element_height = MAX_HEIGHT if MAX_HEIGHT < (orig_height * 2) else (orig_height * 2)
	# maximum_element_width = MAX_WIDTH if MAX_WIDTH < (orig_width * 2) else (orig_width * 2)
	maximum_element_height = MAX_HEIGHT
	maximum_element_width = MAX_WIDTH
	if importance != "low": 
		while computed_width < maximum_element_width and computed_height < maximum_element_height: 
				increase_factor_id += 1

				computed_height += grid
				computed_width = computed_height * aspect_ratio
				computed_width = int(round(computed_width, 0))
				width_diff = computed_width % 2
				computed_width = computed_width - width_diff

				if computed_width <= maximum_element_width and computed_height <= maximum_element_height: 
					domain.append([computed_width, computed_height, increase_factor_id])

	return domain
# Constants that Scout uses to compute domains for element sizes. # Largest while subtracting the smallest amount of padding # Largest while subtracting the smallest amount of padding # sort of arbitrary now, but could # We precompute these values all ahead of time to avoid # needing to use multiplication or division in the solver as # the performance generally tends to be slow for these operators Checks whether the baseline grid is not prevented by the locks an on element Checks whether the layout grid is not prevented by the locks on an element # Check if the baseline grid is consistent with the locks on the element # Consistency of the height values with the baseline grid value Checks whether a layout grid is consistesnt with locks on an element. # Check if the layout grid combination is consistent with the locks on the element # Consistency of the width values with the margins # TODO - check consistency of height values wrt width values in columns Checks whether a layout grid value is consistent with locks on elements in the element tree Checks whether a baseline grid value is consistent with locks on elements in the element tree Selects a layout grid value to use based on the current set of element locks # Now, randomly sample one to use # Return any grid if we could not select a consistent one Select a consistent baseline grid with the values that are locked # Now, randomly sample one to use # Return any grid if we could not select a consistent one Return the full set of layout grids Computes the size domain a priori for elements that will change their width only and are  		also located on the root of the tree.   		Attributes:  			importance: Current emphasis value  			width: original width  			height: original height  			layout_grids: possible set of layout grids that we will compute the sizes from. Root elements have to be placed on a column. # For touch targets, the calcuated sizes should only # increase/decrease the width (buttons, fields) # First, round the values down to a mult of the grid constant # if (width_value > width and importance != "low") or (width_value <= width and importance != "high"): Computes the size domain a priori for elements that will change width and height to maintain their aspect ratio.  		also located on the root of the tree.   		Attributes:  			importance: Current emphasis value  			width: original width  			height: original height  			layout_grids: possible set of layout grids that we will compute the sizes from. Root elements have to be placed on a column. # For touch targets, the calcuated sizes should only # increase/decrease the width (buttons, fields) # First, round the values down to a mult of the grid constant # if (width_value >= width and importance != "low") or (width_value <= width and importance != "high"): Computes the size domain a priori for elements that will change their width only, and are  		not located on the root of the tree.   		Attributes:  			importance: Current emphasis value  			width: original width  			height: original height  			layout_grids: possible set of layout grids that we will compute the sizes from. Root elements have to be placed on a column. # For touch targets, the calcuated sizes should only # increase/decrease the width (buttons, fields) # First, round the values down to a mult of the grid constant Computes the size domain a priori for elements that will change their width and height in order 		to maintain their aspect ratio, and are not located on the root of the tree.   		Attributes:  			importance: Current emphasis value  			width: original width  			height: original height  			layout_grids: possible set of layout grids that we will compute the sizes from. Root elements have to be placed on a column. # First, round the values down to a mult of the grid constant # height_diff = height % SNAP_GRID_CONSTANT # Don't reduce height greater than half from the original # minimum_element_height = MIN_HEIGHT if MIN_HEIGHT > (orig_height/2) else (orig_height/2) # minimum_element_width = MIN_WIDTH if MIN_WIDTH > (orig_width/2)  else (orig_width/2) # maximum_element_height = MAX_HEIGHT if MAX_HEIGHT < (orig_height * 2) else (orig_height * 2) # maximum_element_width = MAX_WIDTH if MAX_WIDTH < (orig_width * 2) else (orig_width * 2)