import java.util.HashMap;
import java.util.Map;

public class HashMapTest {
	private static final char MIN_NAME = 'A'; 
    private static final char MAX_NAME = 'K'; 
    private static final int EXPECTED_NUMBER_OF_ELEMENTS = MAX_NAME - MIN_NAME + 1; 
     
    private HashMap<Person, Integer> personToAgeMap; 
     
    HashMapTest(){
    	personToAgeMap = new HashMap<>(); 
    }
    
    public static void main(String[] args){
    	HashMapTest objHashMap = new HashMapTest();
    	System.out.println("Initial Size of Map: " + objHashMap.getPersonToAgeMap().size());
    	objHashMap.whenOverridingEqualElements_thenSizeOfTheMapIsStable();
    	objHashMap.whenGettingElementUsingPersonOfAge1_thenOverridenValuesAreReturned();
    	objHashMap.whenGettingElementUsingPersonOfAge100_thenOverridenValuesAreReturned();
    	objHashMap.whenGettingElementUsingPersonOfAge50_thenOverridenValuesAreReturned();
    	objHashMap.whenGettingElementUsingPersonOfAgeMinus1_thenOverridenValuesAreReturned();
    }
    
    public HashMap<Person, Integer> getPersonToAgeMap(){
    	return personToAgeMap;
    }
    
    public void whenOverridingEqualElements_thenSizeOfTheMapIsStable() { 
        System.out.println("Adding elements with age 1..");
        putAllPeopleWithAge(personToAgeMap, 1);
        System.out.println(personToAgeMap);
        System.out.println("Expected Number Of elements: " + EXPECTED_NUMBER_OF_ELEMENTS+ "\nActual Number of elements: "+personToAgeMap.size());
        
        System.out.println();
        System.out.println("Overwriting map, with value 100..");
        putAllPeopleWithAge(personToAgeMap, 100);
        System.out.println(personToAgeMap);
        System.out.println("Expected Number Of elements: " + EXPECTED_NUMBER_OF_ELEMENTS+ "\nActual Number of elements: "+personToAgeMap.size());
        System.out.println();
    } 
     

    public void whenGettingElementUsingPersonOfAge1_thenOverridenValuesAreReturned() {    	
        useAgeToCheckAllHashMapValuesAre(1, 100); 
    } 
     

    public void whenGettingElementUsingPersonOfAge100_thenOverridenValuesAreReturned() {  
        useAgeToCheckAllHashMapValuesAre(100, 100); 
    } 

    public void whenGettingElementUsingPersonOfAge50_thenOverridenValuesAreReturned() {  
        useAgeToCheckAllHashMapValuesAre(50, 100); 
    } 
     
   
    public void whenGettingElementUsingPersonOfAgeMinus1_thenOverridenValuesAreReturned() {        
        useAgeToCheckAllHashMapValuesAre(-10, 100); 
    }
     
    private void useAgeToCheckAllHashMapValuesAre(int age, Integer expectedValue) { 
    	System.out.println("Checking the values corresponding to age = " + age);
        StringBuilder sb = new StringBuilder(); 
         
        int count = countAllPeopleUsingAge(personToAgeMap, age); 
        System.out.println("Count of People with age " + age+" =" + count);
        
        if (EXPECTED_NUMBER_OF_ELEMENTS != count) { 
            sb.append("Size of the map ").append(" is wrong: ") 
                .append("expected <").append(EXPECTED_NUMBER_OF_ELEMENTS).append("> actual <").append(count).append(">.\n"); 
        } 
         
        for (char name = MIN_NAME; name <= MAX_NAME; name++) { 
            Person key = new Person(name, age); 
            Integer value = personToAgeMap.get(key); 
            if (!expectedValue.equals(value)) { 
                sb.append("Unexpected value for ").append(key).append(": ") 
                .append("expected <").append(expectedValue).append("> actual <").append(value).append(">.\n"); 
            } 
        } 
         
        if (sb.length() > 0) { 
            System.out.println(sb.toString());
        } 
    } 
     
     void putAllPeopleWithAge(Map<Person, Integer> map, int age) { 
        for (char name = MIN_NAME; name <= MAX_NAME; name++) { 
            map.put(new Person(name, age), age); 
        } 
    } 
     
     int countAllPeopleUsingAge(Map<Person, Integer> map, int age) { 
        int counter = 0; 
        for (char name = MIN_NAME; name <= MAX_NAME; name++) { 
            if (map.containsKey(new Person(name, age))) { 
                counter++; 
            } 
        } 
        return counter; 
    } 
     
   String getAllPeopleUsingAge(Map<Person, Integer> map, int age) { 
        StringBuilder sb = new StringBuilder(); 
        for (char name = MIN_NAME; name <= MAX_NAME; name++) { 
            Person key = new Person(name, age); 
            sb.append(key).append('=').append(map.get(key)).append('\n'); 
        } 
        return sb.toString(); 
    } 

     class Person implements Comparable<Person> { 
        char name; 
        int age; 
         
        public Person(char name, int age) { 
            this.name = name; 
            this.age = age; 
        } 
         
        //Making sure all elements end up in the very same bucket 
        //Nothing wrong with it except performance... 
        @Override 
        public int hashCode() { 
            return 0; 
        } 
         
        //equals is only by name 
        @Override 
        public boolean equals(Object other) { 
            Person otherPerson = (Person)other; 
            return this.name == otherPerson.name; 
        } 
         
        public String toString() { 
            return name + "[age=" + age + "]"; 
        } 
         
        //comparing by age 
        //NOTE: compareTo is inconsistent with equals which should be OK in non-sorted collections 
        //https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html 
        @Override 
        public int compareTo(Person other) { 
            return this.age - other.age; 
        } 
    } 
}
