/*
 * steve diverdi
 * cs125 project 4
 *
 * tftp client
 * see RFC 1350
 */


// INCLUDES-------------------------------------------------------------

#include "common.hh"

// ---------------------------------------------------------------------

	int		main(int, char**);
	void		usageError();

	int		readFile(char*, int, struct sockaddr*);
	int		writeFile(char*, int, struct sockaddr*);

	int		sendRRQ(int, char*, struct sockaddr*, int);
	int		sendWRQ(int, char*, struct sockaddr*, int);

// ---------------------------------------------------------------------

/*
 * Top level call.  gets the command line arguments, creates the socket,
 * and determines if this is a read or write, calling the appropriate
 * function.  Returns 0 if the transfer succeeded, -1 if not.
 */
int main (
    int         argc,
    char*       argv[])
{
    char* server;
    int port;
    int op;
    char* filename;

    if (argc == 4)
    {
        server = argv[1];
        port = SERVER_PORT;

        if (strncmp("-r", argv[2], 2) == 0)
            op = RRQ;
        else if (strncmp("-w", argv[2], 2) == 0)
            op = WRQ;
        else
            usageError();

        filename = argv[3];
    }
    else if (argc == 5)
    {
        server = argv[1];
        port = atoi(argv[2]);

        if (strncmp("-r", argv[2], 2) == 0)
            op = RRQ;
        else if (strncmp("-w", argv[2], 2) == 0)
            op = WRQ;
        else
            usageError();

        filename = argv[3];
    }
    else
        usageError();

    int s = createSocket(0);
    struct sockaddr server_addr;
    createAddr((struct sockaddr_in*) &server_addr, server, port);

    int result;
    if (op == RRQ)
        result = readFile(filename, s, &server_addr);
    else
        result = writeFile(filename, s, &server_addr);

    return result;
}



/*
 * Prints the usage message and exits.
 */
void usageError ()
{
    cerr << "usage: tftp client [ port ] [ -r | -w ] filename" << endl;
    exit(-1);
}


// ---------------------------------------------------------------------


/*
 * call to read a file from the server and put it in a local file of the
 * same name.  returns 0 on success, -1 on failure.  Arguments are the 
 * name of the file to retrieve, a socket id, and the address of the 
 * server
 */
int readFile (
    char*		filename,
    int			s,
    struct sockaddr*	server_addr)
{
    // open the local file for writing
    int file;
    mode_t mode = S_IRUSR|S_IWUSR;
    if ((file = creat(filename, mode)) == -1)
    {
        cerr << "Couldn't open file for writing.  Error " << errno << endl;
        return -1;
    }

    // send the initial read request to the server
    int server_len = sizeof(struct sockaddr);
    if (sendRRQ(s, filename, server_addr, server_len) == -1)
        return -1;

    int server_tid = -1;
    bool firstLoop;
    int blockNum = 1;
    int blen;
    struct data_packet incoming;
    int ilen = MAX_PACKET;

    // loop as long as we're receiving data packets 512 bytes long.  we
    // know we're done when we get the last packet, which is less than
    // 512 bytes.
    while (ilen == MAX_PACKET)
    {
        // receive the data packet
        if (recvDATA(s, &incoming, &ilen, blockNum, server_tid, 
                     server_addr, &server_len) == -1)
            return -1;

        // print a . for each 512 byte packet received
        cout << "." << flush;

        // first time we receive a packet from the server, store its port
        // number for later use
        if (firstLoop)
            server_tid = ((struct sockaddr_in*) server_addr)->sin_port;
        firstLoop = false;

        // write the data to the local file
        if ((blen = write(file, incoming.data, ilen - 4)) == -1)
        {
            if (errno == EDQUOT)
                error(TDSKFUL, "Disk full or allocation exceeded", s,
                      server_addr, server_len);
            else
                error(TNOTDEF, "Couldn't write file", s, server_addr, 
                      server_len);

            return -1;
        }

        // get the block number off of the received data packet
        blockNum = getBlock((struct packet*) &incoming);

        // send an acknowledgement to the server
        if (sendACK(s, blockNum, server_addr, server_len, 
                    (ilen == MAX_PACKET)) == -1)
            return -1;

        blockNum++;
    }

    // we're done, so close the new file and exit
    cout << endl;
    close(file);
    return 0;
}


/*
 * call to write a local file to the server. returns 0 on success, -1 on
 * failure.  Arguments are the name of the file to write, a socket id, 
 * and the address of the server
 */
int writeFile (
    char*		filename,
    int			s,
    struct sockaddr*	server_addr)
{
    // open the local file for reading
    int file;
    if ((file = open(filename, O_RDONLY)) == -1)
    {
        cerr << "Couldn't open file for reading. Error " << errno << endl;
        return -1;
    }

    // send the initial write request to the server
    int server_len = sizeof(struct sockaddr);
    sendWRQ(s, filename, server_addr, server_len);

    int server_tid = -1;
    bool firstLoop = true;
    int blockNum = 0;
    char blockData[MAX_DATA];
    int blen = MAX_DATA;

    // keep looping as long as we're still reading in the max amount of
    // data possible.  when we read less than a full packet of data,
    // we know we've reached the last packet.
    while (blen == MAX_DATA)
    {
        // receive the server's acknowledgement
        if (recvACK(s, blockNum, server_tid, server_addr, &server_len) == -1)
            return -1;

        // store the server's port number after we receive our first 
        // packet, for later verification
        if (firstLoop)
            server_tid = ((struct sockaddr_in*) server_addr)->sin_port;
        firstLoop = false;

        // read the next block of data from the file
        blockNum++;
        blen = read(file, blockData, MAX_DATA);

        // send the data to the server
        if (sendDATA(s, blockNum, blockData, blen, server_addr, server_len)
                     == -1)
            return -1;

        // print one . for each 512 bytes of transfer
        cout << "." << flush;
    }

    // receive the final acknowledgement
    if (recvACK(s, blockNum, server_tid, server_addr, &server_len) == -1)
        return -1;

    // all done, so close the file and return 0
    cout << endl;
    close(file);
    return 0;
}



// ---------------------------------------------------------------------


/*
 * Form and send a read request to a server.  Takes as arguments, a 
 * socket id, the file name to read, the server's address and the length
 * of the server's address.  returns 0 on success, -1 on failure
 */
int sendRRQ (
    int			s,
    char*		filename,
    struct sockaddr*	server_addr,
    int			server_len)
{
    // make the request packet
    struct packet request;
    int rlen;
    makeRRQ(&request, &rlen, filename, "octet");

    // send the request packet to the server
    if (sendto(s, &request, rlen, 0, server_addr, server_len) < 0)
    {
        cerr << "Couldn't send request.  Error " << errno << endl;
        return -1;
    }

    bool response = false;
    for (int i = 0; (i < RETRIES) && (! response); i++)
    {
        // check if a response is ready within the time out
        response = ! timeout(s);

        if (! response)
        {
            // since nothing was returned, resend our request packet
            if (sendto(s, &request, rlen, 0, server_addr, server_len) < 0)
            {
                cerr << "Couldn't send request.  Error " << errno << endl;
                return -1;
            }
        }
    }

    // check if a response timed out the last time.  if it does, fail
    if (!response)
    {
        error(TNOTDEF, "Connection timed out", s, server_addr, server_len);
        return -1;
    }

    return 0;
}


/*
 * form and send a write request.  Takes as arguments a socket id, the
 * file to write, the server's address, and the length of the server's
 * address.
 */
int sendWRQ (
    int			s,
    char*		filename,
    struct sockaddr*	server_addr,
    int			server_len)
{
    // make the request packet
    struct packet request;
    int rlen;
    makeWRQ(&request, &rlen, filename, "octet");

    // send the request packet to the server
    if (sendto(s, &request, rlen, 0, server_addr, server_len) < 0)
    {
        cerr << "Couldn't send request.  Error " << errno << endl;
        return -1;
    }

    bool response = false;
    for (int i = 0; (i < RETRIES) && (! response); i++)
    {
        // check if a response is ready within the time out
        response = ! timeout(s);

        if (! response)
        {
            // no response was waiting, so resend our request packet
            if (sendto(s, &request, rlen, 0, server_addr, server_len) < 0)
            {
                cerr << "Couldn't send request.  Error " << errno << endl;
                return -1;
            }

        }
    }

    // check if a response comes within the time out.  if one doesn't
    // this time, fail.
    if (! response)
    {
        error(TNOTDEF, "Connection timed out", s, server_addr, server_len);
        return -1;
    }

    return 0;
}


