/******************************************************************************
Written in onlinegdb.
Sample Solution for CIS 3362 Homework #6 from Fall 2017

pdf: http://www.cs.ucf.edu/courses/cis3362/fall2017/hmk/CIS3362-Fall17-Hmk7.pdf
hash func: http://www.cs.ucf.edu/courses/cis3362/fall2017/hmk/hash.java

Date: 11/22/2021
Coded for CIS 3362 Fall 2021 class for lecture on Birthday Paradox.
*******************************************************************************/

import java.util.*;

public class Main
{
    
    public static Random r;
    
	public static void main(String[] args) {
	    
	    r = new Random();	
	    
		// Will map the hash VALUES to strings that produce that hash value.
	    HashMap<Long,char[]> strMap = new HashMap<Long,char[]>();
	    
		// Keep going till I find a hit.
	    while (true) {
	        
			// Generate a random string and get its hash value.
	        char[] tmp = rndString(20);
	        long hashval = hashFunction(tmp);
	        
			// A hit is a string that produces an old hash value but is different than the string we used to get that hash value.
	        if (strMap.containsKey(hashval) && !equal(strMap.get(hashval),tmp)) {
				
				// Just printing out all we wanted.
	            System.out.println("Got a match!");
	            System.out.println("Two strings "+new String(tmp)+" "+new String(strMap.get(hashval)));
	            System.out.println(hashval+" "+hashFunction(strMap.get(hashval)));
	            System.out.println("size of table "+strMap.size());
	            break;
	        }
	        
			// Add this value mapped to this string.
	        strMap.put(hashval, tmp);
	    }
	}
	
	// Returns true iff the contents of a are identical to b.
	public static boolean equal(char[] a, char[] b) {
	    if (a.length != b.length) return false;
	    for (int i=0; i<a.length; i++)
	        if (a[i] != b[i])
	            return false;
	    return true;
	}
	
	// Returns a random lowercase char[] of n letters.
	public static char[] rndString(int n) {
	    
	    char[] res = new char[20];
	    for (int i=0; i<20; i++) {
	        int add = r.nextInt(26);
	        res[i] = (char)('a'+add);
	    }
	    return res;
	    
	}
	
   	// This is the hash function you must use for homework #6.
    public static long hashFunction(char[] input) {

        int i = 0, n = input.length;
        long buffer = 0x23456789abcdL;
        while (i < n) {
            long temp = 0L;
            int j;
            for (j=0; j<6 && i+j<n; j++)
                temp |=( ( (long)input[i+j])  << (8*j)  );
            i += 6;
            
            for (j=0; j<20; j++)
            	temp = round(temp, j);

            buffer ^= temp;
        }
        return buffer;
    }
    
    public static long round(long value, int index) {
    	long buffer = 0x6ed54291f3c7L;
    	for (int i=0; i<16; i++) {
    		value = rotate(value, i);
    		value = applyBox(value, index);
    	}
    	return value;
    }

    // value must be 8 bits. Swaps the left and the right halves and runs
    // each through an s-box.
    public static long rotate(long value, int round) {
    	int[] shifts = {0,2,3,1,1,3,1,2,2,2,2,1,4,1,3,2};
        int bits = shifts[round]*3;
        long lsbs = value >> (48-bits);
        long msbs = (value << bits) & ((1L<<48)-1);
        return msbs | lsbs;
    }
    
    public static long applyBox(long value, int numTimes) {
    	long res = 0;
    	for (int i=0; i<16; i++) {
    		long tmp = ((value >> (3*i)) & 7);
    		tmp = repeatBox((int)tmp, numTimes);
    		res = res | (tmp << (3*i));
    	}
    	return res;
    }

    // Random s-box of values. 
    public static int box(int value) {
        int ans[] = {3, 5, 0, 2, 7, 1, 4, 6};
        return ans[value];
    }

	// Composes the function box with itself numTimes times and returns result
	// if input is the input.
	public static int repeatBox(int input, int numTimes) {
		for (int i=0; i<numTimes; i++)
			input = box(input);
		return input;
	}
}
