/*
 * io.cpp: Spigot definitions which provide numbers read from input
 * files or fds.
 *
 * (The fds part of that is conditional on running on an OS that
 * supports fds at all, to ease porting.)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <ctype.h>

#include <string>

#include "spigot.h"
#include "funcs.h"
#include "error.h"

struct FileBlock {
    unsigned char data[16384];
    unsigned data_used;
    struct FileBlock *next;
    FileBlock() { data_used = 0; next = NULL; }
};

struct FileReaderPos {
    FileBlock *current;
    unsigned currpos;
};

struct FileReader {
    std::string filename;
    FILE *fp;
    FileBlock *head;

    FileReader(const std::string &afilename, FILE *afp)
        : filename(afilename)
    {
        fp = afp;
        head = new FileBlock;
    }

    ~FileReader()
    {
        while (head) {
            FileBlock *tmp = head->next;
            delete head;
            head = tmp;
        }
        if (fp)
            fclose(fp);
    }

    void newpos(FileReaderPos *pos)
    {
        pos->current = head;
        pos->currpos = 0;
    }

    int getch(FileReaderPos *pos)
    {
        /*
         * The simple cases of just returning some existing data, or 
         */
        if (pos->current &&
            pos->currpos == pos->current->data_used &&
            pos->current->next)
            pos->current = pos->current->next;
        if (pos->current && pos->currpos < pos->current->data_used)
            return pos->current->data[pos->currpos++];

        /*
         * If we get here, then we know that pos->current points to
         * a block from which we've already returned all the data,
         * and that there is no pointer to a next block. So now we
         * must read from the file, if it's still open.
         */
        if (!fp)
            return EOF;
        int c = fgetc(fp);
        if (c == EOF) {
            fclose(fp);
            fp = NULL;
            return EOF;
        } else {
            if (pos->current->data_used < sizeof(pos->current->data)) {
                pos->current->data[pos->current->data_used++] = c;
                pos->currpos++;
            } else {
                pos->current->next = new FileBlock;
                pos->current = pos->current->next;
                pos->current->data[0] = c;
                pos->currpos = pos->current->data_used = 1;
            }
            return c;
        }
    }
};

class CfracFile : public CfracSource {
    FileReader *file;
    FileReaderPos pos;
    bool first_term;
    bool exact;

  public:
    CfracFile(FileReader *afile, bool aexact)
    {
        file = afile;
        file->newpos(&pos);
        first_term = true;
        exact = aexact;
    }

    virtual CfracFile *clone() { return new CfracFile(file, exact); }

    virtual bool gen_term(bigint *term)
    {
        int c, prevc;

        /*
         * We keep track of the character we saw just _before_ the
         * first digit, in case it was a minus sign (and we're in a
         * mood to recognise one - they're only supported in the
         * very first term of the continued fraction).
         */

        c = EOF;
        do {
            prevc = c;
            c = file->getch(&pos);
        } while (c != EOF && !isdigit(c));

        *term = 0;
        while (c != EOF && isdigit(c)) {
            *term = *term * 10 + (c - '0');
            c = file->getch(&pos);
        }

        if (c == EOF) {
            if (exact) {
                /*
                 * In exact mode, EOF is interpreted as the
                 * termination of the continued fraction expansion,
                 * i.e. we actually have a rational.
                 */
                return false;
            } else {
                /*
                 * If we're in non-exact mode, throw the
                 * out-of-precision exception.
                 */
                throw spigot_eof(file->filename.c_str());
            }
        }

        if (prevc == '-' && first_term)
            *term = -*term;

        first_term = false;
        return true;
    }
};


class BaseFile : public BaseSource {
    FileReader *file;
    FileReaderPos pos;
    bool first_digit;
    int base;
    bool exact;

  public:
    BaseFile(int abase, FileReader *afile, bool aexact)
        : BaseSource(abase, true), base(abase)
    {
        file = afile;
        file->newpos(&pos);
        first_digit = true;
        exact = aexact;
        assert(base > 0);
    }

    virtual BaseFile *clone() { return new BaseFile(base, file, exact); }

    int digitval(int c)
    {
        if (isdigit(c) && (c - '0') < base) {
            return c - '0';
        } else if (c >= 'A' && c <= 'Z' &&
                   (c - 'A' + 10) < base) {
            return c - 'A' + 10;
        } else if (c >= 'a' && c <= 'z' &&
                   (c - 'a' + 10) < base) {
            return c - 'a' + 10;
        } else
            return -1;
    }

    virtual bool gen_digit(bigint *digit)
    {
        int c, dv;

        if (first_digit) {
            /*
             * Read the integer part of the number, complete with a
             * leading minus sign if desired.
             */
            bool negate = false, seen_digit = false;

            *digit = 0;

            do {
                c = file->getch(&pos);
                if (c == '-' && !seen_digit) {
                    negate = true;
                } else if ((dv = digitval(c)) >= 0) {
                    seen_digit = true;
                    *digit *= base;
                    *digit += dv;
                }
            } while (c != '.' && c != EOF);

            if (negate)
                *digit = -1-*digit;

            first_digit = false;

            return true;
        } else {
            /*
             * Read a single digit from the fraction part of the
             * number.
             */
            do {
                c = file->getch(&pos);
                if (c == EOF) {
                    if (exact) {
                        /*
                         * In exact mode, EOF is interpreted as the
                         * termination of the base expansion, i.e. we
                         * actually have a rational.
                         */
                        return false;
                    } else {
                        /*
                         * If we're in non-exact mode, throw the
                         * out-of-precision exception.
                         */
                        throw spigot_eof(file->filename.c_str());
                    }
                }
            } while ((dv = digitval(c)) < 0);
            *digit = dv;
            return true;
        }
    }
};

Spigot *spigot_basefile(int base, const char *filename, bool exact)
{
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        throw spigot_error("unable to open '%s': %s",
                           filename, strerror(errno));
    }
    return new BaseFile(base, new FileReader(filename, fp), exact);
}

Spigot *spigot_cfracfile(const char *filename, bool exact)
{
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        throw spigot_error("unable to open '%s': %s",
                           filename, strerror(errno));
    }
    return new CfracFile(new FileReader(filename, fp), exact);
}

#ifdef HAVE_FDS

#include <signal.h>

#include <termios.h>
#include <unistd.h>

bool set_term_modes = false;

struct fdrec {
    int fd;
    bool do_term_modes;
    struct termios old;
    FileReader *fr;
};

fdrec *fds = NULL;
int nfds = 0, fdsize = 0;

FileReader *register_fd(int fd, bool do_term_modes)
{
    for (int i = 0; i < nfds; i++)
        if (fds[i].fd == fd)
            return fds[i].fr;

    FILE *fp = fdopen(fd, "r");
    if (!fp) {
        throw spigot_error("unable to access fd %d for reading: %s",
                           fd, strerror(errno));
    }
    char name[64];
#if defined _MSC_VER
#define snprintf _snprintf
#endif
    snprintf(name, sizeof(name), "<fd %d>", fd);
    FileReader *fr = new FileReader(name, fp);

    if (nfds >= fdsize) {
        fdsize = nfds * 3 / 2 + 16;
        fds = (fdrec *)realloc(fds, fdsize * sizeof(fdrec));
    }
    fds[nfds].fd = fd;
    fds[nfds].do_term_modes = do_term_modes;
    fds[nfds].fr = fr;
    if (do_term_modes)
        tcgetattr(fds[nfds].fd, &fds[nfds].old);
    nfds++;

    return fr;
}

void cleanup()
{
    if (set_term_modes) {
        for (int i = 0; i < nfds; i++)
            if (fds[i].do_term_modes)
                tcsetattr(fds[i].fd, TCSANOW, &fds[i].old);
    }
}

void sighandler(int sig)
{
    cleanup();
    signal(sig, SIG_DFL);
    raise(sig);
}

void begin_fd_input()
{
    if (!set_term_modes)
        return;

    signal(SIGINT, sighandler);
    signal(SIGTERM, sighandler);
    atexit(cleanup);

    for (int i = 0; i < nfds; i++)
        if (fds[i].do_term_modes) {
            struct termios newattrs = fds[i].old;   /* structure copy */
            newattrs.c_lflag &= ~ICANON;
            newattrs.c_lflag &= ~ECHO;
            tcsetattr(fds[i].fd, TCSANOW, &newattrs);
        }
}

/*
 * The fd functions always pass exact=true, because the whole point of
 * using an fd in place of a file is that fds have a way to actually
 * _distinguish_ 'expansion has terminated' from 'more data not yet
 * available', namely EOF and blocking respectively.
 */

Spigot *spigot_basefd(int base, int fd)
{
    FileReader *fr = register_fd(fd, true);
    return new BaseFile(base, fr, true);
}

Spigot *spigot_cfracfd(int fd)
{
    FileReader *fr = register_fd(fd, true);
    return new CfracFile(fr, true);
}

#endif
