package game.logic;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Scanner;

/**
 * A class that holds our implementation of a vector, according to our needs.
 */
public class Vector {
	private double[] values;
	private int size;
	
	/**
	 * Initialize a vector with the given size.
	 * @param size the size of the vector.
	 */
	public Vector(int size){
		this.values = new double[size];
		this.size = size;
	}
	
	/* Getters */
	public int getSize(){
		return this.size;
	}
	
	public double[] getValues(){
		return this.values;
	}
	
	/**
	 * Set a value at the given index.
	 * 
	 * @param index the index in the vector.
	 * @param newValue the value to be assigned to that index.
	 */
	public void setValue(int index, double newValue){
		this.values[index] = newValue;
	}
	
	/**
	 * Get a value of a given index. If index is not a valid number within the range,
	 * return Double.MAX_VALUE
	 * 
	 * @param index the index of the element we want.
	 * @return the value at that index.
	 */
	public double getValue(int index){
		if (index < size && index >= 0){
			return values[index];
		} else {
			return Double.MAX_VALUE;
		}
	}
	
	/**
	 * Zero all values of the vector.
	 */
	public void setZero(){
		for (int i = 0; i < this.size; i++){
			this.values[i] = 0.0;
		}
	}
	
	/**
	 * Add a given value to a given index.
	 * 
	 * @param index the index we want to add the value to.
	 * @param addValue value to be added.
	 * 
	 * @return 0 iff function succeeded.
	 */
	public int addValueToIndex(int index, double addValue){
		int status = 0;
		
		if (index >= 0 && index < this.size){
			this.values[index] += addValue;
		} else {
			status = -1;
		}
		
		return status;
	}
	
	/**
	 * This method copies all the values of the given vector into this vector,
	 * in case the sizes are equal.
	 * @param v1 The vector to copy the weights from.
	 * 
	 * @return 0 iff function succeeded.
	 */
	public int copyVector(Vector v1){
		int status = 0;
		
		if (this.size == v1.getSize()){
			for (int i = 0; i < this.size; i++){
				this.setValue(i, v1.getValue(i));
			}
		} else {
			status = -1;
		}
		
		return status;
	}
	
	/**
	 * Generate a string of the vector with indices next to values.
	 * 
	 * @return the string representing the vector.
	 */
	public String indexVector(){
		String output = "[";
		
		for (int i = 0; i < size; i++){
			if (i == size - 1){
				output = output + "[" + i + "] " + values[i] + "]";
			} else {
				output = output + "[" + i + "] " + values[i] + ", ";
			}
		}
		
		return output;
	}
	
	@Override
	public String toString(){
		String output = "[";
		
		for (int i = 0; i < size; i++){
			if (i == size - 1){
				output = output + values[i] + "]";
			} else {
				output = output + values[i] + ", ";
			}
		}
		
		return output;
	}
	
	/**
	 * This method multiplies the given vector by the given scalar,
	 * and returns the resulting Vector.
	 * 
	 * @param v vector to be multiplied.
	 * @param alpha the value to multiply.
	 * 
	 * @return a new vector which is the result of this multiplication.
	 */
	public static Vector MultiplyByScalar(Vector v, double alpha){
		Vector result = new Vector(v.getSize());
		result.setZero();
		
		for (int i = 0; i < v.getSize(); i++){
			result.setValue(i, alpha * v.getValue(i));
		}
		
		return result;
	}
	
	/**
	 * Generate the dot product of two vectors of the same size.
	 * 
	 * @param v1 the first vector
	 * @param v2 the second vector
	 * 
	 * @return a vector which is the result of this dot product.
	 */
	public static double dotProduct(Vector v1, Vector v2){
		double sum = 0.0;
		
		if (v1.getSize() == v2.getSize()){
			for (int i = 0; i < v1.getSize(); i++){
				sum += v1.getValue(i)*v2.getValue(i);
			}
		} else {
			System.out.println("Cannot multiply vectors with different length");
		}
		
		return sum;
	}
	
	/**
	 * This function normalizes the vector v1 by calculating the vector's norm
	 * and dividing each value by the norm. 
	 * @param v1 The vector to be normalized.
	 * 
	 * @return The normalized vector.
	 */
	public static Vector NormalizeVector(Vector v1) {
		/*calculate vectors norm*/
		double norm = vectorNorm(v1);
		return Vector.MultiplyByScalar(v1, 1/norm);
	}
	
	/**
	 * Calculates the vector norm.
	 * 
	 * @param v1 The vector to calculate the norm on.
	 * 
	 * @return The value representing the vector's norm.
	 */
	public static double vectorNorm (Vector v1) {	
		double norm = 0.0;
		double sum = 0.0;
		for (int i = 0; i < v1.getSize(); i++){
			sum += Math.pow((v1.getValue(i)),2);
		}
		norm = Math.sqrt(sum); 
		return norm;
		
	}
	
	/**
	 * A way to normalize the weights by dividing the vector by the maximum absolute value. 
	 * @param v1 the first vector
	 * @param v2 the second vector
	 * 
	 * @return The dot product after the normalization.
	 */
	public static double NormaldotProduct(Vector v1, Vector v2){
		Vector result = new Vector(v1.getSize());
		double max = 0.0;
		
		double sum = 0.0;
		
		if (v1.getSize() == v2.getSize()){
			for (int i = 0; i < v1.getSize(); i++){
				result.setValue(i, v1.getValue(i) * v2.getValue(i));
			}
		} else {
			System.out.println("Cannot multiply vectors with different length");
		}
		
		for (int i = 0; i < result.getSize(); i++){
			if (max < Math.abs(result.getValue(i))){
				max = Math.abs(result.getValue(i));
			}
		}
		
		if (max != 0){
			result = Vector.MultiplyByScalar(result, 1.0/max);
		}
		
		for (int i = 0; i < result.getSize(); i++){
			sum += result.getValue(i);
		}
		
		return sum;
	}
	
	/**
	 * Add two vectors of the same size.
	 * @param v1 the first vector.
	 * @param v2 the second vector.
	 * 
	 * @return a vector which is the result of this addition.
	 */
	public static Vector AddVectors(Vector v1, Vector v2){
		Vector result = null;
		
		if (v1.getSize() == v2.getSize()){
			result = new Vector(v1.getSize());
			result.setZero();
			
			for (int i = 0; i < v1.getSize(); i++){
				result.setValue(i, v1.getValue(i) + v2.getValue(i));
			}
		}
		
		return result;
	}
	
	/**
	 * Write the given vector to the file indicated by filename.
	 * 
	 * @param filename the file to write the vector to.
	 * @param vector the vector to be written.
	 */
	public static void writeToFile(String filename, Vector vector){

		PrintWriter out = null;
		try {
			out = new PrintWriter(new BufferedWriter(new FileWriter(filename,
					true)));
			out.println(vector);
		} catch (FileNotFoundException e) {

			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		out.close();

	}
	
	/**
	 * Read a vector from the given file. The vector must be written in the format
	 * of the toString method.
	 * 
	 * @param filename the file containing the vector.
	 * 
	 * @return the vector that was read.
	 */
	public static Vector readfromFile(String filename) {
		String lastLine = null;

		Scanner scanner;
 		try {
			scanner = new Scanner(new File(filename));

			while (scanner.hasNextLine())
				lastLine = scanner.nextLine();

			scanner.close();
		} catch (IOException e) {

			e.printStackTrace();
		}
		
		Vector v = Vector.StringToVector(lastLine);
		
		return v;
	}
	
	public static Vector readfromFile(InputStream filestream) {
		String lastLine = null;

		Scanner scanner;
 		
			scanner = new Scanner(filestream);

			while (scanner.hasNextLine())
				lastLine = scanner.nextLine();

			scanner.close();
	
		Vector v = Vector.StringToVector(lastLine);
		
		return v;
	}
	
	/**
	 * Parse a String object into a Vector object.
	 *  
	 * @param str a string representing the vector.
	 * 
	 * @return the parsed vector.
	 */
	public static Vector StringToVector(String str) {
		Vector v = new Vector(GameBoard.VectorSize);
		
		/*take off '[' and ']'*/
		str = str.substring(1, str.length()-1);
		
		String[] weights = str.split(",");
		for (int i = 0; i < weights.length; i++) {
			v.setValue(i, Double.parseDouble(weights[i]));
		}
		return v;
		
	}
	
}
