/**
 * Copyright (c) 2007, University of Pittsburgh
 * <p>
 * All rights reserved.
 * <p>
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * <p>
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * <p>
 * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution.
 * <p>
 * Neither the name of the University of Pittsburgh nor the names
 * of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written
 * permission.
 * <p>
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **/

package pitt.search.semanticvectors.vectors;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.logging.Logger;

import org.netlib.blas.BLAS;

import pitt.search.semanticvectors.FlagConfig;

/**
 * This class provides some standard vector methods. Many old methods have been removed
 * and are now implemented in vector and util classes for real, complex, and binary vectors.
 */
public class VectorUtils {
  @SuppressWarnings("unused")
  private static final Logger logger = Logger.getLogger(VectorUtils.class.getCanonicalName());

  /**
   * Get nearest vector from list of candidates.
   * @param vector The vector whose nearest neighbor is to be found.
   * @param candidates The list of vectors from whoe the nearest is to be chosen.
   * @return Integer value referencing the position in the candidate list of the nearest vector.
   */
  public static int getNearestVector(Vector vector, Vector[] candidates) {
    int nearest = 0;
    double maxSim = vector.measureOverlap(candidates[0]);
    for (int i = 1; i < candidates.length; ++i) {
      double thisDist = vector.measureOverlap(candidates[i]);
      if (thisDist > maxSim) {
        maxSim = thisDist;
        nearest = i;
      }
    }
    return nearest;
  }

  /**
   * Returns the sum overlap of the testVector with the subspace generated by the vectors,
   * which are presumed to be orthogonal.
   * See also {@link BinaryVectorUtils#compareWithProjection} for the binary case.
   */
  public static double compareWithProjection(Vector testVector, ArrayList<Vector> vectors) {

    float score = 0;
    for (int i = 0; i < vectors.size(); ++i) {
      score += Math.pow(testVector.measureOverlap(vectors.get(i)), 2);
    }
    return (float) Math.sqrt(score);

  }

  /**
   * The orthogonalize function takes an array of vectors and
   * orthogonalizes them using the Gram-Schmidt process. The vectors
   * are orthogonalized in place, so there is no return value.  Note
   * that the output of this function is order dependent, in
   * particular, the jth vector in the array will be made orthogonal
   * to all the previous vectors. Since this means that the last
   * vector is orthogonal to all the others, this can be used as a
   * negation function to give an vector for
   * vectors[last] NOT (vectors[0] OR ... OR vectors[last - 1].
   *
   * @param list vectors to be orthogonalized in place.
   */
  public static void orthogonalizeVectors(List<Vector> list) {
    switch (list.get(0).getVectorType()) {
      case REAL:
        RealVectorUtils.orthogonalizeVectors(list);
        break;
      case COMPLEX:
        ComplexVectorUtils.orthogonalizeVectors(list);
        break;
      case BINARY:
        BinaryVectorUtils.orthogonalizeVectors(list);
        break;
      default:
        throw new IncompatibleVectorsException("Type not recognized: " + list.get(0).getVectorType());
    }
  }

  /**
   * Returns a superposition of the form leftWeight*left + rightWeight*right.
   */
  public static Vector weightedSuperposition(
      Vector left, double leftWeight, Vector right, double rightWeight) {
    if ((left.getVectorType() != right.getVectorType())
        || (left.getDimension() != right.getDimension())) {
      throw new IncompatibleVectorsException(
          String.format("Incompatible vectors:\n%s\n%s", left.toString(), right.toString()));
    }
    switch (left.getVectorType()) {
      case REAL:
      case COMPLEX:
        Vector superposition = VectorFactory.createZeroVector(
            left.getVectorType(), left.getDimension());
        superposition.superpose(left, leftWeight, null);
        superposition.superpose(right, rightWeight, null);
        superposition.normalize();
        return superposition;
      case BINARY:
        return BinaryVectorUtils.weightedSuperposition(
            (BinaryVector) left, leftWeight, (BinaryVector) right, rightWeight);
      default:
        throw new IncompatibleVectorsException("Type not recognized: " + left.getVectorType());
    }
  }

  /**
   * Generates a basic sparse vector
   * with mainly zeros and some 1 and -1 entries (seedLength/2 of each)
   * each vector is an array of length seedLength containing 1+ the index of a non-zero
   * value, signed according to whether this is a + or -1.
   * <br>
   * e.g. +20 would indicate a +1 in position 19, +1 would indicate a +1 in position 0.
   *      -20 would indicate a -1 in position 19, -1 would indicate a -1 in position 0.
   * <br>
   * The extra offset of +1 is because position 0 would be unsigned,
   * and would therefore be wasted. Consequently we've chosen to make
   * the code slightly more complicated to make the implementation
   * slightly more space efficient.
   *
   * @return Sparse representation of basic ternary vector. Array of
   * short signed integers, indices to the array locations where a
   * +/-1 entry is located.
   */
  public static short[] generateRandomVector(int seedLength, int dimension, Random random) {
    boolean[] randVector = new boolean[dimension];
    short[] randIndex = new short[seedLength];

    int testPlace, entryCount = 0;

    /* put in +1 entries */
    while (entryCount < seedLength / 2) {
      testPlace = random.nextInt(dimension);
      if (!randVector[testPlace]) {
        randVector[testPlace] = true;
        randIndex[entryCount] = new Integer(testPlace + 1).shortValue();
        entryCount++;
      }
    }

    /* put in -1 entries */
    while (entryCount < seedLength) {
      testPlace = random.nextInt(dimension);
      if (!randVector[testPlace]) {
        randVector[testPlace] = true;
        randIndex[entryCount] = new Integer((1 + testPlace) * -1).shortValue();
        entryCount++;
      }
    }

    return randIndex;
  }

  /**
   * Utility method to compute scalar product (hopefully) quickly using BLAS routines
   * Arguably, this should be disseminated across the individual Vector classes
   */
  public static double scalarProduct(Vector v1, Vector v2, FlagConfig flagConfig, BLAS blas) throws IncompatibleVectorsException {
    if (!v1.getVectorType().equals(v2.getVectorType()))
      throw new IncompatibleVectorsException();

    switch (v1.getVectorType()) {
      case REAL:
        return blas.sdot(v1.getDimension(), ((RealVector) v1).getCoordinates(), 1, ((RealVector) v2).getCoordinates(), 1);
      case COMPLEX: //hermitian scalar product
        return blas.sdot(v1.getDimension()*2, ((ComplexVector) v1).getCoordinates(), 1, ((ComplexVector) v2).getCoordinates(), 1);
      case BINARY:
        ((BinaryVector) v1).tallyVotes();
        ((BinaryVector) v2).tallyVotes();
        return v1.measureOverlap(v2); 
      default:
        return 0;

    }
  }

  
  /**
   * Utility method to compute scalar product (hopefully) quickly using BLAS routines
   * Arguably, this should be disseminated across the individual Vector classes
   */
  public static double scalarProduct(Vector v1, Vector v2, FlagConfig flagConfig, BLAS blas, int[] permutations) throws IncompatibleVectorsException {
    
	  if (permutations == null)
		  return scalarProduct(v1, v2, flagConfig, blas);
	  
	  if (!v1.getVectorType().equals(v2.getVectorType()) || !v1.getVectorType().equals(VectorType.REAL))
      throw new IncompatibleVectorsException();
    
	   	double score = 0;
    
    	  for (int q=0; q < v1.getDimension(); q++)
    		  score += ((RealVector) v1).getCoordinates()[permutations[q]] * 
    				  ((RealVector) v2).getCoordinates()[q];
  		
       return score;
     }
 
  public static void superposeInPlace(Vector toBeAdded, Vector toBeAltered, FlagConfig flagConfig, BLAS blas, double weight, int[] permutation) throws IncompatibleVectorsException {
	    
	  if (permutation == null)
		  superposeInPlace(toBeAdded, toBeAltered, flagConfig, blas, weight);
		  else
		  {
		  
	  if (!toBeAdded.getVectorType().equals(toBeAltered.getVectorType()) || !toBeAdded.getVectorType().equals(VectorType.REAL))
	        throw new IncompatibleVectorsException();
	      
	  	     for (int q=0; q < toBeAdded.getDimension(); q++)
	  	    	 ((RealVector) toBeAltered).getCoordinates()[permutation[q]]
	  	    			 +=  ((RealVector) toBeAdded).getCoordinates()[q]*weight;
	      		
	       }
  }
  

  /**
   * Utility method to perform superposition (hopefully) quickly using BLAS routines
   * Arguably, this should be disseminated across the individual Vector classes
   *
   *
   * @param toBeAdded
   * @param toBeAltered
   * @param blas
   * @return
   */
  public static void superposeInPlace(Vector toBeAdded, Vector toBeAltered, FlagConfig flagConfig, BLAS blas, double weight) throws IncompatibleVectorsException {
    if (!toBeAdded.getVectorType().equals(toBeAltered.getVectorType()))
      throw new IncompatibleVectorsException();

    switch (toBeAdded.getVectorType()) {
      case REAL:
        blas.saxpy(flagConfig.dimension(), (float) weight, ((RealVector) toBeAdded).getCoordinates(), 1, ((RealVector) toBeAltered).getCoordinates(), 1);
        break;
      case COMPLEX:
        blas.saxpy(flagConfig.dimension()*2, (float) weight, ((ComplexVector) toBeAdded).getCoordinates(), 1, ((ComplexVector) toBeAltered).getCoordinates(), 1);
        break;
      case BINARY: //first attempt at this - add the results of the election multiplied by the number of votes to date
        ((BinaryVector) toBeAdded).tallyVotes();
        toBeAltered.superpose(toBeAdded, weight, null);
        break;
      default:
        break;

    }


  }


  /**
   * quick check for NaNs in real and complex vectors (returns false for binary vectors)
   * @param toTest
   * @return
   */
  public static boolean containsNaN(Vector toTest)
  {
	  if (toTest.getVectorType().equals(VectorType.BINARY)) return false;
	  
	  float[] coords = new float[0];
	  
	  if (toTest.getVectorType().equals(VectorType.REAL)) 
		  coords = ((RealVector) toTest).getCoordinates();
	  else if (toTest.getVectorType().equals(VectorType.COMPLEX)) 
		  coords = ((ComplexVector) toTest).getCoordinates();

	  for (float x:coords)
		  if (Float.isNaN(x)) return true;
	  
	  return false;
	  
  }
  
  
}

