#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef int bool;
#define true 1
#define false 0

#define PROTEIN_LENGTH 30
#define CODON_LENGTH 3
#define NO_OF_CODONS ((int)(PROTEIN_LENGTH / CODON_LENGTH))
#define NO_OF_AMINO_ACIDS 21
#define NO_OF_KNOWN_PROTEINS 5

#define PROTEIN_A "AUGGUGGCGGAGGGGACGAAGAGGAUCUAA"
#define PROTEIN_B "AUGGGAGAAGCAGUAAGAAAAACAAUAUAG"
#define PROTEIN_C "AUGUUUUCCUAUUGCCUGCCACAACGCUGA"
#define PROTEIN_D "AUGUUCUUGGUCCCUACUUACGAUCAUUAA"
#define PROTEIN_E "AUGUUUUCCUAUUGCCUGCCAAAACGCUGA"

int number_of_nucleotides_of_entered_protein = 0;
int number_of_codons_of_entered_protein = 0;
bool it_is_a_known_protein = false;
int index_of_matched_protein = -1;

typedef struct
{
    char sequence[CODON_LENGTH + 1];
} Codon;

typedef struct
{
    char slc[2];
} AminoAcid;

typedef struct
{
    AminoAcid *amino_acids;
} Protein;

static const char *ISOLEUCINE_CODONS[] = {"I3", "AUU", "AUC", "AUA"};
static const char *LEUCINE_CODONS[] = {"L6", "UUA", "UUG", "CUU", "CUC", "CUA", "CUG"};
static const char *VALINE_CODONS[] = {"V4", "GUU", "GUC", "GUA", "GUG"};
static const char *PHENYLALANINE_CODONS[] = {"F2", "UUU", "UUC"};
static const char *METHIONINE_CODONS[] = {"M1", "AUG"};
static const char *CYSTEINE_CODONS[] = {"C2", "UGU", "UGC"};
static const char *ALANINE_CODONS[] = {"A4", "GCU", "GCC", "GCA", "GCG"};
static const char *GLYCINE_CODONS[] = {"G4", "GGU", "GGC", "GGA", "GGG"};
static const char *PROLINE_CODONS[] = {"P4", "CCU", "CCC", "CCA", "CCG"};
static const char *THREONINE_CODONS[] = {"T4", "ACU", "ACC", "ACA", "ACG"};
static const char *SERINE_CODONS[] = {"S6", "UCU", "UCC", "UCA", "UCG", "AGU", "AGC"};
static const char *TYROSINE_CODONS[] = {"Y2", "UAU", "UAC"};
static const char *TRYPTOPHAN_CODONS[] = {"W1", "UGG"};
static const char *GLUTAMINE_CODONS[] = {"Q2", "CAA", "CAG"};
static const char *ASPARAGINE_CODONS[] = {"N2", "AAU", "AAC"};
static const char *HISTIDINE_CODONS[] = {"H2", "CAU", "CAC"};
static const char *GLUTAMIC_ACID_CODONS[] = {"E2", "GAA", "GAG"};
static const char *ASPARTIC_ACID_CODONS[] = {"D2", "GAU", "GAC"};
static const char *LYSINE_CODONS[] = {"K2", "AAA", "AAG"};
static const char *ARGININE_CODONS[] = {"R6", "CGU", "CGC", "CGA", "CGG", "AGA", "AGG"};
static const char *STOP_CODONS[] = {".3", "UAA", "UAG", "UGA"};

static const char **ALL_CODONS[NO_OF_AMINO_ACIDS] = {
    ISOLEUCINE_CODONS,
    LEUCINE_CODONS,
    VALINE_CODONS,
    PHENYLALANINE_CODONS,
    METHIONINE_CODONS,
    CYSTEINE_CODONS,
    ALANINE_CODONS,
    GLYCINE_CODONS,
    PROLINE_CODONS,
    THREONINE_CODONS,
    SERINE_CODONS,
    TYROSINE_CODONS,
    TRYPTOPHAN_CODONS,
    GLUTAMINE_CODONS,
    ASPARAGINE_CODONS,
    HISTIDINE_CODONS,
    GLUTAMIC_ACID_CODONS,
    ASPARTIC_ACID_CODONS,
    LYSINE_CODONS,
    ARGININE_CODONS,
    STOP_CODONS};

static const char PROTEIN_NAMES[] = {'A', 'B', 'C', 'D', 'E'};

bool no_of_args_is_valid(int size)
{
    bool valid = size == 2;
    if (!valid)
    {
        printf("You have to enter one argument. You entered %d.\n", size - 1);
    }
    return valid;
}

/* Function needed for allocating memory for number of amino acids in protein. */
int get_file_content_length(char filename[])
{
    FILE *f = fopen(filename, "rb");
    if (f == NULL)
    {
        char msg[31];
        sprintf(msg, "Error while opening the file %s\n", filename);
        perror(msg);
        exit(EXIT_FAILURE);
    }

    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    fseek(f, 0, SEEK_SET);

    fclose(f);

    return fsize;
}

char *read_file(char filename[], int fsize)
{
    FILE *f = fopen(filename, "rb");

    char *string = malloc(fsize + 1);
    fread(string, fsize, 1, f);
    fclose(f);

    string[fsize] = '\0';

    return string;
}

void fill_codon_array(Codon *codons, char *nucleotide_seq, int size)
{
    int i;
    for (i = 0; i < size; i++)
    {
        Codon codon;

        char first = nucleotide_seq[i * 3];
        char second = nucleotide_seq[i * 3 + 1];
        char third = nucleotide_seq[i * 3 + 2];

        char curr_codon[CODON_LENGTH + 1] = {first, second, third, '\0'};

        strcpy(codon.sequence, curr_codon);
        codons[i] = codon;
    }
}

int get_number_of_codons_from_array(const char prefix[])
{
    return atoi(prefix);
}

void construct_protein(Protein *protein, Codon codons[], int number_of_codons)
{
    (*protein).amino_acids = malloc((number_of_codons /*This - 1*/) * sizeof(*protein).amino_acids);
    // For number of codons current protein has.
    int i;
    for (i = 0; i < number_of_codons; i++)
    {
        // For all amino acids.
        int j;
        for (j = 0; j < NO_OF_AMINO_ACIDS; j++)
        {
            // To convert string into int.
            char str_no_of_codons[2] = {ALL_CODONS[j][0][1], '\0'};
            int no_of_codons_of_aa = get_number_of_codons_from_array(str_no_of_codons);

            // For number of unique codons of an amino acid has.
            int k;
            for (k = 1; k < no_of_codons_of_aa + 1; k++)
            {
                if (strcmp(ALL_CODONS[j][k], codons[i].sequence) == 0)
                {
                    char slc[2] = {ALL_CODONS[j][0][0], '\0'};

                    // This
                    // Bypasses the stop codon.
                    //if (strcmp(slc, ".") != 0)
                    //{
                    strcpy((*protein).amino_acids[i].slc, slc);
                    //}
                }
            }
        }
    }
}

bool nucleotide_seq_len_is_valid(int length)
{
    return length % 3 == 0;
}

void construct_known_proteins(Protein *known_proteins)
{
    Codon codons_a[NO_OF_CODONS], codons_b[NO_OF_CODONS], codons_c[NO_OF_CODONS], codons_d[NO_OF_CODONS], codons_e[NO_OF_CODONS];

    fill_codon_array(codons_a, PROTEIN_A, NO_OF_CODONS);
    fill_codon_array(codons_b, PROTEIN_B, NO_OF_CODONS);
    fill_codon_array(codons_c, PROTEIN_C, NO_OF_CODONS);
    fill_codon_array(codons_d, PROTEIN_D, NO_OF_CODONS);
    fill_codon_array(codons_e, PROTEIN_E, NO_OF_CODONS);

    construct_protein(&known_proteins[0], codons_a, NO_OF_CODONS);
    construct_protein(&known_proteins[1], codons_b, NO_OF_CODONS);
    construct_protein(&known_proteins[2], codons_c, NO_OF_CODONS);
    construct_protein(&known_proteins[3], codons_d, NO_OF_CODONS);
    construct_protein(&known_proteins[4], codons_e, NO_OF_CODONS);
}

char *build_amino_acid_sequence(Protein protein, int size)
{
    char *string = malloc((size - 1) + (size - 2) + 1);
    int i;
    for (i = 0; i < size - 1; i++)
    {
        if (i == 0) // If at start.
        {
            strcpy(string, protein.amino_acids[i].slc);
            strcat(string, "-");
        }
        else if (i == size - 2) // If at the end.
        {
            strcat(string, protein.amino_acids[i].slc);
            // This
            strcat(string, protein.amino_acids[number_of_codons_of_entered_protein - 1].slc);
        }
        else
        {
            strcat(string, protein.amino_acids[i].slc);
            strcat(string, "-");
        }
    }

    return string;
}

void check_if_it_is_a_known_protein(Protein protein, Protein known_proteins[])
{
    int i;
    for (i = 0; i < NO_OF_KNOWN_PROTEINS; i++)
    {
        if (strcmp(build_amino_acid_sequence(protein, number_of_codons_of_entered_protein), build_amino_acid_sequence(known_proteins[i], NO_OF_CODONS)) == 0)
        {
            printf("Protein%c is identified in the input sequence.\n", PROTEIN_NAMES[i]);
            it_is_a_known_protein = true;
            index_of_matched_protein = i;
            return;
        }
    }
    printf("It is not a known protein.\n");
    return;
}

void check_if_it_might_be_a_protein(Protein protein, Codon *codons)
{
    if (strcmp(protein.amino_acids[0].slc, "M") == 0)
    {
        char str_no_of_stop_codons[2] = {STOP_CODONS[0][1], '\0'};
        int number_of_stop_codons = get_number_of_codons_from_array(str_no_of_stop_codons);
        bool no_stop_codon = true;
        int i;
        for (i = 1; i < number_of_stop_codons + 1; i++)
        {
            if (strcmp(codons[number_of_codons_of_entered_protein - 1].sequence, STOP_CODONS[i]) == 0)
            {
                bool inappropriate_stop_codon = false;
                no_stop_codon = false;
                int j;
                for (j = 0; j < number_of_codons_of_entered_protein - 1; j++)
                {
                    int k;
                    for (k = 1; k < number_of_stop_codons + 1; k++)
                    {
                        if (strcmp(codons[j].sequence, STOP_CODONS[k]) == 0)
                        {
                            inappropriate_stop_codon = true;
                        }
                    }
                }

                if (!inappropriate_stop_codon)
                {
                    if (it_is_a_known_protein)
                    {
                        printf("Amino acids of Protein%c: %s\n", PROTEIN_NAMES[index_of_matched_protein],
                               build_amino_acid_sequence(protein, number_of_codons_of_entered_protein));
                    }
                    else
                        printf("It is probably a new protein. Amino acid sequence: %s \n", build_amino_acid_sequence(protein, number_of_codons_of_entered_protein));
                }
                else
                {
                    printf("The nucleotide sequence contains more than one stop codon, therefore it\'s not a protein.\n");
                }
                return;
            }
        }
        if (no_stop_codon)
        {
            printf("There is no stop codon at the end of the sequence, therefore it\'s not a protein.\n");
            return;
        }
    }
    else
    {
        printf("The nucleotide sequence does not start with Methionine, therefore it\'s not a protein.\n");
    }
    return;
}

int main(int argc, char *argv[])
{
    if (no_of_args_is_valid(argc))
    {

        int file_content_length = get_file_content_length(argv[1]);
        number_of_nucleotides_of_entered_protein = file_content_length;
        number_of_codons_of_entered_protein = number_of_nucleotides_of_entered_protein / 3;

        char nucleotide_seq[number_of_nucleotides_of_entered_protein + 1];
        strcpy(nucleotide_seq, read_file(argv[1], file_content_length));

        if (!nucleotide_seq_len_is_valid(number_of_nucleotides_of_entered_protein))
        {
            printf("Nucleotide sequence length must be divisible by 3.\n");
            exit(EXIT_FAILURE);
        }

        Codon codons[number_of_codons_of_entered_protein];
        fill_codon_array(codons, nucleotide_seq, number_of_codons_of_entered_protein);

        Protein protein;
        construct_protein(&protein, codons, number_of_codons_of_entered_protein);

        Protein protein_a, protein_b, protein_c, protein_d, protein_e;
        Protein known_proteins[NO_OF_KNOWN_PROTEINS] = {protein_a, protein_b, protein_c, protein_d, protein_e};
        construct_known_proteins(known_proteins);

        check_if_it_is_a_known_protein(protein, known_proteins);
        check_if_it_might_be_a_protein(protein, codons);

        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE;
}
