/*____________________________________________________________________________

    Copyright 2000-2016 Adobe Systems Incorporated. All Rights Reserved.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use these files except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
____________________________________________________________________________*/

#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "hb.h"
#include "hb-ot.h"

static const bool verbose = true;

struct TestData
{
    TestData(hb_buffer_t  *buffer_,
             hb_face_t    *face_,
             hb_font_t    *font_,
             hb_feature_t *features_,
             int           num_features_)
        : buffer(buffer_), face(face_), font(font_),
          features(features_), num_features(num_features_)
    { }
    ~TestData()
    {
        free (features);
        hb_face_destroy (face);
        hb_font_destroy (font);
        hb_buffer_destroy (buffer);
    }

    hb_buffer_t  *buffer;
    hb_face_t    *face;
    hb_font_t    *font;
    hb_feature_t *features;
    int           num_features;
};

TestData
runTest(const char *testName,
        const char *fontfileName,
        unsigned int *in, int nbIn,
        unsigned int *select, int nbSelect)
{
    FILE *f = fopen (fontfileName, "rb");
    fseek(f, 0, SEEK_END);
    long fontsize = ftell(f);
    fseek(f, 0, SEEK_SET);
    char *fontdata = (char *)malloc (fontsize);
    fread(fontdata, fontsize, 1, f);
    fclose(f);

    if (verbose) {
        printf ("------------------------------- %s\n", testName);
    }

    // setup font
    hb_blob_t *blob = hb_blob_create(fontdata, fontsize,
                                     HB_MEMORY_MODE_WRITABLE,
                                     0, 0);
    hb_face_t *face = hb_face_create(blob, 0);
    hb_font_t *font = hb_font_create(face);
    unsigned int upem = hb_face_get_upem (face);

    hb_font_set_scale(font, upem, upem);
    hb_ot_font_set_funcs (font);

    // setup buffer
    hb_buffer_t *buffer = hb_buffer_create();
    hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
    hb_buffer_set_script(buffer, HB_SCRIPT_LATIN);
    hb_buffer_set_language(buffer, hb_language_from_string("en", 2));

    hb_buffer_add_utf32(buffer, in, nbIn, 0, nbIn);

    // setup features
    hb_feature_t *features;
    int nbFeatures;

    if (nbSelect == 0)
    {
        nbFeatures = 1;

        features = (hb_feature_t *) malloc (sizeof (*features));
        features[0].tag = HB_TAG('t', 'e', 's', 't');
        features[0].value = 1;
        features[0].start = HB_FEATURE_GLOBAL_START;
        features[0].end = HB_FEATURE_GLOBAL_END;
    }
    else
    {
        nbFeatures = 0;

        features = (hb_feature_t *) malloc (sizeof (*features) * nbSelect);
        for (int i = 0; i < nbSelect; i++) {
            if (select[i] != -1) {
                features[nbFeatures].tag = HB_TAG('t', 'e', 's', 't');
                features[nbFeatures].value = select[i];
                features[nbFeatures].start = i;
                features[nbFeatures].end = i + 1;
                nbFeatures++;
            }
        }
    }

    // shape
    hb_shape(font, buffer, features, nbFeatures);

    hb_blob_destroy(blob);

    return TestData(buffer, face, font, features, nbFeatures);
}


void printArray (const char* s, int *a, int n)
{
    printf ("%s  %d : ", s, n);
    for (int i = 0; i < n; i++) {
        printf (" %d", a[i]);
    }
    printf ("\n");
}

void printUArray (const char* s, unsigned int *a, int n)
{
    printArray (s, (int *) a, n);
}

bool gsub_test(const char *testName,
               const char *fontfileName,
               int nbIn, unsigned int *in,
               int nbSelect, unsigned int *select,
               int nbExpected, unsigned int *expected)
{
    TestData data = runTest(testName,
                            fontfileName,
                            in, nbIn,
                            select, nbSelect);

    // verify
    hb_glyph_info_t *actual = hb_buffer_get_glyph_infos(data.buffer, 0);
    unsigned int nbActual = hb_buffer_get_length(data.buffer);

    bool ok = true;

    if (nbActual != nbExpected)
        ok = false;
    else {
        for (int i = 0; i < nbActual; i++) {
            if (actual[i].codepoint != expected [i]) {
                ok = false;
                break;
            }
        }
    }


    char test_name[255];
    sprintf (test_name, "../../tests/%.*s.tests", (int) (strrchr (testName, '_') - testName), testName);
    FILE *tests_file = fopen (test_name, "a+");
    if (!ok) fprintf (tests_file, "#");
    fprintf (tests_file, "../fonts/%s;--features=\"", fontfileName + 9);
    for (unsigned int i = 0; i < data.num_features; i++)
    {
        if (i != 0) fprintf (tests_file, ",");
        char buf[255];
        hb_feature_to_string (&data.features[i], buf, sizeof (buf));
        fprintf (tests_file, "%s", buf);
    }
    fprintf (tests_file, "\" --single-par --no-clusters --no-glyph-names --no-positions;");

    for (unsigned int i = 0; i < nbIn; i++)
    {
        if (i != 0) fprintf (tests_file, ",");
        fprintf (tests_file, "U+%04X", in[i]);
    }

    fprintf (tests_file, ";[");
    for (unsigned int i = 0; i < nbActual; i++)
    {
        if (i != 0) fprintf (tests_file, "|");
        fprintf (tests_file, "%d", expected[i]);
    }
    fprintf (tests_file, "]");

    fprintf (tests_file, "\n");
    fclose (tests_file);


    if (! ok) {
        printf ("******* GSUB %s\n", testName);

        printf ("expected %d:", nbExpected);
        for (int i = 0; i < nbExpected; i++) {
            printf (" %d", expected[i]); }
        printf ("\n");

        printf ("  actual %d:", nbActual);
        for (int i = 0; i < nbActual; i++) {
            printf (" %d", actual[i].codepoint); }
        printf ("\n");

    }

    return ok;
}

bool cmap_test(const char *testName,
               const char *fontfileName,
               int nbIn, unsigned int *in,
               int nbSelect, unsigned int *select,
               int nbExpected, unsigned int *expected)
{
    TestData data = runTest(testName,
                            fontfileName,
                            in, nbIn,
                            select, nbSelect);

    // verify
    hb_glyph_info_t *actual = hb_buffer_get_glyph_infos(data.buffer, 0);
    unsigned int nbActual = hb_buffer_get_length(data.buffer);

    bool ok = true;

    if (nbActual != nbExpected)
        ok = false;
    else {
        for (int i = 0; i < nbActual; i++) {
            if (actual[i].codepoint != expected [i]) {
                ok = false;
                break;
            }
        }
    }


    char test_name[255];
    sprintf (test_name, "../../tests/%.*s.tests", (int) (strrchr (testName, '_') - testName), testName);
    FILE *tests_file = fopen (test_name, "a+");
    if (!ok) fprintf (tests_file, "#");
    fprintf (tests_file, "../fonts/%s;--features=\"", fontfileName + 9);
    for (unsigned int i = 0; i < data.num_features; i++)
    {
        if (i != 0) fprintf (tests_file, ",");
        char buf[255];
        hb_feature_to_string (&data.features[i], buf, sizeof (buf));
        fprintf (tests_file, "%s", buf);
    }
    fprintf (tests_file, "\" --single-par --no-clusters --no-glyph-names --no-positions --font-funcs=ot;");

    for (unsigned int i = 0; i < nbIn; i++)
    {
        if (i != 0) fprintf (tests_file, ",");
        fprintf (tests_file, "U+%04X", in[i]);
    }

    fprintf (tests_file, ";[");
    for (unsigned int i = 0; i < nbActual; i++)
    {
        if (i != 0) fprintf (tests_file, "|");
        fprintf (tests_file, "%d", expected[i]);
    }
    fprintf (tests_file, "]");

    fprintf (tests_file, "\n");
    fclose (tests_file);


    if (! ok) {
        printf ("******* cmap %s\n", testName);

        printf ("expected %d:", nbExpected);
        for (int i = 0; i < nbExpected; i++) {
            printf (" %d", expected[i]); }
        printf ("\n");

        printf ("  actual %d:", nbActual);
        for (int i = 0; i < nbActual; i++) {
            printf (" %d", actual[i].codepoint); }
        printf ("\n");

    }

    return ok;
}

bool gpos_test(const char *testName,
               const char *fontfileName,
               int nbIn,
               unsigned int *in,
               int nbOut,
               unsigned int *out,
               int *x,
               int *y)
{
    TestData data = runTest(testName,
                            fontfileName,
                            in, nbIn,
                            0, 0);

    // verify
    unsigned int nbActual;
    hb_glyph_info_t *actual = hb_buffer_get_glyph_infos(data.buffer, &nbActual);
    hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (data.buffer, NULL);

    unsigned int *actualG = (unsigned int *) malloc(sizeof(*actualG) * nbActual);
    int *actualX = (int *) malloc(sizeof(*actualX) * nbActual);
    int *actualY = (int *) malloc(sizeof(*actualY) * nbActual);
    int curX = 0;
    int curY = 0;
    for (int i = 0; i < nbActual; i++) {
        actualG[i] = actual[i].codepoint;
        actualX[i] = curX + pos[i].x_offset;
        actualY[i] = curY + pos[i].y_offset;

        curX += pos[i].x_advance;
        if (hb_ot_layout_get_glyph_class (data.face, actualG[i]) != HB_OT_LAYOUT_GLYPH_CLASS_MARK)
            curX -= 1500;
        curY += pos[i].y_advance;
    }

    bool nbOk = true;
    bool xOk = true;
    bool yOk = true;

    if (nbActual != nbOut)
        nbOk = false;
    else {
        for (int i = 0; i < nbActual; i++) {
            if (actualX[i] != x[i]) {
                xOk = false;
            }
            if (actualY[i] != y[i]) {
                yOk = false;
            }
        }
    }

    bool ok = (nbOk && xOk && yOk);
    if (! ok) {
        printf ("******* GPOS %s\n", testName);

        if (! (nbOk && xOk)) {
            printArray ("expectedX", x, nbOut);
            printArray ("actualX  ", actualX, nbActual);

            printf ("xadv/pos:");
            for (int i = 0; i < nbOut; i++) {
                printf (" %d/%d", pos[i].x_advance, pos[i].x_offset);
            }
            printf ("\n");
        }

        if (! (nbOk && yOk)) {
            printArray ("expectedY", y, nbOut);
            printArray ("actualY  ", actualY, nbActual);

            printf ("yadv/pos:");
            for (int i = 0; i < nbOut; i++) {
                printf (" %d/%d", pos[i].y_advance, pos[i].y_offset);
            }
            printf ("\n");
        }
    }


    char test_name[255];
    sprintf (test_name, "../../tests/%.*s.tests", (int) (strrchr (testName, '_') - testName), testName);
    FILE *tests_file = fopen (test_name, "a+");
    if (!ok) fprintf (tests_file, "#");
    fprintf (tests_file, "../fonts/%s;--features=\"", fontfileName + 9);
    for (unsigned int i = 0; i < data.num_features; i++)
    {
        if (i != 0) fprintf (tests_file, ",");
        char buf[255];
        hb_feature_to_string (&data.features[i], buf, sizeof (buf));
        fprintf (tests_file, "%s", buf);
    }
    fprintf (tests_file, "\" --single-par --no-clusters --no-glyph-names --ned;");

    for (unsigned int i = 0; i < nbIn; i++)
    {
        if (i != 0) fprintf (tests_file, ",");
        fprintf (tests_file, "U+%04X", in[i]);
    }

    fprintf (tests_file, ";[");
    int accumlatedAdvance = 0;
    for (unsigned int i = 0; i < nbActual; i++)
    {
        if (i != 0) fprintf (tests_file, "|");
        fprintf (tests_file, "%d", /*it should be "out[i]"*/ actualG[i]);

        int expected_x = x[i] + accumlatedAdvance;
        int expected_y = y[i];
        if (expected_x || expected_y) fprintf (tests_file, "@%d,%d", expected_x, expected_y);
        if (hb_ot_layout_get_glyph_class (data.face, actualG[i]) != HB_OT_LAYOUT_GLYPH_CLASS_MARK)
            accumlatedAdvance += 1500;
    }
    fprintf (tests_file, "]");

    fprintf (tests_file, "\n");
    fclose (tests_file);


    free(actualG);
    free(actualX);
    free(actualY);

    return ok;
}


int main(int argc, char **argv)
{
    int failures = 0;
    int pass = 0;

#include "hb-aots-tester.h"

    printf ("%d failures, %d pass\n", failures, pass);
}