/* Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). All Rights Reserved.
   This software is licensed as OpenSource, under the Apache License, Version 2.0.
   This license is available at: http://opensource.org/licenses/Apache-2.0. */
/***********************************************************************/

#include "sindex.h"

#include <stdlib.h>
#include <string.h>

/* Standard string ids */
typedef struct {
    SID id;
    char *strng;
} StdId;

/* Standard string names */
static StdId str2sid[] = {
#include "stdstr0.h"
};
#define STD_STR_CNT TABLE_LEN(str2sid)

/* Maps string to id */
typedef struct {
    long index; /* String buffer index */
    unsigned short length;
    SID id;
} Map;

/* Module context */
struct sindexCtx_ {
    dnaDCL(Map, maps);     /* String to id map */
    dnaDCL(char, strings); /* String buffer */
    dnaDCL(char, seen);    /* Flags SIDs already seen in font */
    tcCtx g;               /* Package context */
};

/* Initialize seen element */
static void initSeen(void *ctx, long count, char *seen) {
    long i;
    for (i = 0; i < count; i++) {
        *seen = 0;
        seen++;
    }
    return;
}

/* Initialize module */
void sindexNew(tcCtx g) {
    sindexCtx h = MEM_NEW(g, sizeof(struct sindexCtx_));

    dnaINIT(g->ctx.dnaCtx, h->maps, 260, 1000);
    dnaINIT(g->ctx.dnaCtx, h->strings, 1500, 3000);
    dnaINIT(g->ctx.dnaCtx, h->seen, 450, 1000);
    h->seen.func = initSeen;

    /* Link contexts */
    h->g = g;
    g->ctx.sindex = h;
}

/* Free resources */
void sindexFree(tcCtx g) {
    sindexCtx h = g->ctx.sindex;
    dnaFREE(h->maps);
    dnaFREE(h->strings);
    dnaFREE(h->seen);
    MEM_FREE(g, g->ctx.sindex);
}

/* Match string */
static int CDECL matchString(const void *key, const void *value) {
    const String *strng = key;
    return tc_strncmp(strng->data, strng->length, ((StdId *)value)->strng);
}

/* Search standard strings and return id if found else SID_UNDEF. */
static SID getStdId(sindexCtx h, char *strng, unsigned length) {
    StdId *pStd;
    String key;

    key.length = length;
    key.data = strng;

    pStd = (StdId *)bsearch(&key, str2sid, STD_STR_CNT,
                            sizeof(StdId), matchString);

    return (pStd != NULL) ? pStd->id : SID_UNDEF;
}

/* Lookup string in index and return its index. Returns 1 if found else 0.
   Found or insertion position returned via index parameter. */
static int lookup(sindexCtx h, unsigned length, char *strng, long *index) {
    long lo = 0;
    long hi = h->maps.cnt - 1;

    while (lo <= hi) {
        long mid = (lo + hi) / 2;
        Map *map = &h->maps.array[mid];
        if (length > map->length) {
            lo = mid + 1;
        } else if (length < map->length) {
            hi = mid - 1;
        } else {
            /* String lengths equal, compare strings */
            int cmp = memcmp(strng, &h->strings.array[map->index], length);
            if (cmp > 0) {
                lo = mid + 1;
            } else if (cmp < 0) {
                hi = mid - 1;
            } else {
                *index = mid;
                return 1; /* Found */
            }
        }
    }
    *index = lo;
    return 0; /* Not found */
}

/* Lookup string and return its id. If doesn't exist it's added to table */
SID sindexGetId(tcCtx g, unsigned length, char *strng) {
    sindexCtx h = g->ctx.sindex;
    long index;
    SID id = getStdId(h, strng, length);
    if (id != SID_UNDEF) {
        return id;
    }

    /* Search custom strings */
    if (!lookup(h, length, strng, &index)) {
        /* Add string */
        Map *new = &dnaGROW(h->maps, h->maps.cnt)[index];

        /* Make hole */
        COPY(new + 1, new, h->maps.cnt - index);

        /* Fill record */
        new->id = (SID)(h->maps.cnt++ + STD_STR_CNT);
        new->length = length;
        new->index = h->strings.cnt;
        memcpy(dnaEXTEND(h->strings, (long)length), strng, length);
        return new->id;
    }
    return h->maps.array[index].id;
}

/* Lookup glyph name string (that is supposed to be unique) and return its id
   if not seen already else return SID_UNDEF. */
SID sindexGetGlyphNameId(tcCtx g, unsigned length, char *strng) {
    sindexCtx h = g->ctx.sindex;
    SID id = sindexGetId(g, length, strng);
    char *seen = dnaMAX(h->seen, id);
    if (*seen) {
        /* Flag this glyph as already seen */
        return SID_UNDEF;
    }
    *seen = 1; /* Mark SID as seen for subsequent test */
    return (SID)id;
}

/* Return 1 if glyph name id has been seen else 0. */
int sindexSeenGlyphNameId(tcCtx g, SID sid) {
    sindexCtx h = g->ctx.sindex;
    return (sid <= h->seen.cnt) ? h->seen.array[sid] : 0;
}

/* Lookup string and return its id. If doesn't exist return SID_UNDEF. */
SID sindexCheckId(tcCtx g, unsigned length, char *strng) {
    sindexCtx h = g->ctx.sindex;
    long index;
    SID id = getStdId(h, strng, length);
    if (id != SID_UNDEF) {
        return id;
    }

    /* Search custom strings */
    if (lookup(h, length, strng, &index)) {
        return h->maps.array[index].id;
    }

    return SID_UNDEF;
}

/* Get string from id. Only used for diagnostics so linear search is OK */
char *sindexGetString(tcCtx g, SID id) {
    static char *sid2str[] = {
#include "stdstr1.h"
    };
    sindexCtx h = g->ctx.sindex;
    long i;

    if (id < STD_STR_CNT) {
        return sid2str[id]; /* Return standard string */
    }
    /* Search custom strings */
    for (i = 0; i < h->maps.cnt; i++) {
        Map *map = &h->maps.array[i];
        if (map->id == id) {
            static char strng[101];
            int length = (map->length > 100) ? 100 : map->length;
            memcpy(strng, &h->strings.array[map->index], length);
            strng[length] = '\0';
            return strng;
        }
    }
    tcFatal(g, "can't get string for id [%d]", id);

    return NULL; /* Suppress compiler warning */
}

/* Calculate table size */
long sindexSize(tcCtx g) {
    sindexCtx h = g->ctx.sindex;
    return INDEX_SIZE(h->maps.cnt, h->strings.cnt);
}

/* Compare string ids */
static int CDECL cmpStringIds(const void *first, const void *second) {
    SID a = ((Map *)first)->id;
    SID b = ((Map *)second)->id;
    if (a < b) {
        return -1;
    } else if (a > b) {
        return 1;
    } else {
        return 0;
    }
}

/* Initialize for new font. */
void sindexFontInit(tcCtx g) {
    sindexCtx h = g->ctx.sindex;
    if (h->seen.size != 0) {
        memset(h->seen.array, 0, h->seen.cnt + 1);
    }
    h->seen.cnt = 0;
}

/* Get module into reusable state */
static void reuseInit(tcCtx g, sindexCtx h) {
    h->maps.cnt = 0;
    h->strings.cnt = 0;
}

/* Write string table */
void sindexWrite(tcCtx g) {
    sindexCtx h = g->ctx.sindex;
    long i;
    Offset offset;
    INDEXHdr header;

    /* Construct header */
    header.count = (unsigned short)h->maps.cnt;
    header.offSize = INDEX_OFF_SIZE(h->strings.cnt);

    OUT2(header.count);

    if (header.count == 0) {
        return; /* Empty table just has zero count */
    }
    OUT1(header.offSize);

    /* Sort table into string id order */
    qsort(h->maps.array, h->maps.cnt, sizeof(Map), cmpStringIds);

    /* Write string offset array */
    offset = 1;
    OUTOFF(header.offSize, offset);
    for (i = 0; i < h->maps.cnt; i++) {
        offset += h->maps.array[i].length;
        OUTOFF(header.offSize, offset);
    }

    /* Write string data */
    OUTN(h->strings.cnt, h->strings.array);

    reuseInit(g, h);
}
