/*
* n64-arduino-serial.c
* Communicates with mpkdumper to read and writes memory pak files from Arduino Serial
* Written by Mortal (http://www.nintendojo.fr/) and released under WTFPL
*
*
*    DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
*            Version 2, December 2004
*
*	 Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
*
*Everyone is permitted to copy and distribute verbatim or modified
*copies of this license document, and changing it is allowed as long
*as the name is changed.
*
*    DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
*TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
*     0. You just DO WHAT THE FUCK YOU WANT TO.
*
*
*
*/

#include <stdio.h>    /* Standard input/output definitions */
#include <stdlib.h> 
#include <stdint.h>   /* Standard types */
#include <stdbool.h>  /* Standard bool type */
#include <string.h>   /* String function definitions */
#include <fcntl.h>    /* File control definitions */
#include <termios.h>  /* POSIX terminal control definitions */
#include <getopt.h>

void usage(void);
int serialport_init(const char* serialport, int baud);

void usage(void) {
    printf("Usage: mpkdumper -p <serialport> [OPTIONS]\n"
    "\n"
    "Options:\n"
    "  -h, --help                   Print this help message\n"
	"  -s, --save=file				Give a save order to the Arduino\n"
	"  -r, --restore=file			Give a restore order to the Arduino\n"
    );
}

int main(int argc, char *argv[]) 
{
	int fd_serial = 0, fd_file = 0;
    char serialport[256], filename[256];
	bool save = false;
    int baudrate = B115200;

    if (argc==1) {
        usage();
        exit(EXIT_SUCCESS);
    }

    /* parse options */
    int option_index = 0, opt;
    static struct option loptions[] = {
        {"help",       no_argument,       0, 'h'},
        {"port",       required_argument, 0, 'p'},
        {"save",       required_argument, 0, 's'},
        {"restore",    required_argument, 0, 'r'}
    };

    while(1) {
		opt = getopt_long (argc, argv, "hp:s:r:", loptions, &option_index);
        if (opt==-1) break;
        switch (opt) {
        case '0': break;
        case 'h':
            usage();
            break;
        case 'p':
            strcpy(serialport,optarg);
            fd_serial = serialport_init(serialport, baudrate);
			usleep (3000 * 1000); // wait for Arduino to reset
            if(fd_serial==-1) return -1;
            break;
        case 's':
            strcpy(filename,optarg);
			save = true;
            break;
        case 'r':
			strcpy(filename,optarg);
			save = false;
            break;
        }
    }

	// -p option is mandatory
	if(!fd_serial) {
		printf("-p option is mandatory");
		return -1;
	}

	if(save) { //save action is required
		char command = 's';
		char p = '\0';
		fd_file = open(filename, O_WRONLY | O_CREAT);
		if(!fd_file) {
			printf("Cannot open file for writing !");
			return -1;
		}

		write(fd_serial, &command, 1);

		usleep(5*1000);

		while(1) {
			// read a char
			char c;
			int n = read(fd_serial, &c, 1);

			if (n < 0) {
				// nothing read, skipping til next iteration
				continue;
			}

			n = write(fd_file, &c, 1);

			if (n < 0) {
				// write error on the file
				return -1;
			}

			if (p == '\n' && c == '\n') { // 2 consecutives \n means EOF
				close(fd_serial);
				close(fd_file);
				return 0;
			}

			// saves previous value
			p = c;
		}

	} else { // restore action is required
		char command = 'r';
		char p = '\0';
        fd_file = open(filename, O_RDONLY);
	    if (!fd_file) {
	    	printf("Cannot open file for reading !");
		    return -1;
		}
		
		write(fd_serial, &command, 1);

		usleep(5*1000);

		while (1) {
			char c;

			int n = read(fd_file, &c, 1);

			if (n <= 0) {
				printf("Read error\n");
				return -1;
			}

			// if the line read is empty (just \n)
			if (p == '\n' && c == '\n') {
				close(fd_serial);
				close(fd_file);
				return 0;
			}

			while(write(fd_serial, &c, 1) <= 0) {
				continue;
			}

			p = c;

			// let the arduino breath (lots of calculation are done on Restore() function
			if (c == '\n') {
				usleep(25 * 1000);
			}
		}
	}

    exit(EXIT_SUCCESS);    
} // end main
    
// takes the string name of the serial port (e.g. "/dev/tty.usbserial","COM1")
// and a baud rate (bps) and connects to that port at that speed and 8N1.
// opens the port in fully raw mode so you can send binary data.
// returns valid fd, or -1 on error
int serialport_init(const char* serialport, int baud)
{
    struct termios toptions;
    int fd;
    
    //fprintf(stderr,"init_serialport: opening port %s @ %d bps\n",
    //        serialport,baud);

    fd = open(serialport, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1)  {
        perror("init_serialport: Unable to open port ");
        return -1;
    }
    
    if (tcgetattr(fd, &toptions) < 0) {
        perror("init_serialport: Couldn't get term attributes");
        return -1;
    }
    speed_t brate = baud; // let you override switch below if needed
    switch(baud) {
    case 4800:   brate=B4800;   break;
    case 9600:   brate=B9600;   break;
#ifdef B14400
    case 14400:  brate=B14400;  break;
#endif
    case 19200:  brate=B19200;  break;
#ifdef B28800
    case 28800:  brate=B28800;  break;
#endif
    case 38400:  brate=B38400;  break;
    case 57600:  brate=B57600;  break;
    case 115200: brate=B115200; break;
    }
    cfsetispeed(&toptions, brate);
    cfsetospeed(&toptions, brate);

    toptions.c_cflag =CS8|CREAD|CLOCAL;  // turn on READ & ignore ctrl lines
    toptions.c_iflag = 0; // turn off s/w flow ctrl

    toptions.c_lflag = 0;
    toptions.c_oflag = 0; // make raw

    // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
    toptions.c_cc[VMIN]  = 0;
    toptions.c_cc[VTIME] = 20;
    
    if( tcsetattr(fd, TCSANOW, &toptions) < 0) {
        perror("init_serialport: Couldn't set term attributes");
        return -1;
    }

    return fd;
}
