
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

import java.util.Random;

public class Test_Passwords_Oliver_S {

    Random random = new Random();

    //Mindestanforderungen an das Passwort
    final static int MIN_LENGTH = 20;
    final static int MIN_KLEINBUCHSTABEN = 3;
    final static int MIN_GROSSBUCHSTABEN = 3;
    final static int MIN_ZIFFERN = 3;
    final static int MIN_SONDERZEICHEN = 3;


    final static int TEST_RUNS = 1000000; //Wie häufig die Tests wiederholt werden sollen
    final static int MAX_GEN_PASSWORD_LENGTH = 100; //Wie lang das roh generierte Passwort sein sollte (ohne enthaltene Testcases).
    final static int MAX_EXTRA_CHARACTERS = 10; //Wie viele Extra Zeichen in das Passwort für den Testcase (More than min) eingefügt werden sollen.


    //Spezifiziert die Bereiche in denen verschiedene Arten von Zeichen liegen.
    final static int START_CHAR_ALL = 32;
    final static int STOP_CHAR_ALL = 126;

    final static int START_CHAR_ZIFFERN = 48;
    final static int STOP_CHAR_ZIFFERN = 57;

    final static int START_CHAR_KEINB = 97;
    final static int STOP_CHAR_KLEINB = 122;

    final static int START_CHAR_GROSSB = 65;
    final static int STOP_CHAR_GROSSB = 90;

    final static int START_CHAR_SONDERZ_1 = 32;
    final static int STOP_CHAR_SONDERZ_1 = 47;

    final static int START_CHAR_SONDERZ_2 = 58;
    final static int STOP_CHAR_SONDERZ_2 = 64;

    final static int START_CHAR_SONDERZ_3 = 91;
    final static int STOP_CHAR_SONDERZ_3 = 96;

    final static int START_CHAR_SONDERZ_4 = 123;
    final static int STOP_CHAR_SONDERZ_4 = 126;

    @FunctionalInterface
    interface Check_X_Interface{
        boolean X_Method(String password);
    }

    //Generiert einen zufälligen String mit der Länge "length", mit den Zeichen zwischen "start_char" und "stop_char" und exkluidert ggf. den Bereich von "start_exclude" bis "stop_exclude"
    String generateRandomString(int length,int start_char, int stop_char, int start_exclude, int stop_exclude){

        StringBuilder text = new StringBuilder(length);
        int i = 0;

        while(i < length){
            int random_int_in_range = random.nextInt(start_char,stop_char+1);

            if(start_exclude <= random_int_in_range && random_int_in_range <= stop_exclude){
                i--;
            }
            else{
                text.append((char)random_int_in_range);
            }

            i++;
        }
        return text.toString();
    }

    //Spezielle Mehtode, die einen String ohne Sonderzeichen erzeugt.
    String generateRandomStringWithOutSonderZeichen(int length){
        
        StringBuilder text = new StringBuilder(length);
        int i = 0;

        while(i < length){
            int random_int_in_range = random.nextInt(START_CHAR_ALL,STOP_CHAR_ALL+1);

            if((START_CHAR_SONDERZ_1 <= random_int_in_range && random_int_in_range <= STOP_CHAR_SONDERZ_1)||
               (START_CHAR_SONDERZ_2 <= random_int_in_range && random_int_in_range <= STOP_CHAR_SONDERZ_2)||
               (START_CHAR_SONDERZ_3 <= random_int_in_range && random_int_in_range <= STOP_CHAR_SONDERZ_3)||
               (START_CHAR_SONDERZ_4 <= random_int_in_range && random_int_in_range <= STOP_CHAR_SONDERZ_4)){
                
                i--;
            }
            else{
                text.append((char)random_int_in_range);
            }

            i++;
        }
        return text.toString();    
    }

    char getRandomSonderzeichen(){
        int random_sonderzeichen_region = random.nextInt(0,4); //Because we have 4 Sonderzeichen Areas in ASCII...
        int start_region;
        int end_region;

        if(random_sonderzeichen_region == 0){

            start_region = START_CHAR_SONDERZ_1;
            end_region = STOP_CHAR_SONDERZ_1; 

        } else if(random_sonderzeichen_region == 1){

            start_region = START_CHAR_SONDERZ_2;
            end_region = STOP_CHAR_SONDERZ_2; 

        }else if(random_sonderzeichen_region == 2){

            start_region = START_CHAR_SONDERZ_3;
            end_region = STOP_CHAR_SONDERZ_3; 

        }else{
            start_region = START_CHAR_SONDERZ_4;
            end_region = STOP_CHAR_SONDERZ_4;    
        }

        int random_int_in_range = random.nextInt(start_region,end_region+1);

        return (char)random_int_in_range;
    }

    //Das Herzstück der Tests:
    //Es werden für jede zu testende Methode jeweils 3 Fälle abgefragt:
    //1. Anforderung an die Methode wird nicht erfüllt, z.B zu wenige Ziffern -> Methode sollte immer falsch liefern.
    //2. Anforderungen werden erfüllt, z.B genau 3 Ziffern -> Methode sollte immer wahr liefern.
    //3. Anforderung werden über erfüllt z.B mehr als 3 Ziffern -> Methode sollte immer wahr liefern.

    //Die Test Methoden sind sehr ähnlich aufgebaut und unterscheiden sich nur in der inneren for Schleife und dem assert am Ende. 
    //Alle Zufälle finden in einem vordefinierten Breich statt um die Tests besser kontrollieren zu können.
    void test_checkX_with_less_than_Min_X(int start_char, int stop_char, int start_exclude_char, int stop_exclude_char, int min_X, Check_X_Interface password_check_interface){
  
        for(int i=0; i<TEST_RUNS;i++){
            int random_password_length = random.nextInt(1,MAX_GEN_PASSWORD_LENGTH);
            int random_less_X_character = random.nextInt(1,min_X);

            //Die Test Mehtode generiert einen zufälligen String, mit zufälliger Länge. Dieser enthält KEINE zu testenden Zeichen. -> Wird die Mehtode "checkZiffer" getestet enthält der String bis hier keine Ziffern!
            StringBuilder test_password_build = new StringBuilder(generateRandomString(random_password_length,start_char,stop_char,start_exclude_char,stop_exclude_char));
            //System.out.println(test_password_build.toString());
            
            //Ab hier wir es interessant. Je nach dem welcher Test läuft, wird eine zufälliges zu testendes Zeichen (z.B Ziffer) in den String, in einer zufälligen Position eingefügt.
            //Die Anzahl der Testzeichen liegt wie hier immer immer unter der Mindestanforderung für die zu testende Methode. -> Hier sollte immer false heraus kommen.
            for(int j=0; j<min_X-random_less_X_character;j++){
                int test_password_length = test_password_build.length();
                int insert_index = random.nextInt(test_password_length);
                char random_X = (char)random.nextInt(start_exclude_char,stop_exclude_char+1);
                test_password_build.insert(insert_index,random_X);
            }
            //System.out.println("\n");
            
            //Hier wird die Mehtode nun getestet, und es wird IMMER ein false erwartet!
            String test_password = test_password_build.toString();
            boolean test_result = password_check_interface.X_Method(test_password);
            assertFalse(test_result, "Test failed with Password: " + test_password);
        } //Dies wiederholt sich "TEST_RUNS" mal bis alle Tests erfolgreich sind, oder die zu testende Methode einen falschen Wert liefert. -> Der Test ist dann fehlgeschlagen.
    }

    void test_checkX_with_exact_Min_X(int start_char, int stop_char, int start_exclude_char, int stop_exclude_char, int min_X, Check_X_Interface password_check_interface){
  
        for(int i=0; i<TEST_RUNS;i++){
            int random_password_length = random.nextInt(1,MAX_GEN_PASSWORD_LENGTH);

            StringBuilder test_password_build = new StringBuilder(generateRandomString(random_password_length,start_char,stop_char,start_exclude_char,stop_exclude_char));
            //System.out.println(test_password_build.toString());

            //Hier das gleiche wie oben beschrieben nur diesmal wird die Mindestanforderung erfüllt.
            for(int j=0; j<min_X;j++){
                int test_password_length = test_password_build.length();
                int insert_index = random.nextInt(test_password_length);
                char random_X = (char)random.nextInt(start_exclude_char,stop_exclude_char+1);

                test_password_build.insert(insert_index,random_X);
            }

            //Hier sollte IMMER wahr herauskommen.
            String test_password = test_password_build.toString();
            boolean test_result = password_check_interface.X_Method(test_password);
            assertTrue(test_result, "Test failed with Password: " + test_password);
        }
    }

    void test_checkX_with_more_than_Min_X(int start_char, int stop_char, int start_exclude_char, int stop_exclude_char, int min_X, Check_X_Interface password_check_interface){
  
        for(int i=0; i<TEST_RUNS;i++){
            int random_password_length = random.nextInt(1,MAX_GEN_PASSWORD_LENGTH);

            StringBuilder test_password_build = new StringBuilder(generateRandomString(random_password_length,start_char,stop_char,start_exclude_char,stop_exclude_char));
            //System.out.println(test_password_build.toString());

            int extra_X = random.nextInt(MAX_EXTRA_CHARACTERS);
            
            //Das gleiche wie oben, nur hier wird die Mindestanforderung über erfüllt.
            for(int j=0; j<min_X+extra_X;j++){
                int test_password_length = test_password_build.length();
                int insert_index = random.nextInt(test_password_length);
                char random_X = (char)random.nextInt(start_exclude_char,stop_exclude_char+1);

                test_password_build.insert(insert_index,random_X);
            }
            //Es sollte dann entsprechend auch IMMER wahr herauskommen!
            String test_password = test_password_build.toString();
            boolean test_result = password_check_interface.X_Method(test_password);
            assertTrue(test_result, "Test failed with Password: " + test_password);
        }
    }


    //Tests für Methode "checkLaenge":
    //------------------------------------------------------------------------------------------------------------------------------

    @Test 
    void test_checkLaenge_with_null_Password(){
        String test_password = null;
        boolean test_result = Passwort_Check.checkLaenge(test_password);
        assertFalse(test_result);

    }


    @Test  
    void test_checkLaenge_with_empty_Password(){
        String test_password = "";
        boolean test_result = Passwort_Check.checkLaenge(test_password);
        assertFalse(test_result);

    }

    @Test  
    void test_checkLaenge_with_less_than_Min_Length(){

        for(int i=0; i<TEST_RUNS;i++){
            int random_password_length = random.nextInt(0,MIN_LENGTH);
            String test_password = generateRandomString(random_password_length, START_CHAR_ALL, STOP_CHAR_ALL,0,-1); //From char 32 to char 126, Exclude: nothing!
            boolean test_result =  Passwort_Check.checkLaenge(test_password);
            assertFalse(test_result, "Test failed with Password: " + test_password);  
        }   
    }

    @Test 
    void test_checkLaenge_with_exact_Min_Length(){

        for(int i=0; i<TEST_RUNS;i++){
            int random_password_length = MIN_LENGTH;
            String test_password = generateRandomString(random_password_length, START_CHAR_ALL, STOP_CHAR_ALL,0,-1); //From char 32 to char 126, Exclude: nothing!
            boolean test_result =  Passwort_Check.checkLaenge(test_password);
            assertTrue(test_result, "Test failed with Password: " + test_password);  
        }
    }

    @Test 
    void test_checkLaenge_with_more_than_Min_Length(){

        for(int i=0; i<TEST_RUNS;i++){
            int random_password_length = random.nextInt(MIN_LENGTH,MAX_GEN_PASSWORD_LENGTH);
            String test_password = generateRandomString(random_password_length, START_CHAR_ALL, STOP_CHAR_ALL,0,-1); //From char 32 to char 126, Exclude: nothing!
            boolean test_result =  Passwort_Check.checkLaenge(test_password);
            assertTrue(test_result, "Test failed with Password: " + test_password);  
        }
    }


    //------------------------------------------------------------------------------------------------------------------------------
    //Tests für Methode "checkKleinBuchstaben":
    @Test 
    void test_checkKleinBuchstaben_with_null_Password(){
        String test_password = null;
        boolean test_result = Passwort_Check.checkKleinBuchstaben(test_password);
        assertFalse(test_result);

    } 

    @Test 
    void test_checkKleinBuchstaben_with_empty_Password(){
        String test_password = "";
        boolean test_result = Passwort_Check.checkKleinBuchstaben(test_password);
        assertFalse(test_result);

    }

    @Test 
    void test_checkKleinBuchstaben_with_less_than_Min_Klein_Buchstaben(){
        test_checkX_with_less_than_Min_X(START_CHAR_ALL,STOP_CHAR_ALL,START_CHAR_KEINB,STOP_CHAR_KLEINB,MIN_KLEINBUCHSTABEN,Passwort_Check::checkKleinBuchstaben);
    }

    @Test 
    void test_checkKleinBuchstaben_with_exact_Min_Klein_Buchstaben(){
        test_checkX_with_exact_Min_X(START_CHAR_ALL,STOP_CHAR_ALL,START_CHAR_KEINB,STOP_CHAR_KLEINB,MIN_KLEINBUCHSTABEN,Passwort_Check::checkKleinBuchstaben);
    }

    @Test  
    void test_checkKleinBuchstaben_with_more_than_Min_Klein_Buchstaben(){
        test_checkX_with_more_than_Min_X(START_CHAR_ALL,STOP_CHAR_ALL,START_CHAR_KEINB,STOP_CHAR_KLEINB,MIN_KLEINBUCHSTABEN,Passwort_Check::checkKleinBuchstaben);
    }
    

    //------------------------------------------------------------------------------------------------------------------------------
    //Tests für Methode "checkGrossBuchstaben":
    @Test 
    void test_checkGrossBuchstaben_with_null_Password(){
        String test_password = null;
        boolean test_result = Passwort_Check.checkGrossBuchstaben(test_password);
        assertFalse(test_result);
    } 

    @Test  
    void test_checkGrossBuchstaben_with_empty_Password(){
        String test_password = "";
        boolean test_result = Passwort_Check.checkGrossBuchstaben(test_password);
        assertFalse(test_result);
    }

    @Test  
    void test_checkGrossBuchstaben_with_less_than_Min_Gross_Buchstaben(){
        test_checkX_with_less_than_Min_X(START_CHAR_ALL,STOP_CHAR_ALL,START_CHAR_GROSSB,STOP_CHAR_GROSSB,MIN_GROSSBUCHSTABEN,Passwort_Check::checkGrossBuchstaben);
    }

    @Test  
    void test_checkGrossBuchstaben_with_exact_Min_Gross_Buchstaben(){
        test_checkX_with_exact_Min_X(START_CHAR_ALL,STOP_CHAR_ALL,START_CHAR_GROSSB,STOP_CHAR_GROSSB,MIN_GROSSBUCHSTABEN,Passwort_Check::checkGrossBuchstaben);
    }

    @Test 
    void test_checkGrossBuchstaben_with_more_than_Min_Gross_Buchstaben(){
        test_checkX_with_more_than_Min_X(START_CHAR_ALL,STOP_CHAR_ALL,START_CHAR_GROSSB,STOP_CHAR_GROSSB,MIN_GROSSBUCHSTABEN,Passwort_Check::checkGrossBuchstaben);
    }


    //------------------------------------------------------------------------------------------------------------------------------
    //Tests für Methode "checkZiffer":
    @Test  
    void test_checkZiffer_with_null_Password(){
        String test_password = null;
        boolean test_result = Passwort_Check.checkZiffer(test_password);
        assertFalse(test_result);
    }
    
    @Test  
    void test_checkZiffer_with_empty_Password(){
        String test_password = "";
        boolean test_result = Passwort_Check.checkZiffer(test_password);
        assertFalse(test_result);
    } 

    @Test  
    void test_checkZiffern_with_less_than_Min_Ziffern(){
        test_checkX_with_less_than_Min_X(START_CHAR_ALL,STOP_CHAR_ALL,START_CHAR_ZIFFERN,STOP_CHAR_ZIFFERN,MIN_ZIFFERN,Passwort_Check::checkZiffer);
    }

    @Test 
    void test_checkZiffern_with_exact_Min_Ziffern(){
        test_checkX_with_exact_Min_X(START_CHAR_ALL,STOP_CHAR_ALL,START_CHAR_ZIFFERN,STOP_CHAR_ZIFFERN,MIN_ZIFFERN,Passwort_Check::checkZiffer);
    }

    @Test 
    void test_checkZiffern_with_more_than_Min_Ziffern(){
        test_checkX_with_more_than_Min_X(START_CHAR_ALL,STOP_CHAR_ALL,START_CHAR_ZIFFERN,STOP_CHAR_ZIFFERN,MIN_ZIFFERN,Passwort_Check::checkZiffer);
    }


    //------------------------------------------------------------------------------------------------------------------------------
    //Tests für Methode "checkSonderZeichen":
    @Test 
    void test_checkSonderZeichen_with_null_Password(){
        String test_password = null;
        boolean test_result = Passwort_Check.checkSonderZeichen(test_password);
        assertFalse(test_result);
    } 

    @Test  
    void test_checkSonderZeichen_with_empty_Password(){
        String test_password = "";
        boolean test_result = Passwort_Check.checkSonderZeichen(test_password);
        assertFalse(test_result);
    } 


    //Da die Sonderzeichen ein etwas anderes Verhalten haben, werden die Testmehtoden von oben leicht abgewandelt.
    @Test   
    void test_checkSonderZeichen_with_less_than_Min_Sonder_Zeichen(){
        
        for(int i=0; i<TEST_RUNS;i++){
            int random_password_length = random.nextInt(1,MAX_GEN_PASSWORD_LENGTH);
            int random_less_Sonder_Zeichen_character = random.nextInt(1,MIN_SONDERZEICHEN);

            StringBuilder test_password_build = new StringBuilder(generateRandomStringWithOutSonderZeichen(random_password_length));
            //System.out.println(test_password_build.toString());
            
            for(int j=0; j<MIN_SONDERZEICHEN-random_less_Sonder_Zeichen_character;j++){
                int test_password_length = test_password_build.length();
                int insert_index = random.nextInt(test_password_length);

                char random_X = getRandomSonderzeichen();
                test_password_build.insert(insert_index,random_X);
            }
            //System.out.println("\n");
                
            String test_password = test_password_build.toString();
            boolean test_result = Passwort_Check.checkSonderZeichen(test_password);
            assertFalse(test_result, "Test failed with Password: " + test_password);
        }
    }

    @Test 
    void test_checkSonderZeichen_with_exact_Min_Sonder_Zeichen(){

        for(int i=0; i<TEST_RUNS;i++){
            int random_password_length = random.nextInt(1,MAX_GEN_PASSWORD_LENGTH);

            StringBuilder test_password_build = new StringBuilder(generateRandomStringWithOutSonderZeichen(random_password_length));
            //System.out.println(test_password_build.toString());
            
            for(int j=0; j<MIN_SONDERZEICHEN;j++){
                int test_password_length = test_password_build.length();
                int insert_index = random.nextInt(test_password_length);

                char random_X = getRandomSonderzeichen();
                test_password_build.insert(insert_index,random_X);
            }
            //System.out.println("\n");
                
            String test_password = test_password_build.toString();
            boolean test_result = Passwort_Check.checkSonderZeichen(test_password);
            assertTrue(test_result, "Test failed with Password: " + test_password);
        }     
    }

    @Test  
    void test_checkSonderZeichen_with_more_than_Min_Sonder_Zeichen(){

        for(int i=0; i<TEST_RUNS;i++){
            int random_password_length = random.nextInt(1,MAX_GEN_PASSWORD_LENGTH);

            StringBuilder test_password_build = new StringBuilder(generateRandomStringWithOutSonderZeichen(random_password_length));
            //System.out.println(test_password_build.toString());

             int extra_X = random.nextInt(MAX_EXTRA_CHARACTERS);
            
            for(int j=0; j<MIN_SONDERZEICHEN+extra_X;j++){
                int test_password_length = test_password_build.length();
                int insert_index = random.nextInt(test_password_length);

                char random_X = getRandomSonderzeichen();
                test_password_build.insert(insert_index,random_X);
            }
            //System.out.println("\n");
                
            String test_password = test_password_build.toString();
            boolean test_result = Passwort_Check.checkSonderZeichen(test_password);
            assertTrue(test_result, "Test failed with Password: " + test_password);
        }    
    }
}
