package is2.data;



import gnu.trove.TIntDoubleHashMap;


import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

public final class FVR extends IFV { 
	
	private FVR subfv1;
	private FVR subfv2;
	private boolean negateSecondSubFV = false;
	
    private int size;
   
    
    
	// content of the nodes NxC
	private int m_index[];
	private float m_value[];
	
	// type of the nodes NxT
	
	public FVR() {
		this(10);
	}

	public FVR(int initialCapacity) {
		m_index = new int[initialCapacity];
		m_value = new float[initialCapacity];
	}

/*
	public FVR (FVR fv1, FVR fv2) {
		subfv1 = fv1;
		subfv2 = fv2;
	}
*/
	public FVR (FVR fv1, FVR fv2, boolean negSecond) {
		this(0);
		subfv1 = fv1;
		subfv2 = fv2;
		negateSecondSubFV = negSecond;
	}

	/**
	 * Read a feature vector
	 * @param index
	 * @param value
	 */
	public FVR(DataInputStream dos, int capacity) throws IOException {
		this(capacity);
		size= m_index.length;
		
		for (int i=0; i<size; i++) m_index[i] = dos.readInt();	
	}


	/**
	 * Read a feature vector
	 * @param index
	 * @param value
	 */
	public FVR(DataInputStream dos) throws IOException {
		this(dos.readInt());
		size= m_index.length;
		
		for (int i=0; i<size; i++)  m_index[i] = dos.readInt();
		
		
	}

	/**
     * Increases the capacity of this <tt>Graph</tt> instance, if
     * necessary, to ensure  that it can hold at least the number of nodes
     * specified by the minimum capacity argument. 
     *
     * @param   minCapacity   the desired minimum capacity.
     */
    private void ensureCapacity(int minCapacity) {
	
    	
    	if (minCapacity > m_index.length) {
    		
    		int oldIndex[] = m_index;
     		float oldValue[] = m_value;
     	    	
    		int newCapacity = ( m_index.length * 3)/2 + 1;
    
    		
    		if (newCapacity < minCapacity) newCapacity = minCapacity;

    		m_index = new int[newCapacity];
     		m_value = new float[newCapacity];
     	    
     		System.arraycopy(oldIndex, 0, m_index, 0, oldIndex.length);
     		System.arraycopy(oldValue, 0, m_value, 0, oldValue.length);
    		
    	}
    }
	
   
    final public int size() {
	  return size;
    }

    final public boolean isEmpty() {
	  return size == 0;
    }

    @Override
	final public void clear() {
    	size = 0;
    }
    
    
    final public int createFeature(int i, float v) {
    	
    	ensureCapacity(size+1); 
    	m_index[size] =i;
    	m_value[size] =v;
    	size++;
    	return size-1;
    }
   /* 
    final public int createFeature(int i) {
    	
    	ensureCapacity(size+1); 
    	m_index[size] =i;
    	size++;
    	return size-1;
    }
   */
    
	final public int getIndex(int i) {
		return m_index[i];
	}

	public void setIndex(int p, int i) {
		m_index[p] = i;
	}
    
	
	 /**
     * Trims the capacity of this <tt>Graph</tt> instance to true size.
     *  An application can use this operation to minimize
     * the storage of an <tt>Graph</tt> instance.
     */
    public void trimToSize() {

		if (size < m_index.length) {
		
		
			int oldIndex[] = m_index;
			
			m_index = new int[size];
			System.arraycopy(oldIndex, 0, m_index, 0, size);
			
		}
		
    }

    
	
	
    
 	final public void add(int i) {
        if (i>=0) {
			ensureCapacity(size+1); 
			m_index[size] =i;
			m_value[size] =1.0f;
			size++;
		}
    }

    final public void add(int i, float f) {
        if (i>=0) createFeature(i,f);
    }
   
	
	// fv1 - fv2
	public FVR getDistVector(FVR fl2) {
		return new FVR(this, fl2, true);
	}

	
	public double getScore(double[] parameters, boolean negate) {
		double score = 0.0;

		if (null != subfv1) {
			score += subfv1.getScore(parameters, negate);

			if (null != subfv2) {
				if (negate) score += subfv2.getScore(parameters, !negateSecondSubFV);
				else score += subfv2.getScore(parameters, negateSecondSubFV);
				
			}
		}

		if (negate) for(int i=0;i<size;i++)	score -= parameters[m_index[i]];
		else for(int i=0;i<size;i++) score += parameters[m_index[i]];
		
			
		return score;
	}
	
	
	final public float getScore(float[] parameters, boolean negate) {
		float score = 0.0F;

		if (null != subfv1) { 
			score += subfv1.getScore(parameters, negate);

			if (null != subfv2) {
				if (negate) score += subfv2.getScore(parameters, !negateSecondSubFV);
				else score += subfv2.getScore(parameters, negateSecondSubFV);
				
			}
		}
		
		// warning changed the  value 
		
		if (negate) for(int i=0;i<size;i++)	score -= parameters[m_index[i]]*m_value[i];
		else for(int i=0;i<size;i++) score += parameters[m_index[i]]*m_value[i];
			
		return score;
	}

	final public int getScore(short[] parameters, boolean negate) {
		int score = 0;

		if (null != subfv1) { 
			score += subfv1.getScore(parameters, negate);

			if (null != subfv2) {
				if (negate) score += subfv2.getScore(parameters, !negateSecondSubFV);
				else score += subfv2.getScore(parameters, negateSecondSubFV);
				
			}
		}
		
		// warning changed the value 
		
		if (negate) for(int i=0;i<size;i++)	score -= parameters[m_index[i]]*m_value[i];
		else for(int i=0;i<size;i++) score += parameters[m_index[i]]*m_value[i];
			
		return score;
	}

	
	

	
	
	public final void update(float[] parameters, float[] total, double alpha_k, double upd, boolean negate) {

		if (null != subfv1) {
			subfv1.update(parameters, total, alpha_k, upd, negate);

			if (null != subfv2 && negate) {
					subfv2.update(parameters, total, alpha_k, upd, !negateSecondSubFV);
				} else {
					subfv2.update(parameters, total, alpha_k, upd, negateSecondSubFV);
				}
			
		}
	
		if (negate) {
			for(int i=0;i<size;i++){ 
				parameters[getIndex(i)] -= alpha_k*m_value[i];
				total[getIndex(i)] -= upd*alpha_k*m_value[i];  
			}
		} else {
			for(int i=0;i<size;i++){ 
				parameters[getIndex(i)] += alpha_k*m_value[i];
				total[getIndex(i)] += upd*alpha_k*m_value[i]; //
			}
		}


	}
	
	

//	private static IntIntHash hm1;
//	private static IntIntHash hm2;
	
	private static TIntDoubleHashMap hd1;
	private static TIntDoubleHashMap hd2;
	
	
	public int dotProduct(FVR fl2) {

		if (hd1==null) hd1 = new TIntDoubleHashMap(size(),0.4F);
		else hd1.clear();
		
		addFeaturesToMap(hd1);
		
		if (hd2==null)hd2 = new TIntDoubleHashMap(fl2.size,0.4F);
		else hd2.clear();
		
		fl2.addFeaturesToMap(hd2);

		int[] keys = hd1.keys();

		int result = 0;
		for(int i = 0; i < keys.length; i++) result += hd1.get(keys[i])*hd2.get(keys[i]);

		return result;

	}

	
	private void addFeaturesToMap(TIntDoubleHashMap map) {
		
		if (null != subfv1) {
			subfv1.addFeaturesToMap(map);

			if (null != subfv2) {
			 subfv2.addFeaturesToMap(map, negateSecondSubFV);
				
			}
		}

	
		for(int i=0;i<size;i++) if (!map.adjustValue(getIndex(i), m_value[i])) map.put(getIndex(i), m_value[i]);
	
		
	
	}

	
	
	private void addFeaturesToMap(IntIntHash map, boolean negate) {
		
		if (null != subfv1) {
			subfv1.addFeaturesToMap(map, negate);

			if (null != subfv2) {
				if (negate)  subfv2.addFeaturesToMap(map, !negateSecondSubFV);
				else  subfv2.addFeaturesToMap(map, negateSecondSubFV);
				
			}
		}

		if (negate) { 
			for(int i=0;i<size;i++) if (!map  . adjustValue(getIndex(i), -1)) map.put(getIndex(i), -1);
		} else {
			for(int i=0;i<size;i++) if (!map.adjustValue(getIndex(i), 1)) map.put(getIndex(i), 1);
		}
		
	
	}

	private void addFeaturesToMap(TIntDoubleHashMap map, boolean negate) {
		
		if (null != subfv1) {
			subfv1.addFeaturesToMap(map, negate);

			if (null != subfv2) {
				if (negate)  subfv2.addFeaturesToMap(map, !negateSecondSubFV);
				else  subfv2.addFeaturesToMap(map, negateSecondSubFV);
				
			}
		}

		if (negate) { 
			for(int i=0;i<size;i++) if (!map  . adjustValue(getIndex(i), -m_value[i])) map.put(getIndex(i), -m_value[i]);
		} else {
			for(int i=0;i<size;i++) if (!map.adjustValue(getIndex(i), m_value[i])) map.put(getIndex(i), m_value[i]);
		}
		
	
	}
	
	
	
	@Override
	public final String toString() {
		StringBuilder sb = new StringBuilder();
		toString(sb);
		return sb.toString();
	}

	private final void toString(StringBuilder sb) {
		if (null != subfv1) {
			subfv1.toString(sb);

			if (null != subfv2)
				subfv2.toString(sb);
		}
		for(int i=0;i<size;i++)
			sb.append(getIndex(i)).append('=').append(m_value[i]).append(' ');
	}

	public void writeKeys(DataOutputStream dos) throws IOException {
		
	//	int keys[] = keys();
	//	dos.writeInt(keys.length);
	//	for(int i=0;i<keys.length;i++) {
	//		dos.writeInt(keys[i]);
	//	}
		
		
		//int keys[] = keys();
		dos.writeInt(size);
		for(int i=0;i<size;i++) {
			dos.writeInt(m_index[i]);
		}
	
	}

	/*

	final public static FVR cat(FVR f1,FVR f2) {
		if (f1==null) return f2;
		if (f2==null) return f1;
		return new FVR(f1, f2);
	}

	final public static FVR cat(FVR f1,FVR f2, FVR f3) {
		return FVR.cat(f1, FVR.cat(f2, f3));
	}
	final public static FVR cat(FVR f1,FVR f2, FVR f3, FVR f4) {
		return FVR.cat(f1, FVR.cat(f2, FVR.cat(f3, f4)));
	}
	*/

	
	final public static FVR read(DataInputStream dis) throws IOException {
		int cap = dis.readInt();
		if (cap == 0) return null; 
		return new FVR(dis,cap);

	}

	/* (non-Javadoc)
	 * @see is2.IFV#getScore()
	 */
	@Override
	public double getScore() {
		System.out.println("not implemented");
		new Exception().printStackTrace();
		// TODO Auto-generated method stub
		return 0;
	}

	/* (non-Javadoc)
	 * @see is2.IFV#clone()
	 */
	@Override
	public IFV clone() {
		FVR f= new FVR(this.size);
		for(int i=0;i<this.size;i++) {
			f.m_index[i]=m_index[i];
			f.m_value[i]=m_value[i];
		}
		f.size=this.size;
		return f;
	}

	

}
