// SPDX-License-Identifier: MIT OR MPL-2.0 OR LGPL-2.1-or-later OR GPL-2.0-or-later
// Copyright (C) 2005 www.thanlwinsoft.org, SIL International
/*
Responsibility: Keith Stribley, Martin Hosken
Description:
A simple console app that creates a segment using FileFont and dumps a
diagnostic table of the resulting glyph vector to the console.
If graphite has been built with -DTRACING then it will also produce a
diagnostic log of the segment creation in grSegmentLog.txt
*/

#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <climits>
#include <iomanip>
#include <cstring>

#include "UtfCodec.h"

#include "graphite2/Types.h"
#include "graphite2/Segment.h"
#include "graphite2/Log.h"

class Gr2TextSrc
{

public:
    Gr2TextSrc(const gr_uint32* base, size_t len) : m_buff(base), m_len(len) { }
    gr_encform utfEncodingForm() const { return gr_utf32; }
    size_t getLength() const { return m_len; }
    const void* get_utf_buffer_begin() const { return m_buff; }

private:
    const gr_uint32* m_buff;
    size_t m_len;
};

#ifndef HAVE_STRTOF
float strtof(char * text, char ** /*ignore*/)
{
  return static_cast<float>(atof(text));
}
#endif

#ifndef HAVE_STRTOL
long strtol(char * text, char ** /*ignore*/)
{
  return atol(text);
}
#endif

class Parameters
{
public:
    Parameters();
    ~Parameters();
    void clear();
    void closeLog();
    bool loadFromArgs(int argc, char *argv[]);
    int testFileFont() const;
    gr_feature_val* parseFeatures(const gr_face * face) const;
    void printFeatures(const gr_face * face) const;
public:
    const char * fileName;
    const char * features;
    float pointSize;
    int dpi;
    bool lineStart;
    bool lineEnd;
    bool ws;
    bool rtl;
    bool useLineFill;
    bool noprint;
    int useCodes;
    bool autoCodes;
    int justification;
    float width;
    int textArgIndex;
    unsigned int * pText32;
    size_t charLength;
    size_t offset;
    FILE * log;
    char * trace;
    char * alltrace;
    int codesize;
    gr_face_options opts;

private :  //defensive since log should not be copied
    Parameters(const Parameters&);
    Parameters& operator=(const Parameters&);
};

Parameters::Parameters()
{
  log = stdout ;
  clear();
}


Parameters::~Parameters()
{
  free(pText32);
  pText32 = NULL;
  closeLog();
}

void Parameters::clear()
{
    closeLog() ;
    fileName = "";
    features = nullptr;
    pointSize = 12.0f;
    dpi = 72;
    lineStart = false;
    lineEnd = false;
    rtl = false;
    ws = false;
    useLineFill = false;
    useCodes = 0;
    autoCodes = false;
    noprint = false;
    justification = 0;
    width = 100.0f;
    pText32 = NULL;
    textArgIndex = 0;
    charLength = 0;
    codesize = 4;
    offset = 0;
    log = stdout;
    trace = NULL;
    alltrace = NULL;
    opts = gr_face_preloadAll;
}


void Parameters::closeLog()
{
  if (log==stdout)
    return ;

  fclose(log);
  log = stdout;
}

int lookup(size_t *map, size_t val);

namespace gr2 = graphite2;

template <typename utf>
size_t convertUtf(const void * src, unsigned int * & dest)
{
    dest = static_cast<unsigned int *>(malloc(sizeof(unsigned int)*(strlen(reinterpret_cast<const char *>(src))+1)));
    if (!dest)
    	return 0;

    typename utf::const_iterator ui = src;
    unsigned int * out = dest;
	while ((*out = *ui) != 0 && !ui.error())
	{
		++ui;
		++out;
	}

	if (ui.error())
	{
		free(dest);
		dest = 0;
		return size_t(-1);
	}

	return (out-dest);
}


bool Parameters::loadFromArgs(int argc, char *argv[])
{
    int mainArgOffset = 0;
    pText32 = NULL;
    features = NULL;
    log = stdout;
    codesize = 4;
    bool argError = false;
    char* pText = NULL;
    typedef enum
    {
        NONE,
        POINT_SIZE,
        DPI,
        JUSTIFY,
        LINE_START,
        LINE_END,
        LINE_FILL,
        CODES,
        AUTOCODES,
        FEAT,
        LOG,
        TRACE,
        ALLTRACE,
        SIZE
    } TestOptions;
    TestOptions option = NONE;
    char * pIntEnd = NULL;
    char * pFloatEnd = NULL;
    long lTestSize = 0;
    float fTestSize = 0.0f;
    for (int a = 1; a < argc; a++)
    {
        switch (option)
        {
        case DPI:
            pIntEnd = NULL;
            lTestSize = strtol(argv[a],&pIntEnd, 10);
            if (lTestSize > 0 && lTestSize < INT_MAX && lTestSize != LONG_MAX)
            {
                dpi = lTestSize;
            }
            else
            {
                fprintf(stderr,"Invalid dpi %s\n", argv[a]);
            }
            option = NONE;
            break;
        case POINT_SIZE:
            pFloatEnd = NULL;
            fTestSize = strtof(argv[a],&pFloatEnd);
            // what is a reasonable maximum here
            if (fTestSize > 0 && fTestSize < 5000.0f)
            {
                pointSize = fTestSize;
            }
            else
            {
                fprintf(stderr,"Invalid point size %s\n", argv[a]);
                argError = true;
            }
            option = NONE;
            break;
        case JUSTIFY:
            pIntEnd = NULL;
            justification = strtol(argv[a], &pIntEnd, 10);
            if (justification <= 0)
            {
                fprintf(stderr, "Justification value must be > 0 but was %s\n", argv[a]);
                justification = 0;
            }
            option = NONE;
            break;
        case LINE_FILL:
            pFloatEnd = NULL;
            fTestSize = strtof(argv[a],&pFloatEnd);
            // what is a good max width?
            if (fTestSize > 0 && fTestSize < 10000)
            {
                width = fTestSize;
            }
            else
            {
                fprintf(stderr,"Invalid line width %s\n", argv[a]);
                argError = true;
            }
            option = NONE;
            break;
        case FEAT:
                features = argv[a];
                option = NONE;
                break;
        case LOG:
            closeLog();
            log = fopen(argv[a], "w");
            if (log == NULL)
            {
                fprintf(stderr,"Failed to open %s\n", argv[a]);
                log = stdout;
            }
            option = NONE;
            break;
        case TRACE:
            trace = argv[a];
            option = NONE;
            break;
        case ALLTRACE:
            alltrace = argv[a];
            option = NONE;
            break;
        case SIZE :
            pIntEnd = NULL;
            codesize = strtol(argv[a],&pIntEnd, 10);
            option = NONE;
            break;
        default:
            option = NONE;
            if (argv[a][0] == '-')
            {
                if (strcmp(argv[a], "-pt") == 0)
                {
                    option = POINT_SIZE;
                }
                else if (strcmp(argv[a], "-dpi") == 0)
                {
                    option = DPI;
                }
                else if (strcmp(argv[a], "-ls") == 0)
                {
                    option = NONE;
                    lineStart = true;
                }
                else if (strcmp(argv[a], "-le") == 0)
                {
                    option = NONE;
                    lineEnd = true;
                }
                else if (strcmp(argv[a], "-le") == 0)
                {
                    option = NONE;
                    lineEnd = true;
                }
                else if (strcmp(argv[a], "-rtl") == 0)
                {
                    option = NONE;
                    rtl = true;
                }
                else if (strcmp(argv[a], "-ws") == 0)
                {
                    option = NONE;
                    ws = true;
                }
                else if (strcmp(argv[a], "-feat") == 0)
                {
                    option = FEAT;
                }
                else if (strcmp(argv[a], "-bytes") == 0)
                {
                    option = SIZE;
                }
                else if (strcmp(argv[a], "-noprint") == 0)
                {
                    noprint = true;
                }
                else if (strcmp(argv[a], "-codes") == 0)
                {
                    option = NONE;
                    useCodes = 4;
                    // must be less than argc
                    //pText32 = new unsigned int[argc];
                    pText32 = (unsigned int *)malloc(sizeof(unsigned int) * argc);
                    fprintf(log, "Text codes\n");
                }
                else if (strcmp(argv[a], "-auto") == 0)
                {
                    const unsigned kCodeLimit = 0x1000;
                    charLength = kCodeLimit - 1;
                    pText32 = (unsigned int *)malloc(sizeof(unsigned int) * kCodeLimit);
                    unsigned int i;
                    for (i = 1; i < kCodeLimit; ++i)
                        pText32[i - 1] = i;
                    pText32[charLength] = 0;
                    autoCodes = true;
                    option = NONE;
                }
                else if (strcmp(argv[a], "-linefill") == 0)
                {
                    option = LINE_FILL;
                    useLineFill = true;
                }
                else if (strcmp(argv[a], "-j") == 0)
                {
                    option = JUSTIFY;
                }
                else if (strcmp(argv[a], "-log") == 0)
                {
                    option = LOG;
                }
                else if (strcmp(argv[a], "-trace") == 0)
                {
                    option = TRACE;
                }
                else if (strcmp(argv[a], "-alltrace") == 0)
                {
                    option = ALLTRACE;
                }
                else if (strcmp(argv[a], "-demand") == 0)
                {
                    option = NONE;
                    opts = gr_face_default;
                }
                else
                {
                    argError = true;
                    fprintf(stderr,"Unknown option %s\n",argv[a]);
                }
            }
            else if (mainArgOffset == 0)
            {
                fileName = argv[a];
                mainArgOffset++;
            }
            else if (useCodes)
            {
                pIntEnd = NULL;
                mainArgOffset++;
                unsigned int code = strtol(argv[a],&pIntEnd, 16);
                if (code > 0)
                {
// convert text to utfOut using iconv because its easier to debug string placements
                    assert(pText32);
                    pText32[charLength++] = code;
                    if (charLength % 10 == 0)
                        fprintf(log, "%4x\n",code);
                    else
                        fprintf(log, "%4x\t",code);
                }
                else
                {
                    fprintf(stderr,"Invalid dpi %s\n", argv[a]);
                }
            }
            else if (mainArgOffset == 1)
            {
                mainArgOffset++;
                pText = argv[a];
                textArgIndex = a;
            }
            else
            {
                argError = true;
                fprintf(stderr,"too many arguments %s\n",argv[a]);
            }
            break;
        }
    }
    if (mainArgOffset < 1) argError = true;
    else if (mainArgOffset > 1)
    {
        if (!autoCodes && !useCodes && pText != NULL)
        {
            charLength = convertUtf<gr2::utf8>(pText, pText32);
            if (!pText32)
            {
            	if (charLength == ~0)
            		perror("decoding utf-8 data failed");
            	else
            		perror("insufficent memory for text buffer");
            }
            fprintf(log, "String has %d characters\n", (int)charLength);
            size_t ci;
            for (ci = 0; ci < 10 && ci < charLength; ci++)
            {
                    fprintf(log, "%d\t", (int)ci);
            }
            fprintf(log, "\n");
            for (ci = 0; ci < charLength; ci++)
            {
                    fprintf(log, "%04x\t", (int)ci);
                    if (((ci + 1) % 10) == 0)
                        fprintf(log, "\n");
            }
            fprintf(log, "\n");
        }
        else
        {
            assert(pText32);
            pText32[charLength] = 0;
            fprintf(log, "\n");
        }
    }
    return (argError) ? false : true;
}

typedef gr_uint32 tag_t;

void Parameters::printFeatures(const gr_face * face) const
{
    gr_uint16 numFeatures = gr_face_n_fref(face);
    fprintf(log, "%d features\n", numFeatures);
    gr_uint16 langId = 0x0409;
    for (gr_uint16 i = 0; i < numFeatures; i++)
    {
        const gr_feature_ref * f = gr_face_fref(face, i);
        gr_uint32 length = 0;
        char * label = reinterpret_cast<char *>(gr_fref_label(f, &langId, gr_utf8, &length));
        const tag_t featId = gr_fref_id(f);
        if (label)
            if ((char(featId >> 24) >= 0x20 && char(featId >> 24) < 0x7F) &&
                (char(featId >> 16) >= 0x20 && char(featId >> 16) < 0x7F) &&
                (char(featId >> 8)  >= 0x20 && char(featId >> 8)  < 0x7F) &&
                (char(featId)       >= 0x20 && char(featId)       < 0x7F))
            {
                fprintf(log, "%d %c%c%c%c %s\n", featId, featId >> 24, featId >> 16, featId >> 8, featId, label);
            }
            else
            {
                fprintf(log, "%d %s\n", featId, label);
            }
        else
            fprintf(log, "%d\n", featId);
        gr_label_destroy(reinterpret_cast<void*>(label));
        gr_uint16 numSettings = gr_fref_n_values(f);
        for (gr_uint16 j = 0; j < numSettings; j++)
        {
            gr_int16 value = gr_fref_value(f, j);
            label = reinterpret_cast<char *>(gr_fref_value_label
                (f, j, &langId, gr_utf8, &length));
            fprintf(log, "\t%d\t%s\n", value, label);
            gr_label_destroy(reinterpret_cast<void*>(label));
        }
    }
    gr_uint16 numLangs = gr_face_n_languages(face);
    fprintf(log, "Feature Languages:");
    for (gr_uint16 i = 0; i < numLangs; i++)
    {
    	const tag_t lang_id = gr_face_lang_by_index(face, i);
        fprintf(log, "\t");
        for (size_t j = 4; j; --j)
        {
        	const unsigned char c = lang_id >> (j*8-8);
            if ((c >= 0x20) && (c < 0x80))
                fprintf(log, "%c", c);
        }
    }
    fprintf(log, "\n");
}

gr_feature_val * Parameters::parseFeatures(const gr_face * face) const
{
    gr_feature_val * featureList = NULL;
    const char * pLang = NULL;
    tag_t lang_id = 0;
    if (features && (pLang = strstr(features, "lang=")))
    {
        pLang += 5;
        for (int i = 4; i; --i)
        {
            lang_id <<= 8;
        	if (!*pLang || *pLang == '0' || *pLang == '&') continue;
        	lang_id |= *pLang++;
        }
    }
    featureList = gr_face_featureval_for_lang(face, lang_id);
    if (!features || strlen(features) == 0)
        return featureList;
    size_t featureLength = strlen(features);
    const char * name = features;
    const char * valueText = NULL;
    size_t nameLength = 0;
    gr_int32 value = 0;
    const gr_feature_ref* ref = NULL;
    tag_t feat_id = 0;
    for (size_t i = 0; i < featureLength; i++)
    {
        switch (features[i])
        {
            case ',':
            case '&':
                assert(valueText);
                value = atoi(valueText);
                if (ref)
                {
                    gr_fref_set_feature_value(ref, value, featureList);
                    ref = NULL;
                }
                valueText = NULL;
                name = features + i + 1;
                nameLength = 0;
                feat_id = 0;
                break;
            case '=':
                if (nameLength <= 4)
                {
                    ref = gr_face_find_fref(face, feat_id);
                }
                if (!ref)
                {
                    assert(name);
                    feat_id = atoi(name);
                    ref = gr_face_find_fref(face, feat_id);
                }
                valueText = features + i + 1;
                name = NULL;
                break;
            default:
                if (valueText == NULL)
                {
                    if (nameLength < 4)
                    	feat_id = feat_id << 8 | features[i];
                }
                break;
        }
        if (ref)
        {
            assert(valueText);
            value = atoi(valueText);
            gr_fref_set_feature_value(ref, value, featureList);
            if (feat_id > 0x20000000)
            {
                fprintf(log, "%c%c%c%c=%d\n", feat_id >> 24, feat_id >> 16, feat_id >> 8, feat_id, value);
            }
            else
                fprintf(log, "%u=%d\n", feat_id, value);
            ref = NULL;
        }
    }
    return featureList;
}

int Parameters::testFileFont() const
{
    int returnCode = 0;
//    try
    {
        gr_face *face = NULL;
        FILE * testfile = fopen(fileName, "rb");
        if (!testfile)
        {
            fprintf(stderr, "Unable to open font file\n");
            return 4;
        }
        else
            fclose(testfile);

        if (alltrace) gr_start_logging(NULL, alltrace);
        face = gr_make_file_face(fileName, opts);

        // use the -trace option to specify a file
    	if (trace)	gr_start_logging(face, trace);

        if (!face)
        {
            fprintf(stderr, "Invalid font, failed to read or parse tables\n");
            return 3;
        }
        if (charLength == 0)
        {
            printFeatures(face);
            gr_stop_logging(face);
            gr_face_destroy(face);
            return 0;
        }

        gr_font *sizedFont = gr_make_font(pointSize * dpi / 72, face);
        gr_feature_val * featureList = NULL;
        Gr2TextSrc textSrc(pText32, charLength);
        gr_segment* pSeg = NULL;
        if (features)
            featureList = parseFeatures(face);
        if (codesize == 2)
        {
            unsigned short *pText16 = (unsigned short *)malloc((textSrc.getLength() * 2 + 1) * sizeof(unsigned short));
            gr2::utf16::iterator ui = pText16;
            unsigned int *p = pText32;
            for (unsigned int i = 0; i < textSrc.getLength(); ++i)
            {
                *ui = *p++;
                ui++;
            }
            *ui = 0;
            pSeg = gr_make_seg(sizedFont, face, 0, features ? featureList : NULL, (gr_encform)codesize, pText16, textSrc.getLength(), rtl ? 1 : 0);
        }
        else if (codesize == 1)
        {
            unsigned char *pText8 = (unsigned char *)malloc((textSrc.getLength() + 1) * 4);
            gr2::utf8::iterator ui = pText8;
            unsigned int *p = pText32;
            for (unsigned int i = 0; i < textSrc.getLength(); ++i)
            {
                *ui = *p++;
                ui++;
            }
            *ui = 0;
            pSeg = gr_make_seg(sizedFont, face, 0, features ? featureList : NULL, (gr_encform)codesize, pText8, textSrc.getLength(), rtl ? 1 : 0);
            free(pText8);
        }
        else
            pSeg = gr_make_seg(sizedFont, face, 0, features ? featureList : NULL, textSrc.utfEncodingForm(),
                textSrc.get_utf_buffer_begin(), textSrc.getLength(), rtl ? 1 : 0);

        if (pSeg && !noprint)
        {
            int i = 0;
            float advanceWidth;
    #ifndef NDEBUG
            int numSlots = gr_seg_n_slots(pSeg);
    #endif
    //        size_t *map = new size_t [seg.length() + 1];
            if (justification > 0)
                advanceWidth = gr_seg_justify(pSeg, gr_seg_first_slot(pSeg), sizedFont, gr_seg_advance_X(pSeg) * justification / 100., gr_justCompleteLine, NULL, NULL);
            else
                advanceWidth = gr_seg_advance_X(pSeg);
            size_t *map = (size_t*)malloc((gr_seg_n_slots(pSeg) + 1) * sizeof(size_t));
            for (const gr_slot* slot = gr_seg_first_slot(pSeg); slot; slot = gr_slot_next_in_segment(slot), ++i)
            { map[i] = (size_t)slot; }
            map[i] = 0;
            fprintf(log, "Segment length: %d\n", gr_seg_n_slots(pSeg));
            fprintf(log, "pos  gid   attach\t     x\t     y\tins bw\t  chars\t\tUnicode\t");
            fprintf(log, "\n");
            i = 0;
            for (const gr_slot* slot = gr_seg_first_slot(pSeg); slot; slot = gr_slot_next_in_segment(slot), ++i)
            {
                // consistency check for last slot
                assert((i + 1 < numSlots) || (slot == gr_seg_last_slot(pSeg)));
                float orgX = gr_slot_origin_X(slot);
                float orgY = gr_slot_origin_Y(slot);
                const gr_char_info *cinfo = gr_seg_cinfo(pSeg, gr_slot_original(slot));
                fprintf(log, "%02d  %4d %3d@%d,%d\t%6.1f\t%6.1f\t%2d%4d\t%3d %3d\t",
                        i, gr_slot_gid(slot), lookup(map, (size_t)gr_slot_attached_to(slot)),
                        gr_slot_attr(slot, pSeg, gr_slatAttX, 0),
                        gr_slot_attr(slot, pSeg, gr_slatAttY, 0), orgX, orgY, gr_slot_can_insert_before(slot) ? 1 : 0,
                        cinfo ? gr_cinfo_break_weight(cinfo) : 0, gr_slot_before(slot), gr_slot_after(slot));

                if (pText32 != NULL && gr_slot_before(slot) + offset < charLength
                                    && gr_slot_after(slot) + offset < charLength)
                {
                    fprintf(log, "%7x\t%7x",
                        pText32[gr_slot_before(slot) + offset],
                        pText32[gr_slot_after(slot) + offset]);
                }
                fprintf(log, "\n");
            }
            assert(i == numSlots);
            // assign last point to specify advance of the whole array
            // position arrays must be one bigger than what countGlyphs() returned
            fprintf(log, "Advance width = %6.1f\n", advanceWidth);
            unsigned int numchar = gr_seg_n_cinfo(pSeg);
            fprintf(log, "\nChar\tUnicode\tBefore\tAfter\tBase\n");
            for (unsigned int j = 0; j < numchar; j++)
            {
                const gr_char_info *c = gr_seg_cinfo(pSeg, j);
                fprintf(log, "%d\t%04X\t%d\t%d\t%d\n", j, gr_cinfo_unicode_char(c), gr_cinfo_before(c), gr_cinfo_after(c), int(gr_cinfo_base(c)));
            }
            free(map);
        }
        if (pSeg)
            gr_seg_destroy(pSeg);
        if (featureList) gr_featureval_destroy(featureList);
        gr_font_destroy(sizedFont);
        if (trace) gr_stop_logging(face);
        gr_face_destroy(face);
        if (alltrace) gr_stop_logging(NULL);
    }
    return returnCode;
}

int lookup(size_t *map, size_t val)
{
    int i = 0;
    for ( ; map[i] != val && map[i]; i++) {}
    return map[i] ? i : -1;
}

int main(int argc, char *argv[])
{

    Parameters parameters;

    if (!parameters.loadFromArgs(argc, argv))
    {
        fprintf(stderr,"Usage: %s [options] fontfile utf8text \n",argv[0]);
        fprintf(stderr,"Options: (default in brackets)\n");
        fprintf(stderr,"-dpi d\tDots per Inch (72)\n");
        fprintf(stderr,"-pt d\tPoint size (12)\n");
        fprintf(stderr,"-codes\tEnter text as hex code points instead of utf8 (false)\n");
        fprintf(stderr,"\te.g. %s font.ttf -codes 1000 102f\n",argv[0]);
        fprintf(stderr,"-auto\tAutomatically generate a test string of all codes 1-0xFFF\n");
        fprintf(stderr,"-noprint\tDon't print results\n");
        //fprintf(stderr,"-ls\tStart of line = true (false)\n");
        //fprintf(stderr,"-le\tEnd of line = true (false)\n");
        fprintf(stderr,"-rtl\tRight to left = true (false)\n");
        fprintf(stderr,"-j percentage\tJustify to percentage of string width\n");
        //fprintf(stderr,"-ws\tAllow trailing whitespace = true (false)\n");
        //fprintf(stderr,"-linefill w\tuse a LineFillSegment of width w (RangeSegment)\n");
        fprintf(stderr,"\nIf a font, but no text is specified, then a list of features will be shown.\n");
        fprintf(stderr,"-feat f=g\tSet feature f to value g. Separate multiple features with ,\n");
        fprintf(stderr,"-log out.log\tSet log file to use rather than stdout\n");
        fprintf(stderr,"-trace trace.json\tDefine a file for the JSON trace log\n");
        fprintf(stderr,"-demand\tDemand load glyphs and cmap cache\n");
        fprintf(stderr,"-bytes\tword size for character transfer [1,2,4] defaults to 4\n");
        return 1;
    }
    return parameters.testFileFont();
}
