/* an mspack_system implementation which reads one or more files, and
 * only writes to one file; the file is not actually written to, but
 * an MD5 sum is computed and is available once the written-to file is
 * closed. You can use anything for the written-to filename, NULL is
 * probably the most obvious. The code is not multithreadable.
 */

#include <md5.h>
#include <stdio.h>
#include <stdarg.h>

struct md5_ctx md5_context;
char md5_string[33];

struct mspack_file_p {
    FILE *fh;
    const char *filename;
};

static struct mspack_file *m_open(struct mspack_system *self, const char *filename, int mode) {
    struct mspack_file_p *fh;
    if (mode != MSPACK_SYS_OPEN_WRITE &&
        mode != MSPACK_SYS_OPEN_READ) return NULL;

    if ((fh = (struct mspack_file_p *) malloc(sizeof(struct mspack_file_p)))) {
        if (mode == MSPACK_SYS_OPEN_WRITE) {
            fh->fh = NULL;
            fh->filename = "<output>";
            md5_init_ctx(&md5_context);
            return (struct mspack_file *) fh;
        }
        else {
            if ((fh->fh = fopen(filename, "rb"))) {
                fh->filename = filename;
                return (struct mspack_file *) fh;
            }
        }
        /* error - free file handle and return NULL */
        free(fh);
    }
    return NULL;
}

static void m_close(struct mspack_file *file) {
  struct mspack_file_p *self = (struct mspack_file_p *) file;
  if (self) {
      if (self->fh) fclose(self->fh);
      else {
          unsigned char md5[16];
          md5_finish_ctx(&md5_context, (void *) &md5);
          snprintf(md5_string, sizeof(md5_string),
                   "%02x%02x%02x%02x%02x%02x%02x%02x"
                   "%02x%02x%02x%02x%02x%02x%02x%02x",
                   md5[0],  md5[1],  md5[2],  md5[3],
                   md5[4],  md5[5],  md5[6],  md5[7],
                   md5[8],  md5[9],  md5[10], md5[11],
                   md5[12], md5[13], md5[14], md5[15]);
      }
      free(self);
  }
}

static int m_read(struct mspack_file *file, void *buffer, int bytes) {
  struct mspack_file_p *self = (struct mspack_file_p *) file;
  if (self && self->fh && buffer && bytes >= 0) {
      size_t count = fread(buffer, 1, bytes, self->fh);
      if (!ferror(self->fh)) return (int) count;
  }
  return -1;
}

static int m_write(struct mspack_file *file, void *buffer, int bytes) {
    struct mspack_file_p *self = (struct mspack_file_p *) file;
    if (!self || self->fh || !buffer || bytes < 0) return -1;
    md5_process_bytes(buffer, bytes, &md5_context);
    return bytes;
}

static int m_seek(struct mspack_file *file, off_t offset, int mode) {
    struct mspack_file_p *self = (struct mspack_file_p *) file;
    if (self && self->fh) {
        switch (mode) {
        case MSPACK_SYS_SEEK_START: mode = SEEK_SET; break;
        case MSPACK_SYS_SEEK_CUR:   mode = SEEK_CUR; break;
        case MSPACK_SYS_SEEK_END:   mode = SEEK_END; break;
        default: return -1;
        }
#if HAVE_FSEEKO
        return fseeko(self->fh, offset, mode);
#else
        return fseek(self->fh, offset, mode);
#endif
    }
    return -1;
}

static off_t m_tell(struct mspack_file *file) {
    struct mspack_file_p *self = (struct mspack_file_p *) file;
#if HAVE_FSEEKO
    return (self && self->fh) ? (off_t) ftello(self->fh) : 0;
#else
    return (self && self->fh) ? (off_t) ftell(self->fh) : 0;
#endif
}

static void m_msg(struct mspack_file *file, const char *format, ...) {
    va_list ap;
    if (file) {
        fprintf(stderr, "%s: ", ((struct mspack_file_p *) file)->filename);
    }
    va_start(ap, format);
    vfprintf(stderr, format, ap);
    va_end(ap);
    fputc((int) '\n', stderr);
    fflush(stderr);
}
static void *m_alloc(struct mspack_system *self, size_t bytes) {
    return malloc(bytes);
}
static void m_free(void *buffer) {
    free(buffer);
}
static void m_copy(void *src, void *dest, size_t bytes) {
    memcpy(dest, src, bytes);
}

static struct mspack_system read_files_write_md5 = {
    &m_open, &m_close, &m_read, &m_write, &m_seek,
    &m_tell, &m_msg, &m_alloc, &m_free, &m_copy, NULL
};
