#include "Config.h"

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>

#include "Bootstrap.h"
#include "Str.h"
#include "Thread.h"
#include "Exception.h"
#include "AssertException.h"


/**
 * Exception unity tests
 */

#define THREADS 200

Exception_T A = {"AException"};
Exception_T B = {"BException"};
Exception_T C = {"CException"};
Exception_T D = {"DException"};

void throwA() {
        THROW(A, NULL);
}

void throwB() {
        THROW(B, NULL);
}

void throwC() {
        THROW(C, "A cause");
}

void throwD() {
        THROW(D, "A cause");
}

void indirectA() {
        throwA();
}

/* Throw and catch exceptions and check that we got the expected exception. 
 * If the exception stack is corrupt somehow this should be detected
 */
void *thread(void *args) {
        TRY
                THROW(A, "A cause");
                assert(false); // Should not be reached
        CATCH(A)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                THROW(B, "A cause");
                assert(false); // Should not be reached
        CATCH(B)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                THROW(D, NULL);
                assert(false); // Should not be reached
        CATCH(D)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                THROW(C, NULL);
                assert(false); // Should not be reached
        CATCH(C)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                THROW(A, NULL);
                assert(false); // Should not be reached
        CATCH(A)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                throwA();
                throwB();
                throwC();
                throwD();
        CATCH(A)
                // Ok
        CATCH(B)
                assert(false); // Should not be reached
        CATCH(C)
                assert(false); // Should not be reached
        CATCH(D)
                assert(false); // Should not be reached
        END_TRY;
        TRY
                indirectA();
        CATCH(A)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                THROW(B, NULL);
                assert(false); // Should not be reached
        CATCH(B)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                THROW(B, NULL);
                assert(false); // Should not be reached
        CATCH(B)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                THROW(B, NULL);
                assert(false); // Should not be reached
        CATCH(B)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                THROW(B, "A cause %s", "and another cause");
                assert(false); // Should not be reached
        CATCH(B)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                THROW(A, NULL);
                assert(false); // Should not be reached
        CATCH(A)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                THROW(B, NULL);
                assert(false); // Should not be reached
        CATCH(B)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                THROW(D, NULL);
                assert(false); // Should not be reached
        CATCH(D)
                // Ok
        ELSE
                assert(false); // Should not be reached
        END_TRY;
        TRY
                RETURN (NULL);
                assert(false); // Should not be reached
        CATCH(A)
                assert(false); // Should not be reached
        END_TRY;
        return NULL; 
}


int main(void) {
        Bootstrap(); // Need to initialize library
        printf("============> Start Exeption Tests\n\n");

        printf("=> Test1: TRY-CATCH\n");
        {
                TRY
                        THROW(A, "A cause");
                        assert(false); // Should not be reached
                CATCH(A)
                        printf("\tResult: Ok\n");
                END_TRY;
        }
        printf("=> Test1: OK\n\n");

        printf("=> Test2: TRY-CATCH indirect throw\n");
        {
                TRY
                        indirectA();
                        assert(false); // Should not be reached
                CATCH(A)
                        printf("\tResult: Ok\n");
                END_TRY;
        }
        printf("=> Test2: OK\n\n");

        printf("=> Test3: TRY-ELSE\n");
        {
                TRY
                        THROW(B, NULL);
                        assert(false); // Should not be reached
                ELSE
                        printf("\tResult: Ok\n");
                END_TRY;
        }
        printf("=> Test3: OK\n\n");

        printf("=> Test4: TRY-CATCH-ELSE\n");
        {
                TRY
                        throwB();
                        assert(false); // Should not be reached
                CATCH(A)
                        assert(false); // Should not be reached
                ELSE
                        printf("\tResult: Ok\n");
                END_TRY;
        }
        printf("=> Test4: OK\n\n");

        printf("=> Test5: TRY-FINALLY\n");
        {
                int i = 0;
                TRY
                        i++;
                FINALLY
                        printf("\tResult: ok\n");
                        assert(i==1);
                END_TRY;
        }
        printf("=> Test5: OK\n\n");

        printf("=> Test6: TRY-CATCH-FINALLY\n");
        {
                volatile int i = 0;
                TRY
                        i++;
                        THROW(C, "A cause");
                CATCH(C)
                        i++;
                FINALLY
                        printf("\tResult: ok\n");
                        assert(i == 2);
                END_TRY;
        }
        printf("=> Test6: OK\n\n");

        printf("=> Test7: CATCH NumberFormatException\n");
        {
                TRY
                        Str_parseInt("not a number");
                        assert(false); // Should not be reached
                CATCH(NumberFormatException)
                        printf("\tResult: ok got NumberFormatException\n");
                END_TRY;
        }
        printf("=> Test7: OK\n\n");

        printf("=> Test8: CATCH AssertException\n");
        {
                TRY
                        assert(false); // Throws AssertException
                        printf("Test8 failed\n"); 
                        exit(1);
                CATCH(AssertException)
                        printf("\tResult: ok got AssertException\n");
                END_TRY;
        }
        printf("=> Test8: OK\n\n");

        printf("=> Test9: Nested TRY-CATCH\n");
        {
                TRY
                        TRY
                                THROW(B, "A cause");
                                THROW(A, "Another cause");
                                assert(false); // Should not be reached
                        CATCH(A)
                                assert(false); // Should not be reached
                        END_TRY;
                CATCH(B)
                        printf("\tResult: ok\n");
                END_TRY;
        }
        printf("=> Test9: OK\n\n");

        printf("=> Test10: RETHROW\n");
        {
                TRY
                        TRY
                                THROW(A, "A cause %s", "and a cause");
                                assert(false); // Should not be reached
                        CATCH(A)
                                RETHROW;
                        END_TRY;
                CATCH(A)
                        printf("\tResult: ok got Exception\n");
                END_TRY;
        }
        printf("=> Test10: OK\n\n");

        printf("=> Test11: No exception thrown\n");
        {
                TRY
                        int i = 0; i++;
                ELSE
                        assert(false); // Should not be reached
                END_TRY;
        }
        printf("=> Test11: OK\n\n");

        printf("=> Test12: Exception message append\n");
        {
                TRY
                        TRY
                                THROW(AssertException, "Original reason");
                                exit(0); // Should not be reached
                        CATCH(AssertException)
                                THROW(AssertException, "%s - Appended reason", Exception_frame.message);
                        END_TRY;
                CATCH(AssertException)
                        printf("\tResult: Got '%s' with reason: %s\n", AssertException.name, Exception_frame.message);
                        assert(Str_startsWith(Exception_frame.message, "Original"));
                END_TRY;
        }
        printf("=> Test12: OK\n\n");


        printf("=> Test13: FINALLY rethrows exception\n");
        {
                TRY
                {
                        TRY
                        {
                                THROW(AssertException, "A reason");
                                exit(0); // Should not be reached
                        }
                        FINALLY
                        END_TRY;
                        exit(0); // Should not be reached
                }
                CATCH(AssertException)
                {
                        printf("\tResult: Got '%s' thrown from FINALLY\n", AssertException.name);
                }
                END_TRY;
        }
        printf("=> Test13: OK\n\n");


        printf("=> Test14: Test thread-safeness\n");
        {
                int i;
                Thread_T threads[THREADS];
                for (i = 0; i < THREADS; i++) {
                        Thread_create(threads[i], thread, NULL);
                }
                for (i = 0; i<THREADS; i++)
                        Thread_join(threads[i]);
        }
        printf("=> Test14: OK\n\n");

        printf("============> Exception Tests: OK\n\n");

        return 0;
}

