import os
import typing

import numpy as np
import torch
import torchvision as tv
from PIL import Image
from torch.utils import data

import util
from config import Config
from dataset import PoseNetDataSet
from logger import Logger
from posenet import PoseNet

def getErrorVector ( output: np.ndarray, target: np.ndarray ) -> typing.Tuple[np.ndarray, np.ndarray]:
	"""
	Subtracts the output from the target elementwise then averages out the differences per dimension (X Y Z W P Q R).

	:param output: The output of the network.
	:param target: The labels of the data.
	:return: The average of the elementwise subtraction of the output and the target with position being [0] and orientation being [1].
	"""

	err: np.ndarray = np.subtract ( output, target )
	# Take the average of all the x y z w p q r values within the columns.
	err = np.mean ( err, axis = 0 )

	return err[:3], err[3:]

def ANEES ( error: np.ndarray, uncertainty: np.ndarray, numOfImages: int ) -> float:
	"""
	ANEES (Average Normalized Error Estimate Squared). It's the mean of the error * the inverse of the uncertainty *
	the transpose of the error.

	:param error: The error of the network between the output and the target.
	:param uncertainty: The covariance matrix of the network's output.
	:param numOfImages: How many images are in the dataset.
	:return: The average of all of that.
	"""
	anees: np.ndarray = np.zeros ( numOfImages )
	for j in range ( numOfImages ):
		anees[j] = error[j] @ np.linalg.inv ( uncertainty[j] ) @ error[j].transpose ()
	return anees.mean ( dtype = float )

def getUncertainty ( output: np.ndarray, returnMatrix: bool = False ) -> typing.Union[
	typing.Tuple[float, float], typing.Tuple[np.ndarray, np.ndarray, float, float]]:
	"""
	Gets the uncertainty of the output from the network. If return matrix is true, the function will also return
	the covariance matrix along with the uncertainty values.

	:param output: the output from the network.
	:param returnMatrix: whether to return the covariance matrix or not.
	:return: the uncertainty values and the covariance matrix if returnMatrix is true.
	"""
	outPos: np.ndarray = output[:, :3]
	outOri: np.ndarray = output[:, 3:]

	covPos: np.ndarray = np.cov ( outPos, rowvar = 0 )
	covOri: np.ndarray = np.cov ( outOri, rowvar = 0 )

	uncertaintyPos: float = covPos.diagonal ().sum ()
	uncertaintyOri: float = covOri.diagonal ().sum ()
	if returnMatrix:
		return covPos, covOri, uncertaintyPos, uncertaintyOri
	return uncertaintyPos, uncertaintyOri

def getDifference ( output: torch.Tensor, target: torch.Tensor ) -> typing.Tuple[np.ndarray, np.ndarray]:
	"""
	Return the difference between the expected output of the network and the actual output of the network.
	Basically a wrapper for getErrorVector to do torch.Tensor's.

	:param output: Given output from the network
	:param target: Expected output of the network
	:return: a dictionary of the position and orientation differences between the two parameters.
	"""
	return getErrorVector ( output = output.cpu ().detach ().numpy (),
	                        target = target.cpu ().detach ().numpy () )

# Loss function for posenet
# outputPosition and outputOrientation are both the output provided by the neural network
# target is the output that is expected from the neural network
# beta is a constant to keep errors from data collection from getting in the way
# The target Tensor is supposed to be in the form of [batch_size, 7]
# with the second dimension being [x, y, z, w, p, q, r]
# where the position vector is [x, y, z]
# and the orientation vector is [w, p, q, r]
def posenetLoss ( output: torch.Tensor, target: torch.Tensor, beta: int ) -> torch.Tensor:
	"""
	The loss function defined in the original paper of PoseNet.

	:param output: The given output from the network
	:param target: The expected output from the network
	:param beta: The error constant
	:return: The loss of the network
	"""

	Logger.log ( "Computing loss." )
	Logger.log (
			"Types of things: output {} target {} beta {}".format ( type ( output ), type ( target ), type ( beta ) ) )
	Logger.log ( "Data types of things: output {} target {}".format ( output.dtype, target.dtype ) )
	Logger.log ( "Is on Cuda? Output {} target {}".format ( output.device, target.device ) )
	# Normalize the difference of the two position vectors
	positionLoss: torch.Tensor = torch.norm ( output[:, :3] - target[:, :3] )
	# Normalize the difference of the two orientation vectors
	# and multiply by the error constant.
	# Normalizing is the same thing as finding the unit vector.
	orientationDifference: torch.Tensor = torch.norm ( output[:, 3:] ) - torch.norm ( target[:, 3:] )
	orientationLoss: torch.Tensor = torch.norm ( orientationDifference ) * beta
	# return their sum
	return positionLoss + orientationLoss

def trainNetwork ( network: torch.nn.Module, optimizer: torch.optim.Optimizer, dataloader: data.DataLoader, epoch: int,
                   beta: int, print_every: int ) -> typing.Tuple[float, typing.Union[float, np.ndarray], np.ndarray]:
	"""
	Trains the network with the given parameters.

	:param network: The network that is to be trained.
	:param optimizer: The optimizer to teach the network what to do.
	:param dataloader: The data that the network will train on.
	:param epoch: The current epoch of this training session
	:param beta: The error constant to be used in the loss function
	:param print_every: Print loss and difference every x batches.
	:return: the average loss and differences of this training session.
	"""
	Logger.log ( "Training!", logger = "min" )
	# get the training data from the dataloader with the current batch number
	past_losses: typing.List[torch.Tensor] = []
	# Previous differences
	pastPosDiff: typing.List[np.ndarray] = []
	pastOriDiff: typing.List[np.ndarray] = []

	Logger.log ( "Epoch {}".format ( epoch ) )
	for batchNum, (data, labels) in enumerate ( dataloader ):
		# Log current batch number
		Logger.log ( "Training Batch #{}".format ( batchNum ) )

		labels: torch.Tensor = labels.float ()

		# Set network to start training.
		network.train ()

		Logger.log ( "Getting data" )
		# get the current batch.

		util.logTensorInfo ( data, "data" )
		util.logTensorInfo ( labels, "labels" )

		# If we're working with GPUs, put them on there.
		data = util.putOnDevice ( data )
		labels = util.putOnDevice ( labels )

		util.logTensorInfo ( data, "data" )
		util.logTensorInfo ( labels, "labels" )

		Logger.log ( "Resetting optimizer." )
		# reset the optimizer (right?)
		optimizer.zero_grad ()

		Logger.log ( "Getting output from network." )
		# run the network based on the current batch
		output: torch.Tensor = network ( data )

		util.logTensorInfo ( output, "output" )

		# Put the output onto the GPU
		output = util.putOnDevice ( output )

		util.logTensorInfo ( output, "output" )

		Logger.log ( "Calculating loss." )

		# calculate the error between the output and the expected output
		loss: torch.Tensor = posenetLoss ( output = output, target = labels, beta = beta )

		# This is a 1 dimensional vector that is the average of the differences between the output and target.
		difference: typing.Tuple[np.ndarray, np.ndarray] = getDifference ( output = output, target = labels )
		pastPosDiff.append ( difference[0] )
		pastOriDiff.append ( difference[1] )
		past_losses.append ( loss.detach ().cpu ().numpy () )

		# Put the loss onto the GPU if we can use it.
		if Config.useCuda ():
			loss = loss.cuda ()

		Logger.log ( "Calculating gradient." )
		# Calculate the gradient necessary to fix the network
		loss.backward ()

		Logger.log ( "Optimizing network" )
		# Assign those changes from the loss function to the network to
		# find the local (and preferably absolute) minimum
		optimizer.step ()

		# Check the accuracy every nth batches.
		if batchNum % print_every == 0:
			Logger.log ( "At epoch %i, minibatch %i. Loss: %.4f." % (epoch, batchNum, past_losses[-1]), logger = "min" )
			Logger.log ( "Error of position and orientation: {} {}".format (
					pastPosDiff[-1], np.linalg.norm ( pastOriDiff[-1] ) ), logger = "min" )
			Logger.log ( "Progress: {:.2%}".format ( (batchNum / len ( dataloader )) ), logger = "main" )

	avgLoss: float = np.average ( past_losses )
	# Print average losses
	Logger.log ( "Average training losses for epoch {}: {}".format ( epoch, avgLoss ), logger = "min" )
	avgPositionDifference: np.ndarray = np.average ( pastPosDiff, axis = 0 )
	avgOrientationDifference: np.ndarray = np.linalg.norm ( np.average ( pastOriDiff, axis = 0 ) )

	Logger.log ( "Average training difference for epoch {}: Pos: {} Ori: {}".format ( epoch, avgPositionDifference,
	                                                                               avgOrientationDifference ),
	             logger = "min" )

	return avgLoss, np.linalg.norm ( avgPositionDifference ), avgOrientationDifference

def validate ( network: torch.nn.Module, dataloader: data.DataLoader, beta: int, print_every: int,
               epoch: int ) -> typing.Tuple[float, float, float]:
	"""
	Validates that the network trained effectively based on the given parameters.

	:param network: The network to be validated.
	:param dataloader: The data that will be used to validate
	:param beta: The data error constant that is to be used with the loss function
	:param print_every: For x batches, print the current loss and the differences of the network
	:param epoch: The current epoch
	:return: The average validation loss and difference of this validation session
	"""
	# Begin validation.
	Logger.log ( "Validating data!", logger = "min" )
	# Turn the network onto testing mode.
	network.eval ()

	# record past validation losses.
	validation_past_losses: typing.List[torch.Tensor] = []

	pastPosDiff: typing.List[np.ndarray] = []
	pastOriDiff: typing.List[np.ndarray] = []

	# Go through each thing in the testing data.
	for batchNum, (data, labels) in enumerate ( dataloader ):
		# Log current batch number
		Logger.log ( "Validation Batch #{}".format ( batchNum ) )

		labels: torch.Tensor = labels.float ()

		Logger.log ( "Getting data" )
		# If we want to use the GPU, use it.
		if Config.useCuda ():
			labels = labels.cuda ()
			data: torch.Tensor = data.cuda ()

		Logger.log ( "Getting output from network." )
		# Get the output of the network based on the given data.
		output: torch.Tensor = network ( data )
		# Put output onto the GPU.
		if Config.useCuda ():
			output = output.cuda ()

		Logger.log ( "Calculating loss" )

		# calculate the error between the output and the expected output
		loss: torch.Tensor = posenetLoss ( output = output, target = labels, beta = beta )
		differences: typing.Tuple[np.ndarray, np.ndarray] = getDifference ( output = output, target = labels )
		# These contain vectors.
		pastPosDiff.append ( differences[0] )
		pastOriDiff.append ( differences[1] )

		validation_past_losses.append ( loss.detach ().cpu ().numpy () )

		if batchNum % print_every == 0:
			Logger.log ( "Validation loss for epoch {} batch #{}: {}".format ( epoch, batchNum,
			                                                                   validation_past_losses[-1] ),
			             logger = "min" )
			Logger.log ( "Error of position and orientation: {} {}".format ( pastPosDiff[-1],
			                                                                 np.linalg.norm ( pastOriDiff[-1] ) ),
			             logger = "min" )
			Logger.log ( "Progress: {:.2%}".format ( (batchNum / len ( dataloader )) ), logger = "main" )
	avgLoss: float = np.average ( validation_past_losses )
	Logger.log ( "Total average validation loss: " + str ( avgLoss ), logger = "min" )

	# Compute average of position diff in the columns (per dimension like X Y Z)
	avgPositionDifference: float = np.average ( pastPosDiff, axis = 0 )
	# Get the average per column then get the magnitude of the average difference in orientation.
	avgOrientationDifference: float = np.linalg.norm ( np.average ( pastOriDiff, axis = 0 ) )

	Logger.log ( "Average validation difference for epoch {}: Pos: {} Ori: {}".format ( epoch, avgPositionDifference,
	                                                                               avgOrientationDifference ),
	             logger = "min" )
	return avgLoss, np.linalg.norm ( avgPositionDifference ), avgOrientationDifference

def testSingleImage ( network: torch.nn.Module, image: Image, transforms ) -> typing.Tuple[
	np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
	Logger.log ( "Testing single image!", logger = "main" )
	data: torch.Tensor = transforms ( image )

	data = util.putOnDevice(data)

	Logger.log ( "Resizing image." )
	data.resize_ ( 1, 3, 224, 224 )

	if Config.useCuda():
		network = network.cuda()
	else:
		network = network.cpu()

	Logger.log ( "Triggering dropout layers." )
	network.train ()
	outputs: typing.List[torch.Tensor] = []

	for i in range ( Config.getArgs ().num_of_tests ):
		outputs.append ( network ( data ) )

	outs: np.ndarray = np.zeros ( (len ( outputs ), 7) )
	for i in range ( len ( outputs ) ):
		outs[i] = outputs[i].cpu ().detach ().numpy ()

	Logger.log ( "Getting uncertainty." )
	certPosMat, certOriMat, _, _ = getUncertainty ( output = torch.from_numpy ( outs ), returnMatrix = True )

	Logger.log ( "Getting actual output.", logger = "main" )
	network.eval ()
	output: np.ndarray = network ( data ).detach ().cpu ().numpy ()
	return output[0], outs, certPosMat, certOriMat

def test ( network: torch.nn.Module, dataloader: torch.utils.data.DataLoader ) -> typing.Tuple[
	float, float, float, float, float, float]:
	"""
	Uses testSingleImage() to test the network. Goes through each image individually  and gets the uncertainty, the error,
	and ANEES.

	:param network: the network to be tested on.
	:param dataloader: The testing data
	:return: average uncertainty, average error, and ANEES. Each are tuples in the format of (position, orientation)
	"""

	Logger.log ( "Testing!", logger = "min" )
	uncertainties: typing.List[typing.Tuple[np.ndarray, np.ndarray]] = []
	errors: typing.List[typing.Tuple[np.ndarray, np.ndarray]] = []

	# Trigger the dropout layers.
	network.train ()

	print_every = Config.getArgs ().print_every

	if not Config.getArgs ().test_print_lots:
		print_every *= Config.getArgs ().batch_size

	# The batch size is 1
	for batchNum, (data, labels) in enumerate ( dataloader ):
		if Config.useCuda ():
			data = data.cuda ()
			labels = labels.cuda ()
		outputs: typing.List[np.ndarray] = []
		for i in range ( Config.getArgs ().num_of_tests ):
			# Get 1 output from the network.
			output: torch.Tensor = network ( data )
			outputs.append ( output.detach ().cpu ().numpy () )
		# Concat output into a single np.ndarray
		outputNP: np.ndarray = np.zeros ( (len ( outputs ), outputs[0].shape[1]) )
		for i in range ( len ( outputs ) ):
			outputNP[i] = outputs[i]
		error: typing.Tuple[np.ndarray, np.ndarray] = getErrorVector ( outputNP,
		                                                               target = labels.cpu ().detach ().numpy () )
		uncertainty: typing.Tuple[np.ndarray, np.ndarray, float, float] = getUncertainty ( outputNP,
		                                                                              returnMatrix = True )

		errors.append ( error )
		# A list of the uncertainty matrices
		uncertainties.append ( uncertainty[:2] )
		if batchNum % print_every == 0:
			Logger.log ( "Last Error: Pos {} Ori {}".format ( errors[-1][0], np.linalg.norm ( errors[-1][1] ) ),
			             logger = "min" )
			Logger.log ( "Last uncertainty: Pos {} Ori {}".format ( np.sqrt ( uncertainty[0].diagonal () ),
			                                                        np.sqrt ( uncertainty[3] ) ), logger = "min" )
			Logger.log ( "Progress: {:4}%".format ( 100.0 * (float ( batchNum ) / float ( len ( dataloader ) )) ),
			             logger = "min" )

	errorsNP: np.ndarray = np.ndarray ( (len ( errors ), 7) )
	for i in range ( len ( errors ) ):
		errorsNP[i] = np.concatenate ( errors[i], axis = 0 )
	posErrors: np.ndarray = errorsNP[:, :3]
	oriErrors: np.ndarray = errorsNP[:, 3:]

	averagePosErr: float = np.average ( posErrors, axis = 0 )
	averageOriErr: float = np.linalg.norm ( np.average ( oriErrors, axis = 0 ) )

	averageError: typing.Tuple[float, float] = (np.linalg.norm ( averagePosErr ), averageOriErr)

	Logger.log (
			"Average Error during test: Pos: {} Ori: {}".format ( averagePosErr, averageError[1] ),
			logger = "min" )

	certaintyNP: typing.Tuple[np.ndarray, np.ndarray] = (
		np.ndarray ( (len ( uncertainties ), 3, 3) ),
		np.ndarray ( (len ( uncertainties ), 4, 4) )
	)
	for i in range ( len ( uncertainties ) ):
		certaintyNP[0][i] = uncertainties[i][0]
		certaintyNP[1][i] = uncertainties[i][1]
	posCerts: np.ndarray = certaintyNP[0]
	oriCerts: np.ndarray = certaintyNP[1]

	avgPosCert: np.ndarray = np.ndarray ( (len ( posCerts ), 3) )
	for i in range ( len ( posCerts ) ):
		avgPosCert[i] = posCerts[i].diagonal ()
	avgPosCert = np.average ( avgPosCert, axis = 0 )

	avgOriCert: np.ndarray = np.ndarray ( (len ( oriCerts ), 4) )
	for i in range ( len ( oriCerts ) ):
		avgOriCert[i] = oriCerts[i].diagonal ()
	avgOriCert = np.average ( avgOriCert, axis = 0 )
	avgOriCert = avgOriCert.sum ()

	averageUncertainty: typing.Tuple[float, float] = (np.sqrt ( avgPosCert.sum () ), np.sqrt ( avgOriCert ))

	Logger.log ( "Average uncertainty: Pos {} Ori {}".format ( np.sqrt ( avgPosCert ),
	                                                           averageUncertainty[1] ) )

	aneesPos: float = ANEES ( posErrors, posCerts, len ( dataloader ) )
	aneesOri: float = ANEES ( oriErrors, oriCerts, len ( dataloader ) )

	Logger.log ( "Average Normalized Error Estimate Squared: Pos {} Ori {}".format ( aneesPos, aneesOri ),
	             logger = "min" )

	return averageUncertainty[0], averageUncertainty[1], averageError[0], averageError[1], aneesPos, aneesOri

def loadModel () -> typing.Dict[str, typing.Union[torch.nn.Module, torch.optim.Optimizer,
                                                  typing.List, float, float, int, typing.Tuple[float, float],
                                                  typing.Tuple[float, float], typing.Tuple[float, float], typing.Tuple[
	                                                  float, float], typing.Tuple[float, float]]]:
	"""
	Loads the model from file. If any keys mismatch, it'll wrap the network in a nn.DataParallel. If it gets any other errors
	it will initiate the network from the default GoogLeNet model found in pretrained-models. Also loads epochs, training
	and validation differences, and a plethora of other stuff. See util.saveModel for more information on what is loaded.

	:return: Everything that was in the file.
	"""
	# Create the network.
	network: torch.nn.Module = PoseNet ( input_nc = 3 )

	# Default network should be placed on the GPU so that the other default things
	# can expect that to be the case.
	if Config.useCuda ():
		network = network.cuda ()

	Logger.log ( "Creating learning objects.", logger = "main" )

	optimizer: torch.optim.Optimizer = torch.optim.Adam ( network.parameters (), lr = Config.getArgs ().learning_rate )

	scheduler1: torch.optim.lr_scheduler.ReduceLROnPlateau = torch.optim.lr_scheduler.ReduceLROnPlateau (
			optimizer = optimizer, threshold = Config.getArgs ().beta,
			verbose = Logger.shouldLog (), threshold_mode = "abs",
			factor = Config.getArgs ().factor )

	scheduler2: torch.optim.lr_scheduler.StepLR = torch.optim.lr_scheduler.StepLR ( optimizer = optimizer,
	                                                                               step_size = 80,
	                                                                               gamma = Config.getArgs ().factor )
	trainingLoss: typing.List[float] = []
	validationLoss: typing.List[float] = []
	trainingDiff: typing.List[float] = []
	validationDiff: typing.List[float] = []
	testingDiff: typing.List[float] = []
	uncertainty: typing.List[float] = []
	anees: typing.List[float] = []

	starting_epoch: int = 0

	hasParallelilized: bool = False

	defaultPretrainedModel: str = "pretrained-models/places-googlenet.pickle"

	Logger.log ( "Loading from pretrained model {}".format ( Config.getArgs ().pretrained_model ), logger = "main" )
	# Load from pretrained model.
	path: str = Config.getArgs ().pretrained_model
	if path is not None or Config.getArgs ().resume:
		try:
			# Load checkpoint from file
			checkpoint: typing.Dict[str, typing.Any] = util.getPretrainedModel ( path = path )
			# If we have version in the data of checkpoint
			if "version" in checkpoint:
				# But it's not the current version...
				if checkpoint["version"] != Config.version:
					# Load in GoogLeNet
					Logger.warn ( "User wants to load outdated model file!" )
				# If the version matches, load in the posenet model

				try:
					# We can't load in from googlenet here because googlenet doesn't have a version key
					network.load_state_dict ( checkpoint["model"] )
				except RuntimeError:
					# If we get a RuntimeError it's probably because we're trying to load something
					# that was wrapped in the nn.DataParallel layer.
					# We could prewrap it all beforehand, but that would mean we would need to
					# circumnavigate GoogLeNet and its keys.
					network = torch.nn.DataParallel ( network )
					hasParallelilized = True
					try:
						network.load_state_dict ( checkpoint["model"] )
					except RuntimeError:
						# if we still get an error message, that means we changed the architecture.
						Logger.warn (
								"Trying to load from version that has different architecture than current version." )
						Logger.log ( "Loading from default model.", logger = "min" )
						network = torch.nn.DataParallel (
								PoseNet ( input_nc = 3, weights = util.getPretrainedModel ( defaultPretrainedModel ) ) )
				if checkpoint["version"] == Config.version:
					# We wouldn't want to load in these things from a different version.
					optimizer.load_state_dict ( checkpoint["optimizer"] )
					scheduler1 = checkpoint["schedulers"][0]
					scheduler2.load_state_dict ( checkpoint["schedulers"][1] )
					trainingLoss = checkpoint["trainingLoss"]
					validationLoss = checkpoint["validationLoss"]
					trainingDiff = checkpoint["trainingDifference"]
					validationDiff = checkpoint["validationDifference"]
					starting_epoch = checkpoint["epoch"] + 1
					testingDiff = checkpoint["testingDifference"]
					uncertainty = checkpoint["uncertainty"]
					anees = checkpoint["anees"]
			else:  # If we don't have a version in the model file, we'll load GoogLeNet.
				Logger.log ( "Loading network from default pretrained model." )
				network = PoseNet ( input_nc = 3, weights = util.getPretrainedModel ( defaultPretrainedModel ) )
		except FileNotFoundError:
			Logger.error ( "Cannot find pretrained model file!" )
			Logger.log ( "Loading from default pretrained model." )
			# Load from googleNet
			network = PoseNet ( input_nc = 3, weights = util.getPretrainedModel ( defaultPretrainedModel ) )
	else:
		Logger.log ( "Loading from default pretrained model." )
		# Load from googleNet
		network = PoseNet ( input_nc = 3, weights = util.getPretrainedModel ( defaultPretrainedModel ) )

	# This will be the same on GPU or CPU so don't you worry baby.
	# Don't you worry OHHHH-OHOH
	if not hasParallelilized:
		network = torch.nn.DataParallel ( network )

	# If we have access to GPUs, put PoseNet on them.
	if Config.useCuda ():
		network = network.cuda ()

	return {
		"network":              network,
		"optimizer":            optimizer,
		"schedulers":           [scheduler1, scheduler2],
		"trainingLoss":         trainingLoss,
		"validationLoss":       validationLoss,
		"startingEpoch":        starting_epoch,
		"validationDifference": validationDiff,
		"trainingDifference":   trainingDiff,
		"testingDifference":    testingDiff,
		"uncertainty":          uncertainty,
		"anees":                anees
	}

def main ():
	"""
	The main training script of the program. Runs through the whole shebang!

	"""
	args = Config.getArgs ()
	if args.threads is 7:
		Logger.warn ( "Number of threads has been limited to 7 for PyTorch reasons." )
	if args.force_threads and args.threads >= 8:
		Logger.warn ( "Remember PyTorch can be a little wonky with 8+ threads!" )
	Logger.log ( "Use Cuda? {}".format ( Config.useCuda () ) )
	if Config.useCuda ():
		Logger.log ( "CUDA Properties is: {}".format ( torch.cuda.get_device_properties ( 0 ) ) )

	Logger.log ( "Parsed args", logger = "main" )
	Logger.log ( str ( args ) )

	# Create validation data by stealing some from training data.
	util.createValidationDataFile ( filenameTrain = os.path.join ( args.database_root, "dataset_train.txt" ),
	                                filenameValidate = os.path.join ( args.database_root, "dataset_validate.txt" ) )

	Logger.log ( "Creating PoseNet.", logger = "main" )

	# Get the mean and std of the database that we'll be using.
	# Btw this takes a while.
	Logger.log ( message = "Getting mean and std of dataset", logger = "main" )
	Logger.log ( message = "This can take a while...", logger = "main" )
	if not args.skip_training:
		normalizeDataset: torch.utils.data.Dataset = PoseNetDataSet ( dataroot = args.database_root,
		                                                              transform = tv.transforms.ToTensor () )
	else:
		normalizeDataset: torch.utils.data.Dataset = PoseNetDataSet ( dataroot = args.database_root,
		                                                              transform = tv.transforms.ToTensor (),
		                                                              phase = "test" )
	mean, std = util.getMeanAndStd ( normalizeDataset )

	Logger.log ( "Mean and std is {} {}".format ( mean, std ) )

	# Create the transformation functions we'll use on each of our images.
	transformImg: tv.transforms.Compose = tv.transforms.Compose ( [  # Resize the image to 256x256
		tv.transforms.Resize ( (256, 256) ),  # Crop the image to 224 x 224
		tv.transforms.RandomCrop ( (224, 224) ),  # Make the image a tensor with dimensions 3x256x256
		# (3 because of rgb values within the image)
		tv.transforms.ToTensor (),  # Normalize the images
		tv.transforms.Normalize ( mean = mean, std = std )] )

	Logger.log ( "Loading dataset.", logger = "main" )
	# Create the dataset based on the KingsCollege database that should be in
	# the working directory of the program.
	datasetTrain: PoseNetDataSet = PoseNetDataSet ( dataroot = args.database_root, transform = transformImg )
	datasetValid: PoseNetDataSet = PoseNetDataSet ( dataroot = args.database_root, transform = transformImg,
	                                                phase = "validate" )
	datasetTest: PoseNetDataSet = PoseNetDataSet ( dataroot = args.database_root, transform = transformImg,
	                                               phase = "test" )

	# Create the dataloader which takes in the KingsCollege dataset and
	# allows us to use it in our training.
	dataloaderTrain: data.DataLoader = data.DataLoader ( dataset = datasetTrain, batch_size = args.batch_size,
	                                                     shuffle = True,
	                                                     num_workers = Config.getArgs ().threads )
	dataloaderValidate: data.DataLoader = data.DataLoader ( dataset = datasetValid, batch_size = args.batch_size,
	                                                        shuffle = True,
	                                                        num_workers = Config.getArgs ().threads )
	# Same as above but with testing.
	dataloaderTest: data.DataLoader = data.DataLoader ( dataset = datasetTest, batch_size = 1,
	                                                    num_workers = Config.getArgs ().threads )

	# The typing for loadModel is already horrible. We won't do it here.
	bigDictionary = loadModel ()
	network = bigDictionary["network"]
	optimizer = bigDictionary["optimizer"]

	schedulers: typing.List = bigDictionary["schedulers"]
	schedulerPlateau: torch.optim.lr_scheduler.ReduceLROnPlateau = schedulers[0]
	schedulerStep: torch.optim.lr_scheduler.StepLR = schedulers[1]

	trainingLoss: typing.List[float] = bigDictionary["trainingLoss"]
	validationLoss: typing.List[float] = bigDictionary["validationLoss"]
	trainingDiff: typing.List[typing.Tuple[float, float]] = bigDictionary["trainingDifference"]
	validationDiff: typing.List[typing.Tuple[float, float]] = bigDictionary["validationDifference"]
	starting_epoch: int = bigDictionary["startingEpoch"]
	testUncertainties: typing.List[typing.Tuple[float, float]] = bigDictionary["uncertainty"]
	testDifferences: typing.List[typing.Tuple[float, float]] = bigDictionary["testingDifference"]
	anees: typing.List[typing.Tuple[float, float]] = bigDictionary["anees"]

	ending_epoch: int = starting_epoch + args.epochs

	Logger.log ( "Optimizer state: {}".format ( optimizer.state ) )

	if Config.useCuda ():
		network = network.cuda ()
	else:
		network = network.cpu ()

	# for each epoch that we want to do...
	for epoch in range ( starting_epoch, ending_epoch ):
		if not args.skip_training:
			# If the current epoch is divisible by 80, decrease it by 90%
			schedulerStep.step ()
			# train network
			trainLoss, trainPosDiff, trainOriDiff = trainNetwork ( network = network, optimizer = optimizer,
			                                                       dataloader = dataloaderTrain,
			                                                       epoch = epoch,
			                                                       beta = args.beta,
			                                                       print_every = args.print_every )
			trainingLoss.append ( trainLoss )
			trainingDiff.append ( (trainPosDiff, trainOriDiff) )

			# validate network
			validLoss, validPosDiff, validOriDiff = validate ( network = network, dataloader = dataloaderValidate,
			                                                   beta = args.beta,
			                                                   print_every = args.print_every, epoch = epoch )

			validationLoss.append ( validLoss )
			validationDiff.append ( (validPosDiff, validOriDiff) )

			# Decrease threshold of scheduler when our loss is less than the current threshold
			# times a certain factor.
			if validationLoss[-1] < schedulerPlateau.threshold * args.threshold_factor:
				schedulerPlateau.threshold /= 2

			schedulerPlateau.step ( epoch = epoch, metrics = validationLoss[-1] )

			# Save the network
			util.saveModel ( model_filename = args.model_file,
			                 schedulers = [schedulerPlateau, schedulerStep.state_dict ()],
			                 epoch = epoch, network = network, optimizer = optimizer,
			                 trainingLoss = trainingLoss, validationLoss = validationLoss,
			                 trainingDifference = trainingDiff, validationDifference = validationDiff,
			                 testingDifference = testDifferences, uncertainty = testUncertainties, anees = anees )

		if not args.skip_testing and (epoch % args.test_every == 0 or epoch + 1 == ending_epoch):
			# Test the network
			posUncert, oriUncert, posDiff, oriDiff, aneesPos, aneesOri = test ( network = network,
			                                                                    dataloader = dataloaderTest )
			testUncertainties.append ( (posUncert, oriUncert) )
			testDifferences.append ( (posDiff, oriDiff) )
			anees.append ( (aneesPos, aneesOri) )
			# Save the network
			util.saveModel ( model_filename = args.model_file,
			                 schedulers = [schedulerPlateau, schedulerStep.state_dict ()],
			                 epoch = epoch, network = network, optimizer = optimizer,
			                 trainingLoss = trainingLoss, validationLoss = validationLoss,
			                 trainingDifference = trainingDiff, validationDifference = validationDiff,
			                 testingDifference = testDifferences, uncertainty = testUncertainties, anees = anees )


from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, ReplyKeyboardRemove

def base_reply_kb(chat_type: str):
    if chat_type == "private":
        return (
            ReplyKeyboardMarkup(resize_keyboard=True)
            .row(KeyboardButton("/help"), KeyboardButton("/about"))
            .add(KeyboardButton("Отправить местоположение", request_location=True))
        )
    return ReplyKeyboardRemove()


from Setting.setting_file import xdw_TestCase
from Setting.cycl import cycl_TestCase
import unittest
from selenium import webdriver
from selenium.webdriver.support.ui import Select
import time
from Setting.setting_file import login_name_user1,login_name_password1,login_name_user4,login_name_password4,setting_xls
import xlrd
import random
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By

Testdata = xlrd.open_workbook(setting_xls)
sheet_passenger=Testdata.sheet_by_name('旅客信息')
sheet_goods=Testdata.sheet_by_name('物品信息')

class xdwcy_TestCase(xdw_TestCase,cycl_TestCase):
    global test_num
    test_num = random.randint(1, 10)

    def Initialization_1(self):
        self.login_keyuan(login_name_user1, login_name_password1)
        time.sleep(2)

    def Initialization_2(self):
        self.login_keyuan(login_name_user4, login_name_password4)
        time.sleep(2)

    def test01_销毁_录单(self):
        self.Initialization_1()
        self.xdw_Inspection_addition(test_num, '销毁', '')
        global id, jld_id
        id = self.dr.find_element_by_xpath('//*[@id="list"]/tbody[1]/tr/td[3]').text
        jld_id = self.dr.find_element_by_xpath('//*[@id="jldbh"]').get_attribute('value')

    def test02_销毁_转库(self):
        self.Initialization_1()
        self.dr.find_element_by_xpath('//*[@id="f683e4f46b094a8ba68ad201a6e5b7bf"]/a/span').click()
        iframes = self.dr.find_element_by_xpath('//*[@id="sub_f683e4f46b094a8ba68ad201a6e5b7bf"]/iframe')
        self.dr.switch_to.frame(iframes)
        self.dr.find_element_by_xpath('//*[@id="wpbh"]').send_keys(id)
        self.dr.find_element_by_xpath('//*[@id="search"]/span').click()
        time.sleep(2)
        self.dr.find_element_by_xpath('//*[@id="list"]/tbody/tr/td[10]/div/button').click()
        time.sleep(2)
        self.dr.find_element_by_xpath('//*[@id="transferBtn"]').click()
        time.sleep(2)
        Select(self.dr.find_element_by_xpath('//*[@id="selCkid"]')).select_by_visible_text('仓库2')
        time.sleep(2)
        self.dr.find_element_by_xpath('/html/body/div[5]/div[3]/div/button[2]').click()
        time.sleep(3)
        handles = self.dr.window_handles
        self.assertEqual(2,len(handles))

    def test03_销毁_转库单据查询(self):
        self.Initialization_1()
        self.dr.find_element_by_xpath('//*[@id="dcefdfba1ea04e2db15876bd8673651d"]/a/span').click()
        self.dr.find_element_by_xpath('//*[@id="047b073794584993be96d086e3b9b5cb"]/a/span').click()
        iframe=self.dr.find_element_by_xpath('//*[@id="sub_047b073794584993be96d086e3b9b5cb"]/iframe')
        self.dr.switch_to.frame(iframe)
        self.dr.find_element_by_xpath('//*[@id="form"]/div[1]/div/div[2]/input').send_keys(id)
        self.dr.find_element_by_xpath('//*[@id="search"]/span').click()
        self.assertEqual(id,self.dr.find_element_by_xpath('//*[@id="list"]/tbody/tr[1]/td[2]').text)

    def test04_销毁_转库确认接收(self):
        self.Initialization_2()
        self.dr.find_element_by_xpath('//*[@id="436c1edb6d364195beb2ca044dceb58f"]/a').click()
        self.dr.find_element_by_xpath('//*[@id="d98aa4bffc93479b984febde6df22110"]/a/span').click()
        iframe=self.dr.find_element_by_xpath('//*[@id="sub_d98aa4bffc93479b984febde6df22110"]/iframe')
        self.dr.switch_to.frame(iframe)
        search_value = {'wpbh': id, 'wpmc': '', 'start': '', 'end': ''}
        self.xdw_Transfer_confirmation_receipt_search(search_value)
        search_num = self.dr.find_element_by_xpath('//*[@id="tbList"]/div[2]/div[1]/div/div[4]/div[1]/span[1]').text
        self.pagination_num(search_num, search_value['wpbh'], 2, 3)
        self.xdw_Transfer_confirmation_receipt()

    def test05_销毁_出库(self):
        self.Initialization_2()
        self.dr.find_element_by_xpath('//*[@id="436c1edb6d364195beb2ca044dceb58f"]/a').click()
        self.dr.find_element_by_xpath('//*[@id="6c25cddc87d7418fb9825b762737a2c0"]/a').click()
        iframes=self.dr.find_element_by_xpath('//*[@id="sub_6c25cddc87d7418fb9825b762737a2c0"]/iframe')
        self.dr.switch_to.frame(iframes)
        time.sleep(1)
        # js = "window.scrollTo(0, document.body.scrollHeight)"
        # self.dr.execute_script(js)
        # time.sleep(3)
        # self.dr.find_element_by_xpath(
        #     '/html/body/div[1]/div/div[3]/div[2]/div[1]/div/div[4]/div[1]/span[2]/span/button').click()
        # self.dr.find_element_by_xpath(
        #     '/html/body/div[1]/div/div[3]/div[2]/div[1]/div/div[4]/div[1]/span[2]/span/ul/li[1]/a').click()
        # time.sleep(1)
        search_value = {'wpbh': id, 'wpmc': '', 'startTime': '', 'endTime': '', 'clff': '--全部--', 'ckjglx': '--全部--'}
        self.xdw_ck_normal_search(search_value)
        search_num = self.dr.find_element_by_xpath(
            '/html/body/div[1]/div/div[3]/div[2]/div[1]/div/div[4]/div[1]/span[1]').text
        self.pagination_num(search_num, search_value['wpbh'], 2, 0)
        self.xdw_ck_normal_xhck()

    def test06_销毁单据(self):
        self.Initialization_2()
        self.dr.find_element_by_xpath('//*[@id="dcefdfba1ea04e2db15876bd8673651d"]/a/span').click()
        self.dr.find_element_by_xpath('//*[@id="2de3690e1692476caa343748aa27c5ec"]/a/span').click()
        iframe=self.dr.find_element_by_xpath('//*[@id="sub_2de3690e1692476caa343748aa27c5ec"]/iframe')
        self.dr.switch_to.frame(iframe)
        self.dr.find_element_by_xpath('//*[@id="form"]/div/div/div[2]/input').send_keys(id)
        self.dr.find_element_by_xpath('//*[@id="search"]/span').click()
        time.sleep(2)
        self.assertEqual(id,self.dr.find_element_by_xpath('//*[@id="list"]/tbody/tr/td[3]').text)

if __name__ == '__main__':
    unittest.main()


#! /usr/bin/env python
from decimal import Decimal
import json
import sys
import re

# The sales tax charged when buying the wine at auction.
SALES_TAX_RATE = 0.09
# The commission charged when selling the wine after the auction.
SALE_COMMISSION = 0.15
# The target profit for any wine purchased at auction.
TARGET_PROFIT = 0.1

def format_cents(cents):
    return '$%.2f' % (float(cents) / 100)

if not len(sys.argv) == 3:
    raise Exception('Usage: python wine.py auctions.json sales.json')

auctions = json.load(open(sys.argv[1]))
sales = json.load(open(sys.argv[2]))

biddable_auctions = []
for auction in auctions:
    matched_sales = []
    # Get the list of matching sales.
    for sale in sales:
        if (auction.get('vintage') == int(sale['year']) and
                sale['winery'] in auction['title'] and
                sale['grape'] in auction['title'] and
                sale['name'] in auction['title'] and
                auction['size'] == 750):
            matched_sales.append(sale)
    # If there were matching sales, we can determine if bidding on the auction would be a good idea.
    if len(matched_sales) > 0:
        # Price per bottle at the current auction price.
        current_price_per_bottle = auction['current_bid'] / auction['quantity']
        auction['current_price_per_bottle'] = current_price_per_bottle

        # Market price per bottle for each sale.
        sale_prices = [int(Decimal(re.sub(r'[^\d.]', '', sale['sold'])) * 100) for sale in matched_sales]
        auction['sale_prices'] = sale_prices
        # print '\tSale prices: %s' % sale_prices
        max_sale_price_per_bottle = max(sale_prices)
        auction['max_sale_price_per_bottle'] = max_sale_price_per_bottle

        # Break even bid is based on the maximum sale price.
        revenue_per_bottle = max_sale_price_per_bottle * (1 - SALE_COMMISSION)
        auction['revenue_per_bottle'] = revenue_per_bottle
        profitable_price_per_bottle = revenue_per_bottle / (1 + TARGET_PROFIT)
        auction['profitable_price_per_bottle'] = profitable_price_per_bottle
        max_bid_per_bottle = profitable_price_per_bottle / (1 + SALES_TAX_RATE)
        auction['max_bid_per_bottle'] = max_bid_per_bottle
        auction['max_bid'] = max_bid_per_bottle * auction['quantity']

        if max_bid_per_bottle > current_price_per_bottle:
            min_profit = (revenue_per_bottle - profitable_price_per_bottle) * auction['quantity']
            auction['min_profit'] = min_profit
            max_profit = (revenue_per_bottle - current_price_per_bottle * (1 + SALES_TAX_RATE)) * auction['quantity']
            auction['max_profit'] = max_profit
            # If there's a potential for sufficient profit, add this to the list of biddable auctions.
            biddable_auctions.append(auction)

print(json.dumps(biddable_auctions, indent=4, sort_keys=True))


#!/usr/bin/env python3
import time
import platform

from ant.core import driver
from ant.core import node

from usb.core import find

from PowerMeterTx import PowerMeterTx
from SpeedCadenceSensorRx import SpeedCadenceSensorRx
from config import DEBUG, LOG, NETKEY, POWER_CALCULATOR, POWER_SENSOR_ID, SENSOR_TYPE, SPEED_SENSOR_ID

antnode = None
speed_sensor = None
power_meter = None

def stop_ant():
    if speed_sensor:
        print("Closing speed sensor")
        speed_sensor.close()
        speed_sensor.unassign()
    if power_meter:
        print("Closing power meter")
        power_meter.close()
        power_meter.unassign()
    if antnode:
        print("Stopping ANT node")
        antnode.stop()

pywin32 = False
if platform.system() == 'Windows':
    def on_exit(sig, func=None):
        stop_ant()
    try:
        import win32api
        win32api.SetConsoleCtrlHandler(on_exit, True)
        pywin32 = True
    except ImportError:
        print("Warning: pywin32 is not installed, use Ctrl+C to stop")

try:
    print("Using " + POWER_CALCULATOR.__class__.__name__)

    devs = find(find_all=True, idVendor=0x0fcf)
    for dev in devs:
        if dev.idProduct in [0x1008, 0x1009]:
            # If running on the same PC as the receiver app (with two identical sticks)
            # the first stick may be already claimed, so continue trying
            stick = driver.USB2Driver(log=LOG, debug=DEBUG, idProduct=dev.idProduct, bus=dev.bus, address=dev.address)
            try:
                stick.open()
            except:
                continue
            stick.close()
            break
    else:
        print("No ANT devices available")
        exit()

    antnode = node.Node(stick)
    print("Starting ANT node")
    antnode.start()
    key = node.Network(NETKEY, 'N:ANT+')
    antnode.setNetworkKey(0, key)

    print("Starting speed sensor")
    try:
        # Create the speed sensor object and open it
        speed_sensor = SpeedCadenceSensorRx(antnode, SENSOR_TYPE, SPEED_SENSOR_ID & 0xffff)
        speed_sensor.open()
        # Notify the power calculator every time we get a speed event
        speed_sensor.notify_change(POWER_CALCULATOR)
    except Exception as e:
        print("speed_sensor error: " + repr(e))
        speed_sensor = None

    print("Starting power meter with ANT+ ID " + repr(POWER_SENSOR_ID))
    try:
        # Create the power meter object and open it
        power_meter = PowerMeterTx(antnode, POWER_SENSOR_ID)
        power_meter.open()
    except Exception as e:
        print("power_meter error: " + repr(e))
        power_meter = None

    # Notify the power meter every time we get a calculated power value
    POWER_CALCULATOR.notify_change(power_meter)

    stopped = True
    last_time = 0
    last_update = 0
    print("Main wait loop")
    while True:
        try:
            # Some apps keep the last received power value if the sensor stops broadcasting
            # and some drop the power to zero if the interval between updates is > 1 second
            if not stopped:
                t = int(time.time())
                if t >= last_update + 3:
                    if speed_sensor.currentData.speedEventTime == last_time:
                        # Set power to zero if speed sensor doesn't update for 3 seconds
                        power_meter.powerData.instantaneousPower = 0
                        stopped = True
                    last_time = speed_sensor.currentData.speedEventTime
                    last_update = t
                # Force an update every second to avoid power drops
                power_meter.update(power_meter.powerData.instantaneousPower)
            elif power_meter.powerData.instantaneousPower:
                stopped = False
            time.sleep(1)
        except (KeyboardInterrupt, SystemExit):
            break

except Exception as e:
    print("Exception: " + repr(e))
finally:
    if not pywin32:
        stop_ant()


# -*- coding: utf-8 -*-
"""
Created on Mon Nov 25 10:24:39 2019

@author: lennie
"""

import pytest
import sys

sys.path.append(".")
sys.path.append("./src")

from src import add_module

class TestAddModule():
    @pytest.fixture(scope='session')
    def create_object_for_testing(self):
        """ Sets up the object for further testing
        """
        return add_module.AddClass()

    @pytest.mark.parametrize("test_input,expected", [((3,1), 4), ((0,0), 0), ((1e6,1e6), 2e6),((-1,1), 0)])
    def test_basic_addition(self,test_input,expected,create_object_for_testing):
        """ Does a test on normal inputs.
        """
        actual = create_object_for_testing.Add(*test_input)
        print(actual)
        message = "Failed with Add({}) giving: {}, instead of the expected: {} ".format(test_input,actual,expected)
        assert actual==expected,message

    def test_type_input(self,create_object_for_testing):
        """ throws in wrong input type.
        """
        actual = create_object_for_testing.Add(0,'s')
        expected = None
        message = "Failed with Add({}) giving: {}, instead of the expected: {} ".format((0,'s'),actual,expected)
        assert actual==expected, message


'''
cache_mode:
0:使用cache缓存必要参数
1:使用cache缓存必要参数，对cache进行8bit量化节省显存，带来小许延时增长
2:不使用cache，耗时约为mode0的2倍，但是显存不受输入图像分辨率限制，tile_mode填得够大，1.5G显存可超任意比例
'''
import os
import sys

import numpy as np
import torch
from torch import nn as nn
from torch.nn import functional as F

root_path = os.path.abspath('.')
sys.path.append(root_path)

def q(inp, cache_mode):
    maxx = inp.max()
    minn = inp.min()
    delta = maxx - minn
    if (cache_mode == 2):
        return ((inp - minn) / delta * 255).round().byte().cpu(), delta, minn, inp.device  # 大概3倍延时#太慢了，屏蔽该模式
    elif (cache_mode == 1):
        return ((inp - minn) / delta * 255).round().byte(), delta, minn, inp.device  # 不用CPU转移

def dq(inp, if_half, cache_mode, delta, minn, device):
    if (cache_mode == 2):
        if (if_half == True):
            return inp.to(device).half() / 255 * delta + minn
        else:
            return inp.to(device).float() / 255 * delta + minn
    elif (cache_mode == 1):
        if (if_half == True):
            return inp.half() / 255 * delta + minn  # 不用CPU转移
        else:
            return inp.float() / 255 * delta + minn

class SEBlock(nn.Module):
    def __init__(self, in_channels, reduction=8, bias=False):
        super(SEBlock, self).__init__()
        print(in_channels)
        print(in_channels // reduction)
        self.conv1 = nn.Conv2d(in_channels, in_channels // reduction, 1, 1, 0, bias=bias)
        self.conv2 = nn.Conv2d(in_channels // reduction, in_channels, 1, 1, 0, bias=bias)

    def forward(self, x):
        if ("Half" in x.type()):  # torch.HalfTensor/torch.cuda.HalfTensor
            x0 = torch.mean(x.float(), dim=(2, 3), keepdim=True).half()
        else:
            x0 = torch.mean(x, dim=(2, 3), keepdim=True)
        x0 = self.conv1(x0)
        x0 = F.relu(x0, inplace=True)
        x0 = self.conv2(x0)
        x0 = torch.sigmoid(x0)
        x = torch.mul(x, x0)
        return x

    def forward_mean(self, x, x0):
        x0 = self.conv1(x0)
        x0 = F.relu(x0, inplace=True)
        x0 = self.conv2(x0)
        x0 = torch.sigmoid(x0)
        x = torch.mul(x, x0)
        return x

class UNetConv(nn.Module):
    def __init__(self, in_channels, mid_channels, out_channels, se):
        super(UNetConv, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(in_channels, mid_channels, 3, 1, 0),
            nn.LeakyReLU(0.1, inplace=True),
            nn.Conv2d(mid_channels, out_channels, 3, 1, 0),
            nn.LeakyReLU(0.1, inplace=True),
        )
        if se:
            self.seblock = SEBlock(out_channels, reduction=8, bias=True)
        else:
            self.seblock = None

    def forward(self, x):
        z = self.conv(x)
        if self.seblock is not None:
            z = self.seblock(z)
        return z

class UNet1(nn.Module):
    def __init__(self, in_channels, out_channels, deconv):
        super(UNet1, self).__init__()
        self.conv1 = UNetConv(in_channels, 32, 64, se=False)
        self.conv1_down = nn.Conv2d(64, 64, 2, 2, 0)
        self.conv2 = UNetConv(64, 128, 64, se=True)
        self.conv2_up = nn.ConvTranspose2d(64, 64, 2, 2, 0)
        self.conv3 = nn.Conv2d(64, 64, 3, 1, 0)

        if deconv:
            #self.conv_bottom = nn.ConvTranspose2d(64, out_channels, 4, 2, 3)
            self.conv_bottom = nn.ConvTranspose2d(64, out_channels, 4, 2, 3)
        else:
            self.conv_bottom = nn.Conv2d(64, out_channels, 3, 1, 0)

        for m in self.modules():
            if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)

    def forward(self, x):
        x1 = self.conv1(x)
        x2 = self.conv1_down(x1)
        x1 = F.pad(x1, (-4, -4, -4, -4))
        x2 = F.leaky_relu(x2, 0.1, inplace=True)
        x2 = self.conv2(x2)
        x2 = self.conv2_up(x2)
        x2 = F.leaky_relu(x2, 0.1, inplace=True)
        x3 = self.conv3(x1 + x2)
        x3 = F.leaky_relu(x3, 0.1, inplace=True)
        z = self.conv_bottom(x3)
        return z

    def forward_a(self, x):
        x1 = self.conv1(x)
        x2 = self.conv1_down(x1)
        x1 = F.pad(x1, (-4, -4, -4, -4))
        x2 = F.leaky_relu(x2, 0.1, inplace=True)
        x2 = self.conv2.conv(x2)
        return x1, x2

    def forward_b(self, x1, x2):
        x2 = self.conv2_up(x2)
        x2 = F.leaky_relu(x2, 0.1, inplace=True)
        x3 = self.conv3(x1 + x2)
        x3 = F.leaky_relu(x3, 0.1, inplace=True)
        z = self.conv_bottom(x3)
        return z

class UNet2(nn.Module):
    def __init__(self, in_channels, out_channels, deconv):
        super(UNet2, self).__init__()

        self.conv1 = UNetConv(in_channels, 32, 64, se=False)
        self.conv1_down = nn.Conv2d(64, 64, 2, 2, 0)
        self.conv2 = UNetConv(64, 64, 128, se=True)
        self.conv2_down = nn.Conv2d(128, 128, 2, 2, 0)
        self.conv3 = UNetConv(128, 256, 128, se=True)
        self.conv3_up = nn.ConvTranspose2d(128, 128, 2, 2, 0)
        self.conv4 = UNetConv(128, 64, 64, se=True)
        self.conv4_up = nn.ConvTranspose2d(64, 64, 2, 2, 0)
        self.conv5 = nn.Conv2d(64, 64, 3, 1, 0)

        if deconv:
            self.conv_bottom = nn.ConvTranspose2d(64, out_channels, 4, 2, 3)
        else:
            self.conv_bottom = nn.Conv2d(64, out_channels, 3, 1, 0)

        for m in self.modules():
            if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)

    def forward(self, x, alpha=1):
        x1 = self.conv1(x)
        x2 = self.conv1_down(x1)
        x1 = F.pad(x1, (-16, -16, -16, -16))
        x2 = F.leaky_relu(x2, 0.1, inplace=True)
        x2 = self.conv2(x2)
        x3 = self.conv2_down(x2)
        x2 = F.pad(x2, (-4, -4, -4, -4))
        x3 = F.leaky_relu(x3, 0.1, inplace=True)
        x3 = self.conv3(x3)
        x3 = self.conv3_up(x3)
        x3 = F.leaky_relu(x3, 0.1, inplace=True)
        x4 = self.conv4(x2 + x3)
        x4 *= alpha
        x4 = self.conv4_up(x4)
        x4 = F.leaky_relu(x4, 0.1, inplace=True)
        x5 = self.conv5(x1 + x4)
        x5 = F.leaky_relu(x5, 0.1, inplace=True)
        z = self.conv_bottom(x5)
        return z

    def forward_a(self, x):  # conv234结尾有se
        x1 = self.conv1(x)
        x2 = self.conv1_down(x1)
        x1 = F.pad(x1, (-16, -16, -16, -16))
        x2 = F.leaky_relu(x2, 0.1, inplace=True)
        x2 = self.conv2.conv(x2)
        return x1, x2

    def forward_b(self, x2):  # conv234结尾有se
        x3 = self.conv2_down(x2)
        x2 = F.pad(x2, (-4, -4, -4, -4))
        x3 = F.leaky_relu(x3, 0.1, inplace=True)
        x3 = self.conv3.conv(x3)
        return x2, x3

    def forward_c(self, x2, x3):  # conv234结尾有se
        x3 = self.conv3_up(x3)
        x3 = F.leaky_relu(x3, 0.1, inplace=True)
        x4 = self.conv4.conv(x2 + x3)
        return x4

    def forward_d(self, x1, x4):  # conv234结尾有se
        x4 = self.conv4_up(x4)
        x4 = F.leaky_relu(x4, 0.1, inplace=True)
        x5 = self.conv5(x1 + x4)
        x5 = F.leaky_relu(x5, 0.1, inplace=True)

        z = self.conv_bottom(x5)
        return z

class UpCunet2x(nn.Module):
    def __init__(self, in_channels=3, out_channels=3):
        super(UpCunet2x, self).__init__()
        self.unet1 = UNet1(in_channels, out_channels, deconv=True)
        self.unet2 = UNet2(in_channels, out_channels, deconv=False)

    def forward(self, x, tile_mode, cache_mode, alpha, pro):
        n, c, h0, w0 = x.shape
        if ("Half" in x.type()):
            if_half = True
        else:
            if_half = False
        if (tile_mode == 0):  # 不tile
            #将奇数变为下一个最近的偶数
            ph = ((h0 - 1) // 2 + 1) * 2
            pw = ((w0 - 1) // 2 + 1) * 2
            #填充h w 维度 （h前填充，h后填充，w前填充，w后填充）
            x = F.pad(x, (18, 18 + pw - w0, 18, 18 + ph - h0), 'reflect')  # 需要保证被2整除
            x = self.unet1.forward(x)
            x0 = self.unet2.forward(x, alpha)
            x = F.pad(x, (-20, -20, -20, -20))
            x = torch.add(x0, x)
            if (w0 != pw or h0 != ph): x = x[:, :, :h0 * 2, :w0 * 2]
            if (pro):
                return ((x - 0.15) * (255 / 0.7)).round().clamp_(0, 255).byte()
            else:
                return (x * 255).round().clamp_(0, 255).byte()
        elif (tile_mode == 1):  # 对长边减半
            if (w0 >= h0):
                crop_size_w = ((w0 - 1) // 4 * 4 + 4) // 2  # 减半后能被2整除，所以要先被4整除
                crop_size_h = (h0 - 1) // 2 * 2 + 2  # 能被2整除
            else:
                crop_size_h = ((h0 - 1) // 4 * 4 + 4) // 2  # 减半后能被2整除，所以要先被4整除
                crop_size_w = (w0 - 1) // 2 * 2 + 2  # 能被2整除
            crop_size = (crop_size_h, crop_size_w)
        elif (tile_mode >= 2):
            tile_mode=min(h0, w0) // 128
            #tile_mode = min(min(h0, w0) // 128, int(tile_mode))  # 最小短边为128*128
            t2 = tile_mode * 2
            crop_size = (((h0 - 1) // t2 * t2 + t2) // tile_mode, ((w0 - 1) // t2 * t2 + t2) // tile_mode)
        else:
            print("tile_mode config error")
            os._exit(233)

        ph = ((h0 - 1) // crop_size[0] + 1) * crop_size[0]
        pw = ((w0 - 1) // crop_size[1] + 1) * crop_size[1]
        x = F.pad(x, (18, 18 + pw - w0, 18, 18 + ph - h0), 'reflect')
        n, c, h, w = x.shape
        if (if_half):
            se_mean0 = torch.zeros((n, 64, 1, 1), device=x.device, dtype=torch.float16)
        else:
            se_mean0 = torch.zeros((n, 64, 1, 1), device=x.device, dtype=torch.float32)
        n_patch = 0
        tmp_list = []
        i_length=(h - 36)//crop_size[0]
        j_length=(w - 36) //crop_size[1]
        for i in range(i_length):
            tmp_list.append([])
            for j in range(j_length):
                print(f"i:{i},j:{j}")
                x_crop = x[:, :, i*crop_size[0]:(i +1)* crop_size[0] + 36, j*(crop_size[1]):(j+1)* crop_size[1] + 36]
                n, c1, h1, w1 = x_crop.shape
                tmp0, x_crop = self.unet1.forward_a(x_crop)
                if (if_half):  # torch.HalfTensor/torch.cuda.HalfTensor
                    tmp_se_mean = torch.mean(x_crop.float(), dim=(2, 3), keepdim=True).half()
                else:
                    tmp_se_mean = torch.mean(x_crop, dim=(2, 3), keepdim=True)
                se_mean0 += tmp_se_mean
                n_patch += 1
                #tmp_dict[i][j] = (tmp0, x_crop)
                tmp_list[i].append([])
                # tmp_list[i][j].append(tmp0)
                # tmp_list[i][j].append(x_crop)
                tmp_list[i][j] = [tmp0, x_crop]

        se_mean0 /= n_patch
        if (if_half):
            se_mean1 = torch.zeros((n, 128, 1, 1), device=x.device, dtype=torch.float16)
        else:
            se_mean1 = torch.zeros((n, 128, 1, 1), device=x.device, dtype=torch.float32)

        print(f"{'-'*10}")

        for i in range(i_length):
            for j in range(j_length):
                print(f"i:{i},j:{j}")
                tmp0, x_crop =  tmp_list[i][j][0],tmp_list[i][j][1]
                x_crop = self.unet1.conv2.seblock.forward_mean(x_crop, se_mean0)
                opt_unet1 = self.unet1.forward_b(tmp0, x_crop)
                tmp_x1, tmp_x2 = self.unet2.forward_a(opt_unet1)
                opt_unet1 = F.pad(opt_unet1, (-20, -20, -20, -20))
                if (cache_mode): opt_unet1, tmp_x1 = q(opt_unet1, cache_mode), q(tmp_x1, cache_mode)
                if (if_half):  # torch.HalfTensor/torch.cuda.HalfTensor
                    tmp_se_mean = torch.mean(tmp_x2.float(), dim=(2, 3), keepdim=True).half()
                else:
                    tmp_se_mean = torch.mean(tmp_x2, dim=(2, 3), keepdim=True)
                if (cache_mode): tmp_x2 = q(tmp_x2, cache_mode)
                se_mean1 += tmp_se_mean
                tmp_list[i][j] = [opt_unet1, tmp_x1, tmp_x2]

        se_mean1 /= n_patch
        if (if_half):
            se_mean0 = torch.zeros((n, 128, 1, 1), device=x.device, dtype=torch.float16)
        else:
            se_mean0 = torch.zeros((n, 128, 1, 1), device=x.device, dtype=torch.float32)

        print(f"{'-' * 10}")
        for i in range(i_length):
            for j in range(j_length):
                print(f"i:{i},j:{j}")
                opt_unet1, tmp_x1, tmp_x2 = tmp_list[i][j][0],tmp_list[i][j][1],tmp_list[i][j][2]
                if (cache_mode): tmp_x2 = dq(tmp_x2[0], if_half, cache_mode, tmp_x2[1], tmp_x2[2], tmp_x2[3])
                tmp_x2 = self.unet2.conv2.seblock.forward_mean(tmp_x2, se_mean1)
                tmp_x2, tmp_x3 = self.unet2.forward_b(tmp_x2)
                if (cache_mode): tmp_x2 = q(tmp_x2, cache_mode)
                if (if_half):  # torch.HalfTensor/torch.cuda.HalfTensor
                    tmp_se_mean = torch.mean(tmp_x3.float(), dim=(2, 3), keepdim=True).half()
                else:
                    tmp_se_mean = torch.mean(tmp_x3, dim=(2, 3), keepdim=True)
                if (cache_mode): tmp_x3 = q(tmp_x3, cache_mode)
                se_mean0 += tmp_se_mean
                tmp_list[i][j] = [opt_unet1, tmp_x1, tmp_x2, tmp_x3]
        se_mean0 /= n_patch
        if (if_half):
            se_mean1 = torch.zeros((n, 64, 1, 1), device=x.device, dtype=torch.float16)
        else:
            se_mean1 = torch.zeros((n, 64, 1, 1), device=x.device, dtype=torch.float32)
        print(f"{'-' * 10}")
        for i in range(i_length):
            for j in range(j_length):
                print(f"i:{i},j:{j}")
                opt_unet1, tmp_x1, tmp_x2, tmp_x3 = tmp_list[i][j][0],tmp_list[i][j][1],tmp_list[i][j][2],tmp_list[i][j][3]
                if (cache_mode): tmp_x3 = dq(tmp_x3[0], if_half, cache_mode, tmp_x3[1], tmp_x3[2], tmp_x3[3])
                tmp_x3 = self.unet2.conv3.seblock.forward_mean(tmp_x3, se_mean0)
                if (cache_mode): tmp_x2 = dq(tmp_x2[0], if_half, cache_mode, tmp_x2[1], tmp_x2[2], tmp_x2[3])
                tmp_x4 = self.unet2.forward_c(tmp_x2, tmp_x3) * alpha
                if (if_half):  # torch.HalfTensor/torch.cuda.HalfTensor
                    tmp_se_mean = torch.mean(tmp_x4.float(), dim=(2, 3), keepdim=True).half()
                else:
                    tmp_se_mean = torch.mean(tmp_x4, dim=(2, 3), keepdim=True)
                if (cache_mode): tmp_x4 = q(tmp_x4, cache_mode)
                se_mean1 += tmp_se_mean
                tmp_list[i][j] = [opt_unet1, tmp_x1, tmp_x4]
        se_mean1 /= n_patch
        res = torch.zeros((n, c, h * 2 - 72, w * 2 - 72), dtype=torch.uint8, device=x.device)
        print(f"{'-' * 10}")
        for i in range((h - 36) // crop_size[0]):
            for j in range((w - 36) // crop_size[1]):
                print(f"i:{i},j:{j}")
                x, tmp_x1, tmp_x4 = tmp_list[i][j][0],tmp_list[i][j][1],tmp_list[i][j][2]
                if (cache_mode): tmp_x4 = dq(tmp_x4[0], if_half, cache_mode, tmp_x4[1], tmp_x4[2], tmp_x4[3])
                tmp_x4 = self.unet2.conv4.seblock.forward_mean(tmp_x4, se_mean1)
                if (cache_mode): tmp_x1 = dq(tmp_x1[0], if_half, cache_mode, tmp_x1[1], tmp_x1[2], tmp_x1[3])
                x0 = self.unet2.forward_d(tmp_x1, tmp_x4)
                if (cache_mode): x = dq(x[0], if_half, cache_mode, x[1], x[2], x[3])
                #del tmp_list[i][j]
                x = torch.add(x0, x)  # x0是unet2的最终输出
                if (pro):
                    res[:, :, i*crop_size[0] * 2:i*crop_size[0] * 2 + h1 * 2 - 72, j*crop_size[1] * 2:j *crop_size[1]* 2 + w1 * 2 - 72] = (
                            (x - 0.15) * (255 / 0.7)).round().clamp_(0, 255).byte()
                else:
                    res[:, :, i*crop_size[0] * 2:i*crop_size[0] * 2 + h1 * 2 - 72, j*crop_size[1] * 2:j*crop_size[1] * 2 + w1 * 2 - 72] = (x * 255).round().clamp_(0,
                                                                                255).byte()
        del tmp_list
        # torch.cuda.empty_cache()
        if (w0 != pw or h0 != ph): res = res[:, :, :h0 * 2, :w0 * 2]
        return res

class RealWaifuUpScaler(object):
    def __init__(self, scale, weight_path, half, device):
        weight = torch.load(weight_path, map_location="cpu")
        self.pro = "pro" in weight
        if (self.pro): del weight["pro"]
        #
        self.model = eval("UpCunet%sx" % scale)()
        if (half == True):
            self.model = self.model.half().to(device)
        else:
            self.model = self.model.to(device)
        self.model.load_state_dict(weight, strict=True)
        self.model.eval()
        self.half = half
        self.device = device

    def np2tensor(self, np_frame):
        if (self.pro):
            # half半精度
            if (self.half == False):
                return torch.from_numpy(np.transpose(np_frame, (2, 0, 1))).unsqueeze(0).to(self.device).float() / (
                        255 / 0.7) + 0.15
            else:
                return torch.from_numpy(np.transpose(np_frame, (2, 0, 1))).unsqueeze(0).to(self.device).half() / (
                        255 / 0.7) + 0.15
        else:
            if (self.half == False):
                return torch.from_numpy(np.transpose(np_frame, (2, 0, 1))).unsqueeze(0).to(self.device).float() / 255
            else:
                return torch.from_numpy(np.transpose(np_frame, (2, 0, 1))).unsqueeze(0).to(self.device).half() / 255

    def tensor2np(self, tensor):
        return (np.transpose(tensor.squeeze().cpu().numpy(), (1, 2, 0)))

    def __call__(self, frame, tile_mode, cache_mode, alpha):
        with torch.no_grad():
            # numpy转为tensor 将HWC转为HCW
            tensor = self.np2tensor(frame)
            if (cache_mode == 3):
                result = self.tensor2np(self.model.forward_gap_sync(tensor, tile_mode, alpha, self.pro))
            elif (cache_mode == 2):
                result = self.tensor2np(self.model.forward_fast_rough(tensor, tile_mode, alpha, self.pro))
            else:
                result = self.tensor2np(self.model(tensor, tile_mode, cache_mode, alpha, self.pro))
        return result

if __name__ == "__main__":
    ###########inference_img
    import time, cv2, sys
    from time import time as ttime
    """
    mode: 在其中填写video或者image决定超视频还是超图像；
scale: 超分倍率；
model_path: 填写模型参数路径(目前3倍4倍超分只有3个模型，2倍有4个不同降噪强度模型和1个保守模型)；
device: 显卡设备号。如果有多卡超图片，建议手工将输入任务平分到不同文件夹，填写不同的卡号；
超图像，需要填写输入输出文件夹；超视频，需要指定输入输出视频的路径。
cache_mode:根据个人N卡显存大小调节缓存模式.mode2/3可超任意大小分辨率（瓶颈不在显存）图像
0: 默认使用cache缓存必要参数
1: 使用cache缓存必要参数，对缓存进行8bit量化节省显存，带来15%延时增长，肉眼完全无法感知的有损模式
tile: 数字越大显存需求越低，相对地可能会小幅降低推理速度 {0, 1, x, auto}
0: 直接使用整张图像进行推理，大显存用户或者低分辨率需求可使用
1: 对长边平分切成两块推理
x: 宽高分别平分切成x块推理
auto: 当输入图片文件夹图片分辨率不同时，填写auto自动调节不同图片tile模式，未来将支持该模式。

alpha: 该值越大AI修复程度、痕迹越小，越模糊；alpha越小处理越烈，越锐化，色偏（对比度、饱和度增强）越大；默认为1不调整，推荐调整区间(0.7,1.3)；
half: 半精度推理，>=20系显卡直接写True开着就好

tile 0
cache_mode 1

    """
    tile_mode=3
    cache_mode=0
    alpha=1
    scale=2
    weight_name = "weights_pro/pro-no-denoise-up2x.pth".split("/")[-1].split(".")[0]
    # cpu下 half需要为False
    upscaler2x = RealWaifuUpScaler(scale, "weights_pro/pro-no-denoise-up2x.pth", half=False, device="cpu:0")
    frame = cv2.imread("inputs/test3.jpeg")[:, :, [2, 1, 0]]

    result = upscaler2x(frame, tile_mode=tile_mode, cache_mode=cache_mode, alpha=alpha)[:, :, ::-1]
    cv2.imwrite("output/pt_2x_3.png", result)

    # input_dir = "%s/inputs" % root_path
    # output_dir = "%s/output-dir-all-test" % root_path
    # os.makedirs(output_dir, exist_ok=True)
    # for name in os.listdir(input_dir):
    #     print(name)
    #     tmp = name.split(".")
    #     inp_path = os.path.join(input_dir, name)
    #     suffix = tmp[-1]
    #     prefix = ".".join(tmp[:-1])
    #     tmp_path = os.path.join(root_path, "tmp", "%s.%s" % (int(time.time() * 1000000), suffix))
    #     print(inp_path, tmp_path)
    #     # 支持中文路径
    #     # os.link(inp_path, tmp_path)#win用硬链接
    #     os.symlink(inp_path, tmp_path)  # linux用软链接
    #     # 加载图片为矩阵 H W C（BGR） (2, 1, 0指的是指读取rgb)
    #     frame = cv2.imread(tmp_path)[:, :, [2, 1, 0]]
    #     t0 = ttime()
    #     # 调用upscaler2x __call__
    #     result = upscaler2x(frame, tile_mode=tile_mode, cache_mode=cache_mode, alpha=alpha)[:, :, ::-1]
    #     t1 = ttime()
    #     print(prefix, "done", t1 - t0, "tile_mode=%s" % tile_mode, cache_mode)
    #     tmp_opt_path = os.path.join(root_path, "tmp", "%s.%s" % (int(time.time() * 1000000), suffix))
    #     cv2.imwrite(tmp_opt_path, result)
    #     n = 0
    #     while (1):
    #         if (n == 0):
    #             suffix = "_%sx_tile%s_cache%s_alpha%s_%s.png" % (
    #                 scale, tile_mode, cache_mode, alpha, weight_name)
    #         else:
    #             suffix = "_%sx_tile%s_cache%s_alpha%s_%s_%s.png" % (
    #                 scale, tile_mode, cache_mode, alpha, weight_name, n)
    #         if (os.path.exists(os.path.join(output_dir, prefix + suffix)) == False):
    #             break
    #         else:
    #             n += 1
    #     final_opt_path = os.path.join(output_dir, prefix + suffix)
    #     os.rename(tmp_opt_path, final_opt_path)
    #     os.remove(tmp_path)

    # for weight_path, scale in [("weights_v3/up2x-latest-denoise3x.pth", 2), ("weights_v3/up3x-latest-denoise3x.pth", 3),
    #                            ("weights_v3/up4x-latest-denoise3x.pth", 4), ("weights_pro/pro-denoise3x-up2x.pth", 2),
    #                            ("weights_pro/pro-denoise3x-up3x.pth", 3), ]:
    #
    #
    #     for tile_mode in [0, 5]:
    #         for cache_mode in [0, 1, 2, 3]:
    #             for alpha in [1]:
    #                 weight_name = weight_path.split("/")[-1].split(".")[0]
    #                 #cpu下 half需要为False
    #                 upscaler2x = RealWaifuUpScaler(scale, weight_path, half=False, device="cpu:0")
    #                 input_dir = "%s/inputs" % root_path
    #                 output_dir = "%s/output-dir-all-test" % root_path
    #                 os.makedirs(output_dir, exist_ok=True)
    #                 for name in os.listdir(input_dir):
    #                     print(name)
    #                     tmp = name.split(".")
    #                     inp_path = os.path.join(input_dir, name)
    #                     suffix = tmp[-1]
    #                     prefix = ".".join(tmp[:-1])
    #                     tmp_path = os.path.join(root_path, "tmp", "%s.%s" % (int(time.time() * 1000000), suffix))
    #                     print(inp_path, tmp_path)
    #                     # 支持中文路径
    #                     # os.link(inp_path, tmp_path)#win用硬链接
    #                     os.symlink(inp_path, tmp_path)  # linux用软链接
    #                     # 加载图片为矩阵 H W C（BGR） (2, 1, 0指的是指读取rgb)
    #                     frame = cv2.imread(tmp_path)[:, :, [2, 1, 0]]
    #                     t0 = ttime()
    #                     # 调用upscaler2x __call__
    #                     result = upscaler2x(frame, tile_mode=tile_mode, cache_mode=cache_mode, alpha=alpha)[:, :, ::-1]
    #                     t1 = ttime()
    #                     print(prefix, "done", t1 - t0, "tile_mode=%s" % tile_mode, cache_mode)
    #                     tmp_opt_path = os.path.join(root_path, "tmp", "%s.%s" % (int(time.time() * 1000000), suffix))
    #                     cv2.imwrite(tmp_opt_path, result)
    #                     n = 0
    #                     while (1):
    #                         if (n == 0):
    #                             suffix = "_%sx_tile%s_cache%s_alpha%s_%s.png" % (
    #                                 scale, tile_mode, cache_mode, alpha, weight_name)
    #                         else:
    #                             suffix = "_%sx_tile%s_cache%s_alpha%s_%s_%s.png" % (
    #                                 scale, tile_mode, cache_mode, alpha, weight_name, n)
    #                         if (os.path.exists(os.path.join(output_dir, prefix + suffix)) == False):
    #                             break
    #                         else:
    #                             n += 1
    #                     final_opt_path = os.path.join(output_dir, prefix + suffix)
    #                     os.rename(tmp_opt_path, final_opt_path)
    #                     os.remove(tmp_path)


from django.http.response import HttpResponse
from django.core.paginator import Paginator
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from .models import Lista
from .forms import ListaForm
from django.contrib import messages
import datetime
from django.db.models import Sum
import json

@login_required
def itemList(request):
    item_list = Lista.objects.filter(user=request.user, done='Comprar').order_by('-created_at')

    paginator = Paginator(item_list, 20)

    page = request.GET.get('page')

    items = paginator.get_page(page)

    return render(request, 'lista/list.html', {'items': items})
@login_required
def history(request):
    search = request.GET.get('search')

    if search:
        items = Lista.objects.filter(produto__icontains=search, user=request.user, done='Comprado')
    else:
        items_list = Lista.objects.filter(user=request.user, done='Comprado').order_by('-created_at')

        paginator = Paginator(items_list, 20)

        page = request.GET.get('page')
        items = paginator.get_page(page)

    return render(request, 'lista/history.html', {'items': items})

@login_required
def listaView(request, id):
    lista = get_object_or_404(Lista, pk=id)
    return render(request, 'lista/produto.html', {'lista':lista})

@login_required
def newItem(request):
    if request.method == 'POST':
        form = ListaForm(request.POST)

        if form.is_valid():
            item = form.save(commit=False)
            item.done = 'Comprar'
            item.user = request.user
            item.save()
            messages.info(request, 'Item adicionado com Sucesso')
            return redirect('/')
    else:
        form = ListaForm()
        return render(request, 'lista/additem.html', {'form':form})

@login_required()
def editItem(request, id):
    item = get_object_or_404(Lista, pk=id)
    form = ListaForm(instance=item)

    if(request.method == 'POST'):
        form = ListaForm(request.POST, instance=item)

        if (form.is_valid()):
            item.save()

            messages.info(request, 'Item editado com Sucesso')

            return redirect('/')
        else:
            return render(request, 'lista/edititem.html', {'form': form, 'item': item})
    else:
        return render(request, 'lista/edititem.html', {'form': form, 'item': item})

@login_required
def deleteItem(request, id):
    item = get_object_or_404(Lista, pk=id)
    item.delete()

    messages.info(request, 'Item deletado com Sucesso')

    return redirect('/')

@login_required
def changeStatus(request, id):
    item = get_object_or_404(Lista, pk=id)

    if(item.done == 'Comprar'):
        item.done = 'Comprado'
    else:
        item.done = 'Comprar'

    item.save()

    return redirect('/')

@login_required()
def dashboard(request):
    valorGastoRecently = Lista.objects.filter(user=request.user, valor__isnull=False,
                                              updated_at__gt=datetime.datetime.now() - datetime.timedelta(
                                                  days=30)).aggregate(Sum('valor'))
    itensComprar = Lista.objects.filter(user=request.user, done='Comprar').count()
    itensComprado = Lista.objects.filter(user=request.user, done='Comprado').count()

    return render(request, 'lista/dashboard.html', {'valorgastorecently': valorGastoRecently, 'itenscomprar': itensComprar, 'itenscomprado': itensComprado})


#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# pip install TwitterAPI

# http://www.moongiant.com/fullmoons/2018/

import sys
from json import load
from time import localtime
from time import strftime
from time import sleep
from os import path
from os import listdir
from random import randint
#from TwitterAPI import TwitterAPI
from mastodon import Mastodon

local_time = localtime()

year =  strftime("%Y", local_time)

with open(sys.path[0]+"/ilargia-"+year+".json", 'r') as f:
	j = load(f)

local_time = strftime("%Y-%m-%d", local_time)

found = False
for date in j:
        if date == local_time:
                found = True

if found == False:
	sys.exit()

# ze motatako irudidxek okinguz kontutan
allow_extension = ['.mp4', '.gif']

# irudiaren path-a hartu
#image_path = path.dirname(path.realpath(__file__))+"/images"
image_path = sys.path[0]+"/images"

if path.isdir(image_path):
	files = listdir(image_path)
else:
	print("Karpeta "+image_path+" ez da existitzen")
	sys.exit()

# irudi danak images dict-en sartun
images = {}
j = 0
for i in files:
	file_path = path.join(image_path, i)
	if path.isfile(file_path):
		extension = path.splitext(file_path)[1]
		if extension.lower() in allow_extension:
			images[j] = file_path
			j = j+1

# twitterrera argazkidxe idxen

#r = 0
#images = [sys.path[0]+"/ilargi_gorri.jpg"]

mastodon = Mastodon(
    access_token = sys.path[0]+"/mastodon.credentials",
    api_base_url = 'https://botsin.space'
)

# ikusi ia baten bat badauen
images_all = images
len = len(images.keys())
if len>0:
	r = randint(0, j-1)
	file = images[r]
	a = mastodon.media_post(file);
	images = []
	images.append([a.id])
else:
	images = []

# 30 segundu utziko diogu mastodonera irudi/bideoa igotzeko...
sleep(30)

title = 'ilargi betie... 🐺🌕 #zitalbot'

m = mastodon.status_post(title, None, images)

data = open(images_all[2], 'rb') # 01.gif
data = data.read()

"""
#title = title.decode('utf-8')+"\n"

with open(sys.path[0]+"/twitter.credentials", 'r') as f:
	j = load(f)

api = TwitterAPI(j['CONSUMER_KEY'], j['CONSUMER_SECRET'], j['ACCESS_TOKEN_KEY'], j['ACCESS_TOKEN_SECRET'])
r = api.request('media/upload', None, {'media': data})
media_id = r.json()['media_id']

r = api.request('statuses/update', {'status':title+"\n"+m.url, 'media_ids':media_id})

if r.status_code == 200:
	print("Txioa ondo bidali da ;)")
else:
	print("Errorea txioa bidaltzerakoan status code: "+r.status_code)

"""
sys.exit()


import uuid

import PySimpleGUI as gui

from src import database, pokemon, types
from src.element import Element
from src.custom_elements import SummaryElement, MoveElement, LocationElement, SearchableListBox, TeamAnalysisElement, TeamDisplayElement, ThemeChangeElement

# TODO: Move sorts/filters?
def sortByNum(pkmn_name):
    assert pkmn_name in database.instance.pokemon, f"can't find {pkmn_name} in ingested pokemon"
    return int(database.instance.pokemon[pkmn_name].num)

def sortByOverall(pkmn_name):
    assert pkmn_name in database.instance.pokemon, f"can't find {pkmn_name} in ingested pokemon"
    theoretical_max = (pokemon.Stats.Attribute.ATTR_MAX * len(pokemon.Stats.ALL_ATTR_NAMES))
    # We subtract the actual from the theoretical max so the list is sorted in descending order
    return theoretical_max - sum([int(getattr(database.instance.pokemon[pkmn_name].stats, attr_name).value) for attr_name in pokemon.Stats.ALL_ATTR_NAMES])

def sortByAttr(attr_name):
    def innerSort(pkmn_name):
        assert pkmn_name in database.instance.pokemon, f"can't find {pkmn_name} in ingested pokemon"
        theoretical_max = pokemon.Stats.Attribute.ATTR_MAX
        # We subtract the actual from the theoretical max so the list is sorted in descending order
        return theoretical_max - int(getattr(database.instance.pokemon[pkmn_name].stats, attr_name).value)
    return innerSort

def filterByType(expected_type):
    def innerFilter(pkmn_name):
        assert pkmn_name in database.instance.pokemon, f"can't find {pkmn_name} in ingested pokemon"
        pkmn = database.instance.pokemon[pkmn_name]
        return expected_type in (pkmn.type.primary, pkmn.type.secondary)
    return innerFilter

def sortMoveByAttr(attr_name):
    def innerSort(move_name):
        assert move_name in database.instance.moves, f"can't find {move_name} in ingested moves"
        theoretical_max = pokemon.Stats.Attribute.ATTR_MAX
        # We subtract the actual from the theoretical max so the list is sorted in descending order
        return theoretical_max - int(getattr(database.instance.moves[move_name], attr_name).value)
    return innerSort

def filterMoveByCategory(expected_category):
    def innerFilter(move_name):
        assert move_name in database.instance.moves, f"can't find {move_name} in ingested moves"
        move = database.instance.moves[move_name]
        return expected_category in (move.category.primary, move.category.secondary)
    return innerFilter

def filterMoveByType(expected_type):
    def innerFilter(move_name):
        assert move_name in database.instance.moves, f"can't find {move_name} in ingested moves"
        move = database.instance.moves[move_name]
        return expected_type in (move.type.primary, move.type.secondary)
    return innerFilter

class WindowElement(Element):
    def __init__(self):
        # Home
        self.file_ingested = False
        self.ingest_button = gui.Button("Ingest", key="button_ingest")
        self.home_tab = gui.Tab("Home", [ [gui.InputText(key="input_text_file", default_text=database.default_source_location), gui.FileBrowse(button_text="Browse For Log File")] ,
                                          [self.ingest_button, gui.Text(self.file_ingested, key="text_ingested_boolean"), gui.Text("Version:"), gui.Text("", size=(15,1), key="text_ingested_version")],
                                          [gui.Button("Stat Averages", key="stat_averages"), gui.Button("Close"), gui.Button("???")],
                                        ])

        # Summary
        self.summary_slb = SearchableListBox(SummaryElement())
        self.summary_add_to_team_builder_button = gui.Button("Add To Team Builder", key="summary_add_to_team_builder_button", metadata=self.summary_slb)

        self.summary_slb.registerSort("Num", sortByNum)
        self.summary_slb.registerSort("Overall", sortByOverall)
        for attr_name in pokemon.Stats.ALL_ATTR_NAMES:
            self.summary_slb.registerSort(pokemon.Stats.short_name(attr_name), sortByAttr(attr_name))

        for type_name in types.all_types:
            self.summary_slb.registerFilter(type_name, filterByType(type_name))

        self.display_tab = gui.Tab("Summary", [ [gui.Column(self.summary_slb.element.layout()), gui.Column([ *self.summary_slb.layout(),
                                                                                [self.summary_add_to_team_builder_button],
                                                                                ], justification="left")],
                                              ])

        # Moves
        self.move_slb = SearchableListBox(MoveElement())
        for attr_name in pokemon.Move.ALL_ATTR_NAMES:
            self.move_slb.registerSort(pokemon.Stats.short_name(attr_name), sortMoveByAttr(attr_name))
        for category in types.all_categories:
            self.move_slb.registerFilter(category, filterMoveByCategory(category))
        for type_name in types.all_types:
            self.move_slb.registerFilter(type_name, filterMoveByType(type_name))
        self.moves_tab = gui.Tab("Moves", [ [gui.Column(self.move_slb.element.layout()), gui.Sizer(350, 0), gui.Column(self.move_slb.layout(), justification="left")],
                                          ])

        # Locations
        self.location_slb = SearchableListBox(LocationElement())
        self.locations_tab = gui.Tab("Locations", [ [gui.Column(self.location_slb.element.layout()), gui.Sizer(10, 0), gui.Column(self.location_slb.layout(), justification="left")],
                                                  ])

        # Team Builder
        self.team_builder_elements = [TeamDisplayElement(i) for i in range(6)]
        self.team_analysis_element = TeamAnalysisElement(self.team_builder_elements)
        tb_column1 = gui.Column([ *self.team_builder_elements[0].layout(), *self.team_builder_elements[2].layout(), *self.team_builder_elements[4].layout() ])
        tb_column2 = gui.Column([ *self.team_builder_elements[1].layout(), *self.team_builder_elements[3].layout(), *self.team_builder_elements[5].layout() ])
        left_column = gui.Column([  [ gui.Frame("Pokemon Team", [[tb_column1, tb_column2]]) ],
                                    [ gui.Button("Randomize Team", key="team_builder_randomize_team_button", size=(10,2)),
                                      gui.Button("Randomize Remaining", key="team_builder_randomize_remaining_button", size=(10,2)),
                                      gui.Button("Clear Team", key="team_builder_clear_team_button", size=(10,2)),
                                    ],
                                 ])
        right_column = gui.Column(self.team_analysis_element.layout())
        self.team_builder_tab = gui.Tab("Team Builder", [ [left_column, right_column] ]
                                                    , element_justification="top")

        # Options
        self.theme_slb = SearchableListBox(ThemeChangeElement())
        self.options_tab = gui.Tab("Options", [ [gui.Text("Select A Theme:", font="Arial 16"), gui.Text(f"[Currently: {gui.theme()}]", font="Arial 16")],
                                                *self.theme_slb.layout()
                                              ]
                                            , element_justification="right")

        self.tab_group = gui.TabGroup([[self.home_tab, self.display_tab, self.moves_tab, self.locations_tab, self.team_builder_tab, self.options_tab]]
                                    , key=f"main_window_layout_tab_group_{uuid.uuid4().hex}"
                                    , font="Impact 12")

    def update(self, obj):
        # Do Nothing (for now?)
        pass

    def layout(self):
        return [[self.tab_group]]

    def populateThemes(self):
        # SearchableListBoxes require a mapping of keys and values, so when the keys
        # ARE the values, we just convert the list [x,y] to a mapping of {x:x, y:y}.
        self.theme_slb.populate({x:x for x in gui.theme_list()})

    def expandButtons(self):
        # TODO: Can we do this is a more elegant way?
        self.summary_slb.expandButtons()
        self.move_slb.expandButtons()
        self.theme_slb.expandButtons()


import cv_utils
import numpy as np
import math
import logging

logger = logging.getLogger(__name__)

def compare_calibrations(
    old_calibrations,
    new_calibrations
):
    old_device_ids = set(old_calibrations.keys())
    new_device_ids = set(new_calibrations.keys())
    common_device_ids = old_device_ids.intersection(new_device_ids)
    logger.info('Comparing {} old calibrations with {} new calibrations'.format(
        len(old_device_ids),
        len(new_device_ids)
    ))
    logger.info('Old and new calibrations contain {} common devices'.format(
        len(common_device_ids)
    ))
    calibration_comparisons = dict()
    for device_id in common_device_ids:
        relative_rotation_vector, relative_translation_vector = cv_utils.compose_transformations(
            rotation_vector_1=new_calibrations[device_id]['rotation_vector'],
            translation_vector_1=np.array([0.0, 0.0, 0.0]),
            rotation_vector_2=-old_calibrations[device_id]['rotation_vector'],
            translation_vector_2=np.array([0.0, 0.0, 0.0])
        )
        orientation_difference_angle_radians = np.linalg.norm(relative_rotation_vector)
        orientation_difference_angle_degrees = orientation_difference_angle_radians*360/(2*math.pi)
        orientation_difference_direction = relative_rotation_vector/orientation_difference_angle_radians
        old_position = cv_utils.extract_camera_position(
            rotation_vector=old_calibrations[device_id]['rotation_vector'],
            translation_vector=old_calibrations[device_id]['translation_vector'],
        )
        new_position = cv_utils.extract_camera_position(
            rotation_vector=new_calibrations[device_id]['rotation_vector'],
            translation_vector=new_calibrations[device_id]['translation_vector'],
        )
        position_difference = new_position - old_position
        position_difference_distance = np.linalg.norm(position_difference)
        position_difference_direction = position_difference/position_difference_distance
        calibration_comparisons[device_id] = {
            'orientation_difference_angle_radians': orientation_difference_angle_radians,
            'orientation_difference_angle_degrees': orientation_difference_angle_degrees,
            'orientation_difference_direction': orientation_difference_direction,
            'position_difference_distance': position_difference_distance,
            'position_difference_direction': position_difference_direction
        }
    return calibration_comparisons


"""Create a CLI_ actions class in which the lock function is overwritten to demand an input
Create a CLI_ controller with logCallBack and actionEndTarget functions to feed into operations
Use these dummies to place the sorting algorithm

Set up a CLI_ visuals system
"""

from operations import Operations
from data import DataStructure, VariableData, PointerData
from actions import Null, CallableActions, Prompts, PseudoCode
from pathfinder import resource_path

class CLI_Actions(CallableActions):
	def lock(self):
		self.lockCallBack(self.logTexts)
		self.logTexts = []

		input("Press Enter to continue...")

class CLI_Controller:
	def __init__(self, source):
		self.source = source(lockCallBack = self.lockCallBack,
			actionEndTarget = self.endTarget)

		self.displays = []
		self.configureDisplay()

		self.pseudoCode = self.source.insertion_Sort_PseudoCode()

		self.pseudoCode.highlight([1, 2, 4])

	def configureDisplay(self):
		# Reads source to find if data table (data property), variable table (variables property)
		# and pointer table (pointers property) exist

		source = self.source

		# Each item at dataObjs is associated with an item at allDisplays with the same index pos
		dataObjs = [source.data, source.variables, source.pointers]
		allDisplays = [self.displayDataTable, self.displayVariables, self.displayPointers]

		# If anyone one of the attributes in the dataObjs list are not defined in the source class
		# They will take the None value assigned at the base Operations class
		for index in range(len(dataObjs)):
			dataObj = dataObjs[index]

			# If a particular data object is defined (not None), register the function used to
			#  display it as one of those to be used by display()
			if dataObj != None:
				self.displays.append(allDisplays[index])

	def display(self):
		for displayFunc in self.displays:
			displayFunc()

		self.displayAvailableActions()

		self.displayPseudoCode()

	def displayPseudoCode(self):
		pseudo = self.pseudoCode
		for i in range(pseudo.length):
			activity = pseudo.statements[i]["activity"]
			statement = pseudo.statements[i]["statement"]

			lineCount = "{:0>3}".format(i)

			prefix = suffix = ""

			if activity:
				prefix = "*"
				suffix = ""

			representation = "{p:^3} {lc} {st} {s}".format(p = prefix, lc = lineCount, st = statement,	s = suffix)

			print(representation)

	def displayDataTable(self):
		padding = 12

		itemNames = list(self.source.data.data.keys())

		itemNamesdisplay = self.getFormattedRow(itemNames, padding)
		demarc = "-" * len(itemNamesdisplay)

		print(demarc + "\n" + itemNamesdisplay + "\n" + demarc)

		for index in range(self.source.data.size):
			items = []
			for item in itemNames:
				items += self.source.data.getValue(item, index)

			print(self.getFormattedRow(items, padding))

		print(demarc)

	def displayVariables(self):
		padding = 30

		variableNames = list(self.source.variables.data.keys())

		itemsdisplay = self.getFormattedRow(["Variables", "Value"], padding)
		demarc = "-" * len(itemsdisplay)

		print(demarc + "\n" + itemsdisplay + "\n" + demarc)

		for variable in variableNames:
			item = self.source.variables.getValue(variable)
			print(self.getFormattedRow([variable, item], padding))

		print(demarc)

	def displayPointers(self):
		padding = 30

		pointerNames = list(self.source.pointers.data.keys())

		itemsdisplay = self.getFormattedRow(["Pointers", "Value"], padding)
		demarc = "-" * len(itemsdisplay)

		print(demarc + "\n" + itemsdisplay + "\n" + demarc)

		for pointer in pointerNames:
			item = self.source.pointers.getValue(pointer)
			print(self.getFormattedRow([pointer + " Pointer", item], padding))

		print(demarc)

	def getFormattedRow(self, items, padding):
		"""Takes in a list (items) and an integer (padding)

		for list ['Item', 'Val', 'Ptr'] and padding of 4,
		returns the string '|Item| Val| Ptr|'

		The gap between '|'s contains <padding> no. of characters (w/ spaces for padding)
		If string in list is too long, the first <padding> no. of characters show up
		"""

		items = list(map(lambda x: str(x)[:padding], items))
		placeholder = "|"

		for i in range(len(items)):
			placeholder += "{:^" + str(padding) + "}|"

		return placeholder.format(*items)

	def displayAvailableActions(self):
		padding = 30

		itemsdisplay = self.getFormattedRow(["Available Operations"], padding)
		demarc = "-" * len(itemsdisplay)

		print(demarc + "\n" + itemsdisplay + "\n" + demarc)

		for action in self.source.getActions():
			item = action.name.title()
			print(self.getFormattedRow([item], padding))

		print(demarc)

	def loop(self):
		pass

	def lockCallBack(self, logTexts):
		print(logTexts)

	def endTarget(self):
		print("Operation ended.")

class CLI_Op(Operations):
	def __init__(self, **kwargs):
		super(CLI_Op, self).__init__(**kwargs)

		self.variables = VariableData(["Index", "Number of items", "Item to be inserted", "Pointer to current item"])
		self.data = DataStructure(["List Index", "List Item"], name = "List", size = 5)

		sort = CLI_Actions(name = 'insertion sort',
			functionToExecute = self.insertionSort,
			lockCallBack = self.lockCallBack,
			endTarget = self.endTarget,
			codeObj = self.insertion_Sort_PseudoCode())

		self.addAction(sort)
		self.initializeData()

	def insertionSort(self, log = Null, lock = Null, light = Null):

		light([0])

		log("Here we will be exploring how the")
		log("insertion sort algorithm works")
		log("using the data, variables and pointers indicated.")

		lock()

		log("In this algorithm,")
		log("For each item in the list,")
		log("we momentarily focus on ordering")
		log("with respect to it only,")

		lock()

		log("We keep moving it towards the front of the list as long as")
		log("it makes sense to do so.")

		lock()

		log("Note that the list being sorted has index")
		log("starting from 0")

		lock()

		light([1])

		log("Since the item at index 0 cannot be moved any more to the front")
		log("we start by considering index 1 in an iteration")
		self.variables.setValue("Number of items", self.data.size)

		lock()

		log("'Number of items' is the length of the list being sorted")
		log("at {}".format(self.data.size))

		lock()

		for index in range(1, self.data.size):

			light([1])
			log("Currently the index we are considering is {}".format(index))

			self.variables.setValue("Index", index)
			lock()

			itemToBeInserted = self.data.getValue("List Item", index)

			light([2])
			log("The item at this index is {}".format(itemToBeInserted))
			log("")
			log("in this algorithm, the items before the one being considered")
			log("are gradually shifted a step back each as appropriate.")

			lock()

			log("Since the item at the current index will get overwritten,")
			log("it is temporarily stored elsewhere,")
			log("'Item to be inserted' will be set as {}".format(itemToBeInserted))
			log("for this purpose.")

			lock()

			self.variables.setValue("Item to be inserted", itemToBeInserted)

			light([3])

			log("We now need to consider each item before the one at the current index,")
			log("i.e, the'Current Item Pointer' is going to be set as the index just one step before")
			log("{} - 1 gives {} which will be set as the Current Item Pointer.".format(index, index - 1))

			lock()

			currentItemPointer = index - 1
			self.pointers.setValue("Current Item", currentItemPointer)

			log("We now iterate until it is appropriate to stop,")
			log("We know to stop if the item at the 'Current Item Pointer' is no longer")
			log("suitable to be shifted backward.")

			lock()

			log("We will keep shifting items back whilst the items are larger than")
			log("the item which is being temporarily stored elsewhere.")

			lock()

			log("If the item reached is no longer larger than the stored item 'Item to be inserted'")
			log("the place from which the last item was shited is the perfect spot for inserting")
			log("this item as the item before it is smaller than it and the one after is larger than it")

			lock()

			log("the item at a particular index in a list is accessed")
			log("for read and write by the 'List[Index]' notation")

			lock()

			while (self.data.getValue("List Item", currentItemPointer) > itemToBeInserted) and currentItemPointer >= 0:

				light([4])

				log("It is true that the item pointed to by the 'Current Item Pointer' is")
				log("larger than our 'Item to be inserted'.")
				log("")
				log("{} > {}".format(self.data.getValue("List Item", currentItemPointer), itemToBeInserted))

				lock()

				log("It is also true that the value of 'Current Item Pointer', {}".format(currentItemPointer))
				log("is greater than or equal to 0.")

				lock()

				light([5])

				log("We will now shift the item at the 'Current Item Pointer' a step backward")
				log("we will do this by dumping the value at the 'Current Item Pointer' index,")

				listValue = self.data.getValue("List Item", currentItemPointer)

				log("which is {} at index {}".format(listValue, currentItemPointer))
				log("into the index position one greater than 'Current Item Pointer'")
				log("which is {} + 1, or  {}".format(currentItemPointer, currentItemPointer + 1))

				lock()

				self.data.setValue("List Item", currentItemPointer + 1, listValue)

				light([6])

				log("With this done, we will now decrease 'Current Item Pointer'")
				log("by 1 so that the item before it is considered")

				lock()

				currentItemPointer -= 1
				self.pointers.setValue("Current Item", currentItemPointer)

				log("Notice that the item at the previous 'Current Item Pointer'")
				log("{} is itself untouched for now,".format(listValue))

				lock()

				log("depending on the item before it, it will be overwritten")
				log("by 'Item to be inserted' or the item at the index of {}".format(currentItemPointer))
				log("the index given by the 'Current Item Pointer'")

			light([7])

			if currentItemPointer < 0:
				log("The item pointed to by the 'Current Item Pointer' is")
				log("no longer larger than our 'Item to be inserted'.")
				log("")
				log("{} < {}".format(self.data.getValue("List Item", currentItemPointer), itemToBeInserted))

			else:
				log("The value of 'Current Item Pointer', {} is no longer".format(currentItemPointer))
				log("greater than or equal to 0.")

			lock()

			light([8])

			log("The index right behind 'Current Item Pointer', {}'".format(currentItemPointer + 1))
			log("is where 'Item to be inserted', {}".format(itemToBeInserted))
			log("should be dumped as the item at behind that, at {}".format(currentItemPointer + 2))
			log("is larger than 'Item to be inserted")

			lock()

			self.data.setValue("List Item", currentItemPointer + 1, itemToBeInserted)

			log("If applicable,")
			log("we will now consider the next index behind the one from which we")
			log("took 'Item to be inserted', i.e index {}".format(index + 1))

			lock()

		light([9])

		log("The index {} does not exist".format(index + 1))
		log("and we have finished going through the entire list.")
		log("The list is sorted now.")

		lock()

	def initializeData(self):
		for i in range(5):
			self.data.setValue("List Index", i, str(i))
			self.data.setValue("List Item", i, chr(ord('z') - i * 2))

	def binarySort(self):
		X = [chr(ord('Z')-i) for i in range(10)]
		numberOfItems = len(X)

		for i in range(numberOfItems - 2):
			for j in range(numberOfItems - 2 - i):
				if X[j] > X[j + 1]:
					item = X[j]
					X[j] = X[j + 1]
					X[j + 1] = Item

		print(X)

	def insertion_Sort_PseudoCode(self):
		f = open(resource_path("insertionSort_pseudo.txt"), "r")
		p = PseudoCode()
		p.extract(f.read())
		return p

c = CLI_Controller(source = CLI_Op)
c.display()