/* Copyright 2017-2022
 * Kaz Kylheku <kaz@kylheku.com>
 * Vancouver, Canada
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdarg.h>
#include <wchar.h>
#include "config.h"
#include "lib.h"
#include "stream.h"
#include "gc.h"
#include "eval.h"
#include "struct.h"
#include "buf.h"
#include "strudel.h"

struct strudel_base { /* stru-ct del-egate :) */
  struct strm_base a;
  val obj;
};

static void strudel_base_mark(struct strudel_base *sb)
{
  strm_base_mark(&sb->a);
  gc_mark(sb->obj);
}

static void strudel_mark_op(val stream)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  strudel_base_mark(sb);
}

static val strudel_put_string(val stream, val str)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, put_string_s);
  return funcall2(meth, obj, str);
}

static val strudel_put_char(val stream, val ch)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, put_char_s);
  return funcall2(meth, obj, ch);
}

static val strudel_put_byte(val stream, int byte)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, put_byte_s);
  return funcall2(meth, obj, num_fast(byte));
}

static val strudel_get_line(val stream)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, get_line_s);
  return funcall1(meth, obj);
}

static val strudel_get_char(val stream)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, get_char_s);
  return funcall1(meth, obj);
}

static val strudel_get_byte(val stream)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, get_byte_s);
  return funcall1(meth, obj);
}

static val strudel_unget_char(val stream, val ch)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, unget_char_s);
  return funcall2(meth, obj, ch);
}

static val strudel_unget_byte(val stream, int byte)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, unget_byte_s);
  return funcall2(meth, obj, num_fast(byte));
}

static ucnum strudel_put_buf(val stream, mem_t *ptr, ucnum len, ucnum pos)
{
  val self = lit("put-buf");
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  obj_t buf_obj;
  val buf = init_borrowed_buf(&buf_obj, unum(len), ptr);
  val obj = sb->obj;
  val meth = slot(obj, put_buf_s);
  return c_unum(funcall3(meth, obj, buf, num(pos)), self);
}

static ucnum strudel_fill_buf(val stream, mem_t *ptr, ucnum len, ucnum pos)
{
  val self = lit("fill-buf");
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  obj_t buf_obj;
  val buf = init_borrowed_buf(&buf_obj, unum(len), ptr);
  val obj = sb->obj;
  val meth = slot(obj, fill_buf_s);
  return c_unum(funcall3(meth, obj, buf, num(pos)), self);
}

static val strudel_close(val stream, val throw_on_error)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, close_s);
  return funcall2(meth, obj, default_null_arg(throw_on_error));
}

static val strudel_flush(val stream)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, flush_s);
  return funcall1(meth, obj);
}

static val strudel_seek(val stream, val off, enum strm_whence whence)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val wh;
  val obj = sb->obj;
  val meth = slot(obj, seek_s);

  switch (whence) {
  default:
  case strm_start:
    wh = from_start_k;
    break;
  case strm_cur:
    wh = from_current_k;
    break;
  case strm_end:
    wh = from_end_k;
    break;
  }

  return funcall3(meth, obj, off, wh);
}

static val strudel_truncate(val stream, val len)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, truncate_s);
  return funcall2(meth, obj, len);
}

static val strudel_get_prop(val stream, val ind)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, get_prop_s);
  return funcall2(meth, obj, ind);
}

static val strudel_set_prop(val stream, val ind, val value)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, set_prop_s);
  return funcall3(meth, obj, ind, value);
}

static val strudel_get_error(val stream)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, get_error_s);
  return funcall1(meth, obj);
}

static val strudel_get_error_str(val stream)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, get_error_str_s);
  return funcall1(meth, obj);
}

static val strudel_clear_error(val stream)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, clear_error_s);
  return funcall1(meth, obj);
}

static val strudel_get_fd(val stream)
{
  struct strudel_base *sb = coerce(struct strudel_base *, stream->co.handle);
  val obj = sb->obj;
  val meth = slot(obj, get_fd_s);
  return funcall1(meth, obj);
}

static struct strm_ops strudel_ops =
  strm_ops_init(cobj_ops_init(eq,
                              stream_print_op,
                              stream_destroy_op,
                              strudel_mark_op,
                              cobj_eq_hash_op),
                wli("struct-delegate-stream"),
                strudel_put_string, strudel_put_char, strudel_put_byte,
                strudel_get_line, strudel_get_char, strudel_get_byte,
                strudel_unget_char, strudel_unget_byte,
                strudel_put_buf, strudel_fill_buf,
                strudel_close, strudel_flush, strudel_seek,
                strudel_truncate, strudel_get_prop, strudel_set_prop,
                strudel_get_error, strudel_get_error_str,
                strudel_clear_error, strudel_get_fd);

val make_struct_delegate_stream(val target_obj)
{
  struct strudel_base *sb = coerce(struct strudel_base *,
                                   chk_malloc(sizeof *sb));
  val stream;
  strm_base_init(&sb->a);
  sb->obj = nil;
  stream = cobj(coerce(mem_t *, sb), stream_cls, &strudel_ops.cobj_ops);
  sb->obj = target_obj;
  return stream;
}

void strudel_init(void)
{
  reg_fun(intern(lit("make-struct-delegate-stream"), user_package), func_n1(make_struct_delegate_stream));
  fill_stream_ops(&strudel_ops);
}