import sys
from cmath import nan
from image import Image, Color
from model import Model
from shape import Point, Line, Triangle
from vector import Vector
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from math import radians, cos, sin, tan

def getVertexNormal(vertIndex, faceNormalsByVertex):
	# Compute vertex normals by averaging the normals of adjacent faces
	normal = Vector(0, 0, 0)
	for adjNormal in faceNormalsByVertex[vertIndex]:
		normal = normal + adjNormal

	return normal / len(faceNormalsByVertex[vertIndex])

# translation matrix
def translate(dx, dy, dz):
	translate_matrix = np.matrix([
		[1,	  	0, 		0, 		dx],
		[0,		1,     	0, 		dy],
		[0, 	0,  	1, 		dz],
		[0, 	0, 		0, 		1]
	], dtype=float)
	return translate_matrix

# rotation matrices
def rotate_x(degrees):
	theta = radians(degrees)

	rot_matrix = np.matrix([
		[1,	  	0, 				0, 					0],
		[0,		cos(theta),     -sin(theta), 		0],
		[0, 	sin(theta),  	cos(theta), 		0],
		[0, 	0, 				0, 					1]
	], dtype=float)

	return rot_matrix

def rotate_y(degrees):
	theta = radians(degrees)

	rot_matrix = np.matrix([
		[cos(theta), 	0, 		sin(theta), 		0],
		[0,				1,     	0, 					0],
		[-sin(theta), 	0,  	cos(theta), 		0],
		[0,			 	0, 		0, 					1]
	], dtype=float)

	return rot_matrix

def rotate_z(degrees):
	theta = radians(degrees)

	rot_matrix = np.matrix([
		[cos(theta), 	-sin(theta), 	0, 			0],
		[sin(theta),	cos(theta),		0,      	0],
		[0, 			0,  	 		1, 			0],
		[0,			 	0, 				0, 			1]
	], dtype=float)
	return rot_matrix

# scaling matrix
def scale(sx, sy, sz):
	scale_matrix = np.matrix([
		[sx,	0, 		0, 		0],
		[0,		sy,    	0, 		0],
		[0, 	0,  	sz, 	0],
		[0, 	0, 		0, 		1]
	], dtype=float)
	return scale_matrix

# orthographic projection
def getOrthographicProjection(x, y, z):
	# Convert vertex from world space to screen space
	# by dropping the z-coordinate (Orthographic projection)
	screenX = int((x+1.0)*width/2.0)
	screenY = int((y+1.0)*height/2.0)

	return screenX, screenY

# helper function for normalisation
def norm(v):
    m = np.sqrt(np.sum(v ** 2))
    if m == 0:
        return v
    return v / m

# get view transform
def getViewTransform():
	eye = np.array([
		[0],
		[0],
		[-3]
		], dtype=float)
	center = np.array([
		[0],
		[0],
		[10]
		], dtype=float)
	up = np.array([
		[0],
		[1],
		[-3]
		], dtype=float)
	up = norm(up)
	o = eye-center
	cam_z = norm(o)
	v_i = np.cross(up, cam_z, axis=0)
	cam_x = norm(v_i)
	cam_y = np.cross(cam_z, cam_x, axis=0)

	r = np.matrix([
		[cam_x[0], cam_x[1], cam_x[2], 0],
		[cam_y[0], cam_y[1], cam_y[2], 0],
		[cam_z[0], cam_z[1], cam_z[2], 0],
		[0,0,0, 1]
	], dtype=float	)

	t = np.matrix([
		[1, 0, 0, -(eye[0])],
		[0, 1, 0, -(eye[1])],
		[0, 0, 1, -(eye[2])],
		[0, 0, 0, 1],
	], dtype=float)

	V = np.dot(r, t)
	return V
# perspective projection
def getPerspectiveProjection(V, M, x, y, z, n):

	# vertex vector
	p = np.array([
		[x],
		[y],
		[z],
		[1]], dtype=float)

	# normal vector
	n_ = np.array([
		[n.x],
		[n.y],
		[n.z],
	], dtype=float)

	# perspective
	near = 250.0
	far = 1000.0
	top = height / 2
	bottom = -(height / 2)
	left = -(width /2 )
	right = width/2
	r_l = right-left
	t_b = top-bottom
	P = np.matrix([
		[(2*near)/r_l, 0, (right + left)/r_l, 0],
		[0, 2*near/t_b, (top+bottom)/(t_b), 0],
		[0, 0, -(far+near)/(far-near), -(2*far*near)/(far-near)],
		[0, 0, -1, 0],
	], dtype=float)

	clip = P * V * p

	n_ = (np.linalg.inv(V[:3, :3]) * np.linalg.inv(M)).T * n_

	# perspective divide
	D = np.array([
		[clip[0,0]/clip[3,0]],
		[clip[1,0]/clip[3,0]],
		[clip[2,0] /clip[3,0]],
		[1]
		], dtype=float)

	# window transform
	screenX = int((D[0]+1) * (width/2.0))
	screenY = int((D[1]+1) * (height/2.0))
	screenZ = int((D[2]) * (1/2.0) )

	return screenX, screenY, screenZ, n_

# function that gets conjugate of a given quaternion
def quaternion_conjugation(q):
	return [q[0], -q[1], -q[2], -q[3]]

# quaternion multiplication of two quaternions
def quaternion_product(a, b):
	return np.array([(a[0] * b[0]) - (a[1] * b[1]) - (a[2] * b[2]) - (a[3] * b[3]),
					(a[0] * b[1]) + (b[0]* a[1] ) - (a[2] * b[3]) + (b[2] * a[3]),
					(a[0] * b[2]) + (b[3] * a[1]) + (b[0] * a[2]) - (a[3] * b[1]),
					(a[0] * b[3]) - (b[2] * a[1]) + (a[2] * b[1]) + (b[0] * a[3])], dtype=np.float64)

# function ton convert euler angle to quaternion
def euler_to_quaternion(pitch, yaw, roll):
	cos_pitch = np.cos(pitch / 2)
	cos_yaw = np.cos(yaw / 2)
	cos_roll = np.cos(roll / 2)
	sin_pitch = np.sin(pitch / 2)
	sin_yaw = np.sin(yaw / 2)
	sin_roll = np.sin(roll / 2)

	q0 = (cos_pitch * cos_yaw * cos_roll) + (sin_pitch * sin_yaw * sin_roll)
	q1 = (cos_pitch * cos_yaw * sin_roll) - (sin_pitch * sin_yaw * cos_roll)
	q2 = (sin_pitch * cos_yaw * cos_roll) + (cos_pitch * sin_yaw * sin_roll)
	q3 = (cos_pitch * sin_yaw * cos_roll) - (sin_pitch * cos_yaw * sin_roll)

	return [q0, q1, q2, q3]

# function to convert quaternion to euler angle
def compute_euler_angle(q0, q1, q2, q3):

	# pitch
	sin_pitch = 2 * ((q0 * q2) - (q1 * q3))
	if abs(sin_pitch) >= 1:
		if sin_pitch < 0:
			pitch = -np.pi/2
		else:
			pitch = np.pi/2
	else:
		pitch = np.arcsin(sin_pitch)

	# yaw
	sin_yaw_cos_pitch = 2 * ((q0 * q3) + (q1*q2))
	cos_yaw_cos_pitch = 1 - 2 * (q2**2 + q3**2)

	yaw=np.arctan2(sin_yaw_cos_pitch, cos_yaw_cos_pitch)

	# roll
	sin_roll_cos_pitch=2 * ((q0 * q1) + (q2*q3))
	cos_roll_cos_pitch=1 - 2 * (q1**2 + q2**2)
	roll=np.arctan2(sin_roll_cos_pitch, cos_roll_cos_pitch)

	return pitch, yaw, roll

# function to perform data normalisation and rotation units
def convert_data(df):

	# function for normalising accelerometer and magnetometer values
	def normalise_values(row, axis, value_type):
		if value_type == "a":
			X = row[" accelerometer.X"]
			Y = row[" accelerometer.X"]
			Z = row[" accelerometer.Z"]
		else:
			X = row[" magnetometer.X"]
			Y = row[" magnetometer.X"]
			Z = row[" magnetometer.Z "]

		mag = np.sqrt((X ** 2) + (Y ** 2) + (Z ** 2))

		if mag == 0 or mag == nan:
			if axis == 'x':
				return X
			elif axis == 'y':
				return Y
			else:
				return Z
		else:
			if axis == 'x':
				return X/mag
			elif axis == 'y':
				return Y/mag
			else:
				return Z/mag

	# convert rotational rates to rads/s
	df[" gyroscope.X"] = df[" gyroscope.X"].apply(lambda x: x * 0.017453)
	df[" gyroscope.Y"] = df[" gyroscope.Y"].apply(lambda x: x * 0.017453)
	df[" gyroscope.Z"] = df[" gyroscope.Z"].apply(lambda x: x * 0.017453)

	# normalise accel. and magnetometer magnitude
	df[" accelerometer.X"] = df.apply(normalise_values, args=("x", "a") ,axis=1)
	df[" accelerometer.Y"] = df.apply(normalise_values, args=("y", "a") ,axis=1)
	df[" accelerometer.Z"] = df.apply(normalise_values, args=("z", "a"), axis=1)

	df[" magnetometer.X"] = df.apply(normalise_values, args=("x", "m") ,axis=1)
	df[" magnetometer.Y"] = df.apply(normalise_values, args=("y", "m") ,axis=1)
	df[" magnetometer.Z "] = df.apply(normalise_values, args=("z", "m"), axis=1)
	return df

# function to get quaternion given axis-angle representation
def get_quaternion(axis, angle):
	# previous implementation
	q0 = np.cos(angle/2)
	q1 = axis[0 ,0] * np.sin(angle/2)
	q2 = axis[1, 0] * np.sin(angle/2)
	q3 = axis[2, 0] * np.sin(angle/2)

	return [q0, q1, q2, q3]

# retrieve axis
def getAxis(q0, q1, q2, q3):
	rot = 2*np.arccos(q0)
	if q0 == 1:
		return 1, 0, 0
	else:

		v = [(q1/np.sin(rot/2)), (q2/np.sin(rot/2)), (q3/np.sin(rot/2))]
		return v[0], v[1], v[2]

# function to get global acceleration from local space
def get_world_accel(a,q, q_inv):
	return quaternion_product(quaternion_product(q_inv, a), q)

def complementary_filter(data, previous_obs, mode, mag_data=None, sample_rate=256):
	# simple dead reckoning filter to estimate orientation via gyroscopic measurements
	def drf(x,y,z):

		# compute magnitude of the quaternion (?)
		magnitude = np.sqrt((x**2) + (y**2) +(z**2) )

		# compute instantaneous axis of rotation
		rotation_axis = np.array([
			[x],
			[y],
		 	[z]
		], dtype=float) / magnitude

		# compute amount of rotation
		rotation_angle = magnitude * (1000 / sample_rate)

		# compute estimated rotation axis
		orientation_change = get_quaternion(rotation_axis, rotation_angle)

		# compute quaternion change
		orientation = quaternion_product(orientation_change, previous_obs)

		# return quaternion difference between current and previously observed orientation
		return orientation

	# simple dead reckoning filter with just gyro readings
	if mode == 1:
		orientation = drf(data[" gyroscope.X"], data[" gyroscope.Y"], data[" gyroscope.Z"])
		orientation_inv = quaternion_conjugation(orientation)
		return orientation, orientation_inv

	# dead reckoning filter with accelerometer readings integrated
	if mode == 2:
		orientation = drf(data[" gyroscope.X"], data[" gyroscope.Y"], data[" gyroscope.Z"])
		orientation_inv = quaternion_conjugation(orientation)

		# get accel. sensor readings
		a = [0, data[" accelerometer.X"], data[" accelerometer.Y"], data[" accelerometer.Z"]]

		# convert readings to world frame and normalise
		world_a = get_world_accel(a, orientation, orientation_inv)
		world_a = getAxis(world_a[0],world_a[1],world_a[2], world_a[3])
		world_a = world_a /np.sqrt((world_a[0] ** 2) + (world_a[1] ** 2) + (world_a[2] ** 2))

		# compute angle between up and accelerometer vectors
		phi = np.arccos(world_a[1])

		# fusion of gyroscope and accelerometer signals
		alpha = 0.9
		n = np.array([
			[-world_a[2]],
			[0],
			 [world_a[0]]
			 ])

		# normalise n
		n_mag = np.sqrt((n[0] ** 2) + (n[1] ** 2) + (n[2] ** 2))
		if n_mag == 0 or n_mag == nan:
			n_norm = n
		else:
			n_norm = n / n_mag

		# construct orientation quaternion
		q_c = quaternion_product(get_quaternion(n_norm, (1-alpha)*phi), orientation)
		q_c_inv = quaternion_conjugation(q_c)

		# return orientation and inverse
		return q_c, q_c_inv
	# dead reckoning filter with both accelerometer and magnetometer readings integrated
	else:
		orientation = drf(data[" gyroscope.X"], data[" gyroscope.Y"], data[" gyroscope.Z"])
		orientation_inv = quaternion_conjugation(orientation)

		# get accel. sensor readings
		a = [0, data[" accelerometer.X"], data[" accelerometer.Y"], data[" accelerometer.Z"]]

		# convert readings to world frame and normalise
		world_a = get_world_accel(a, orientation, orientation_inv)
		world_a = getAxis(world_a[0],world_a[1],world_a[2], world_a[3])
		world_a = world_a /np.sqrt((world_a[0] ** 2) + (world_a[1] ** 2) + (world_a[2] ** 2))

		# compute angle between up and accelerometer vectors
		phi = np.arccos(world_a[1])

		# fusion of gyroscope and accelerometer signals
		alpha = 0.1
		n = np.array([
			[-world_a[2]],
			[0],
			 [world_a[0]]
			 ])

		# normalise n
		n_mag = np.sqrt((n[0] ** 2) + (n[1] ** 2) + (n[2] ** 2))
		if n_mag == 0 or n_mag == nan:
			n_norm = n
		else:
			n_norm = n / n_mag

		# construct orientation quaternion
		q_c = quaternion_product(get_quaternion(n_norm, (1-alpha)*phi), orientation)
		q_c_inv = quaternion_conjugation(q_c)

		m = [0, data[" magnetometer.X"], data[" magnetometer.Y"], data[" magnetometer.Z "]]

		alpha2 = 0.5
		theta = np.arctan2(m[1], m[3])
		theta_r = np.arctan2(mag_data[1], mag_data[3])

		q_c = quaternion_product(get_quaternion(np.array([[0], [1], [0]]), -alpha2*(theta-theta_r)), orientation)
		q_c_inv = quaternion_conjugation(q_c)

		# return orientation and inverse
		return q_c, q_c_inv

# sequence 1
def dead_reckoning_filter(model, df, projection_type="perspective"):
	# array to track the observations at each timestep
	orientation_data = []
	plt.ion()
	for i in range(len(df)):
		# init image
		image = Image(width, height, Color(255, 255, 255, 255))

		# Init z-buffer
		zBuffer = [-float('inf')] * width * height

		# get previous observation
		if i == 0:
			orientation = [1,0,0,0]
		else:
			orientation = orientation_data[i-1]

		# get estimated transformation quaternion and inverse from sensor measurements at current timestep
		q_obs, q_inv = complementary_filter(df.iloc[i], orientation, mode=1)

		# save orientation data for next iteration
		orientation_data.append(q_obs)

		for face in model.faces:
			p0, p1, p2=[model.vertices[i] for i in face]
			n0, n1, n2=[vertexNormals[i] for i in face]

			# Define the light direction
			lightDir=Vector(0, 0, -1)

			# Set to true if face should be culled
			cull=False

			# Transform vertices and calculate lighting intensity per vertex
			transformedPoints=[]
			for p, n in zip([p0, p1, p2], [n0, n1, n2]):
				# convert points to quaternion
				p_ = [0, p.x, p.y, p.z]

				# rotation: qpq^-1
				inner = quaternion_product(q_obs,p_)
				q0, q1, q2, q3 = quaternion_product(inner,q_inv)

				# convert quaternion to x, y, z
				px, py, pz = getAxis(q0, q1, q2, q3)

				# get rotation matrix from quaternions for pixel intensity computation
				M = np.matrix([
					[1-2*q2**2-2*q3**2, 2*q1*q2+2*q0*q3, 2*q1*q3-2*q0*q2],
					[2*q1*q2-2*q0*q3, 1-2*q1**2-2*q3**2 , 2*q2*q3+2*q0*q1],
					[2*q1*q3+2*q0*q2, 2*q2*q3-2*q0*q1, 1-2*q1**2-2*q2**2],
				], dtype=float)

				if projection_type == "orthographic":
					# normal vector
					n_ = np.array([
						[n.x],
						[n.y],
						[n.z],
					], dtype=float)

					# compute intensities
					n_ = (np.linalg.inv(V[:3, :3]) * np.linalg.inv(M)).T * n_

					# perform projection
					screenX, screenY = getOrthographicProjection(px, py, pz)

					# compute pixel intensities as a combination of transformed vertices and normals
					vec_intensities = [screenX, screenY, p.z]
					intensity =np.clip( Vector(vec_intensities[0], vec_intensities[1], vec_intensities[2]).normalize() * lightDir, a_min=None, a_max=1.0)

					# Intensity < 0 means light is shining through the back of the face
					# In this case, don't draw the face at all ("back-face culling")
					if intensity < 0:
						cull=True
						break

					transformedPoints.append(Point(screenX, screenY, pz, Color(intensity*255, intensity*255, intensity*255, 255)))

				else:
					screenX, screenY, screenZ, n_ = getPerspectiveProjection(V,M, px, py, pz, n)

					# compute pixel intensities as a combination of transformed vertices and normals
					vec_intensities = [screenX+n_[0, 0], screenY+n_[1, 0], screenZ+n_[2, 0]]
					intensity =np.clip( Vector(vec_intensities[0], vec_intensities[1], vec_intensities[2]).normalize() * lightDir, a_min=None, a_max=1.0)

					# Intensity < 0 means light is shining through the back of the face
					# In this case, don't draw the face at all ("back-face culling")
					if intensity < 0:
						cull=True
						break

					transformedPoints.append(Point(screenX, screenY, screenZ, Color(intensity*255, intensity*255, intensity*255, 255)))

			if not cull:
				Triangle(transformedPoints[0], transformedPoints[1],
						transformedPoints[2]).draw(image, zBuffer)
		# real time output
		img = image.saveAsPNG()
		plt.imshow(img)
		plt.pause(0.000001)

# sequence 2
def accel_filter(model, df, projection_type="perspective"):
	# array to track the observations at each timestep
	orientation_data = []
	plt.ion()
	for i in range(len(df)):
		# init image
		image = Image(width, height, Color(255, 255, 255, 255))

		# Init z-buffer
		zBuffer = [-float('inf')] * width * height

		# get previous observation
		if i == 0:
			orientation = [1,0,0,0]
		else:
			orientation = orientation_data[i-1]

		# get estimated transformation quaternion and inverse from given sensor measurements at current timestep
		q_obs, q_inv = complementary_filter(df.iloc[i], orientation, mode=2)

		# get estimated transformation quaternion and inverse from sensor measurements at current timestep
		orientation_data.append(q_obs)

		for face in model.faces:
			p0, p1, p2=[model.vertices[i] for i in face]
			n0, n1, n2=[vertexNormals[i] for i in face]

			# Define the light direction
			lightDir=Vector(0, 0, -1)

			# Set to true if face should be culled
			cull=False

			# Transform vertices and calculate lighting intensity per vertex
			transformedPoints=[]
			for p, n in zip([p0, p1, p2], [n0, n1, n2]):
				# convert points to quaternion
				p_ = [0, p.x, p.y, p.z]

				# rotation: qpq^-1
				inner = quaternion_product(q_obs,p_)
				q0, q1, q2, q3 = quaternion_product(inner,q_inv)

				# convert quaternion to x, y, z
				px, py, pz = getAxis(q0, q1, q2, q3)

				# get rotation matrix from quaternions for pixel intensity computation
				M = np.matrix([
					[1-2*q2**2-2*q3**2, 2*q1*q2+2*q0*q3, 2*q1*q3-2*q0*q2],
					[2*q1*q2-2*q0*q3, 1-2*q1**2-2*q3**2 , 2*q2*q3+2*q0*q1],
					[2*q1*q3+2*q0*q2, 2*q2*q3-2*q0*q1, 1-2*q1**2-2*q2**2],
				], dtype=float)

				if projection_type == "orthographic":
					# normal vector
					n_ = np.array([
						[n.x],
						[n.y],
						[n.z],
					], dtype=float)

					# compute intensities
					n_ = (np.linalg.inv(V[:3, :3]) * np.linalg.inv(M)).T * n_

					# perform projection
					screenX, screenY = getOrthographicProjection(px, py, pz)

					# compute pixel intensities as a combination of transformed vertices and normals
					vec_intensities = [screenX, screenY, p.z]
					intensity =np.clip( Vector(vec_intensities[0], vec_intensities[1], vec_intensities[2]).normalize() * lightDir, a_min=None, a_max=1.0)

					# Intensity < 0 means light is shining through the back of the face
					# In this case, don't draw the face at all ("back-face culling")
					if intensity < 0:
						cull=True
						break

					transformedPoints.append(Point(screenX, screenY, pz, Color(intensity*255, intensity*255, intensity*255, 255)))

				else:
					screenX, screenY, screenZ, n_ = getPerspectiveProjection(V,M, px, py, pz, n)

					# compute pixel intensities as a combination of transformed vertices and normals
					vec_intensities = [screenX+n_[0, 0], screenY+n_[1, 0], screenZ+n_[2, 0]]
					intensity =np.clip( Vector(vec_intensities[0], vec_intensities[1], vec_intensities[2]).normalize() * lightDir, a_min=None, a_max=1.0)

					# Intensity < 0 means light is shining through the back of the face
					# In this case, don't draw the face at all ("back-face culling")
					if intensity < 0:
						cull=True
						break

					transformedPoints.append(Point(screenX, screenY, screenZ, Color(intensity*255, intensity*255, intensity*255, 255)))

			if not cull:
				Triangle(transformedPoints[0], transformedPoints[1],
						transformedPoints[2]).draw(image, zBuffer)
		# real time output
		img = image.saveAsPNG()
		plt.imshow(img)
		plt.pause(0.000001)

# sequence 3
def mag_filter(model, df, projection_type="projection"):
	# array to track the observations at each timestep
	orientation_data = []
	plt.ion()
	for i in range(len(df)):
		# init image
		image = Image(width, height, Color(255, 255, 255, 255))

		# Init z-buffer
		zBuffer = [-float('inf')] * width * height

		# get previous observation
		if i == 0:
			orientation = [1,0,0,0]
			mag_data = [0, df.iloc[i][" magnetometer.X"], df.iloc[i][" magnetometer.Y"],df.iloc[i][" magnetometer.Z "]]
		else:
			orientation = orientation_data[i-1]

		# get estimated transformation quaternion and inverse from given sensor measurements at current timestep
		q_obs, q_inv = complementary_filter(df.iloc[i], orientation, mode=3, mag_data=mag_data)

		# get estimated transformation quaternion and inverse from sensor measurements at current timestep
		orientation_data.append(q_obs)

		for face in model.faces:
			p0, p1, p2=[model.vertices[i] for i in face]
			n0, n1, n2=[vertexNormals[i] for i in face]

			# Define the light direction
			lightDir=Vector(0, 0, -1)

			# Set to true if face should be culled
			cull=False

			# Transform vertices and calculate lighting intensity per vertex
			transformedPoints=[]
			for p, n in zip([p0, p1, p2], [n0, n1, n2]):
				# convert points to quaternion
				# p_ = [p.x, p.y, p.z, 1]
				p_ = [0, p.x, p.y, p.z]

				# rotation: qpq^-1
				inner = quaternion_product(q_obs,p_)
				q0, q1, q2, q3 = quaternion_product(inner,q_inv)

				# convert quaternion to x, y, z
				px, py, pz = getAxis(q0, q1, q2, q3)

				# get rotation matrix from quaternions for pixel intensity computation
				M = np.matrix([
					[1-2*q2**2-2*q3**2, 2*q1*q2+2*q0*q3, 2*q1*q3-2*q0*q2],
					[2*q1*q2-2*q0*q3, 1-2*q1**2-2*q3**2 , 2*q2*q3+2*q0*q1],
					[2*q1*q3+2*q0*q2, 2*q2*q3-2*q0*q1, 1-2*q1**2-2*q2**2],
				], dtype=float)

				if projection_type == "orthographic":
					# normal vector
					n_ = np.array([
						[n.x],
						[n.y],
						[n.z],
					], dtype=float)

					# compute intensities
					n_ = (np.linalg.inv(V[:3, :3]) * np.linalg.inv(M)).T * n_

					# perform projection
					screenX, screenY = getOrthographicProjection(px, py, pz)

					# compute pixel intensities as a combination of transformed vertices and normals
					vec_intensities = [screenX, screenY, p.z]
					intensity =np.clip( Vector(vec_intensities[0], vec_intensities[1], vec_intensities[2]).normalize() * lightDir, a_min=None, a_max=1.0)

					# Intensity < 0 means light is shining through the back of the face
					# In this case, don't draw the face at all ("back-face culling")
					if intensity < 0:
						cull=True
						break

					transformedPoints.append(Point(screenX, screenY, pz, Color(intensity*255, intensity*255, intensity*255, 255)))

				else:
					screenX, screenY, screenZ, n_ = getPerspectiveProjection(V,M, px, py, pz, n)

					# compute pixel intensities as a combination of transformed vertices and normals
					vec_intensities = [screenX+n_[0, 0], screenY+n_[1, 0], screenZ+n_[2, 0]]

					intensity =np.clip( Vector(vec_intensities[0], vec_intensities[1], vec_intensities[2]).normalize() * lightDir, a_min=None, a_max=1.0)

					# Intensity < 0 means light is shining through the back of the face
					# In this case, don't draw the face at all ("back-face culling")
					if intensity < 0:
						cull=True
						break

					transformedPoints.append(Point(screenX, screenY, screenZ, Color(intensity*255, intensity*255, intensity*255, 255)))

			if not cull:
				Triangle(transformedPoints[0], transformedPoints[1],
						transformedPoints[2]).draw(image, zBuffer)
		# real time output
		img = image.saveAsPNG()
		plt.imshow(img)
		plt.pause(0.000001)

# Initialisation and constant definitions
width = 500
height = 300

# get filter mode
mode = int(sys.argv[3])

# get model path
model_path = sys.argv[2]

# Load the model
model = Model(model_path)
model.normalizeGeometry()

# get dataset path
data_path = sys.argv[1]

# load dataframe and perform conversions necessary normalisations
data = pd.read_csv(data_path)
df=convert_data(data)

# Calculate face normals
faceNormals={}
for face in model.faces:
	p0, p1, p2=[model.vertices[i] for i in face]
	faceNormal=(p2-p0).cross(p1-p0).normalize()

	for i in face:
		if not i in faceNormals:
			faceNormals[i]=[]

		faceNormals[i].append(faceNormal)

# Calculate vertex normals
vertexNormals=[]
for vertIndex in range(len(model.vertices)):
	vertNorm=getVertexNormal(vertIndex, faceNormals)
	vertexNormals.append(vertNorm)

# get view transform matrix
V = getViewTransform()

if mode == 1:
	# Dead-reckoning filter
	print("Sequence 1: Simple dead-reckoning filter...")
	dead_reckoning_filter(model, df)
elif mode == 2:
	# Dead-reckoning filter with accelerometer
	print("Sequence 2: Dead-reckoning filter with accelerometer integration...")
	accel_filter(model, df)
else:
	# Dead-reckoning filter with accelerometer and magnetometer
	print("Sequence 3: Dead-reckoning filter with accelerometer and magnetometer integration...")
	mag_filter(model, df)


import contextlib
import collections
import copy
import functools
import itertools
import numpy as np
import pandas as pd
import re
from matplotlib import pyplot as plt

import advent_tools

GOAL = 2

WALL = 1

EMPTY = 0

ROBOT = 3

class MazeState(advent_tools.StateForGraphs):

    def __init__(self, grid):
        self.grid = grid

    def init_from_file(self):
        lines = advent_tools.read_input_lines()
        char_map = {'.': EMPTY, '#': WALL, '0': ROBOT}
        for num in range(1, 10):
            char_map[str(num)] = GOAL
        for y_pos, line in enumerate(lines):
            for x_pos, char in enumerate(line):
                self.grid[y_pos, x_pos] = char_map[char]

    def __str__(self):
        return self.grid.tostring().decode('utf-16')

    def is_final(self):
        return not((self.grid == GOAL).any())

    def possible_next_states(self):
        y_rob, x_rob = np.argwhere(self.grid == ROBOT)[0]
        to_try = [(x_rob + 1, y_rob),
                  (x_rob - 1, y_rob),
                  (x_rob, y_rob + 1),
                  (x_rob, y_rob - 1)]
        result = set()
        for x_move, y_move in to_try:
            if self.grid[y_move, x_move] != WALL:
                new_grid = np.copy(self.grid)
                new_grid[y_rob, x_rob] = EMPTY
                new_grid[y_move, x_move] = ROBOT
                result.add(MazeState(new_grid))
        return result

    def show(self):
        """Show the grid in a new window

        Execution will be suspended until the window is closed

        Returns:
            None
        """
        plt.clf()
        plt.imshow(self.grid)
        plt.colorbar()
        plt.show()

class PartTwo(MazeState):

    def __init__(self, grid, start_pos=(0, 0)):
        super().__init__(grid)
        self.start_pos = start_pos

    def init_from_file(self):
        super().init_from_file()
        self.start_pos = np.argwhere(self.grid == ROBOT)[0]

    def is_final(self):
        if not super().is_final():
            return False
        robot_pos = np.argwhere(self.grid == ROBOT)[0]
        return (robot_pos == self.start_pos).all()

    def possible_next_states(self):
        y_rob, x_rob = np.argwhere(self.grid == ROBOT)[0]
        to_try = [(x_rob + 1, y_rob),
                  (x_rob - 1, y_rob),
                  (x_rob, y_rob + 1),
                  (x_rob, y_rob - 1)]
        result = set()
        for x_move, y_move in to_try:
            if self.grid[y_move, x_move] != WALL:
                new_grid = np.copy(self.grid)
                new_grid[y_rob, x_rob] = EMPTY
                new_grid[y_move, x_move] = ROBOT
                result.add(PartTwo(new_grid, self.start_pos))
        return result

def run_part_1():
    state = MazeState(np.zeros((37, 184)))
    state.init_from_file()
    print(advent_tools.number_of_bfs_steps(state))

def run_part_2():
    state = PartTwo(np.zeros((37, 184)))
    state.init_from_file()
    print(advent_tools.number_of_bfs_steps(state))

if __name__ == '__main__':
    # run_part_1()
    run_part_2()


import os, datetime, shutil, stat, logging, tempfile
from typing import Callable, Optional, Iterable, List, Tuple

from scan_unused.utils import get_days_ago_str, size_getter_str

'''
Query the in-memory file structure multiple times to forcast what will get deleted /
what is preventing something from getting deleted.
'''

class Node:
    '''
    Used to store file/folder metadata as tree structure.
    '''
    def __init__(self, name: str, is_folder: bool, owner: int, parent: 'Node'=None):
        self.name = name
        self.size = 0
        self.last_access = -1
        self.is_folder = is_folder
        self.children = [] if is_folder else None
        self.owner = owner
        self.parent = parent

    def get_path(self):
        full_list = []
        curr = self
        while curr is not None:
            full_list.append(curr.name)
            curr = curr.parent

        return os.path.join(*reversed(full_list))

    def get_size_str(self):
        return size_getter_str(self.size)

    def get_last_access_str(self):
        return f'{get_days_ago_str(self.last_access)} days ago'

    def __repr__(self):
        return f'{self.get_path()} ({get_days_ago_str(self.last_access)} days since access, {size_getter_str(self.size)})'

class Tree:
    def __init__(self, root: Node):
        self.root = root

    def from_dir(dir: str) -> 'Tree':
        '''
        Construct a tree given any directory.
        '''
        stats = os.stat(dir)
        if not stat.S_ISDIR(stats.st_mode):
            raise Exception('Must provide valid directory')
        root = Node(dir, True, stats.st_uid)

        # Unwrapped recursion for large FS
        stack = [root]
        while len(stack):
            parent = stack.pop()
            full = parent.get_path()
            try:
                for entry in os.scandir(full):
                    stats = entry.stat(follow_symlinks=False)
                    curr = Node(entry.name, entry.is_dir() and not entry.is_symlink(), stats.st_uid, parent)
                    if curr.is_folder:
                        # Folders use mtime (but non-empty will get overwritten in recurse)
                        curr.last_access = stats.st_mtime
                        stack.append(curr)
                    else:
                        # Files use atime
                        curr.last_access = stats.st_atime if not entry.is_symlink() else stats.st_mtime
                        curr.size = stats.st_size
                    parent.children.append(curr)
            except OSError:
                continue

        # Still need to recurse for sizes/access propagation
        def _recurse(node):
            if node.children:
                node.last_access = -1
                node.size = 0
                for c in node.children:
                    if c.children: _recurse(c)
                    node.size += c.size
                    node.last_access = max(node.last_access, c.last_access)
        _recurse(root)
        return Tree(root)

    def count_nodes(self):
        '''
        Count unwrapped tree nodes.
        '''
        def _recurse(node):
            count = 0
            for c in node.children:
                count += 1
                if c.is_folder: count += _recurse(c)
            return count
        return _recurse(self.root)

    def iter_nodes(self, satifies: Callable[[Node], bool]):
        '''
        Iterate unwrapped tree nodes when satisfies is true, skips sub-nodes.
        '''
        def _recurse(node):
            for c in node.children:
                if satifies(c): yield c
                elif c.is_folder: yield from _recurse(c)
        yield from _recurse(self.root)

    def iter_nodes_range(self, range: Tuple[Optional[float], Optional[float]], inverse: bool=False):
        '''
        Iterate nodes in a given (inclusive) timestamp range.
        '''
        yield from self.iter_nodes(lambda node: inverse ^ ((not range[0] or node.last_access >= range[0]) and (not range[1] or node.last_access <= range[1])))

    def iter_nodes_unused(self, days: int, future: int=None):
        '''
        Iterate nodes that have not been accessed in the given number of days.
        Use future to see what files will be deleted on a given day ahead, assuming it is run every day.
        '''
        if future:
            # e.g. (3 days, 1 day in future) ->  in range [-3, -2]
            if future < days:
                from_t = (datetime.datetime.now() - datetime.timedelta(days=future+days-1))
                to_t = (datetime.datetime.now() - datetime.timedelta(days=future-1+days-1))
                yield from self.iter_nodes_range((from_t.timestamp(), to_t.timestamp()))
        else:
            # e.g. (3 days) -> in range [-infinity, -3]
            to_t = (datetime.datetime.now() - datetime.timedelta(days=days))
            yield from self.iter_nodes_range((None, to_t.timestamp()))

    @staticmethod
    def delete_nodes(nodes: Iterable[Node], move_path: str):
        '''
        Delete list of nodes quickly. Assumes nodes are in traversed order.
        '''
        try:
            nodes = list(nodes)
            parent = None
            parent_tmp_path = None
            for node in nodes:
                full = node.get_path()
                if parent != node.parent:
                    try:
                        # Create tempfile temporary with same permissions
                        parent_tmp_path = tempfile.mkdtemp(dir=move_path)
                        shutil.copymode(os.path.dirname(full), parent_tmp_path, follow_symlinks=False)
                        parent = node.parent
                    except OSError as e:
                        logging.exception(f'Failed to create temporary directory {parent_tmp_path}')
                        raise e
                try:
                    shutil.move(full, parent_tmp_path,)
                except OSError as e:
                    logging.exception(f'Failed to move {full}')
            try:
                shutil.rmtree(move_path)
            except OSError as e:
                logging.exception(f'Failed to delete {move_path}')
        except (BaseException) as e:
            logging.exception(f'Interrupted during deletion')
            raise e


import sqlite3
from fpdf import FPDF
import os

import numpy as np

from datetime import datetime
import locale

locale.setlocale(locale.LC_ALL, "german")

class Wod2Pdf:
    def __init__(self, db):
        self.db = sqlite3.connect(db)
        self.cursor = self.db.cursor()
        self.ids = self.get_sorted_id()

    def get_sorted_id(self):
        self.cursor.execute("SELECT * from wodplannerapp_wod;")
        all_data = self.cursor.fetchall()

        ids = np.array([i[0] for i in all_data])
        dates = np.array([datetime.strptime(i[1], '%Y-%m-%d %H:%M:%S') for i in all_data])

        idx_sorted = np.argsort(dates)

        return ids[idx_sorted]

    def _get_wod(self, wid):
        self.cursor.execute(f"SELECT * from wodplannerapp_wod WHERE id={wid};")
        wod_data = list(self.cursor.fetchone())
        header = [i[0] for i in self.cursor.description]
        wod_type = wod_data[4]
        if 'AMRAP' in wod_type:
            wod_header = f'{wod_data[5]}min {wod_type}'
        elif wod_type == 'RFT Varying Reps':
            wod_header = wod_type
        elif 'RFT' in wod_type:
            wod_header = f'{wod_data[5]} {wod_type}'
        elif wod_type == 'FGB':
            wod_header = f'{wod_type}: 3 rounds, 1min per station'
        else:
            wod_header = wod_type

        self.cursor.execute(f"SELECT * from wodplannerapp_strengthmovement WHERE wod_id=={wid}")
        strength_data = self.cursor.fetchall()
        header_strength = [i[0] for i in self.cursor.description]

        self.cursor.execute(f"SELECT * from wodplannerapp_wodmovement WHERE wod_id=={wid}")
        wod_contents = self.cursor.fetchall()

        header.extend(header_strength)
        wod_data.extend(strength_data)

        header_date = datetime.strptime(wod_data[1], '%Y-%m-%d %H:%M:%S')
        strength_comment = wod_data[3] if wod_data[3] != '-' else ''
        wod_comment = wod_data[6] if wod_data[6] != '-' else ''

        return datetime.strftime(header_date, '%d. %B %Y'), strength_data, strength_comment, wod_comment, wod_header, \
               wod_contents

    @classmethod
    def write_pdf(cls, db, filename):
        wod = cls(db)
        pdf = PDF()
        for i, wod_id in enumerate(wod.ids):
            header_date, strength_data, strength_comment, wod_comment, wod_type, wod_data = wod._get_wod(wod_id)
            pdf.write_wods(i+1, header_date, strength_data, strength_comment, wod_comment, wod_type, wod_data)
        pdf.output(filename, 'F')

def get_cell_width(data):
    cell1_length = 0
    cell2_length = 0
    cell3_length = 0
    for i in data:
        cell1_length = len(str(i[2])) if len(str(i[2])) > cell1_length else cell1_length
        cell2_length = len(str(i[1])) if len(str(i[1])) > cell2_length else cell2_length
        cell3_length = len(str(i[3])) if len(str(i[3])) > cell3_length else cell3_length

    cell1_width = int(cell1_length*3) if cell1_length > 0 else 1
    cell2_width = int(cell2_length * 3) if cell2_length > 0 else 1
    cell3_width = int(cell3_length * 3) if cell3_length > 0 else 1

    return cell1_width, cell2_width, cell3_width

class PDF(FPDF):
    def header(self):
        file_path = os.path.dirname(os.path.abspath(__file__))
        self.image(os.path.join(file_path, 'images/carabao_logo.png'), 10, 8, 33)
        self.set_font('Arial', 'B', 15)
        # Move to the right
        self.cell(80)
        # Title
        self.cell(80, 10, 'Carabao WODs', 0, 0, 'C')
        # Line break
        self.ln(20)

    def footer(self):
        # Position at 1.5 cm from bottom
        self.set_y(-15)
        # Arial italic 8
        self.set_font('Arial', 'I', 8)
        # Page number
        self.cell(0, 10, f'Seite {str(self.page_no())}', 0, 0, 'C')

    def wod_title(self, num, label):
        # Arial 12
        self.set_font('Arial', 'B', 16)
        # Background color
        self.set_fill_color(200, 220, 255)
        # Title
        self.cell(0, 6, 'Workout %d: %s' % (num, label), 0, 1, 'L', 1)
        # Line break
        self.ln(4)

    def wod_strength(self, strength_data, strength_comment):
        self.set_font('Arial', 'B', 14)
        self.cell(10, 6, 'Strength/Skill:', 0, 0, 'L', 0)
        self.set_font('Arial', '', 12)
        c1, c2, c3 = get_cell_width(strength_data)
        for movement_data in strength_data:
            self.ln()
            self.cell(c1, 6, f'{movement_data[2]}', 0, 0, 'L', 0)
            self.cell(c2, 6, f'{movement_data[1]}', 0, 0, 'L', 0)
        self.ln()
        self.set_font('Arial', 'I', 12)
        self.multi_cell(0, 6, f'{strength_comment}', 0, 0, 'L', 0)
        self.ln()

    def wod_wod(self, wod_type, wod_data, wod_comment):
        self.set_font('Arial', 'B', 14)
        self.cell(10, 6, 'WOD:', 0, 0, 'L', 0)
        self.set_font('Arial', '', 12)
        self.ln()
        self.cell(10, 6, f'{wod_type}', 0, 0, 'L', 0)

        c1, c2, c3 = get_cell_width(wod_data)

        for movement_data in wod_data:
            self.ln()
            self.cell(c1, 6, f'{movement_data[2]}', 0, 0, 'L', 0)
            self.cell(c2, 6, f'{movement_data[1]}', 0, 0, 'L', 0)
            weight = movement_data[3] if movement_data[3] != '-' else ''
            self.cell(c3, 6, f'{weight}', 0, 0, 'L', 0)
        self.ln()
        # self.cell(10, 6, f'{wod_comment}', 0, 0, 'L', 0)
        self.set_font('Arial', 'I', 12)
        self.multi_cell(0, 6, f'{wod_comment}', 0, 0, 'L', 0)
        self.ln()

    def write_wods(self, wod_i, header_date, strength_data, strength_comment, wod_comment, wod_type, wod_data):
        self.add_page()
        self.wod_title(wod_i, header_date)
        self.wod_strength(strength_data, strength_comment)
        self.wod_wod(wod_type, wod_data, wod_comment)

if __name__ == '__main__':
    pdf = PDF()
    pdf.alias_nb_pages()
    pdf.add_page()
    pdf.set_font('Times', '', 12)
    for l in range(1, 41):
        pdf.cell(0, 10, 'Printing line number ' + str(l), 0, 1)
    pdf.output('tuto2.pdf', 'F')


import re
from calendar import monthrange
from datetime import datetime

from odoo import _, api, exceptions, fields, models
from odoo.tools import config

from .spanish_states_mapping import SPANISH_STATES as ss

class L10nEsAeatReport(models.AbstractModel):
    _name = "l10n.es.aeat.report"
    _inherit = ["mail.thread", "mail.activity.mixin"]
    _description = "AEAT report base module"
    _rec_name = "name"
    _aeat_number = False
    _period_quarterly = True
    _period_monthly = True
    _period_yearly = False
    SPANISH_STATES = ss

    def _default_journal(self):
        return self.env["account.journal"].search(
            [("type", "=", "general"), ("company_id", "=", self.env.company.id)]
        )[:1]

    def get_period_type_selection(self):
        period_types = []
        if self._period_yearly or config["test_enable"]:
            period_types += [("0A", "0A - Anual")]
        if self._period_quarterly:
            period_types += [
                ("1T", "1T - Primer trimestre"),
                ("2T", "2T - Segundo trimestre"),
                ("3T", "3T - Tercer trimestre"),
                ("4T", "4T - Cuarto trimestre"),
            ]
        if self._period_monthly or config["test_enable"]:
            period_types += [
                ("01", "01 - Enero"),
                ("02", "02 - Febrero"),
                ("03", "03 - Marzo"),
                ("04", "04 - Abril"),
                ("05", "05 - Mayo"),
                ("06", "06 - Junio"),
                ("07", "07 - Julio"),
                ("08", "08 - Agosto"),
                ("09", "09 - Septiembre"),
                ("10", "10 - Octubre"),
                ("11", "11 - Noviembre"),
                ("12", "12 - Diciembre"),
            ]
        return period_types

    def _default_period_type(self):
        selection = self.get_period_type_selection()
        return selection and selection[0][0] or False

    def _default_year(self):
        return fields.Date.to_date(fields.Date.today()).year

    def _default_number(self):
        return self._aeat_number

    def _get_export_config(self, date):
        model = self.env["ir.model"].sudo().search([("model", "=", self._name)])
        return self.env["aeat.model.export.config"].search(
            [
                ("model_id", "=", model.id),
                ("date_start", "<=", date),
                "|",
                ("date_end", "=", False),
                ("date_end", ">=", date),
            ],
            limit=1,
        )

    company_id = fields.Many2one(
        comodel_name="res.company",
        string="Company",
        required=True,
        readonly=True,
        default=lambda self: self.env.company,
        states={"draft": [("readonly", False)]},
    )
    company_vat = fields.Char(
        string="VAT number",
        size=9,
        required=True,
        readonly=True,
        states={"draft": [("readonly", False)]},
    )
    number = fields.Char(
        string="Model number",
        size=3,
        required=True,
        readonly=True,
        default=_default_number,
    )
    previous_number = fields.Char(
        string="Previous declaration number",
        size=13,
        states={"done": [("readonly", True)]},
    )
    contact_name = fields.Char(
        string="Full Name",
        size=40,
        help="Must have name and surname.",
        required=True,
        readonly=True,
        states={"draft": [("readonly", False)]},
    )
    contact_phone = fields.Char(
        string="Phone",
        size=9,
        required=True,
        readonly=True,
        states={"draft": [("readonly", False)]},
    )
    contact_email = fields.Char(
        size=50,
        readonly=True,
        states={"draft": [("readonly", False)]},
    )
    representative_vat = fields.Char(
        string="L.R. VAT number",
        size=9,
        readonly=True,
        help="Legal Representative VAT number.",
        states={"draft": [("readonly", False)]},
    )
    year = fields.Integer(
        default=_default_year,
        required=True,
        readonly=True,
        states={"draft": [("readonly", False)]},
    )
    statement_type = fields.Selection(
        selection=[("N", "Normal"), ("C", "Complementary"), ("S", "Substitutive")],
        default="N",
        readonly=True,
        required=True,
        states={"draft": [("readonly", False)]},
    )
    support_type = fields.Selection(
        selection=[("C", "DVD"), ("T", "Telematics")],
        default="T",
        readonly=True,
        required=True,
        states={"draft": [("readonly", False)]},
    )
    calculation_date = fields.Datetime()
    state = fields.Selection(
        selection=[
            ("draft", "Draft"),
            ("calculated", "Processed"),
            ("done", "Done"),
            ("posted", "Posted"),
            ("cancelled", "Cancelled"),
        ],
        default="draft",
        readonly=True,
        tracking=True,
    )
    name = fields.Char(string="Report identifier", size=13, copy=False)
    export_config_id = fields.Many2one(
        comodel_name="aeat.model.export.config",
        string="Export config",
        domain=lambda self: [
            (
                "model_id",
                "=",
                self.env["ir.model"].search([("model", "=", self._name)]).id,
            )
        ],
        compute="_compute_export_config_id",
        readonly=False,
        store=True,
    )
    currency_id = fields.Many2one(
        comodel_name="res.currency",
        string="Currency",
        readonly=True,
        related="company_id.currency_id",
    )
    period_type = fields.Selection(
        selection="get_period_type_selection",
        required=True,
        default=_default_period_type,
        readonly=True,
        states={"draft": [("readonly", False)]},
    )
    date_start = fields.Date(
        string="Starting date",
        required=True,
        readonly=True,
        store=True,
        compute="_compute_dates",
        states={"draft": [("readonly", False)]},
    )
    date_end = fields.Date(
        string="Ending date",
        required=True,
        readonly=True,
        store=True,
        compute="_compute_dates",
        states={"draft": [("readonly", False)]},
    )
    allow_posting = fields.Boolean(compute="_compute_allow_posting")
    counterpart_account_id = fields.Many2one(
        comodel_name="account.account",
        string="Counterpart account",
        help="This account will be the counterpart for all the journal items "
        "that are regularized when posting the report.",
    )
    journal_id = fields.Many2one(
        comodel_name="account.journal",
        string="Journal",
        domain="[('type', '=', 'general'), ('company_id', '=', company_id)]",
        default=_default_journal,
        help="Journal in which post the move.",
        states={"done": [("readonly", True)]},
    )
    move_id = fields.Many2one(
        comodel_name="account.move",
        string="Account entry",
        readonly=True,
        domain=[("type", "=", "entry")],
    )
    partner_id = fields.Many2one(
        comodel_name="res.partner",
        string="Partner",
        related="company_id.partner_id",
        readonly=True,
    )
    partner_bank_id = fields.Many2one(
        comodel_name="res.partner.bank",
        string="Bank account",
        help="Company bank account used for the presentation",
        domain="[('acc_type', '=', 'iban'), ('partner_id', '=', partner_id)]",
    )
    error_count = fields.Integer(
        compute="_compute_error_count",
    )
    _sql_constraints = [
        (
            "name_uniq",
            "unique(name, company_id)",
            "AEAT report identifier must be unique",
        )
    ]

    def _compute_allow_posting(self):
        for report in self:
            report.allow_posting = False

    def _compute_error_count(self):
        """To be overridden by each report."""
        self.error_count = 0

    @api.constrains("statement_type", "previous_number")
    def _check_previous_number(self):
        for report in self:
            if report.statement_type in ("C", "S") and not report.previous_number:
                raise exceptions.UserError(
                    _(
                        "If this declaration is complementary or substitutive, "
                        "a previous declaration number should be provided."
                    )
                )

    def get_taxes_from_templates(self, tax_templates):
        company = self.company_id or self.env.user.company_id
        return company.get_taxes_from_templates(tax_templates)

    def get_account_from_template(self, account_template):
        company = self.company_id or self.env.user.company_id
        return company.get_account_from_template(account_template)

    @api.onchange("company_id")
    def onchange_company_id(self):
        """Load some company data (the VAT number) when company changes."""
        if self.company_id.vat:
            # Remove the ES part from spanish vat numbers
            #  (ES12345678Z => 12345678Z)
            self.company_vat = re.match("(ES){0,1}(.*)", self.company_id.vat).groups()[
                1
            ]
        self.contact_name = self.env.user.name
        self.contact_email = self.env.user.email
        self.contact_phone = self._filter_phone(
            self.env.user.partner_id.phone
            or self.env.user.partner_id.mobile
            or self.env.user.company_id.phone
        )
        if self.journal_id.company_id != self.company_id:
            self.journal_id = self.with_company(self.company_id.id)._default_journal()

    @api.depends("year", "period_type")
    def _compute_dates(self):
        for report in self:
            if not report.year or not report.period_type:
                continue
            else:
                if report.period_type == "0A":
                    # Anual
                    report.date_start = fields.Date.to_date("%s-01-01" % report.year)
                    report.date_end = fields.Date.to_date("%s-12-31" % report.year)
                elif report.period_type in ("1T", "2T", "3T", "4T"):
                    # Trimestral
                    starting_month = 1 + (int(report.period_type[0]) - 1) * 3
                    ending_month = starting_month + 2
                    report.date_start = fields.Date.to_date(
                        "{}-{}-01".format(report.year, starting_month)
                    )
                    report.date_end = fields.Date.to_date(
                        "%s-%s-%s"
                        % (
                            report.year,
                            ending_month,
                            monthrange(report.year, ending_month)[1],
                        )
                    )
                elif report.period_type in (
                    "01",
                    "02",
                    "03",
                    "04",
                    "05",
                    "06",
                    "07",
                    "08",
                    "09",
                    "10",
                    "11",
                    "12",
                ):
                    # Mensual
                    month = int(report.period_type)
                    report.date_start = fields.Date.to_date(
                        "{}-{}-01".format(report.year, month)
                    )
                    report.date_end = fields.Date.to_date(
                        "%s-%s-%s"
                        % (report.year, month, monthrange(report.year, month)[1])
                    )

    @api.depends("date_start")
    def _compute_export_config_id(self):
        for report in self:
            date = report.date_start or fields.Date.today()
            report.export_config_id = report._get_export_config(date)

    @api.model
    def _report_identifier_get(self, vals):
        seq_name = "aeat%s-sequence" % self._aeat_number
        company_id = vals.get("company_id", self.env.user.company_id.id)
        seq = self.env["ir.sequence"].search(
            [("name", "=", seq_name), ("company_id", "=", company_id)], limit=1
        )
        if not seq:
            raise exceptions.UserError(
                _(
                    "AEAT model sequence not found. You can try to restart your "
                    "Odoo service for recreating the sequences."
                )
            )
        return seq.next_by_id()

    @api.model_create_multi
    def create(self, vals_list):
        for vals in vals_list:
            if not vals.get("name"):
                vals["name"] = self._report_identifier_get(vals)
        return super().create(vals_list)

    def button_calculate(self):
        res = self.calculate()
        self.write({"state": "calculated", "calculation_date": fields.Datetime.now()})
        return res

    def button_recalculate(self):
        self.write({"calculation_date": fields.Datetime.now()})
        return self.calculate()

    def _get_previous_fiscalyear_reports(self, date):
        """Get the AEAT reports previous to the given date.

        :param date: Date for looking for previous reports.
        :return: Recordset of the previous AEAT reports. None if there is no
                 previous reports.
        """
        self.ensure_one()
        return self.search([("year", "=", self.year), ("date_start", "<", date)])

    def calculate(self):
        """To be overrided by inherit models"""
        return True

    def button_confirm(self):
        """Set report status to done."""
        self.write({"state": "done"})
        return True

    def _prepare_move_vals(self):
        self.ensure_one()
        return {
            "date": self.date_end,
            "journal_id": self.journal_id.id,
            "ref": self.name,
            "company_id": self.company_id.id,
        }

    def button_post(self):
        """Create any possible account move entry and set state to posted."""
        for report in self:
            report.create_regularization_move()
        self.write({"state": "posted"})
        return True

    def button_cancel(self):
        """Set report status to cancelled."""
        self.write({"state": "cancelled"})
        return True

    def button_unpost(self):
        """Remove created account move entry and set state to cancelled."""
        self.mapped("move_id").with_context(force_delete=True).unlink()
        self.write({"state": "cancelled"})
        return True

    def button_recover(self):
        """Set report status to draft and reset calculation date."""
        self.write({"state": "draft", "calculation_date": False})
        return True

    def button_export(self):
        for report in self:
            export_obj = self.env[
                "l10n.es.aeat.report.%s.export_to_boe" % report.number
            ]
            export_obj.export_boe_file(report)
        return True

    def button_open_move(self):
        self.ensure_one()
        action = self.env.ref("account.action_move_line_form").sudo().read()[0]
        action["view_mode"] = "form"
        action["res_id"] = self.move_id.id
        del action["view_id"]
        del action["views"]
        return action

    def unlink(self):
        if any(item.state not in ["draft", "cancelled"] for item in self):
            raise exceptions.UserError(
                _("Only reports in 'draft' or 'cancelled' state can be removed")
            )
        return super().unlink()

    @api.model
    def _prepare_aeat_sequence_vals(self, sequence, aeat_num, company):
        return {
            "name": sequence,
            "code": "aeat.sequence.type",
            "number_increment": 1,
            "implementation": "no_gap",
            "padding": 13 - len(str(aeat_num)),
            "number_next_actual": 1,
            "prefix": aeat_num,
            "company_id": company.id,
        }

    @api.model
    def _filter_phone(self, phone):
        return (phone or "").replace(" ", "")[-9:]

    def _register_hook(self, companies=None):
        res = None
        if not companies:
            res = super()._register_hook()
        if self._name in ("l10n.es.aeat.report", "l10n.es.aeat.report.tax.mapping"):
            return res
        aeat_num = getattr(self, "_aeat_number", False)
        if not aeat_num:
            raise exceptions.UserError(
                _("Modelo no válido: %s. Debe declarar una variable " "'_aeat_number'")
                % self._name
            )
        seq_obj = self.env["ir.sequence"]
        sequence = "aeat%s-sequence" % aeat_num
        if not companies:
            companies = self.env["res.company"].search([])
        for company in companies:
            seq = seq_obj.search(
                [("name", "=", sequence), ("company_id", "=", company.id)]
            )
            if seq:
                continue
            seq_obj.create(
                self.env[self._name]._prepare_aeat_sequence_vals(
                    sequence, aeat_num, company
                )
            )
        return res

    @api.model
    def _get_formatted_date(self, date):
        """Convert an Odoo date to BOE export date format.

        :param date: Date in Odoo format or falsy value
        :return: Date formatted for BOE export.
        """
        if not date:
            return ""
        return datetime.strftime(fields.Date.to_date(date), "%d%m%Y")

    @api.model
    def get_html(self):
        """Render dynamic view from ir.action.client"""
        result = {}
        rcontext = {}
        rec = self.browse(self.env.context.get("active_id"))
        if rec:
            rcontext["o"] = rec
            result["html"] = self.env.ref(self.env.context.get("template_name")).render(
                rcontext
            )
        return result


import copy
import re
import string
from pprint import pprint

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from tqdm import tqdm

# %%

elevation = np.array([list(line) for line in pd.read_csv('input', header=None)[0].values])

start_pos = tuple([i.item() for i in np.where(elevation == 'S')])
end_pos = tuple([i.item() for i in np.where(elevation == 'E')])
elevation[start_pos] = 'a'
elevation[end_pos] = 'z'

vfunc = np.vectorize(string.ascii_lowercase.index)
elevation = vfunc(elevation)

# %%

# add ring of zeros
elev_mat = np.full((elevation.shape[0] + 2, elevation.shape[1] + 2), -1, dtype=float)
elev_mat[1:-1, 1:-1] = elevation
start_pos = tuple([i + 1 for i in start_pos])
end_pos = tuple([i + 1 for i in end_pos])

# %%

import heapq
import sys

class Graph:

    def __init__(self):
        self.vertices = {}

    def add_vertex(self, name, edges):
        self.vertices[name] = edges

    def shortest_path(self, start, finish):
        distances = {}  # Distance from start to node
        previous = {}  # Previous node in optimal path from source
        nodes = []  # Priority queue of all nodes in Graph

        for vertex in self.vertices:
            if vertex == start:  # Set root node as distance of 0
                distances[vertex] = 0
                heapq.heappush(nodes, [0, vertex])
            else:
                distances[vertex] = sys.maxsize
                heapq.heappush(nodes, [sys.maxsize, vertex])
            previous[vertex] = None

        while nodes:
            smallest = heapq.heappop(nodes)[1]  # Vertex in nodes with smallest distance in distances
            if smallest == finish:  # If the closest node is our target we're done so print the path
                path = []
                while previous[smallest]:  # Traverse through nodes til we reach the root which is 0
                    path.append(smallest)
                    smallest = previous[smallest]
                return path
            if distances[smallest] == sys.maxsize:  # All remaining vertices are inaccessible from source
                # print('inaccessible')
                break

            for neighbor in self.vertices[smallest]:  # Look at all the nodes that this vertex is attached to
                alt = distances[smallest] + self.vertices[smallest][neighbor]  # Alternative path distance
                if alt < distances[neighbor]:  # If there is a new shortest path update our priority queue (relax)
                    distances[neighbor] = alt
                    previous[neighbor] = smallest
                    for n in nodes:
                        if n[1] == neighbor:
                            n[0] = alt
                            break
                    heapq.heapify(nodes)
        return distances

    def __str__(self):
        return str(self.vertices)

# %%

for scenic in [False, True]:  # True for part 2
    g = Graph()  # create graph
    for i in range(1, elev_mat.shape[0] - 1):
        for j in range(1, elev_mat.shape[1] - 1):
            neighbors = [(i + 1, j), (i, j + 1), (i - 1, j), (i, j - 1)]
            neighbor_dict = {neig: 1 for neig in neighbors if ((elev_mat[neig] - elev_mat[i, j]) <= 1) and (elev_mat[neig] != -1)}
            if ((i, j) == start_pos) and scenic:
                neighbor_dict.update({(ii, jj): 0 for ii, jj in zip(*np.where(elev_mat == 0))})
            g.add_vertex((i, j),  neighbor_dict)

            # g.add_vertex((i, j),  {neig: 1 for neig in neighbors if ((elev_mat[neig] - elev_mat[i, j]) <= 1) and (elev_mat[neig] != -1)})

    shortest = g.shortest_path(start_pos, end_pos)

    if scenic:
        # minus one because of old start tile
        print(f'shortest path length: {len(shortest) - 1}')
    else:
        print(f'shortest path length: {len(shortest)}')

    elev_mat_img = elev_mat.copy()
    plt.figure(figsize=(14, 14))
    plt.plot(np.array(shortest).T[1], np.array(shortest).T[0], color='orange', lw=1)
    plt.plot(start_pos[1], start_pos[0], marker='x', color='red', ms='5')
    plt.plot(end_pos[1], end_pos[0], marker='x', color='red', ms=6)
    plt.imshow(elev_mat_img, origin='upper')
    plt.show()

# %%


import setuptools

with open("README.md", "r") as fh:
    long_description = fh.read()

setuptools.setup(
    name="jpegdupes",
    version="2.1.0",
    author="Jesús Jiménez",
    author_email="jesjimenez@gmail.com",
    description="Image duplicate finder that ignores JPEG metadata",
    long_description=long_description,
    long_description_content_type="text/markdown",
    keywords="image duplicate finder metadata jpeg",
    url="https://github.com/jesjimher/imgdupes",
    packages=setuptools.find_packages(),
    python_requires=">=3",
    install_requires=[
        "texttable",
        "jpegtran-cffi>0.5",
        "Pillow",
    ],
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
        "Operating System :: OS Independent",
    ],
    #    scripts=['jpegdupes/jpegdupes.py']
    entry_points={"console_scripts": ["jpegdupes=jpegdupes.jpegdupes:main"]},
)


import numpy as np
import matplotlib.pyplot as plt
import sys

def demo_fass(feature, sample):
    X_tilda = np.load('X_tilda_feature{}.npy'.format(feature))
    T_tilda = np.load('T_tilda_feature{}.npy'.format(feature))
    prob = np.load('prob_feature{}.npy'.format(feature))
    t_max = np.max(T_tilda[sample])

    fig, axs = plt.subplots(2, 1)
    axs[0].plot(X_tilda[sample], T_tilda[sample], color='navy')
    # axs[0].set_xlabel('X feature: {} sample: {}'.format(feature, sample))
    axs[0].set_ylabel('t')
    # axs[0].hlines(t_true_sum, X_tilda[1, 0], X_tilda[1, -1], linestyles='--')
    axs[1].plot(X_tilda[sample], prob[sample], color='navy')
    axs[1].set_xlabel('X feature: {} sample: {}'.format(feature, sample))
    axs[1].set_ylabel('pdf')

    plt.ion()
    plt.show()
    plt.waitforbuttonpress()

    t_arr = np.linspace(0, t_max, 50)
    to_delete = []
    prob_list = []
    for t_iteration in t_arr:
        plt.pause(0.5)
        for td in to_delete:
            td.remove()
        to_delete = []
        to_delete.append(axs[0].hlines(t_iteration, X_tilda[1, 0], X_tilda[1, -1]))
        fill_index = T_tilda[sample] <= t_iteration
        x_fill = X_tilda[sample][fill_index]
        y_fill = T_tilda[sample][fill_index]
        prob_fill = prob[sample][fill_index]
        to_delete.append(axs[0].fill_between(x_fill, y_fill, color='navy', alpha=0.5))
        to_delete.append(axs[1].fill_between(x_fill, prob_fill, color='navy', alpha=0.5))
    plt.waitforbuttonpress()

if __name__ == '__main__':
    feature, sample = 0, 1
    demo_fass(feature, sample)


import numpy as np
from algorithms import metrics
from collections import defaultdict
from scipy.stats import mannwhitneyu

def evaluate(models, models_q=None):
    """
    Evaluates the models based on some evaluation metrics. This includes the model after q recommendation queries and
    also the models after all recommendation queries are processed.
    :param models: List of models
    :param models_q: List of models after each recommendation query
    :return: dict() where the key is the evaluation metric and the value is the average value over all users; and without
    the average
    """
    results, results_samples = _evaluate(models)
    if models_q is not None:
        results_q, results_q_samples = _evaluate_q(models_q)
        return {**results, **results_q}, {**results_samples, **results_q_samples}
    else:
        return {**results, **results_samples}

def _evaluate_q(models):
    """
    Evaluates the models after q recommendation queries
    :param models: List of models
    :return: dict() where the key is the evaluation metric and the value is the average value over all users; and without
    the average
    """
    results = defaultdict(list)
    results_samples = defaultdict(list)
    for k_idx, m in enumerate(models):
        avg, sample = metrics.avg_neighborhood_size_q(m, n_queries=100)
        results["avg_neighborhood_size_q"].append(avg)
        results_samples["avg_neighborhood_size_q"].append(sample)
        avg, sample = metrics.avg_rating_overlap_q(m, n_queries=100)
        results["avg_rating_overlap_q"].append(avg)
        results_samples["avg_rating_overlap_q"].append(sample)
    return results, results_samples

def _evaluate(models, users=None):
    """
    Evaluates the models after all recommendation queries
    :param models: List of models
    :return: dict() where the key is the evaluation metric and the value is the average value over all users; and without
    the average
    """
    results = defaultdict(list)
    results_samples = defaultdict(list)
    for k_idx, m in enumerate(models):
        avg, sample = metrics.mean_absolute_error(m, users=users)
        results["mean_absolute_error"].append(avg)
        results_samples["mean_absolute_error"].append(sample)
        avg, sample = metrics.avg_privacy_risk_dp(m, users=users)
        results["avg_privacy_risk_dp"].append(avg)
        results_samples["avg_privacy_risk_dp"].append(sample)
        avg, sample = metrics.avg_privacy_risk(m, users=users)
        results["avg_privacy_risk"].append(avg)
        results_samples["avg_privacy_risk"].append(sample)
        avg, sample = metrics.ndcg(m, users=users)
        results["avg_ndcg"].append(avg)
        results_samples["avg_ndcg"].append(sample)

        frac_vulnerables = metrics.fraction_vulnerables(m, users=users)
        results["fraction_vulnerables"].append(frac_vulnerables)
        if 1 - frac_vulnerables > 0:
            below_threshold_pr = (m.privacy_risk[m.privacy_risk < m.threshold])

        else:
            # if there are no secures
            below_threshold_pr = [0]
        results["avg_privacy_risk_dp_secures"].append(np.mean(below_threshold_pr))
        results_samples["avg_privacy_risk_dp_secures"].append(below_threshold_pr)

        results["recommendation_frequency"].append(metrics.recommendation_frequency(m, n=10, users=users))

    return results, results_samples

def significance_tests(results1, results2):
    """
    Performs significance tests between results1 and results2
    :param results1: user-wise values for the evaluation metrics
    :param results2: user-wise values for the evaluation metrics
    :return: dictionary of results of the significance tests
    """
    significance = dict()

    mae_significance = dict()
    mae_significance["<"] = _significance_test(results1["mean_absolute_error"], results2["mean_absolute_error"], h0="<")
    mae_significance[">"] = _significance_test(results1["mean_absolute_error"], results2["mean_absolute_error"], h0=">")
    mae_significance["=="] = _significance_test(results1["mean_absolute_error"], results2["mean_absolute_error"], h0="==")
    significance["mean_absolute_error"] = mae_significance

    pr_significance = dict()
    pr_significance["<"] = _significance_test(results1["avg_privacy_risk_dp"], results2["avg_privacy_risk_dp"], h0="<")
    pr_significance[">"] = _significance_test(results1["avg_privacy_risk_dp"], results2["avg_privacy_risk_dp"], h0=">")
    pr_significance["=="] = _significance_test(results1["avg_privacy_risk_dp"], results2["avg_privacy_risk_dp"], h0="==")
    significance["privacy_risk_dp"] = pr_significance

    pr_secure_significance = dict()
    pr_secure_significance["<"] = _significance_test(results1["avg_privacy_risk_dp_secures"], results2["avg_privacy_risk_dp_secures"], h0="<")
    pr_secure_significance[">"] = _significance_test(results1["avg_privacy_risk_dp_secures"], results2["avg_privacy_risk_dp_secures"], h0=">")
    pr_secure_significance["=="] = _significance_test(results1["avg_privacy_risk_dp_secures"], results2["avg_privacy_risk_dp_secures"], h0="==")
    significance["privacy_risk_dp_secures"] = pr_secure_significance

    neighborhood_size_significance = dict()
    neighborhood_size_significance["<"] = _significance_test_q(results1["avg_neighborhood_size_q"], results2["avg_neighborhood_size_q"], h0="<")
    neighborhood_size_significance[">"] = _significance_test_q(results1["avg_neighborhood_size_q"], results2["avg_neighborhood_size_q"], h0=">")
    neighborhood_size_significance["=="] = _significance_test_q(results1["avg_neighborhood_size_q"], results2["avg_neighborhood_size_q"], h0="==")
    significance["avg_neighborhood_size_q"] = neighborhood_size_significance

    rating_overlap_significance = dict()
    rating_overlap_significance["<"] = _significance_test_q(results1["avg_rating_overlap_q"], results2["avg_rating_overlap_q"], h0="<")
    rating_overlap_significance[">"] = _significance_test_q(results1["avg_rating_overlap_q"], results2["avg_rating_overlap_q"], h0=">")
    rating_overlap_significance["=="] = _significance_test_q(results1["avg_rating_overlap_q"], results2["avg_rating_overlap_q"], h0="==")
    significance["avg_rating_overlap_q"] = rating_overlap_significance

    return significance

def _mann_whitney_u_test(x, y, alternative="less"):
    """
    performs the mann whitney u test
    :param x: first sample
    :param y: second sample
    :param alternative: alternative hypothesis
    :return: U score, p value and effect size r
    """
    u, p = mannwhitneyu(x, y, alternative=alternative)
    nx = len(x)
    ny = len(y)
    mu_u = (nx * ny) / 2
    sigma_u = np.sqrt(nx * ny * (nx + ny + 1) / 12)
    z = (u - mu_u) / sigma_u
    n = len(x) + len(y)
    r = z / np.sqrt(n)

    return u, p, r

def _significance_test_q(x, y, h0="<"):
    """
    preforms significance tests after q recommendation queries
    :param x: first sample
    :param y: second sample
    :param h0: null hypothesis
    :return: list of results (for every q one result dict)
    """
    n_ks = len(x)
    results = []
    for k_idx in range(n_ks):
        n_queries = len(x[k_idx])
        results_k = []
        for q in range(n_queries):
            n_samples = len(x[k_idx][q])
            if np.array_equal(x[k_idx][q], y[k_idx][q]):
                results_k.append({"p": np.inf, "sample_size": n_samples, "r": np.nan, "U": np.nan})
            elif h0 == "<":
                u, p, r = _mann_whitney_u_test(x[k_idx][q], y[k_idx][q], alternative="greater")
                results_k.append({"p": p, "sample_size": n_samples, "r": r, "U": u})
            elif h0 == ">":
                u, p, r = _mann_whitney_u_test(x[k_idx][q], y[k_idx][q], alternative="less")
                results_k.append({"p": p, "sample_size": n_samples, "r": r, "U": u})
            elif h0 == "==":
                u, p, r = _mann_whitney_u_test(x[k_idx][q], y[k_idx][q], alternative="two-sided")
                results_k.append({"p": p, "sample_size": n_samples, "r": r, "U": u})
            else:
                print("Unknown h0!")
        results.append(results_k)

    return results

def _significance_test(x, y, h0="<"):
    """
    preforms significance tests after all recommendation queries
    :param x: first sample
    :param y: second sample
    :param h0: null hypothesis
    :return: list of results (for every q one result dict)
    """
    n_ks = len(x)
    n_samples = len(x[0])

    #n_ks, n_samples = np.array(x).shape
    results = []
    for k_idx in range(n_ks):
        if np.array_equal(x[k_idx], y[k_idx]):
            print("x equal y")
            results.append({"p": np.inf, "sample_size": n_samples, "r": np.nan, "U": np.nan})
        elif h0 == "<":
            u, p, r = _mann_whitney_u_test(x[k_idx], y[k_idx], alternative="greater")
            results.append({"p": p, "sample_size": n_samples, "r": r, "U": u})
        elif h0 == ">":
            u, p, r = _mann_whitney_u_test(x[k_idx], y[k_idx], alternative="less")
            results.append({"p": p, "sample_size": n_samples, "r": r, "U": u})
        elif h0 == "==":
            u, p, r = _mann_whitney_u_test(x[k_idx], y[k_idx], alternative="two-sided")
            results.append({"p": p, "sample_size": n_samples, "r": r, "U": u})
        else:
            print("Unknown h0!")

    return results


from django.contrib.auth import login, logout
from django.contrib.auth.models import User
from django.core.mail import send_mail
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.urls import reverse_lazy, reverse
from django.conf import settings

from django.views import View
from django.views.generic import ListView, DetailView, DeleteView, UpdateView, CreateView

from Tours_app.forms import LoginForm, SignUpForm, EmailForm
from Tours_app.models import Tour, Review, Category, Reservation

class HomePageView(View):
    def get(self, request):
        return render(request, 'base.html')

class AboutPageView(View):
    def get(self, request):
        return render(request, 'about.html')

class TourListView(ListView):
    def get(self, request):
        tours = Tour.objects.all()
        types = Category.objects.all()
        return render(request, 'tourlist.html', {'objects':tours , 'types': types})

class CategoryListView(View):
    def get(self, request, pk):
        category = Category.objects.get(pk=pk)
        return render(request, 'categories.html', {'category': category})

class AddTourView(CreateView):
    model = Tour
    fields = ['name', 'days', 'start_date', 'end_date', 'category', 'price']
    template_name = 'touraddform.html'

    def get_success_url(self):
        tour = self.object
        url = reverse('tourlist')
        return url

class UpdateTourView(UpdateView):
    model = Tour
    fields = ['name', 'days', 'start_date', 'end_date', 'category', 'price']
    template_name = 'tourupdate.html'

    def get_success_url(self):
        tour = self.object
        tour.save()
        url = reverse('tourlist')
        return url

class DeleteTourView(DeleteView):
    model = Tour
    template_name = 'tourdelete.html'
    success_url = reverse_lazy('tourlist')

class LoginView(View):
    def get(self, request):
        form = LoginForm()
        return render(request, 'loginform.html', {'form': form})

    def post(self, request):
        loginForm = LoginForm(request.POST)
        if loginForm.is_valid():
            login(request, loginForm.user)
            return redirect("/")
        return render(request, 'loginform.html', {'form': loginForm})

class LogoutView(View):
    def get(self, request):
        logout(request)
        return redirect('/')

class SignUp(View):
    def get(self, request):
        form = SignUpForm()
        return render(request, 'signupform.html', {'form': form})

    def post(self, request):
        form = SignUpForm(request.POST)
        if form.is_valid():
            del form.cleaned_data['re_password']
            User.objects.create_user(**form.cleaned_data)
            return redirect('/')
        return render(request, 'signupform.html', {'form': form})

class AddReviewView(CreateView):
    model = Review
    fields = '__all__'
    template_name = 'reviewform.html'

    def get_success_url(self):
        review = self.object
        url = reverse('review')
        return url

class ReviewView(ListView):
    def get(self, request):
        reviews = Review.objects.all()
        return render(request, 'reviewlist.html', {'reviews': reviews})

class TourDetails(DetailView):
    model = Tour
    fields = '__all__'
    template_name = 'tourdetails.html'

    def get_success_url(self):
        tour = self.object
        url = reverse('tourdetails', args=(tour.id))
        return url

class ContactView(View):
    def get(self, request):
        form = EmailForm()
        return render(request, 'contact.html', {'form': form})

    def post(self, request):
        form = EmailForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            subject = "Kontakt z adventuretours"
            imie = cd['imie']
            tekst = cd['tekst']
            send_mail(subject, imie, settings.DEFAULT_FROM_EMAIL, [cd['odbiorca']])
            messageSent = True
        else:
            form = EmailForm()
        return render(request, 'contact.html', {
            'form': form,
            'messageSent': messageSent,

        })

class ReservationView(CreateView):
    model = Reservation
    fields = '__all__'
    template_name = 'reservationform.html'

    def get_success_url(self):
        url = reverse('reservation')
        return url

class ReservationList(View):
    def get(self, request):
        tour = Tour.objects.all()
        reservation = Reservation.objects.all()
        return render(request, 'reservationlist.html', {'reservation': reservation, 'tour': tour})