#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "hw2.h"

#define ERR_EXIT(s) fprintf(stderr, "name = %s ", friend_info), perror(s), exit(errno)

#define MAX_NODES 30
#define MAX_DEPTH 7
#define FIFO_NAME "Adopt.fifo"
#define MAX_TREEINFO_LEN MAX_FRIEND_NAME_LEN * 35
#define MAX_ARGC 3

typedef struct child {
    pid_t pid;
    int readfd;
    int writefd;
    struct child *next;
} Child;

typedef Child* ChildList;

ChildList childlist = NULL;

// somethings I recommend leaving here, but you may delete as you please
static char root[MAX_FRIEND_INFO_LEN] = "Not_Tako"; // root of tree
static char friend_info[MAX_FRIEND_INFO_LEN];       // current process info
static char friend_name[MAX_FRIEND_NAME_LEN];       // current process name
static int friend_value;                            // current process value

void meet(char *parent, char *child, int print);

void meet_child(char *child);

int check(char *parent);

void send_subtree_info(int depth, int save, char* savebuf);

void graduate(char* parent);

void kill_myself();

void clean_child(Child* child);

void adopt(char* parent, char* child);

int check_parent_in_child(char* parent, char* child);

// Is Root of tree
static inline bool is_Not_Tako() { return (strcmp(friend_name, root) == 0); }

// a bunch of prints for you
void print_direct_meet(char *friend_name)
{
    fprintf(stdout, "Not_Tako has met %s by himself\n", friend_name);
}

void print_indirect_meet(char *parent_friend_name, char *child_friend_name)
{
    fprintf(stdout, "Not_Tako has met %s through %s\n", child_friend_name,
            parent_friend_name);
}

void print_fail_meet(char *parent_friend_name, char *child_friend_name)
{
    fprintf(stdout, "Not_Tako does not know %s to meet %s\n",
            parent_friend_name, child_friend_name);
}

void print_fail_check(char *parent_friend_name)
{
    fprintf(stdout, "Not_Tako has checked, he doesn't know %s\n",
            parent_friend_name);
}

void print_success_adopt(char *parent_friend_name, char *child_friend_name)
{
    fprintf(stdout, "%s has adopted %s\n", parent_friend_name,
            child_friend_name);
}

void print_fail_adopt(char *parent_friend_name, char *child_friend_name)
{
    fprintf(stdout, "%s is a descendant of %s\n", parent_friend_name,
            child_friend_name);
}

void print_compare_gtr(char *friend_name)
{
    fprintf(stdout, "Not_Tako is still friends with %s\n", friend_name);
}

void print_compare_leq(char *friend_name)
{
    fprintf(stdout, "%s is dead to Not_Tako\n", friend_name);
}

void print_final_graduate()
{
    fprintf(stdout,
            "Congratulations! You've finished Not_Tako's annoying tasks!\n");
}

int read_command(char* buf, char** cmd_argv);


/* remember read and write may not be fully transmitted in HW1?
void fully_write(int write_fd, void *write_buf, int write_len);

void fully_read(int read_fd, void *read_buf, int read_len);

please do above 2 functions to save some time
*/

int main(int argc, char *argv[])
{
    // Hi! Welcome to SP Homework 2, I hope you have fun
    pid_t process_pid = getpid(); // you might need this when using fork()
    if (argc != 2)
    {
        fprintf(stderr, "Usage: ./friend [friend_info]\n");
        return 0;
    }
    setvbuf(stdout, NULL, _IONBF,
            0); // prevent buffered I/O, equivalent to fflush() after each
                // stdout, study this as you may need to do it for other friends
                // against their parents

    // put argument one into friend_info
    strncpy(friend_info, argv[1], MAX_FRIEND_INFO_LEN);

    if (strcmp(argv[1], root) == 0)
    {
        // is Not_Tako
        fprintf(stderr, "in tako, pid = %d\n", getpid());
        strncpy(friend_name, friend_info,
                MAX_FRIEND_NAME_LEN); // put name into friend_name
        friend_name[MAX_FRIEND_NAME_LEN - 1] =
            '\0';           // in case strcmp messes with you
        friend_value = 100; // Not_Tako adopting nodes will not mod their values
    } else {
        // is other friends
        // extract name and value from info
        char tmp[MAX_FRIEND_INFO_LEN];
        strcpy(tmp, friend_info);
        char *token = strtok(tmp, "_");
        strncpy(friend_name, token, MAX_FRIEND_NAME_LEN);
        token = strtok(NULL, "_");
        friend_value = atoi(token);
        // where do you read from? Parent read fd
        // anything else you have to do before you start taking commands?
        // Send back to parent that we've met
        char c = 1;
        if (write(PARENT_WRITE_FD, &c, 1) != 1) ERR_EXIT("error telling parent we've met");
    }
    fprintf(stderr, "info: %s, name: %s, value: %d, pid: %d\n", friend_info, friend_name, friend_value, getpid());
    char cmd_buf[MAX_CMD_LEN] = {0}, *cmd_argv[MAX_ARGC] = {0};

    // TODO:
    /* you may follow SOP if you wish, but it is not guaranteed to consider
    every possible outcome

    1. read from parent/stdin
    2. determine what the command is (Meet, Check, Adopt, Graduate,
    Compare(bonus)), I recommend using strcmp() and/or char check
    3. find out who should execute the command (extract information received)
    4. execute the command or tell the requested friend to execute the command
        4.1 command passing may be required here
    5. after previous command is done, repeat step 1.
    */

    // Hint: do not return before receiving the command "Graduate"
    // please keep in mind that every process runs this exact same program, so
    // think of all the possible cases and implement them

    while (1)
    {
        int input_argc = read_command(cmd_buf, cmd_argv);

        // fprintf(stderr, "input_argc = %d\n", input_argc);
#ifdef TEST
        if (input_argc < 1) kill_myself();
#endif /* ifdef TEST */
#ifndef TEST
        if (input_argc < 1) continue;
#endif /* ifndef TEST */
        char* command = cmd_argv[0];
        fprintf(stderr, "%c%s received command = %s = %d, argc = %d\n", is_Not_Tako() ? '\t' : ' ', friend_info, cmd_buf, cmd_buf[0], input_argc);

        if (strcmp(command, "Meet") == 0)
        {
            char *parent = cmd_argv[1], *child = cmd_argv[2];
            // fprintf(stderr, "Meeting %s...\n", child);
            meet(parent, child, 1);
        } else if (strcmp(command, "Check") == 0) {
            char *parent = cmd_argv[1] ;
            // fprintf(stderr, "Checking %s...\n", parent);
            check(parent);
        } else if (strcmp(command, "subtree") == 0) { // util cmd, will return subtree info of current node
            int depth = atoi(cmd_argv[1]);
            // fprintf(stderr, "name = %s, command = subtree %d\n", friend_info, depth);
            send_subtree_info(depth, 0, NULL);
        } else if (strcmp(command, "Graduate") == 0) {
            char *parent = cmd_argv[1];
            if (is_Not_Tako()) {
                // TODO: this will print second time when ending (it's right)
                int ret = check(parent);
                if (ret == -1) { // parent not exists
                    continue;
                }
                if (strcmp(parent, root) == 0) print_final_graduate();
            }
            graduate(parent);
        } else if (strcmp(command, "kys") == 0) {
            kill_myself();
        } else if (strcmp(command, "Adopt") == 0) {
            char *parent = cmd_argv[1], *child = cmd_argv[2];
            if (is_Not_Tako()) {
                int ret = check_parent_in_child(parent, child);
                if (ret) {
                    print_fail_adopt(parent, child);
                    continue;
                }
            }
            // fprintf(stderr, "Adopting %s to %s...\n", child, parent);
            adopt(parent, child);
        } else if (strcmp(command, "Checkin") == 0) {
            char* parent = cmd_argv[1], *child = cmd_argv[2];
            check_parent_in_child(parent, child);
        }
        /* pseudo code
        else if(full_cmd[1] == 'o'){
            Bonus has no hints :D
        } else {
        fprintf(stderr, "name = %s, command = %s", friend_info, command);
        ERR_EXIT("invalid command");
        */
        else {
            // there's an error, we only have valid commmands in the test cases
            fprintf(stderr, "%s received error input : %s\n", friend_name,
                    cmd_buf); // use this to print out what you received
            // ERR_EXIT("invalid input");
        }
    }

    // final print, please leave this in, it may bepart of the test case output
    if (is_Not_Tako())
    {
        print_final_graduate();
    }
    return 0;
}

int read_command(char* buf, char** cmd_argv)
{
    // read from stdin or fd and put it in buf and argv array, returns count of args parsed
    // TODO: param buf may not needed
    int fd = is_Not_Tako() ? STDIN_FILENO : PARENT_READ_FD;
    int bytes_read;
    if (fd == STDIN_FILENO)
    {
        size_t buflen = MAX_CMD_LEN;
        bytes_read = getline(&buf, &buflen, stdin);
        if (bytes_read == -1) {
            // perror("getline");
            return -1;
        }
        else if (bytes_read > 0)
            buf[bytes_read - 1] = '\0';
    } else {
        bytes_read = read(fd, buf, MAX_CMD_LEN);
        if (bytes_read == -1)
            ERR_EXIT("read");
        buf[bytes_read] = '\0';
    }

    int argc = 0;
    char *ptr = strtok(buf, " ");
    while (ptr != NULL && argc < MAX_ARGC)
    {
        cmd_argv[argc++] = ptr;
        ptr = strtok(NULL, " ");
    }
    return argc;
}

void meet(char *parent, char *child, int print) {
    if (strcmp(friend_name, parent) == 0) {
        meet_child(child);
        if (is_Not_Tako()) {
            char *child_name = strtok(child, "_");
            if (print) print_direct_meet(child_name);
        } else { // tell parents we've met
            if (print) {
                char msg = 1;
                if (write(PARENT_WRITE_FD, &msg, 1) != 1)
                    ERR_EXIT("error telling parent we've met");
            }
        }
        return;
    }

    ChildList p = childlist;
    char msg;
    while (p != NULL) {
        char buf[MAX_CMD_LEN];
        sprintf(buf, "Meet %s %s", parent, child);
        if (write(p->writefd, buf, strlen(buf)) != strlen(buf))
            ERR_EXIT("error passing meet to child");

        // wait for child
        fprintf(stderr, "%s waiting for child's meet status...\n", friend_info);
        if (read(p->readfd, &msg, 1) != 1)
            ERR_EXIT("error receiving meet status from child");
        if (msg == 1)
            break;
        p = p->next;
    }
    fprintf(stderr, "%s is telling parent meet status...\n", friend_info);
    if (p == NULL) {
        // meet failed, parent not found
        if (is_Not_Tako()) {
            char *child_name = strtok(child, "_");
            if (print) print_fail_meet(parent, child_name);
        } else {
            if (print) {
                msg = 0;
                if (write(PARENT_WRITE_FD, &msg, 1) != 1)
                    ERR_EXIT("error telling parent meet failed");
            }
        }
    } else {
        // meet succeeded, is an indirect meet
        if (is_Not_Tako()) {
            char *child_name = strtok(child, "_");
            if (print) print_indirect_meet(parent, child_name);
        } else {
            if (print) {
                msg = 1;
                if (write(PARENT_WRITE_FD, &msg, 1) != 1)
                    ERR_EXIT("error telling parent meet succeeded");
            }
        }
    }
}

void meet_child(char *child) {
    // By ChatGPT
    // Allocate memory for a new Child struct and initialize pointers
    Child* child_proc = (Child*)malloc(sizeof(Child));
    child_proc->next = NULL;

    int parent_to_child_pipe[2], child_to_parent_pipe[2]; // Communication channels
    if (pipe(parent_to_child_pipe) < 0 || pipe(child_to_parent_pipe) < 0) {
        ERR_EXIT("meet_child pipe");
    }

    child_proc->readfd = child_to_parent_pipe[0];
    child_proc->writefd = parent_to_child_pipe[1];

    pid_t process_id;
    if ((process_id = fork()) < 0) {
        ERR_EXIT("meet_child fork");
    } else if (process_id == 0) { // Child process
        // Close unnecessary ends of pipes
        close(child_to_parent_pipe[0]);
        close(parent_to_child_pipe[1]);

        // Redirect file descriptors for communication
        dup2(child_to_parent_pipe[1], PARENT_WRITE_FD);
        dup2(parent_to_child_pipe[0], PARENT_READ_FD);

        // Execute the new process
        if (execl("./friend", "friend", child, NULL) < 0) {
            ERR_EXIT("meet_child exec");
        }
    } else { // Parent process
        // Close child-specific ends of pipes
        close(child_to_parent_pipe[1]);
        close(parent_to_child_pipe[0]);

        // Store process ID of the child
        child_proc->pid = process_id;

        // Wait for child reply
        char init_signal;
        if (read(child_proc->readfd, &init_signal, 1) != 1 || init_signal != 1) {
            ERR_EXIT("Failed to receive initial signal from child");
        }
    }

    // Insert new child into the linked list
    if (childlist == NULL) {
        childlist = child_proc;
    }else {
        ChildList temp = childlist;
        while (temp->next != NULL) {
            temp = temp->next;
        }
        temp->next = child_proc;
    }
}

void send_subtree_info(int depth, int save, char* savebuf) {
    // if save == 1, do not print but save result into savemap
    char info[MAX_TREEINFO_LEN] = {0};
    int bytes_printed = 0;
    bytes_printed += sprintf(info + bytes_printed, (depth == 0) ? "%s %d" : ":%s %d", friend_info, depth);

    ChildList p = childlist;
    while (p != NULL) {
        char buf[MAX_CMD_LEN];
        sprintf(buf, "subtree %d", depth + 1);
        if (write(p->writefd, buf, strlen(buf)) != strlen(buf))
            ERR_EXIT("error passing subtree to child");

        char inf[MAX_TREEINFO_LEN] = {0};
        if (read(p->readfd, inf, MAX_TREEINFO_LEN) == -1)
            ERR_EXIT("error receiving subtree info from child");
        fprintf(stderr, "%s receive subtree info from child = %d: %s\n", friend_info, p->pid, inf);
        bytes_printed += sprintf(info + bytes_printed, "%s", inf);
        p = p->next;
    }
    // send back result to parent
    if (depth != 0 && bytes_printed > 0) {
        fprintf(stderr, "%s is sending back tree info back to parent\ninfo = %s\n", friend_info, info);
        if (write(PARENT_WRITE_FD, info, strlen(info)) != strlen(info)) ERR_EXIT("error sending subtree info to parent");
    } else {
        if (save == 0) {  // we are root, decode shits and print
            fprintf(stderr, "check is decoding treeinfo = %s\n", info);
            char map[MAX_DEPTH][MAX_NODES][MAX_FRIEND_INFO_LEN] = {0};
            int map_count[MAX_DEPTH] = {0};
            char* curr = strtok(info, ":");
            while (curr != NULL) {
                char curr_info[MAX_FRIEND_INFO_LEN]; int d;
                sscanf(curr, "%s %d", curr_info, &d);
                strcpy(map[d][map_count[d]++], curr_info);
                curr = strtok(NULL, ":");
            }
            for (int i = 0; i < MAX_DEPTH; i++) {
                for (int j = 0; j < map_count[i]; j++) {
                    fprintf(stdout, "%s%c", map[i][j], " \n"[j==map_count[i] - 1]);
                }
            }
        } else { // we are root but don't print but save
            strcpy(savebuf, info);
        }
    }
}

int check(char* parent) {
    if (strcmp(friend_name, parent) == 0) {
        // BFS check each child
        fprintf(stderr, "BFSing\n");
        send_subtree_info(0, 0, NULL);
        if (!is_Not_Tako()) {
            char msg = 1;
            if (write(PARENT_WRITE_FD, &msg, 1) != 1)
                ERR_EXIT("error telling parent check is complete");
        }
    } else {
        ChildList p = childlist;
        char msg;
        while (p != NULL) {
            char buf[MAX_CMD_LEN];
            sprintf(buf, "Check %s", parent);
            if (write(p->writefd, buf, strlen(buf)) != strlen(buf))
                ERR_EXIT("error passing check to child");

            // wait for child
            fprintf(stderr, "%s waiting for child's check status...\n", friend_info);
            if (read(p->readfd, &msg, 1) != 1)
                ERR_EXIT("error receiving check status from child");
            if (msg == 1)
                break;
            p = p->next;
        }
        if (p == NULL) {
            // check failed since parent was not found
            if (is_Not_Tako()) {
                print_fail_check(parent);
            } else {
                msg = 0;
                if (write(PARENT_WRITE_FD, &msg, 1) != 1)
                    ERR_EXIT("error telling parent check failed");
            }
            return -1;
        } else { // parent is found somewhere down
            if (!is_Not_Tako()) {
                msg = 1;
                if (write(PARENT_WRITE_FD, &msg, 1) != 1)
                    ERR_EXIT("error telling parent check succeeded");
            }
        }
    }
    return 0;
}

void clean_child(Child* child) {
    // call wait() or waitpid() to reap child; // this is blocking
    fprintf(stderr, "%s is waiting to reap child...\n", friend_info);
    if(waitpid(child->pid, NULL, 0) < 0) ERR_EXIT("waitpid error");
    fprintf(stderr, "%s reaped child\n", friend_info);

    close(child->readfd);
    close(child->writefd);
    free(child);
}

void kill_myself() {
    fprintf(stderr, "%s is killing himself\n", friend_info);
    ChildList p = childlist;
    char msg;
    while (p != NULL) {
        char buf[MAX_CMD_LEN];
        sprintf(buf, "kys");
        if (write(p->writefd, buf, strlen(buf)) != strlen(buf)) // tell child to die
            ERR_EXIT("error passing kill signal to child");

        // wait for child
        fprintf(stderr, "%s waiting for child to die\n", friend_info);
        if (read(p->readfd, &msg, 1) != 1)
            ERR_EXIT("error receiving kill status from child");

        // clean child resources
        clean_child(p); // will free here
        p = p->next;
    }
    if (!is_Not_Tako()) {
        msg = 2;
        if (write(PARENT_WRITE_FD, &msg, 1) != 1)
            ERR_EXIT("error telling parent I am killing myself");
        close(PARENT_WRITE_FD);
        close(PARENT_READ_FD);
    }
    fprintf(stderr, "%s is dead\n", friend_info);
    exit(0);
}

void graduate(char* parent) {
    //         Q1: what resources have to be cleaned up and why?
    //         Hint: Check pseudo code above
    //         Q2: what commands needs to be executed? what are their orders to
    // avoid deadlock or infinite blocking? A: (tell child to die, reap child,
    // tell parent you're dead, return (die))
    if (strcmp(friend_name, parent) == 0) {
        // pre order kill every child
        fprintf(stderr, "killing\n");
        kill_myself(); // this will exit
        return;
    }
    ChildList curr = childlist, prev = NULL;
    char msg = -1;
    while (curr != NULL) {
        char buf[MAX_CMD_LEN];
        sprintf(buf, "Graduate %s", parent);
        if (write(curr->writefd, buf, strlen(buf)) != strlen(buf)) // tell child to grad
            ERR_EXIT("error passing graduate signal to child");

        // wait for child
        fprintf(stderr, "%s is waiting for child to die...\n", friend_info);
        if (read(curr->readfd, &msg, 1) != 1)
            ERR_EXIT("error receiving kill status from child");
        fprintf(stderr, "%s receive msg = %d\n", friend_info, msg);
        if (msg == 2) {
            if (prev != NULL) prev->next = curr->next;
            else childlist = curr->next;

            clean_child(curr);

            break;
        }
        prev = curr;
        curr = curr->next;
    }
    if (!is_Not_Tako()) {
        msg = (msg == 2) ? 1 : 0;
        if (write(PARENT_WRITE_FD, &msg, 1) != 1)
            ERR_EXIT("error telling parent kill status");
    }
}

void adopt(char* parent, char* child) {
    // parent and child guaranteed to exist
    // 1. parent: get tree info of child
    // 2. child: send tree info to parent
    //      a. dies
    // 3. create same tree with mod friend value under parent
    
    // child might be under parent so we don't stop when this is parent
    // if child first and parent latter, don't do shit
    // parent 1 child 2
    Child* deadchlid = NULL, *deadchildprev = NULL; // for killing purpose
    ChildList p = childlist, prev = NULL;
    char msgs = 0, msg = -1;
    int is_child = 0, is_parent = 0, waitlist[2] = {-1, -1};
    if (strcmp(friend_name, parent) == 0) msgs += 1, is_parent = 1;
    if (strcmp(friend_name, child) == 0) msgs += 2, is_child = 1;
    while (p != NULL && !is_child) {
        char buf[MAX_CMD_LEN];
        sprintf(buf, "Adopt %s %s", parent, child);
        if (write(p->writefd, buf, strlen(buf)) != strlen(buf))
            ERR_EXIT("error passing adopt to child");

        // wait for child
        fprintf(stderr, "%s is waiting for child adopt status...\n", friend_info);
        if (read(p->readfd, &msg, 1) != 1)
            ERR_EXIT("error receiving adopt status from child");
        if (msg) {
            fprintf(stderr, "%s received child adopt status = %d\n", friend_info, msg);
            msgs += msg;
            if (msg == 1 || msg == 3) // parent
                waitlist[0] = p->readfd;
            if (msg == 2) { // child
                waitlist[1] = p->readfd;
                deadchlid = p, deadchildprev = prev;
            }
        }
        if (msgs == 3) break;
        prev = p;
        p = p->next;
    }
    // didn't found both -> parent is descendant of child, but parent will still open fifo, how to fix? -> check after get subtree info
    if (!is_Not_Tako()) {
        if (write(PARENT_WRITE_FD, &msgs, 1) != 1)
            ERR_EXIT("error telling parent adopt status");
    } else if (is_Not_Tako() && msgs != 3) {
        print_fail_adopt(parent, child);
        return;
    }
    // if (msgs != 3) return;
    char buf[MAX_TREEINFO_LEN] = {0};
    if (is_child || is_parent) {
        if (mkfifo(FIFO_NAME, 0666) == -1 && errno != EEXIST) ERR_EXIT("mkfifo error");

        fprintf(stderr, "%s is opening fifo to write...is_child = %d, is_parent = %d\n", friend_info, is_child, is_parent);
        if (is_parent) {
            // if is parent: receive from child
            int read_fd;
            if ((read_fd = open(FIFO_NAME, O_RDONLY)) == -1) ERR_EXIT("open read fifo error");
            if (read(read_fd, buf, MAX_TREEINFO_LEN) == -1) ERR_EXIT("read fifo error");

            fprintf(stderr, "%s read info from fifo = %s\n", friend_info, buf);

            close(read_fd);
            if (unlink(FIFO_NAME) == -1) ERR_EXIT("unlink fifo");
        } else if (is_child) {
            int write_fd;
            if ((write_fd = open(FIFO_NAME, O_WRONLY)) == -1) ERR_EXIT("open write fifo error");

            fprintf(stderr, "Getting tree info of %s...\n", child);
            send_subtree_info(0, 1, buf);
            fprintf(stderr, "Got tree info of %s\n", buf);

            fprintf(stderr, "%s is sending treeinfo through fifo...\n", friend_info);
            if (write(write_fd, buf, strlen(buf)) != strlen(buf)) ERR_EXIT("write fifo error");
            close(write_fd);
        }
    }

    char c = -1;
    if (waitlist[0] != -1) { // has parent in subtree
        // fprintf(stderr, "%s is waiting parent to finish adopting...\n", friend_info);
        if (read(waitlist[0], &c, 1) != 1) ERR_EXIT("error waiting parent finish adopt"); // 1 for success
        fprintf(stderr, "%s received parent finish adopting\n", friend_info);
    }
    if (waitlist[1] != -1) { // has child in subtree
                             // 1 for success, 2 for direct child needs cleaning (he died)
        // fprintf(stderr, "%s is waiting child to finish adopting...\n", friend_info);
        if (read(waitlist[1], &c, 1) != 1) ERR_EXIT("error waiting child finish adopt");
        fprintf(stderr, "%s received child adopting c = %d\n", friend_info, c);
        if (c == 2) {
            fprintf(stderr, "%s received child is killing himself\n", friend_info);
            if (deadchildprev == NULL) childlist = deadchlid->next;
            else deadchildprev->next = deadchlid->next;
            clean_child(deadchlid);
            // graduate(child);
        } else if (c == 1) fprintf(stderr, "%s received child done reaping\n", friend_info);
    }

    if (is_parent) {
        // decode to a map
        fprintf(stderr, "%s is decoding buf = %s\n", friend_info, buf);
        // maintain newest parent of each level
        char map[MAX_DEPTH][MAX_NODES][MAX_FRIEND_INFO_LEN] = {0};
        int newest_parent[MAX_DEPTH]; for (int i = 0; i < MAX_DEPTH; i++) newest_parent[i] = -1;
        char* ptrinfo;
        char* curr = strtok_r(buf, ":", &ptrinfo);
        while (curr != NULL) {
            char curr_info[MAX_FRIEND_INFO_LEN]; int d, new_value = -1;
            sscanf(curr, "%s %d", curr_info, &d);
            fprintf(stderr, "   old friend_info = %s\n", curr_info);

            char* name, *value, new_info[MAX_FRIEND_INFO_LEN] = {0};
            name = strtok(curr_info, "_"), value = strtok(NULL, "_");
            new_value = atoi(value) % friend_value;
            sprintf(new_info, "%s_%02d", name, new_value);
            fprintf(stderr, "   new friend_info = %s\n", new_info);

            char* new_parent;
            if (d == 0) new_parent = parent;
            else {
                new_parent = map[d - 1][newest_parent[d - 1]];
            }
            strcpy(map[d][++newest_parent[d]], curr_info);
            fprintf(stderr, "%s is meeting %s...\n", new_parent, new_info);
            meet(new_parent, new_info, 0);

            curr = strtok_r(NULL, ":", &ptrinfo);
        }
    }
    if (is_child) {
        // c = 2;
        // if (write(PARENT_WRITE_FD, &c, 1) != 1) ERR_EXIT("error telling parent we need to die");
        kill_myself();
    }

    if (waitlist[0] == -1 && waitlist[1] == -1 && !(is_child || is_parent)) return; // unrelated people just return
    if (is_Not_Tako()) {
        print_success_adopt(parent, child);
    } else {
        c = 1;
        // fprintf(stderr, "%s is telling parent we're done adopting...\n", friend_info);
        if (write(PARENT_WRITE_FD, &c, 1) != 1) ERR_EXIT("error telling parent we're done adopting");
        fprintf(stderr, "%s told parent we've done adopting\n", friend_info);
    }
}

int check_parent_in_child(char* parent, char* child) {
    // 1. get subtree info of child
    // 2. check parent in it
    if (strcmp(friend_name, child) == 0) {
        char buf[MAX_TREEINFO_LEN] = {0};
        send_subtree_info(0, 1, buf);
        // char name[MAX_FRIEND_INFO_LEN] = {0};
        // fprintf(stderr, "checkin treeinfo = %s\n", buf);
        char* ptrinfo;
        char* curr = strtok_r(buf, ":", &ptrinfo);
        int msg = 0;
        while (curr != NULL) {
            char* name = strtok(curr, "_");
            if (strcmp(parent, name) == 0) msg = 1;
            curr = strtok_r(NULL, ":", &ptrinfo);
        }
        // if (msg == 1) fprintf(stderr, "%s is in treeinfo\n", parent);

        if (!is_Not_Tako()) {
            if (write(PARENT_WRITE_FD, &msg, 1) != 1)
                ERR_EXIT("error telling parent checkin status");
        }
        return msg;
    } else {
        ChildList p = childlist;
        char msg = 0;
        while (p != NULL) {
            char buf[MAX_CMD_LEN];
            sprintf(buf, "Checkin %s %s", parent, child);
            if (write(p->writefd, buf, strlen(buf)) != strlen(buf))
                ERR_EXIT("error passing checkin to child");

            // wait for child
            fprintf(stderr, "name = %s, waiting for child...\n", friend_info);
            if (read(p->readfd, &msg, 1) != 1)
                ERR_EXIT("error receiving checkin status from child");
            if (msg == 1)
                break;
            p = p->next;
        }
        if (!is_Not_Tako()) {
            if (write(PARENT_WRITE_FD, &msg, 1) != 1)
                ERR_EXIT("error telling parent checkin status");
        }
        return msg;
    }
    return 0;
}
