#include "transcode_data.h"

<%
  map_normalize = {}
  map_normalize["{00-ff}"] = :func_so

  transcode_generate_node(ActionMap.parse(map_normalize), "universal_newline")

  map_crlf = {}
  map_crlf["{00-09,0b-ff}"] = :nomap
  map_crlf["0a"] = "0d0a"

  transcode_generate_node(ActionMap.parse(map_crlf), "crlf_newline")

  map_cr = {}
  map_cr["{00-09,0b-ff}"] = :nomap
  map_cr["0a"] = "0d"

  transcode_generate_node(ActionMap.parse(map_cr), "cr_newline")
%>

<%= transcode_generated_code %>

#define STATE (sp[0])
#define NORMAL 0
#define JUST_AFTER_CR 1

/* no way to access this information, yet. */
#define NEWLINES_MET (sp[1])
#define MET_LF          0x01
#define MET_CRLF        0x02
#define MET_CR          0x04

static int
universal_newline_init(void *statep)
{
    unsigned char *sp = statep;
    STATE = NORMAL;
    NEWLINES_MET = 0;
    return 0;
}

static ssize_t
fun_so_universal_newline(void *statep, const unsigned char *s, size_t l, unsigned char *o, size_t osize)
{
    unsigned char *sp = statep;
    int len;
    if (s[0] == '\n') {
        if (STATE == NORMAL) {
            NEWLINES_MET |= MET_LF;
        }
        else { /* JUST_AFTER_CR */
            NEWLINES_MET |= MET_CRLF;
        }
        o[0] = '\n';
        len = 1;
        STATE = NORMAL;
    }
    else {
        len = 0;
        if (STATE == JUST_AFTER_CR) {
            o[0] = '\n';
            len = 1;
            NEWLINES_MET |= MET_CR;
        }
        if (s[0] == '\r') {
            STATE = JUST_AFTER_CR;
        }
        else {
            o[len++] = s[0];
            STATE = NORMAL;
        }
    }

    return len;
}

static ssize_t
universal_newline_finish(void *statep, unsigned char *o, size_t osize)
{
    unsigned char *sp = statep;
    int len = 0;
    if (STATE == JUST_AFTER_CR) {
        o[0] = '\n';
        len = 1;
        NEWLINES_MET |= MET_CR;
    }
    STATE = NORMAL;
    return len;
}

static const rb_transcoder
rb_universal_newline = {
    "", "universal_newline", universal_newline,
    TRANSCODE_TABLE_INFO,
    1, /* input_unit_length */
    1, /* max_input */
    2, /* max_output */
    asciicompat_converter, /* asciicompat_type */
    2, universal_newline_init, universal_newline_init, /* state_size, state_init, state_fini */
    NULL, NULL, NULL, fun_so_universal_newline,
    universal_newline_finish
};

static const rb_transcoder
rb_crlf_newline = {
    "", "crlf_newline", crlf_newline,
    TRANSCODE_TABLE_INFO,
    1, /* input_unit_length */
    1, /* max_input */
    2, /* max_output */
    asciicompat_converter, /* asciicompat_type */
    0, NULL, NULL, /* state_size, state_init, state_fini */
    NULL, NULL, NULL, NULL
};

static const rb_transcoder
rb_cr_newline = {
    "", "cr_newline", cr_newline,
    TRANSCODE_TABLE_INFO,
    1, /* input_unit_length */
    1, /* max_input */
    1, /* max_output */
    asciicompat_converter, /* asciicompat_type */
    0, NULL, NULL, /* state_size, state_init, state_fini */
    NULL, NULL, NULL, NULL
};

void
Init_newline(void)
{
    rb_register_transcoder(&rb_universal_newline);
    rb_register_transcoder(&rb_crlf_newline);
    rb_register_transcoder(&rb_cr_newline);
}
