#include "Serial.h"

extern void cleanup();

int open_port(char* port) {
    int fd;
    fd = open(port, O_RDWR | O_NOCTTY | O_SYNC | O_NDELAY);

    if (fd == -1) {
        perror("The serial port could not be opened");
        cleanup();
        exit(1);
    } else {
        fcntl(fd, F_SETFL, 0);
    }

    return fd;
}

int close_port(int fd) {
    return close(fd);
}

void set_speed(void *tty_s, int speed) {
    cfsetospeed(tty_s, speed);
    cfsetispeed(tty_s, speed);
}

bool setup_port(int fd, int speed) {
    struct termios tty;
    if (tcgetattr(fd, &tty) != 0) {
        perror("Error setting port speed, could not read port parameters");
        return false;
    }

    switch (speed) {
        case 0:
            set_speed(&tty, B0);
            break;
        case 50:
            set_speed(&tty, B50);
            break;
        case 75:
            set_speed(&tty, B75);
            break;
        case 110:
            set_speed(&tty, B110);
            break;
        case 134:
            set_speed(&tty, B134);
            break;
        case 150:
            set_speed(&tty, B150);
            break;
        case 200:
            set_speed(&tty, B200);
            break;
        case 300:
            set_speed(&tty, B300);
            break;
        case 600:
            set_speed(&tty, B600);
            break;
        case 1200:
            set_speed(&tty, B1200);
            break;
        case 2400:
            set_speed(&tty, B2400);
            break;
        case 4800:
            set_speed(&tty, B4800);
            break;
        case 9600:
            set_speed(&tty, B9600);
            break;
        case 19200:
            set_speed(&tty, B19200);
            break;
        case 38400:
            set_speed(&tty, B38400);
            break;
        case 57600:
            set_speed(&tty, B57600);
            break;
        case 115200:
            set_speed(&tty, B115200);
            break;
        case 230400:
            set_speed(&tty, B230400);
            break;
        default:
            printf("Error: Invalid port speed %d specified\r\n", speed);
            cleanup();
            exit(1);
            return false;
    }

    // Set 8-bit characters, no parity, one stop bit
    tty.c_cflag |= CS8;
    tty.c_cflag &= ~PARENB;
    tty.c_cflag &= ~CSTOPB;

    // Disable hardware flow control
    tty.c_cflag &= ~CRTSCTS;

    // Enable reading and ignore modem
    // control lines
    tty.c_cflag |= CREAD | CLOCAL;

    // Disable canonical mode, echo
    // and signal characters.
    tty.c_lflag &= ~ICANON;
    tty.c_lflag &= ~ECHO;
    tty.c_lflag &= ~ECHOE;
    tty.c_lflag &= ~ECHONL;
    tty.c_lflag &= ~ISIG;

    // Disable processing of input,
    // just pass the raw data.
    tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);

    // Disable XON/XOFF software flow control.
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);

    // Disable processing output bytes
    // and new line conversions
    tty.c_oflag &= ~OPOST;
    tty.c_oflag &= ~ONLCR;

    // Block forever until at least one byte is read.
    tty.c_cc[VMIN]   = 1;
    tty.c_cc[VTIME]  = 0;

    // TODO: Check these
    // Prevent conversion of tabs to spaces (NOT PRESENT IN LINUX)
    // tty.c_oflag &= ~OXTABS;
    // Prevent removal of C-d chars (0x004) in output (NOT PRESENT IN LINUX)
    // tty.c_oflag &= ~ONOEOT;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        perror("Could not configure serial port parameters");
        return false;
    } else {
        return true;
    }
}

bool set_port_blocking(int fd, bool should_block) {
    struct termios tty;
    memset(&tty, 0, sizeof tty);

    if (tcgetattr(fd, &tty) != 0) {
        perror("Error configuring port blocking behaviour, could not read port parameters");
        return false;
    } else {
        // TODO: Implement this correctly
        if (should_block) {
            // Block forever until at least one byte is read.
            tty.c_cc[VMIN]   = 1;
            tty.c_cc[VTIME]  = 0;
        } else {
            // Never block, always return immediately with
            // whatever is available.
            tty.c_cc[VMIN]   = 0;
            tty.c_cc[VTIME]  = 0;
        }
        if (tcsetattr(fd, TCSANOW, &tty) != 0) {
            perror("Could not set port parameters while configuring blocking behaviour");
            return false;
        } else {
            return true;
        }
    }
}