package Localization.structs;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.math.BigInteger;

import security.DGK.DGKOperations;
import security.DGK.DGKPrivateKey;
import security.DGK.DGKPublicKey;
import security.elgamal.ElGamalCipher;
import security.elgamal.ElGamalPrivateKey;
import security.elgamal.ElGamalPublicKey;
import security.elgamal.ElGamal_Ciphertext;
import security.misc.HomomorphicException;
import security.paillier.PaillierCipher;
import security.paillier.PaillierPrivateKey;
import security.paillier.PaillierPublicKey;

public class LocalizationResult implements Serializable, Comparable<LocalizationResult>
{
    private static final long serialVersionUID = -1884589588377067950L;

    public final Long matches;
    
    // Distances
    private Long plainDistance;
    public BigInteger encryptedDistance;
    public ElGamal_Ciphertext e_d;
    
    // Coordinates
    public final Double [] coordinates = new Double[2];
    public final BigInteger [] encryptedCoordinates = new BigInteger[2];
    public final ElGamal_Ciphertext [] e_xy = new ElGamal_Ciphertext[2];
    
    // PlainText Distances
    public LocalizationResult(Double x, Double  y, Long distance, Long matches)
    {
    	this.coordinates[0] = x;
        this.coordinates[1] = y;
        this.plainDistance = distance;
        this.matches = matches;
        this.encryptedDistance = null;
        this.e_d = null;
    }
    
    // Paillier/DGK Distances
    public LocalizationResult(Double x, Double y, BigInteger distance, Long matches)
    {
    	this.coordinates[0] = x;
        this.coordinates[1] = y;
        this.encryptedDistance = distance;
        this.e_d = null;
        this.matches = matches;
        this.plainDistance = null;
    }
    
    // ElGamal
    public LocalizationResult(Double x, Double y, ElGamal_Ciphertext distance, Long matches)
    {
    	this.coordinates[0] = x;
        this.coordinates[1] = y;
        this.e_d = distance;
        this.encryptedDistance = null;
        this.matches = matches;
        this.plainDistance = null;
    }
    
    public void add_secret_coordinates(PaillierPublicKey pk) throws HomomorphicException
    {
    	// Encrypt Coordinates
    	encryptedCoordinates[0] = PaillierCipher.encrypt(coordinates[0].longValue(), pk);
    	encryptedCoordinates[1] = PaillierCipher.encrypt(coordinates[1].longValue(), pk);
    	
    	// The coordinate stored in plain text get nullified
    	coordinates[0] = null;
    	coordinates[1] = null;
    }
    
    public void add_secret_coordinates(DGKPublicKey pk)
    {
    	// Encrypt Coordinates
    	this.encryptedCoordinates[0] = DGKOperations.encrypt(this.coordinates[0].longValue(), pk);
    	this.encryptedCoordinates[1] = DGKOperations.encrypt(this.coordinates[1].longValue(), pk);
    	
    	// The coordinate stored in plain text get nullified
    	this.coordinates[0] = null;
    	this.coordinates[1] = null;
    }
    
	public void add_secret_coordinates(ElGamalPublicKey pk)
	{
    	// Encrypt Coordinates
		this.e_xy[0] = ElGamalCipher.encrypt(this.coordinates[0].longValue(), pk);
		this.e_xy[1] = ElGamalCipher.encrypt(this.coordinates[1].longValue(), pk);
    	
    	// The coordinate stored in plain text get nullified
		this.coordinates[0] = null;
		this.coordinates[1] = null;
	}
    
    public Double getX()
    {
    	return coordinates[0];
    }

    public Double getY()
    {
    	return coordinates[1];
    }
    
    private void readObject(ObjectInputStream aInputStream) 
    		throws ClassNotFoundException, IOException
    {
        aInputStream.defaultReadObject();
    }

    private void writeObject(ObjectOutputStream aOutputStream) 
    		throws IOException
    {
    	aOutputStream.defaultWriteObject();
    }
    
	public int compareTo(LocalizationResult o)
	{
		return plainDistance.compareTo(o.plainDistance);
	}
	
	// Used to decrypt, if DMA, just divide by distance as well!
	public void decrypt_all(PaillierPrivateKey sk) throws HomomorphicException
	{
		this.plainDistance = PaillierCipher.decrypt(encryptedDistance, sk).longValue();
		if(matches != null)
		{
			this.plainDistance = plainDistance/matches;
		}
	}
	
	// Used to decrypt, if DMA, just divide by distance as well!
	public void decrypt_all(DGKPrivateKey sk)
	{
		this.plainDistance = DGKOperations.decrypt(this.encryptedDistance, sk);
		if(this.matches != null)
		{
			this.plainDistance = plainDistance/matches;
		}
	}
	
	// Used to decrypt, if DMA, just divide by distance as well!
	public void decrypt_all(ElGamalPrivateKey sk)
	{
		try
		{
			plainDistance = ElGamalCipher.decrypt(e_d, sk).longValue();
		}
		catch(IllegalArgumentException e)
		{
			plainDistance = Long.MAX_VALUE;
		}
		if(matches != null)
		{
			plainDistance = plainDistance/matches;
		}
	}
	
	// Used only for Plain-DMA, divide by matches!
	public void plain_decrypt()
	{
		this.plainDistance = plainDistance/matches;
	}
	
	public Long getPlainDistance()
	{
		return this.plainDistance;
	}
	
	public void setEncryptedDistance(BigInteger d)
	{
		this.encryptedDistance = d;
	}
	
	public void setElGamalEncryptedDistance(ElGamal_Ciphertext d)
	{
		this.e_d = d;
	}
	
	public String toString()
	{
		String answer = "";
	    answer += "(x=" + coordinates[0] + ", y=" + coordinates[1] + " ";
	    if(plainDistance != null)
	    {
	    	answer += "d=" + plainDistance;
	    }
	    else
	    {
	    	answer += "[[d]]";
	    }
	    answer += "m=" + matches;
		return answer;
	}
}