Rule *parse_file(FILE *fp) {
    // Create a pointer to the first rule in the linked list.
    Rule *first_rule = create_rule(fp, NULL);

    // Create new rules while the file is not empty.
    Rule *prev_rule = first_rule;
    Rule *new_rule;
    while (new_rule != NULL) {
        // Attempt to create a new rule from current position in Makefile.
        new_rule = create_rule(fp, first_rule);

        // Link the newly created rule to the previous rule if successful.
        if (new_rule != NULL) {
            prev_rule->next_rule = create_rule(fp, first_rule);
            prev_rule = prev_rule->next_rule;
        }
    }

    // Return a pointer to the first rule in the linked list.
    return first_rule;
}

Rule *create_rule(FILE *fp, Rule *first_rule) {
    char line[MAXLINE];
    char buffer[MAXLINE];

    // Read line from fp.
    fgets(line, MAXLINE, fp);

    // Skip comment and empty lines.
    while (is_comment_or_empty(buffer) == 1) {
        // Return NULL pointer if end of file is reached.
        if (fgets(line, MAXLINE, fp) == NULL) {
            return NULL;
        }
    }

    // Read in the rule's target name.
    int len_target = 0;
    while (line[len_target] != ' ') {
        buffer[len_target] = line[len_target];
        len_target += 1;
    }
    buffer[len_target] = '\0';

    // Determine whether a rule with target name already exists in the list.
    Rule *rule = find_rule(first_rule, buffer);
    if (rule == NULL) {
        rule = malloc(sizeof(Rule));
        rule->dependencies = NULL;
        rule->actions = NULL;
        rule->next_rule = NULL;
    }

    // Set rule's target attribute to the read target.
    rule->target = malloc(sizeof(char) * (strlen(buffer) + 1));
    strcpy(rule->target, buffer);
    
    // Read in the dependencies, if they exist.
    int i = len_target + 3;
    int len_dep;
    Rule *dep;
    Dependency *curr_dep;
    while (line[i] != '\n') {
        len_dep = 0;

        // Read a depenency name, if it exists.
        while (line[i] != ' ' && line[i] != '\n') {
            buffer[len_dep] = line[i];
            i += 1;
            len_dep += 1;
        }

        // Add to the list of dependencies if a dependency name was read.
        if (len_dep > 0) {
            buffer[len_dep] = '\0';

            // Attempt to find the dependency in the existing list of rules.
            dep = find_rule(first_rule, buffer);

            // Create a new rule on the heap if it DNE.
            if (dep == NULL) {
                dep = malloc(sizeof(Rule));
                dep->target = malloc(sizeof(char) * (strlen(buffer) + 1));
                strcpy(dep->target, buffer);
                dep->dependencies = NULL;
                dep->actions = NULL;
                dep->next_rule = NULL;
            }

            // Create a new dependency object on the heap.
            Dependency *new_dep = malloc(sizeof(Dependency));
            new_dep->rule = dep;
            new_dep->next_dep = NULL;

                        // Add the dependency to the list of dependencies in rule.
            if (rule->dependencies == NULL) {
                rule->dependencies = new_dep;
            }
            else {
                curr_dep = rule->dependencies;
                while (curr_dep->next_dep != NULL) {
                    curr_dep = curr_dep->next_dep;
                }
                curr_dep->next_dep = new_dep;
            }
        }

        i += 1;
    }

    // Read in arguments from fp and save it to rule's actions.
    int len_arg;
    int num_args;
    int curr_arg;
    Action *prev_action = NULL;
    while (fgets(line, MAXLINE, fp) != NULL && line[0] == ' ') {
        // Skip comment or empty lines.
        while (is_comment_or_empty(line) == 1) {
            fgets(line, MAXLINE, fp);
        }

        // If action lines have finished, exit the loop.
        if (line[0] != ' ' || fp == NULL) {
            break;
        }

        // Determine the number of arguments in the line.
        i = 0;
        num_args = 0;
        while (line[i] != '\n') {
            while (line[i] != ' ') {
                i += 1;
            }
            num_args += 1;
            i += 1;
        }

        // Create an Action node.
        Action *action = malloc(sizeof(Action));
        action->args = malloc(sizeof(char *) * num_args);
        action->next_act = NULL;
        i = 0;
        curr_arg = 0;
        while (line[i] != '\n') {
            len_arg = 0;

            // Read in one argument.
            while (line[i] != ' ') {
                buffer[len_arg] = line[i];
                len_arg += 1;
                i += 1;
            }
            buffer[len_arg] = '\0';

            // Save argument into args array.
            action->args[curr_arg] = malloc(sizeof(char) * (strlen(buffer)+1));
            strcpy(action->args[curr_arg], buffer);
            i += 1;
            curr_arg += 1;
        }

        // Add the Action node to the list of actions.
        if (prev_action == NULL) {
            rule->actions = action;
        }
        else {
            prev_action->next_act = action;
        }

        prev_action = action;
    }

    // Move file pointer back one line.
    fseek(fp, -strlen(line), SEEK_CUR);

    // Return a pointer to the newly created Rule.
    return rule;
}

/* Search for a rule with the same target as the input target.
   Return a pointer to the Rule, if it exists.
   Otherwise, return NULL.
 */
Rule *find_rule(Rule *first_rule, char *target) {
    Rule *curr_rule = first_rule;
    Dependency *curr_dep;

    // Search for a rule with a matching target in the list of rules and their dependencies.
    while (curr_rule != NULL) {
        // Return a pointer to a rule if its target matches the input string.
        if (strcmp(target, curr_rule->target)) {
            return curr_rule;
        }

        // Return a pointer to a rule in a list of dependencies if its target matches the input string.
        curr_dep = curr_rule->dependencies;
        while (curr_dep != NULL) {
            if (strcmp(target, curr_dep->rule->target)) {
                return curr_dep->rule;
            }
            curr_dep = curr_dep->next_dep;
        }

        // Otherwise, continue to traverse the list of rules.
        curr_rule = curr_rule->next_rule;
    }

    // If no rule is found, return NULL.
    return NULL;
}
Read from the open file fp, and create the linked data structure that represents the Makefile contained in the file.