/* print - Implements print function for variable types. */
#include "common.h"
#include "dystring.h"
#include "../compiler/pfPreamble.h"
#include "pfString.h"
#include "net.h"

void _pf_prin(FILE *f, _pf_Stack *stack, boolean quoteString);
/* Print out single variable where type is determined at run time. */

void _pf_getObj(FILE *f, _pf_Stack *stack);
/* Read in variable from file. */

struct file
    {
    int _pf_refCount;			     	/* Number of references. */
    void (*_pf_cleanup)(struct file *obj, int id); /* Called when refCount <= 0 */
    _pf_polyFunType *_pf_polyFun;	/* Polymorphic functions. */
    struct _pf_string *name;		/* The only exported field. */
    FILE *f;				/* File handle. */
    struct dyString *dy;			/* Buffer for reading strings. */
    };

static void fileCleanup(struct file *file, int typeId)
/* Clean up string->s and string itself. */
{
carefulClose(&file->f);
dyStringFree(&file->dy);
_pf_class_cleanup((struct _pf_object *)file, typeId);
}

static FILE *mustFdopen(int fd, char *name, char *mode)
/* fdopen a file (wrap FILE around it) or die trying */
{
FILE *f = fdopen(fd, mode);
if (f == NULL)
    errnoAbort("Couldn't fdopen %s", name);
return f;
}

static FILE *openUrlOrFile(char *spec, char *mode)
/* If it's a URL call the fancy net opener, otherwise
 * call the regular file opener. */
{
if (startsWith("http://", spec) || startsWith("ftp://", spec))
    {
    int fd;
    if (mode[0] != 'r')
        errAbort("Can't open %s in mode %s", spec, mode);
    fd = netUrlOpen(spec);
    return mustFdopen(fd, spec, mode);
    }
else
    return mustOpen(spec, mode);
}

static struct file *fileFromFILE(FILE *f, _pf_String name)
/* Convert a FILE to a ParaFlow file. */
{
struct file *file;
AllocVar(file);
file->_pf_refCount = 1;
file->_pf_cleanup = fileCleanup;
file->name = name;
file->f = f;
file->dy = dyStringNew(0);
return file;
}

void pf_fileOpen(_pf_Stack *stack)
/* paraFlow run time support routine to open file. */
{
struct _pf_string *name = stack[0].String;
struct _pf_string *mode = stack[1].String;
struct file *file;

_pf_nil_check(name);
_pf_nil_check(mode);
file = fileFromFILE(openUrlOrFile(name->s, mode->s), name);

/* Remove mode reference. (We'll keep the name). */
if (--mode->_pf_refCount <= 0)
    mode->_pf_cleanup(mode, 0);

/* Save result on stack. */
stack[0].v = file;
}

static _pf_String readAll(FILE *f)
/* Return a string that contains the whole file. */
{
_pf_String string;
char buf[4*1024];
int bytesRead;
string = _pf_string_new_empty(sizeof(buf));
for (;;)
    {
    bytesRead = fread(buf, 1, sizeof(buf), f);
    if (bytesRead <= 0)
        break;
    _pf_strcat(string, buf, bytesRead);
    }
return string;
}

void pf_fileReadAll(_pf_Stack *stack)
/* Read in whole file to string. */
{
_pf_String name = stack[0].String;
_pf_String string;
FILE *f;
_pf_nil_check(name);

f = openUrlOrFile(name->s, "r");
string = readAll(f);
carefulClose(&f);

stack[0].String = string;
if (--name->_pf_refCount <= 0)
    name->_pf_cleanup(name, 0);
}

void _pf_cm_file_readAll(_pf_Stack *stack)
/* Read all of file. */
{
struct file *file = stack[0].v;
_pf_nil_check(file);
stack[0].String = readAll(file->f);
}

void _pf_cm_file_close(_pf_Stack *stack)
/* Close file explicitly. */
{
struct file *file = stack[0].v;
carefulClose(&file->f);
}

void _pf_cm_file_write(_pf_Stack *stack)
/* paraFlow run time support routine to write string to file. */
{
struct file *file = stack[0].v;
struct _pf_string *string = stack[1].String;

mustWrite(file->f, string->s, string->size);

/* Clean up references on stack. */
if (--string->_pf_refCount <= 0)
    string->_pf_cleanup(string, 0);
}

void _pf_cm_file_writeByte(_pf_Stack *stack)
/* Write a Byte to file. */
{
struct file *file = stack[0].v;
mustWrite(file->f, &stack[1].Byte, sizeof(stack[1].Byte));
}

void _pf_cm_file_writeShort(_pf_Stack *stack)
/* Write a Short to file. */
{
struct file *file = stack[0].v;
mustWrite(file->f, &stack[1].Short, sizeof(stack[1].Short));
}

void _pf_cm_file_writeInt(_pf_Stack *stack)
/* Write a Int to file. */
{
struct file *file = stack[0].v;
mustWrite(file->f, &stack[1].Int, sizeof(stack[1].Int));
}

void _pf_cm_file_writeLong(_pf_Stack *stack)
/* Write a Long to file. */
{
struct file *file = stack[0].v;
mustWrite(file->f, &stack[1].Long, sizeof(stack[1].Long));
}

void _pf_cm_file_writeFloat(_pf_Stack *stack)
/* Write a Float to file. */
{
struct file *file = stack[0].v;
mustWrite(file->f, &stack[1].Float, sizeof(stack[1].Float));
}

void _pf_cm_file_writeDouble(_pf_Stack *stack)
/* Write a Double to file. */
{
struct file *file = stack[0].v;
mustWrite(file->f, &stack[1].Double, sizeof(stack[1].Double));
}

void _pf_cm_file_readByte(_pf_Stack *stack)
/* Read a Byte from file. */
{
struct file *file = stack[0].v;
mustRead(file->f, &stack[0].Byte, sizeof(stack[0].Byte));
}

void _pf_cm_file_readShort(_pf_Stack *stack)
/* Read a Short from file. */
{
struct file *file = stack[0].v;
mustRead(file->f, &stack[0].Short, sizeof(stack[0].Short));
}

void _pf_cm_file_readInt(_pf_Stack *stack)
/* Read a Int from file. */
{
struct file *file = stack[0].v;
mustRead(file->f, &stack[0].Int, sizeof(stack[0].Int));
}

void _pf_cm_file_readLong(_pf_Stack *stack)
/* Read a Long from file. */
{
struct file *file = stack[0].v;
mustRead(file->f, &stack[0].Long, sizeof(stack[0].Long));
}

void _pf_cm_file_readFloat(_pf_Stack *stack)
/* Read a Float from file. */
{
struct file *file = stack[0].v;
mustRead(file->f, &stack[0].Float, sizeof(stack[0].Float));
}

void _pf_cm_file_readDouble(_pf_Stack *stack)
/* Read a Double from file. */
{
struct file *file = stack[0].v;
mustRead(file->f, &stack[0].Double, sizeof(stack[0].Double));
}

static void mustFlush(struct file *file)
/* Flush file or die trying */
{
if (fflush(file->f) < 0)
    errnoAbort("Couldn't flush file %s.", file->name);
}

void _pf_cm_file_flush(_pf_Stack *stack)
/* Flush file */
{
struct file *file = stack[0].v;
_pf_nil_check(file);
mustFlush(file);
}

void _pf_cm_file_writeNow(_pf_Stack *stack)
/* paraFlow run time support routine to write string to file
 * and then flush. */
{
struct file *file = stack[0].v;
struct _pf_string *string = stack[1].String;

mustWrite(file->f, string->s, string->size);
mustFlush(file);

/* Clean up references on stack. */
if (--string->_pf_refCount <= 0)
    string->_pf_cleanup(string, 0);
}


void _pf_cm_file_put(_pf_Stack *stack)
/* Print to file with line feed. */
{
struct file *file = stack[0].v;
FILE *f = file->f;
_pf_prin(f, stack+1, TRUE);
fputc('\n', f);
}

void _pf_cm_file_get(_pf_Stack *stack)
/* Read in variable from file, and then make sure have
 * a newline. */
{
int c;
struct file *file = stack[0].v;
_pf_getObj(file->f, stack);
c = getc(file->f);
if (c != '\n')
    warn("expecting newline, got %c", c);
}


void _pf_cm_file_readLine(_pf_Stack *stack)
/* Read next line from file. */
{
struct file *file = stack[0].v;
FILE *f = file->f;
struct dyString *dy = file->dy;
int c;

dyStringClear(dy);
for (;;)
    {
    c = fgetc(f);
    if (c == EOF)
        break;
    dyStringAppendC(dy, c);
    if (c == '\n')
        break;
    }
stack[0].String = _pf_string_from_const(dy->string);
}

void _pf_cm_file_read(_pf_Stack *stack)
/* Read in a fixed number of bytes to string.
 * This will return a string of length zero at
 * EOF, and a string smaller than what is asked
 * for near EOF. */
{
struct file *file = stack[0].v;
_pf_Int count = stack[1].Int;
_pf_Int bytesRead;
struct _pf_string *string = _pf_string_new(needLargeMem(count+1), count);
bytesRead = fread(string->s, 1, count, file->f);
if (bytesRead <= 0)
    bytesRead = 0;
string->size = bytesRead;
string->s[bytesRead] = 0;
stack[0].String = string;
}

void _pf_cm_file_seek(_pf_Stack *stack)
/* Seek to a position. */
{
struct file *file = stack[0].v;
_pf_Long pos = stack[1].Long;
_pf_Bit fromEnd = stack[2].Bit;
int whence = (fromEnd ? SEEK_END : SEEK_SET);
if (fseek(file->f, pos, whence) < 0)
    errnoAbort("seek error on %s", file->name);
}

void _pf_cm_file_skip(_pf_Stack *stack)
/* Seek relative to current position. */
{
struct file *file = stack[0].v;
_pf_Long pos = stack[1].Long;
if (fseek(file->f, pos, SEEK_CUR) < 0)
    errnoAbort("skip error on %s", file->name);
}

void _pf_cm_file_tell(_pf_Stack *stack)
/* Return current file position. */
{
struct file *file = stack[0].v;
stack[0].Long = ftell(file->f);
}

void pf_fileExists(_pf_Stack *stack)
/* Returns true if file exists. */
{
_pf_String string = stack[0].v;
_pf_nil_check(string);
stack[0].Bit = fileExists(string->s);
/* Clean up references on stack. */
if (--string->_pf_refCount <= 0)
    string->_pf_cleanup(string, 0);
}

void pf_fileRemove(_pf_Stack *stack)
/* Remove file. Punt if there's a problem. */
{
int err;
_pf_String string = stack[0].v;

_pf_nil_check(string);
err = remove(string->s);

/* Clean up references on stack. */
if (--string->_pf_refCount <= 0)
    string->_pf_cleanup(string, 0);

if (err < 0)
    errnoAbort("Couldn't remove file %s", string->s);
}

void pf_fileRename(_pf_Stack *stack)
/* Rename file. */
{
int err;
_pf_String oldName = stack[0].v;
_pf_String newName = stack[1].v;
_pf_nil_check(oldName);
_pf_nil_check(newName);

err = rename(oldName->s, newName->s);

/* Clean up references on stack. */
if (--oldName->_pf_refCount <= 0)
    oldName->_pf_cleanup(oldName, 0);
if (--newName->_pf_refCount <= 0)
    newName->_pf_cleanup(newName, 0);

if (err < 0)
    errnoAbort("Couldn't rename file %s to %s", oldName->s, newName->s);
}

void pf_httpConnect(_pf_Stack *stack)
/* Return an open HTTP connection in a file, with
 * most of the header sent.  Doesn't send the
 * last part though, so you can put out cookies. 
 * Implements:
 *   to httpConnect(string url, method, protocol, agent) into (file f) */
{
/* Fetch input from stack. */
_pf_String url = stack[0].String;
_pf_String method = stack[1].String;
_pf_String protocol = stack[2].String;
_pf_String agent = stack[3].String;
int fd;

/* The usual null checks on input. */
_pf_nil_check(url);
_pf_nil_check(method);
_pf_nil_check(protocol);
_pf_nil_check(agent);

/* Do the real work of opening network connection and saving
 * result on return stack. */
fd = netHttpConnect(url->s, method->s, agent->s, protocol->s, NULL);
stack[0].v = fileFromFILE(mustFdopen(fd, url->s, "r+"), url);

/* Clean up input, except for URL which got moved to file. */
if (--method->_pf_refCount <= 0)
    method->_pf_cleanup(method, 0);
if (--protocol->_pf_refCount <= 0)
    protocol->_pf_cleanup(protocol, 0);
if (--agent->_pf_refCount <= 0)
    agent->_pf_cleanup(agent, 0);
}

