/* This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks.

   Copyright (C) 2002-2018 by Jin-Hwan Cho and Shunsaku Hirata,
   the dvipdfmx project team.

   Copyright (C) 1998, 1999 by Mark A. Wicks <mwicks@kettering.edu>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/

#include "dpx-cmap_read.h"

#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "dpx-cid.h"
#include "dpx-cmap.h"
#include "dpx-dpxconf.h"
#include "dpx-dpxutil.h"
#include "dpx-error.h"
#include "dpx-mem.h"
#include "dpx-pst.h"

#define CMAP_PARSE_DEBUG_STR "CMap_parse:"
#define CMAP_PARSE_DEBUG     3

typedef struct {
    unsigned char *cursor;
    unsigned char *endptr;

    unsigned char *buf;
    size_t  max;
    rust_input_handle_t handle;
    size_t  unread;
} ifreader;

static ifreader *ifreader_create  (rust_input_handle_t handle, size_t remain, size_t bufsize);
static size_t    ifreader_read    (ifreader *reader, size_t size);
static void      ifreader_destroy (ifreader *reader);

static ifreader *
ifreader_create (rust_input_handle_t handle, size_t size, size_t bufsize)
{
    ifreader *reader;

    reader = NEW(1, ifreader);
    reader->buf = NEW(bufsize+1, unsigned char);
    reader->max = bufsize;
    reader->handle = handle;
    reader->unread = size;

    reader->cursor = reader->endptr = reader->buf;
    *reader->endptr = 0;

    return reader;
}

static void
ifreader_destroy (ifreader *reader)
{
    assert(reader);
    free(reader->buf);
    free(reader);
}


static size_t
ifreader_read (ifreader *reader, size_t size)
{
    size_t bytesread = 0, bytesrem = 0;

    assert(reader);
    bytesrem = (size_t) reader->endptr - (size_t) reader->cursor;
    if (size > reader->max) {
        reader->buf = RENEW(reader->buf, size+1, unsigned char);
        reader->max = size;
    }
    if (reader->unread > 0 && bytesrem < size) {
        bytesread = MIN(reader->max - bytesrem, reader->unread);
        memmove(reader->buf, reader->cursor, bytesrem);
        reader->cursor = reader->buf;
        reader->endptr = reader->buf + bytesrem;
        if (ttstub_input_read(reader->handle, (char *) reader->endptr, bytesread) != bytesread)
            _tt_abort("Reading file failed.");
        reader->endptr += bytesread;
        reader->unread -= bytesread;
    }

    *reader->endptr = 0;

    return bytesread + bytesrem;
}

static int check_next_token  (ifreader *input, const char *key);
static int get_coderange     (ifreader *input, unsigned char *codeLo, unsigned char *codeHi, int *dim, int maxlen);

static int handle_codearray  (CMap *cmap, ifreader *input, unsigned char *codeLo, int dim, int count);
static int do_codespacerange (CMap *cmap, ifreader *input, int count);
static int do_notdefrange    (CMap *cmap, ifreader *input, int count);
static int do_bfrange        (CMap *cmap, ifreader *input, int count);
static int do_cidrange       (CMap *cmap, ifreader *input, int count);
static int do_notdefchar     (CMap *cmap, ifreader *input, int count);
static int do_bfchar         (CMap *cmap, ifreader *input, int count);
static int do_cidchar        (CMap *cmap, ifreader *input, int count);

#define TOKEN_LEN_MAX 127

static int
check_next_token (ifreader *input, const char *key)
{
    int      cmp;
    pst_obj *token;
    char    *str;

    if (ifreader_read(input, strlen(key)) == 0)
        return -1;
    if ((token = pst_get_token(&(input->cursor), input->endptr)) == NULL)
        return -1;

    str = (char *) pst_getSV(token);
    cmp = strcmp(str, key) ? -1 : 0;
    free(str);
    pst_release_obj(token);

    return cmp;
}

static int
get_coderange (ifreader *input,
               unsigned char *codeLo, unsigned char *codeHi, int *dim, int maxlen)
{
    pst_obj *tok1, *tok2;
    int      dim1, dim2;

    if ((tok1 = pst_get_token(&(input->cursor), input->endptr)) == NULL)
        return -1;
    if ((tok2 = pst_get_token(&(input->cursor), input->endptr)) == NULL) {
        pst_release_obj(tok1);
        return -1;
    }

    if (!PST_STRINGTYPE(tok1) || !PST_STRINGTYPE(tok2)) {
        pst_release_obj(tok1);
        pst_release_obj(tok2);
        return -1;
    }

    dim1 = pst_length_of(tok1);
    dim2 = pst_length_of(tok2);
    if (dim1 != dim2 || dim1 > maxlen) {
        pst_release_obj(tok1);
        pst_release_obj(tok2);
        return -1;
    }

    memcpy(codeLo, pst_data_ptr(tok1), dim1);
    memcpy(codeHi, pst_data_ptr(tok2), dim2);
    pst_release_obj(tok1);
    pst_release_obj(tok2);

    *dim = dim1;
    return 0;
}

static int
do_codespacerange (CMap *cmap, ifreader *input, int count)
{
    unsigned char codeLo[TOKEN_LEN_MAX], codeHi[TOKEN_LEN_MAX];
    int dim;

    while (count-- > 0) {
        if (get_coderange(input, codeLo, codeHi, &dim, TOKEN_LEN_MAX) < 0)
            return -1;
        CMap_add_codespacerange(cmap, codeLo, codeHi, dim);
    }

    return check_next_token(input, "endcodespacerange");
}

/*
 * bfrange
 *  <codeLo> <codeHi> [destCode1 destCode2 ...]
 */
static int
handle_codearray (CMap *cmap, ifreader *input, unsigned char *codeLo, int dim, int count)
{
    pst_obj *tok = NULL;

    if (dim < 1)
        _tt_abort("Invalid code range.");
    while (count-- > 0) {
        if ((tok = pst_get_token(&(input->cursor), input->endptr)) == NULL)
            return -1;
        else if (PST_STRINGTYPE(tok)) {
            CMap_add_bfchar(cmap, codeLo, dim, (unsigned char *) pst_data_ptr(tok), pst_length_of(tok));
        } else if (PST_MARKTYPE(tok) || !PST_NAMETYPE(tok))
            _tt_abort("%s: Invalid CMap mapping record.", CMAP_PARSE_DEBUG_STR);
        else
            _tt_abort("%s: Mapping to charName not supported.", CMAP_PARSE_DEBUG_STR);
        pst_release_obj(tok);
        codeLo[dim-1] += 1;
    }

    return check_next_token(input, "]");
}

static int
do_notdefrange (CMap *cmap, ifreader *input, int count)
{
    pst_obj *tok;
    unsigned char   codeLo[TOKEN_LEN_MAX], codeHi[TOKEN_LEN_MAX];
    int      dstCID;
    int      dim;

    while (count-- > 0) {
        if (ifreader_read(input, TOKEN_LEN_MAX*3) == 0)
            return -1;
        if (get_coderange(input, codeLo, codeHi, &dim, TOKEN_LEN_MAX) < 0 ||
            (tok = pst_get_token(&(input->cursor), input->endptr)) == NULL)
            return -1;
        if (PST_INTEGERTYPE(tok)) {
            dstCID = pst_getIV(tok);
            if (dstCID >= 0 && dstCID <= CID_MAX)
                CMap_add_notdefrange(cmap, codeLo, codeHi, dim, (CID) dstCID);
        } else
            dpx_warning("%s: Invalid CMap mapping record. (ignored)", CMAP_PARSE_DEBUG_STR);
        pst_release_obj(tok);
    }

    return check_next_token(input, "endnotdefrange");
}

static int
do_bfrange (CMap *cmap, ifreader *input, int count)
{
    pst_obj *tok;
    unsigned char   codeLo[TOKEN_LEN_MAX], codeHi[TOKEN_LEN_MAX];
    int      srcdim;

    while (count-- > 0) {
        if (ifreader_read(input, TOKEN_LEN_MAX*3) == 0)
            return -1;
        if (get_coderange(input, codeLo, codeHi, &srcdim, TOKEN_LEN_MAX) < 0    ||
            (tok = pst_get_token(&(input->cursor), input->endptr)) == NULL)
            return -1;
        if (PST_STRINGTYPE(tok)) {
            CMap_add_bfrange(cmap, codeLo, codeHi, srcdim,
                             (unsigned char *) pst_data_ptr(tok), pst_length_of(tok));
        } else if (PST_MARKTYPE(tok)) {
            if (handle_codearray(cmap, input, codeLo, srcdim,
                                 codeHi[srcdim-1] - codeLo[srcdim-1] + 1) < 0) {
                pst_release_obj(tok);
                return -1;
            }
        } else
            dpx_warning("%s: Invalid CMap mapping record. (ignored)", CMAP_PARSE_DEBUG_STR);
        pst_release_obj(tok);
    }

    return check_next_token(input, "endbfrange");
}

static int
do_cidrange (CMap *cmap, ifreader *input, int count)
{
    pst_obj *tok;
    unsigned char   codeLo[TOKEN_LEN_MAX], codeHi[TOKEN_LEN_MAX];
    int      dstCID;
    int      dim;

    while (count-- > 0) {
        if (ifreader_read(input, TOKEN_LEN_MAX*3) == 0)
            return -1;
        if (get_coderange(input, codeLo, codeHi, &dim, TOKEN_LEN_MAX) < 0 ||
            (tok = pst_get_token(&(input->cursor), input->endptr)) == NULL)
            return -1;
        if (PST_INTEGERTYPE(tok)) {
            dstCID = pst_getIV(tok);
            if (dstCID >= 0 && dstCID <= CID_MAX)
                CMap_add_cidrange(cmap, codeLo, codeHi, dim, (CID) dstCID);
        } else
            dpx_warning("%s: Invalid CMap mapping record. (ignored)", CMAP_PARSE_DEBUG_STR);
        pst_release_obj(tok);
    }

    return check_next_token(input, "endcidrange");
}

static int
do_notdefchar (CMap *cmap, ifreader *input, int count)
{
    pst_obj *tok1, *tok2;
    int      dstCID;

    while (count-- > 0) {
        if (ifreader_read(input, TOKEN_LEN_MAX*2) == 0)
            return -1;
        if ((tok1 = pst_get_token(&(input->cursor), input->endptr)) == NULL)
            return -1;
        if ((tok2 = pst_get_token(&(input->cursor), input->endptr)) == NULL) {
            pst_release_obj(tok1);
            return -1;
        }
        if (PST_STRINGTYPE(tok1) && PST_INTEGERTYPE(tok2)) {
            dstCID = pst_getIV(tok2);
            if (dstCID >= 0 && dstCID <= CID_MAX)
                CMap_add_notdefchar(cmap, pst_data_ptr(tok1), pst_length_of(tok1), (CID) dstCID);
        } else
            dpx_warning("%s: Invalid CMap mapping record. (ignored)", CMAP_PARSE_DEBUG_STR);
        pst_release_obj(tok1);
        pst_release_obj(tok2);
    }

    return check_next_token(input, "endnotdefchar");
}

static int
do_bfchar (CMap *cmap, ifreader *input, int count)
{
    pst_obj *tok1, *tok2;

    while (count-- > 0) {
        if (ifreader_read(input, TOKEN_LEN_MAX*2) == 0)
            return -1;
        if ((tok1 = pst_get_token(&(input->cursor), input->endptr)) == NULL)
            return -1;
        if ((tok2 = pst_get_token(&(input->cursor), input->endptr)) == NULL) {
            pst_release_obj(tok1);
            return -1;
        }
        /* We only support single CID font as descendant font, charName should not come here. */
        if (PST_STRINGTYPE(tok1) && PST_STRINGTYPE(tok2)) {
            CMap_add_bfchar(cmap,
                            (unsigned char *) pst_data_ptr(tok1), pst_length_of(tok1),
                            (unsigned char *) pst_data_ptr(tok2), pst_length_of(tok2));
        } else if (PST_NAMETYPE(tok2))
            _tt_abort("%s: Mapping to charName not supported.", CMAP_PARSE_DEBUG_STR);
        else
            dpx_warning("%s: Invalid CMap mapping record. (ignored)", CMAP_PARSE_DEBUG_STR);
        pst_release_obj(tok1);
        pst_release_obj(tok2);
    }

    return check_next_token(input, "endbfchar");
}

static int
do_cidchar (CMap *cmap, ifreader *input, int count)
{
    pst_obj *tok1, *tok2;
    int      dstCID;

    while (count-- > 0) {
        if (ifreader_read(input, TOKEN_LEN_MAX*2) == 0)
            return -1;
        if ((tok1 = pst_get_token(&(input->cursor), input->endptr)) == NULL)
            return -1;
        if ((tok2 = pst_get_token(&(input->cursor), input->endptr)) == NULL) {
            pst_release_obj(tok1);
            return -1;
        }
        if (PST_STRINGTYPE(tok1) && PST_INTEGERTYPE(tok2)) {
            dstCID = pst_getIV(tok2);
            if (dstCID >= 0 && dstCID <= CID_MAX)
                CMap_add_cidchar(cmap, pst_data_ptr(tok1), pst_length_of(tok1), (CID) dstCID);
        } else
            dpx_warning("%s: Invalid CMap mapping record. (ignored)", CMAP_PARSE_DEBUG_STR);
        pst_release_obj(tok1);
        pst_release_obj(tok2);
    }

    return check_next_token(input, "endcidchar");
}


#define MATCH_NAME(t,n) (PST_NAMETYPE((t))    && (pst_length_of((t)) == strlen((n))) && !memcmp(pst_data_ptr((t)),(n),strlen((n))))
#define MATCH_OP(t,n)   (PST_UNKNOWNTYPE((t)) && (pst_length_of((t)) == strlen((n))) && !memcmp(pst_data_ptr((t)),(n),strlen((n))))

static int
do_cidsysteminfo (CMap *cmap, ifreader *input)
{
    pst_obj   *tok1, *tok2;
    CIDSysInfo csi = {NULL, NULL, -1};
    int        simpledict = 0;
    int        error = 0;

    ifreader_read(input, TOKEN_LEN_MAX*2);
    /*
     * Assuming /CIDSystemInfo 3 dict dup begin .... end def
     * or /CIDSystemInfo << ... >> def
     */
    while ((tok1 = pst_get_token(&(input->cursor), input->endptr)) != NULL) {
        if (PST_MARKTYPE(tok1)) {
            simpledict = 1;
            pst_release_obj(tok1);
            break;
        } else if (MATCH_OP(tok1, "begin")) {
            simpledict = 0;
            pst_release_obj(tok1);
            break;
        } else {
            pst_release_obj(tok1);
            /* continue */
        }
    }
    tok1 = tok2 = NULL;
    while (!error &&
           (tok1 = pst_get_token(&(input->cursor), input->endptr)) != NULL) {
        if (MATCH_OP(tok1, ">>") && simpledict) {
            pst_release_obj(tok1);
            break;
        } else if (MATCH_OP(tok1, "end") && !simpledict) {
            pst_release_obj(tok1);
            break;
        } else if (MATCH_NAME(tok1, "Registry") &&
                   (tok2 = pst_get_token(&(input->cursor), input->endptr)) != NULL) {
            if (!PST_STRINGTYPE(tok2))
                error = -1;
            else if (!simpledict &&
                     check_next_token(input, "def"))
                error = -1;
            if (!error)
                csi.registry = (char *) pst_getSV(tok2);
        } else if (MATCH_NAME(tok1, "Ordering") &&
                   (tok2 = pst_get_token(&(input->cursor), input->endptr)) != NULL) {
            if (!PST_STRINGTYPE(tok2))
                error = -1;
            else if (!simpledict &&
                     check_next_token(input, "def"))
                error = -1;
            if (!error)
                csi.ordering = (char *) pst_getSV(tok2);
        } else if (MATCH_NAME(tok1, "Supplement") &&
                   (tok2 = pst_get_token(&(input->cursor), input->endptr)) != NULL) {
            if (!PST_INTEGERTYPE(tok2))
                error = -1;
            else if (!simpledict &&
                     check_next_token(input, "def"))
                error = -1;
            if (!error)
                csi.supplement = pst_getIV(tok2);
        }
        if (tok2)
            pst_release_obj(tok2);
        if (tok1)
            pst_release_obj(tok1);
        tok1 = tok2 = NULL;
    }
    if (!error &&
        check_next_token(input, "def"))
        error = -1;

    if (!error &&
        csi.registry && csi.ordering &&
        csi.supplement >= 0) {
        CMap_set_CIDSysInfo(cmap, &csi);
    }

    free(csi.registry);
    free(csi.ordering);

    return  error;
}

#define INPUT_BUF_SIZE 4096
#define CMAP_SIG_MAX   64
int
CMap_parse_check_sig (rust_input_handle_t handle)
{
    int  result = -1;
    char sig[CMAP_SIG_MAX+1];

    if (handle == INVALID_HANDLE)
        return -1;

    ttstub_input_seek(handle, 0, SEEK_SET);

    if (ttstub_input_read(handle, sig, CMAP_SIG_MAX) != CMAP_SIG_MAX)
        result = -1;
    else {
        sig[CMAP_SIG_MAX] = 0;
        if (!strstartswith(sig, "%!PS"))
            result = -1;
        else if (strstr(sig+4, "Resource-CMap"))
            result = 0;
    }

    ttstub_input_seek (handle, 0, SEEK_SET);
    return result;
}

int
CMap_parse (CMap *cmap, rust_input_handle_t handle)
{
    pst_obj  *tok1, *tok2;
    ifreader *input;
    int       status = 0, tmpint = -1;

    assert(cmap && handle);

    input = ifreader_create(handle, ttstub_input_get_size(handle), INPUT_BUF_SIZE-1);

    while (status >= 0) {
        tok1 = tok2 = NULL;
        ifreader_read(input, INPUT_BUF_SIZE/2);
        tok1 = pst_get_token(&(input->cursor), input->endptr);
        if (tok1 == NULL)
            break;
        else if (MATCH_NAME(tok1, "CMapName")) {
            if ((tok2 = pst_get_token(&(input->cursor), input->endptr)) == NULL ||
                !(PST_NAMETYPE(tok2) || PST_STRINGTYPE(tok2)) ||
                check_next_token(input, "def") < 0)
                status = -1;
            else
                CMap_set_name(cmap, pst_data_ptr(tok2));
        } else if (MATCH_NAME(tok1, "CMapType")) {
            if ((tok2 = pst_get_token(&(input->cursor), input->endptr)) == NULL ||
                !PST_INTEGERTYPE(tok2) ||
                check_next_token(input, "def") < 0)
                status = -1;
            else
                CMap_set_type(cmap, pst_getIV(tok2));
        } else if (MATCH_NAME(tok1, "WMode")) {
            if ((tok2 = pst_get_token(&(input->cursor), input->endptr)) == NULL ||
                !PST_INTEGERTYPE(tok2) ||
                check_next_token(input, "def") < 0)
                status = -1;
            else
                CMap_set_wmode(cmap, pst_getIV(tok2));
        } else if (MATCH_NAME(tok1, "CIDSystemInfo")) {
            status = do_cidsysteminfo(cmap, input);
        } else if (MATCH_NAME(tok1, "Version") ||
                   MATCH_NAME(tok1, "UIDOffset") ||
                   MATCH_NAME(tok1, "XUID")) {
            /* Ignore */
        } else if (PST_NAMETYPE(tok1)) {
            /* Possibly usecmap comes next */
            if ((tok2 = pst_get_token(&(input->cursor), input->endptr)) != NULL &&
                MATCH_OP(tok2, "usecmap")) {
                int   id;
                CMap *ucmap;
                id = CMap_cache_find(pst_data_ptr(tok1));
                if (id < 0)
                    status = -1;
                else {
                    ucmap = CMap_cache_get(id);
                    CMap_set_usecmap(cmap, ucmap);
                }
            }
        } else if (MATCH_OP(tok1, "begincodespacerange")) {
            status = do_codespacerange(cmap, input, tmpint);
        } else if (MATCH_OP(tok1, "beginnotdefrange")) {
            status = do_notdefrange(cmap, input, tmpint);
        } else if (MATCH_OP(tok1, "beginnotdefchar")) {
            status = do_notdefchar(cmap, input, tmpint);
        } else if (MATCH_OP(tok1, "beginbfrange")) {
            status = do_bfrange(cmap, input, tmpint);
        } else if (MATCH_OP(tok1, "beginbfchar")) {
            status =  do_bfchar(cmap, input, tmpint);
        } else if (MATCH_OP(tok1, "begincidrange")) {
            status = do_cidrange(cmap, input, tmpint);
        } else if (MATCH_OP(tok1, "begincidchar")) {
            status =  do_cidchar(cmap, input, tmpint);
        } else if (PST_INTEGERTYPE(tok1)) {
            tmpint = pst_getIV(tok1);
        } /* else Simply ignore */
        if (tok1)
            pst_release_obj(tok1);
        if (tok2)
            pst_release_obj(tok2);
    }

    ifreader_destroy(input);

    return (status < 0) ? -1 : CMap_is_valid(cmap);
}
