/**************************************************
 * new_credit.c                                   *
 *                                                *
 * CS50                                           *
 *                                                *
 * Prompts the user for a credit card number,     *
 *                                                *
 * determines the type of the card                *
 *                                                *
 * (Visa, American Express, Mastercard)           *
 *                                                *
 * and tests its validity.                        *
 *************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>

#define BUFFER_SIZE 256

char*           get_input(void);
bool            is_number(char *alleged_number);
long long int*  string_to_long_long(char *string);
int             count_digits(long long int number);
void            credit_card_type(long long int credit_card_number, int credit_card_number_length);
bool            is_credit_card_valid(long long int credit_card_number, int credit_card_number_length);

int
main(void)
{
        long long int *credit_card_number = string_to_long_long(get_input());
        int credit_card_number_length = count_digits(*credit_card_number);

        credit_card_type(*credit_card_number, credit_card_number_length);

        return 0;
}

/*
 * Gets a credit card number from the user. 
 */
char*
get_input(void)
{
        char temp_credit_card_number[BUFFER_SIZE];

        // prompt the user for a credit card number
        printf("Enter a credit card number: ");
        // copy from standard input to temp_credit_card_number[]
        while (fgets(temp_credit_card_number, BUFFER_SIZE, stdin) != NULL) {
                // add the trailling '\0' at the end of the string
                for (int i = 0; i < BUFFER_SIZE; i++) {
                        if (temp_credit_card_number[i] == '\n') {
                                temp_credit_card_number[i] = '\0';
                                break;
                        }
                }
                // check user input
                if ( !(is_number(temp_credit_card_number)) ) {
                        printf("Retry: ");
                } else {
                        break;
                }
        }

        // get_input() returns a pointer
        char *value = temp_credit_card_number;

        return value;
}

/*
 * Checks that the user input is a number. 
 */
bool
is_number(char *alleged_number)
{
        long long int number;
        char *invalid;

        number = strtoll(alleged_number, &invalid, 10);
        if (*invalid != '\0') {
                return false;
        }

        return true;
}

/*
 * Converts a string to a long long integer.
 */
long long int*
string_to_long_long(char *string)
{
        char *invalid;
        long long int number = strtoll(string, &invalid, 10);
        long long int *value = &number;

        return value;
}

/*
 * Counts the number of digits of a number.
 */
int
count_digits(long long int number)
{
        int i;

        for (i = 0; number != 0 ; i++) {
                number /= 10;
        }

        return i;
}

/*
 * Determines the type of a credit card, 
 * (Visa, American Express, Mastercard)
 */
void
credit_card_type(long long int credit_card_number, int credit_card_number_length)
{
        // these variables store the first or two first digits of a credit card number.
        int visa_first_digit_13 = (credit_card_number / pow(10, 12));
        int visa_first_digit_16 = (credit_card_number / pow(10, 15));
        int amex_two_first_digits = (credit_card_number / pow(10, 13));
        int mastercard_two_first_digits = (credit_card_number / pow(10, 14));

        // determines the possible type of a card then test its validity
        switch (credit_card_number_length) {
            case 13:
                    // VISA ?
                    if (visa_first_digit_13 == 4) {
                            if (is_credit_card_valid(credit_card_number, credit_card_number_length))
                                    printf("VISA\n");
                    } else {
                            printf("INVALID\n");
                    }
                    break;
                    
            case 15:
                    // AMEX ?
                    if ((amex_two_first_digits == 34) || (amex_two_first_digits == 37)) {
                            if (is_credit_card_valid(credit_card_number, credit_card_number_length))
                                    printf("AMEX\n");
                    } else {
                            printf("INVALID\n");
                    }
                    break;
                    
            case 16:
                    // VISA ?
                    if (visa_first_digit_16 == 4) {
                            if (is_credit_card_valid(credit_card_number, credit_card_number_length))
                                    printf("VISA\n");
                          // MASTERCARD ?
                    } else if ((mastercard_two_first_digits == 51) || 
                               (mastercard_two_first_digits == 52) || 
                               (mastercard_two_first_digits == 53) || 
                               (mastercard_two_first_digits == 54) || 
                               (mastercard_two_first_digits == 55)
                               ) {
                            if (is_credit_card_valid(credit_card_number, credit_card_number_length))
                                    printf("MASTERCARD\n");
                    } else {
                            printf("INVALID\n");
                    }
                    break;
                    
            default:
                    printf("INVALID\n");
        }

        return;
}

/*
 * Tets the syntactic validity of a credit card number,
 * (Peter Luhn's algoritm).
 */
bool
is_credit_card_valid(long long int credit_card_number, int credit_card_number_length)
{
        long long int every_other_digit; 
        int sum_products_digits = 0;

        /* isolate every other digits starting with the number's second-to-last digit, 
           multiply it by 2, and then add those products' digits together */
        for (int i = credit_card_number_length - 2; i > -1; i -= 2 ) {
                every_other_digit = ((long long int) (credit_card_number / pow(10, i))) % 10;
                sum_products_digits += ((every_other_digit * 2) / 10) + ((every_other_digit * 2) % 10);
        }

        long long int left_out_digit;
        int sum_left_out_digits = 0;

        // isolate the digits which weren't multiplied by 2, and then add these digits together
        for (int i = credit_card_number_length - 1; i > -1; i -= 2) {
                left_out_digit = ((long long int) (credit_card_number / pow(10, i))) % 10;
                sum_left_out_digits += left_out_digit;
        }

        int final_sum = sum_products_digits + sum_left_out_digits;

        // test weather the last digit of "final_sum" is 0 or not
        if (final_sum % 10 == 0) {
                return true;
        } else {
                return false;
        }
}