#ifndef IO_STREAM_H
#define IO_STREAM_H

#include <stdio.h>
#ifdef USE_SSL
#include <bio.h>
#include <x509.h>
#include <ssl.h>
#endif
#include "Str.h"
#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

struct stream_buffer {
  unsigned char *buf;
  int size, cur, next;
};

typedef struct stream_buffer *StreamBuffer;

struct file_handle {
  FILE *f;
  void (*close)(FILE *);
};

struct input_stream;

struct delimited_handle {
  struct input_stream *is;
  Str buf;
  int cur;
  int (*delim_p)(const char *, void *);
  void *delim_arg;
};

#ifdef USE_SSL
struct ssl_handle {
  SSL *ssl;
  int sock;
};
#endif

struct ens_handle {
  struct input_stream *is;
  Str s;
  int pos;
  char encoding;
};

struct limited_handle {
  struct input_stream *is;
  clen_t rest;
};

struct chunked_handle {
  struct input_stream *is;
  clen_t rest;
  Str buf;
};

#if LANG == JA
typedef struct JA_CES_stat_st {
  unsigned char hint, si, so, euc, sjis, sjis_kana, iso, iso_kana, found, skip;
} JA_CES_stat_t;

typedef struct JA_CES_conv_st {
  unsigned char cset, silen, solen;
  const char *ShiftIn, *ShiftOut;
} JA_CES_conv_t;

#define JA_CODE_NORMAL	0x00
#define JA_CODE_OK	0x01
#define JA_CODE_BROKEN	0x02
#define JA_CODE_ERROR	0x04
#define JA_EUC_NOSTATE	0x00
#define JA_EUC_MBYTE1	0x10
#define JA_EUC_SS2	0x20
#define JA_EUC_SS3	0x40
#define JA_SJIS_NOSTATE	0x00
#define JA_SJIS_SHIFT_L	0x10
#define JA_SJIS_SHIFT_H	0x20
#define JA_ISO_NOSTATE	0x00
#define JA_ISO_ESC	0x10
#define JA_ISO_CS94	0x20
#define JA_ISO_MBCS	0x40
#define JA_ISO_MBYTE1	0x80

#define JA_CES_stat_init(st, h) \
((st)->hint = (h), \
 (st)->si = (st)->so = '\0', \
 (st)->euc = (JA_CODE_NORMAL | JA_EUC_NOSTATE), \
 (st)->sjis = (JA_CODE_NORMAL | JA_SJIS_NOSTATE), \
 (st)->sjis_kana = JA_CODE_NORMAL, \
 (st)->iso = (JA_CODE_NORMAL | JA_ISO_NOSTATE), \
 (st)->iso_kana = JA_CODE_NORMAL, \
 (st)->found = CODE_UNKNOWN, \
 (st)->skip = FALSE)
#endif

struct tee_handle {
  struct input_stream *is;
  FILE *save;
  int total;
#if LANG == JA || defined(HAVE_MOE)
  int noconv;
#endif
#ifdef HAVE_MOE
  mb_cs_detector_t *st;
  mb_info_t cd;
#elif LANG == JA
  char *buf, *from;
  int from_left, size, len_min, len_max;
  int (*conv)(JA_CES_conv_t *cd, const char **from, int *from_left, char **to, int *to_left);
  JA_CES_stat_t st;
  JA_CES_conv_t cd;
#endif
};

union input_handle {
  void *gen;
  int fd;
  struct file_handle *file;
  struct delimited_handle *delimited;
  Str str;
#ifdef USE_SSL
  struct ssl_handle *ssl;
#endif /* USE_SSL */
  struct ens_handle *ens;
  struct limited_handle *limited;
  struct chunked_handle *chunked;
  struct tee_handle *tee;
};

struct input_stream {
  struct stream_buffer stream;
  union input_handle handle;
  char type;
  char iseos;
  int (*read)(union input_handle, char *, int);
  void (*close)(union input_handle);
};

typedef struct input_stream *InputStream;

extern InputStream newInputStream(int des);
extern InputStream newFileStream(FILE * f, void (*closep)());
extern InputStream newDelimitedStream(InputStream is, Str buf, int (*delim_p)(const char *, void *), void *delim_arg);
extern InputStream newStrStream(Str s);
#ifdef USE_SSL
extern InputStream newSSLStream(SSL * ssl, int sock);
#endif
extern InputStream newEncodedStream(InputStream is, char encoding);
extern InputStream newLimitedStream(InputStream is, clen_t length);
extern InputStream newChunkedStream(InputStream is);
extern InputStream newTeeStream(InputStream is, FILE *save
#if LANG == JA || defined(HAVE_MOE)
				, int noconv, const char *cs, int len_min, int len_max
#endif
				);
extern void ISclose(InputStream stream);
extern int ISgetc(InputStream stream);
extern int ISundogetc(InputStream stream);
extern Str StrISgets(InputStream stream, Str buf);
extern Str StrmyISgets(InputStream stream, Str buf);
extern int ISread(InputStream stream, Str buf, int count);
extern int ISunread(InputStream base, Str buf);
extern int ISreadonce(InputStream stream, char *buf, int count);
extern int ISfileno(InputStream stream);
extern InputStream ISoftype(InputStream is, int type);
extern int ISeos(InputStream stream);
#ifdef USE_SSL
extern void ssl_accept_this_site(char *hostname);
extern Str ssl_get_certificate(SSL *ssl, char *hostname);
#endif
extern clen_t IStotal(InputStream is);
#if LANG == JA || defined(HAVE_MOE)
extern void ISsetces(InputStream is, const char *ces_name);
extern const char *ISgetces(InputStream is);
#endif

enum {
  IST_BASIC,
  IST_FILE,
  IST_STR,
  IST_SSL,
  IST_ENCODED,
  IST_LIMITED,
  IST_CHUNKED,
  IST_DELIMITED,
  IST_TEE,
};

#define IStype(stream) ((stream)->type)
#define is_eos(stream) ISeos(stream)
#define iseos(stream) ((stream)->iseos)
#define file_of(stream) ((stream)->handle.file->f)
#define set_close(stream,closep) ((IStype(stream)==IST_FILE)?((stream)->close=(closep)):0)
#define str_of(stream) ((stream)->handle.str)
#ifdef USE_SSL
#define ssl_socket_of(stream) ((stream)->handle.ssl->sock)
#define ssl_of(stream) ((stream)->handle.ssl->ssl)
#endif

#ifdef USE_BINMODE_STREAM
#define openIS(path) newInputStream(open((path),O_RDONLY|O_BINARY))
#else
#define openIS(path) newInputStream(open((path),O_RDONLY))
#endif				/* USE_BINMODE_STREAM */
#endif
