<?

class pattern_discovery {

	private $points;

	private $distances;

	public function __construct($points) {
		$this->points = $points;
	}

	public function calculate_distances() {
		$m_vector = new m_vector();

		$this->distances = [];

		$object->log($object->toJSON($this->points));

		$object->log('len is');

		$object->log($this->points->length);

		$object->log($this->points[0]->length);

		foreach($this->points as $point_a) {
			foreach($this->points as $point_b) {
				$point_distances = [];
				if(!$object->equals($point_a, $point_b)) {
					foreach($point_a as $key => $row_a) {
						$row_b = $point_b[$key];
						/*$point_a_set = $object->map($row_a, function($a) {
							return $a[0];
						});
						$point_b_set = $object->map($row_b, function($a) {
							return $a[0];
						});*/
						$object->log('point a-b');
						$object->log($object->toJSON($row_a));
						$object->log($object->toJSON($row_b));
						/*$distance = $m_vector->length_value($point_a_set, $point_b_set);
						$object->log('distance: '.$distance);
						$point_distances[] = $distance;*/

						return false;
					}

					/*$distance = $m_vector->length_value($point_a, $point_b);
					$point_distances[] = $distance;*/
				} else {
					$object->log('equals');
				}
			}
			$object->sort($point_distances, function($a, $b) {
				if($a < $b) {
					return 1;
				} else if($a == $b) {
					return 0;
				}
				return (-1);
			});
			if($point_distances->length > 0) {
				$this->distances[] = $point_distances;
			}
		}

		$object->log($object->toJSON($this->distances));

		$set_distances = [...$this->distances];

		$mid_point = $set_distances->length;
		$mid_point = $math->floor($mid_point / 2);

		/*$average_distances = [];

		foreach($set_distances as $distances) {
			$average_distances[] = $m_vector->length_value($distances);
		}

		$object->log($object->toJSON($average_distances));*/

		/*$distances_values = [];

		foreach($set_distances as $distances) {
			$avg_values = [];
			foreach($set_distances as $distances_compare) {
				if(!$object->equals($distances, $distances_compare)) {
					$distances = [...$distances];
					$distances_compare = [...$distances_compare];

					$object->log($object->toJSON($distances_compare));
					$object->log($object->toJSON($distances));

					$subset = $object->splice($distances_compare, 0, $mid_point);
					$subset_compare = $object->splice($distances, 0, $mid_point);

					$avg_values[] = $m_vector->length_value($subset, $subset_compare);
				}
			}
			$distances_values = $avg_values;
		}

		$distances_lengths = [];

		foreach($distances_values as $distance_item) {
			$distances_lengths[] = $m_vector->length_value($distance_item);
		}

		$object->log($object->toJSON($distances_lengths));*/
	}

}

class layer_generator {
	
	private $points; /* each point is an n-dimensional vector */


	/*
		1. originally H,S,L values
		2. filtered layer
		...
		x. highest level patterns / groups
			-the result with the most consistent patterns / groups will be favoured ?
		
		þyrfti að vera þannig að outputtið geti detectað groups frá luminosity

	-þannig eftir þetta fyrsta ferli, verður L assignað við öll layerin ?
	og H,S sem outputtið ?

	-þeir filterar sem skila high level mynstrum með mest consistent litun aetti að vera valin?
	


		nope:

		output : Luminosity ? og svo reversa ferlið ?	
	*/

	/*
		autoencoder result => og svo reversa ferlið?
			1. Luminosity
			...
			output: H,S
	*/	

	public $set_points;

	public function __construct($points, $size, $value_point_dimensions, $filter_count, $layer_count) {
		$generator = new generator();

		$random_points = $generator->random_points($size, $value_point_dimensions, 0);
		/*$object->log('start');
		$object->log($object->video);
		$object->log($object->video->get_img_arr);
		*/


		/*$random_points = [];*/

		/*$this->set_points = $random_points;*/


		$object->log('started');

		$this->markers = $random_points;


		$field = new field($size, $random_points);
		$object->log('field');

		$this->input_field = $field;


		$this->layers = [
			[$field]
		];

		$set_size = 3;/*$layer_count;
		$layer_count = $layer_count + 1;*/

		$is_initial = true;

		$object->log('generate');
		/*while($this->layers->length < $layer_count) {*/
		$this->res_field = $this->generate_layer($filter_count, $field, $set_size, $is_initial);
		/*$this->layers[0][0] = $res_field;*/
		$object->log('generated');
			/*$is_initial = false;
			$set_size = $set_size - 1;
		}*/
		/*$this->generate_layer($filter_count, $field, true);*/
	}

	public $markers = NULL;

	public $input_field;

	public $res_field;

	public function continue() {
		$object->log('continue');
		foreach($this->filters as $filter) {
			$object->log('filter');
			$res_field = $filter->perform_layer($this->layers[0][0]);
			/*$this->layers[0][0] = $res_field;*/
			$this->res_field = $res_field;
			return $res_field;
		}
	}

	public function get_points($layer_index) {
		$field_points = [];

		$layer = $this->layers[$layer_index];

		/*$field_points[] = $this->layers[0][0]->get_field();*/

		/*foreach($this->layers as $layer) {*/

			foreach($layer as $field) {
				$field_points[] = $field->get_field();
			}
		/*}*/

		return $field_points;
	}

	private $layers;

	public $adders = [];

	public $filters = [];

	public $map;

	public function generate_layer($filter_count, $field, $set_size=3, $is_initial=true) {
		/*$layer = [];

		$this->adders = [];

		$this->map = [];*/

		$index = 0;
		while($index < $filter_count) {

			/*$adder_squared = new adder_squared(3);*/

			$filter = new filter();

			$this->filters[] = $filter;

			$adder = new adder_linear($set_size * $set_size);
			$this->adders[] = $adder;
			$filter->adder = $adder;
			$filter->init($set_size, function($slice, $self) {
				$iterator = new iterator();

				$values_linear = [];

				/*$object->log('slice is');
				$object->log($object->toJSON($slice));
				$object->log('size is : '.$set_size);*/

				/*$map_item = $this->map;*/

				$iterator->iterate($set_size, function($x, $y) {
					/*$map_item[] = [
						($x - 1), ($y - 1), $slice[$y][$x][0], $slice[$y][$x][$adder->result_parameter_index]
					];*/

					/*$object->log('x: '.$x.' y: '.$y);
					$object->log('slice value: '.$slice[$y][$x][0]);*/  /*x, y*/
					$values_linear[] = $slice[$y][$x][0];  /*x, y*/

					return NULL;
				});

				/*$this->map[] = $map_item;*/

				/*$object->log('values values_lienar');
				$object->log($object->toJSON($values_linear));*/

				return $adder->perform($values_linear);
			});
		

			/*$res_field = $filter->perform_layer($field);


			return $res_field;*/
			/*$layer[] = $res_field;*/

			$index = $index + 1;
		}

		/*$this->layers[] = $layer;*/
	}

}

/*class adder_squared {

	public $adder;

	public $size;

	public function __construct($size) {
		$this->size = $size;
		$this->adder = new adder_linear($size*$size);
	}

	public function resolve_coordinates($x, $y) {
		$index = ($x*$this->size) + $y;
		return $index;
	}

	public function perform($x, $y) {
		$index = $this->resolve_coordinates($x, $y);
		return $this->adder->perform($index);
	}
}*/

class adder_linear {
	public $error_list = [];

	public $parameters;

	public function __construct($len) {
		$this->parameters = [];
		while($this->parameters->length < $len) {
			$this->parameters[] = ($math->random_int(100) / 100) + (1/100);
		}
		
		$m_vector = new m_vector();

		$this->parameters = $m_vector->normalize($this->parameters);
	}

	public function reset() {
		$m_vector = new m_vector();

		$m_vector->denormalize_vertically($this->parameters);
	}

	/*public function serialize() {
		return $object->toJSON($this->parameters);
	}*/

	public function get_parameters() {
		return $this->parameters;
	}

	public function set_parameters($parameters) {
		$this->parameters = $parameters;
	}

	public $result_parameter_index = 1;

	public $learning_rate = 1/15;


	public function get_error($values) {
		$values = $values[0];
		$result = $values['filtered'];

		$original_values = $values['original_values'];


		/*$object->log('get error');

		$object->log($object->toJSON([$result]));

		$object->log($object->toJSON($original_values));*/
		/*foreach($original_values as $key => $original_value_row) {
			foreach($original_value_row as $row_key => $original_value) {
				$original_value = $original_value[$this->result_parameter_index];

				$error = $original_value - $result;

				$error = $error * $error;

				$object->log('error : '.$error);

				$error = $error * $this->learning_rate;

				$this->adjust_from_error(($row_key + ($key * 3)), $error);
			}
		}*/

		$iterator = new iterator();

		$set_size = $original_values->length;

		$index_value = 0;

		$parameter_index = $this->result_parameter_index;

		$self = $this;

		$error = 0;

		$iterator->iterate($set_size, function($x, $y) {
			$original_value_item = $original_values[$x][$y];
			$original_value = $original_value_item[$parameter_index];


			$error = $original_value - $result;

			/*$error = $error * $error;*/

			$object->log('error : '.$error);

			$error = $error * $this->learning_rate;

			$self->adjust_from_error($index_value, $error);
			$index_value = $index_value + 1;
		});

		return $error;
	}

	public function get_error_alt($input, $target) {
		$values = $values[0];

		$result = $this->perform($input);

		$error = $target - $result;

		$error = $error * $error;

		foreach($this->parameters as $key => $value) {
			/*$this->adjust_from_error($key, ($error * $value));*/
			if($value > 0) {
				$error_value = ($error / $value) * (1/2);
			} else {
				$error_value = $error * (1/2);
			}
			/*$value;*/
			/* var minus */
			if($error_value != 0) {
				$this->parameters[$key] = $this->parameters[$key] / $error_value;
			}

			/*if($this->parameters[$key] <= 0) {
				$this->parameters[$key] = 1;
			}*/
		}

		$m_vector = new m_vector();


		$this->parameters = $m_vector->normalize_linear($this->parameters);

		return $error;
	}

	public function perform_normalize() {
		/*$m_vector = new m_vector();

		$this->parameters = $m_vector->normalize($this->parameters);*/
		return false;
	}


	public function get_error_alt_depr($input, $target) {
		$values = $values[0];

		$result = $this->perform($input);

		$error = $target - $result;

		$error = $error * $error;

		foreach($this->parameters as $key => $value) {
			/*$this->adjust_from_error($key, ($error * $value));*/
			if($value > 0) {
				$error_value = ($error / $value) * (1/2);
			} else {
				$error_value = $error * (1/2);
			}
			/*$value;*/
			/* var minus */
			$this->parameters[$key] = $this->parameters[$key] + $error_value; /*var minus og gaf stækkandi*/

			/*if($this->parameters[$key] <= 0) {
				$this->parameters[$key] = 1;
			}*/
		}


		$m_vector = new m_vector();

		$this->parameters = $m_vector->normalize_linear($this->parameters);

		return $error;
	}

	public function perform($input) {
		$output = 0;
		foreach($input as $key => $value) {
			$output = $output + ($this->parameters[$key] * $value);
		}
		return $output;
	}

	public function adjust_from_error($key, $error_value) {
		/*foreach($this->parameters as $key => $value) {
			if($key != $set_key) {*/
				$this->parameters[$key] = $this->parameters[$key] - $error_value;
			/*}
		}*/
		/*if($this->parameters[$key] <= 0) {
			$this->parameters[$key] = 1;
		}*/

		$m_vector = new m_vector();

		$this->denormalize_vertically($this->parameters);

		$this->parameters = $m_vector->normalize($this->parameters);

	}
}

class filter {

	/*
		input matrixa sem er úrtak úr n-víðu plani

		[
			[h, s, l], [h, s, l], [h, s, l], 
			[h, s, l], [h, s, l], [h, s, l], 
			[h, s, l], [h, s, l], [h, s, l], 
		]

		inntakid vid notkun myndi bara vera ?
		[
			l,	l, 	l,
			l,	l, 	l,
			l,	l, 	l
		]

		er væntanlega hægt að hafa h, s, l sem input
		fa l sem uttak og nota pattern discovery inna milli og svo reversa filterunum ?

		transformation matrixan verkar á þetta input og skilar

		[
			[t0, t1, t2... tn], [t0, t1, t2... tn], [t0, t1, t2... tn],
			[t0, t1, t2... tn], [t0, t1, t2... tn], [t0, t1, t2... tn],
			[t0, t1, t2... tn], [t0, t1, t2... tn], [t0, t1, t2... tn],
		]

		hvert layer hefur sömu dimensions og input matrixan þar af leiðandi
		er h og s gildin borin saman og lika vid loka nidurstoduna?

		loka filterinn skilar

		[
			[l, h, s], [l, h, s], [l, h, s],
			[l, h, s], [l, h, s], [l, h, s],
			[l, h, s], [l, h, s], [l, h, s],
		] ???

		thannig ad nidurstadan er hue og saturation fyrir hvert luminosity gildi
		luminosity gildin koma beint fra fyrst layerinu
		lika haegt ad lata skila:


	*/

	/*private $transformation_matrix;

	public function __construct($transformation_matrix=NULL) {
		$this->transformation_matrix = $transformation_matrix;
	}

	public function init_random($input_x=3, $input_y=3, $value_dimensions=3) {
		return false;
	}*/

	public $adder;

	public function add_adder($adder) {
		$this->adder = $adder;
	}

	public function get_output_field($input_field) {
		$field_layer = $this->perform_layer($input_field);

		$iterator = new iterator();

		$set_points = $field_layer->get_field();

		return $iterator->iterate($set_points->length, function($x, $y) {
			return $set_points[$y][$x]['filtered'][0];  /*x, y*/
		});
	}



	private $callbacks;

	public function __construct() {
		$this->callbacks = $object->create();
	}

	private $size;

	public function init($size=3, $callback) {
		$this->size = $size;
		$this->callbacks['main'] = $callback;
	}

	public function perform_layer($field) {
		/*$new_points = [];*/

		$filter_size = $this->size;
		$callback = $this->callbacks['main'];

		$object->log('new iterator');

		$iterator = new iterator();
		$object->log('after iterator');

		/*$object->log($field);
		$object->log($field->get_size());*/

		$self = $this;

		$new_points = $iterator->iterate($field->get_size(), function($x, $y) {
			$object->log('in iterate callback');
			$slice = $field->get_cut($filter_size, $x, $y);
			if($slice->length > 0) {
				/*$object->log('slice');
				$object->log($object->toJSON($slice));*/
				$filtered = $callback($slice, $self);
				if($filtered != (-1)) {
					/*$object->log('filtered');
					$object->log($filtered);*/


					return ['filtered' => $filtered, 'original_values' => $slice];
				}
			}
			return NULL;
			/*return (-1);*/
		});
		$output_field_layer = new field($field->get_size(), $new_points);
		return $output_field_layer;
	}

}

class field {

	private $size = NULL;

	private $points;

	public function get_size() {
		return $this->size;
	}

	public function set_points($points) {
		$this->points = $points;
	}

	public function __construct($size, $points=NULL) {
		$this->size = $size;
		$this->points = $points; /* two dimensional plane with a n-dimensional vector point type */
	}

	public function get_cut($size=3, $offset_y=0, $offset_x=0) {
		$points = $this->points;
		$results = [];

		if($offset_y >= 0 && $offset_x >= 0 && ($offset_y + $size) <= $this->size && ($offset_x + $size) <= $this->size) {
			$x_index = $offset_x;

			while($x_index < ($offset_x + $size)) {
				$y_vectors = [];
				$y_index = $offset_y;
				while($y_index < ($offset_y + $size)) {
					/*$results[] = $object->splice($points[$x_index], $offset_y, $size);*/
					$y_vectors[] = [...$points[$x_index][$y_index]];
					$y_index = $y_index + 1;
				}
				$results[] = $y_vectors;
				$x_index = $x_index + 1;
			}
		}


		/*$iterator = new iterator($size, function($x, $y) {

		});*/

		return $results;
	}

	public function get_field() {
		return $this->points;
	}
}

/*class iterator {

	public function iterate($size, $set_callback) {
		$object->log('iterate');
		$x_rows = [];
		while($x_rows->length < $size) {
			$y_vectors = [];
			$object->log('y vector');
			while($y_vectors->length < $size) {
				$value_points = $set_callback($x_rows->length, $y_vectors->length);
				$y_vectors[] = $value_points;
			}
			$x_rows[] = $y_vectors;
		}
		return $x_rows;
	}
}*/


class iterator {

	public function iterate($size, $set_callback) {
		$x_rows = [];
		while($x_rows->length < $size) {
			$y_vectors = [];
			while($y_vectors->length < $size) {
				$value_points = $set_callback($x_rows->length, $y_vectors->length);
				$y_vectors[] = $value_points;
			}
			$x_rows[] = $y_vectors;
		}
		return $this->only_values($x_rows);
	}

	public function only_values($x_rows) {

		$x_rows_output = [];
		/*while($x_rows->length < $size) {
			$y_vectors = [];
			$object->log('y vector');
			while($y_vectors->length < $size) {
				$value_points = $x_rows
				$y_vectors[] = $value_points;
			}
			$x_rows[] = $y_vectors;
		}*/
		foreach($x_rows as $y_vectors) {
			$y_output = [];
			foreach($y_vectors as $value_points) {
				if($value_points /*!= (-1)*/ !== NULL) {
					$y_output[] = $value_points;
				}
			}
			if($y_output->length > 0) {
				$x_rows_output[] = $y_output;
			}
		}
		return $x_rows_output;
	}
}

class generator {

	public function random_points($size, $value_dimensions, $set_values=NULL) {
		$x_rows = [];
		while($x_rows->length < $size) {
			$y_vectors = [];
			while($y_vectors->length < $size) {
				$value_points = [];
				if($set_values != NULL) {
					$value_points = ['marked' => false];
				} else {
					while($value_points->length < $value_dimensions) {
						$value_points[] = $math->random_int(100) / 100;
					}
				}
				$y_vectors[] = $value_points;
			}
			$x_rows[] = $y_vectors;
		}
		return $x_rows;
	}

}

?>