jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;

var dumpErrors = false;
var container;

function getViewBox(path) {
    let bounds = path.getBounds();
    return `${(bounds.fLeft-2)*.95} ${(bounds.fTop-2)*.95} ${(bounds.fRight+2)*1.05} ${(bounds.fBottom+2)*1.05}`;
}

function addSVG(testName, expectedPath, actualPath, message) {
    if (!dumpErrors) {
        return;
    }
    if (!container) {
        let styleEl = document.createElement('style');
        document.head.appendChild(styleEl);
        let sheet = styleEl.sheet;
        sheet.insertRule(`svg {
            border: 1px solid #DDD;
            max-width: 45%;
            vertical-align: top;
        }`, 0);

        container = document.createElement('div');
        document.body.appendChild(container);

    }

    let thisTest = document.createElement('div');
    thisTest.innerHTML = `
    <h2>Failed test ${testName}</h2>

    <div>${message}</div>

    <svg class='expected' viewBox='${getViewBox(expectedPath)}'>
        <path stroke=black fill=white stroke-width=0.01 d="${expectedPath.toSVGString()}"></path>
    </svg>

    <svg class='actual' viewBox='${getViewBox(actualPath)}'>
        <path stroke=black fill=white stroke-width=0.01 d="${actualPath.toSVGString()}"></path>
    </svg>
`;
    container.appendChild(thisTest);

}

const TOLERANCE = 0.0001;

function diffPaths(expected, actual) {
    // Look through commands and see if they are within tolerance.
    let eCmds = expected.toCmds(), aCmds = actual.toCmds();
    if (eCmds.length !== aCmds.length) {
        //console.log(`Expected: ${JSON.stringify(eCmds)} and Actual: ${JSON.stringify(aCmds)}`);
        return `Different amount of verbs.  Expected had ${eCmds.length}, Actual had ${aCmds.length}`;
    }
    for(let idx = 0; idx < eCmds.length; idx++){
        let eCmd = eCmds[idx], aCmd = aCmds[idx];
        if (eCmd.length !== aCmd.length) {
            // Should never happen, means WASM code is returning bad ops.
            return `Command index ${idx} differs in num arguments. Expected had ${eCmd.length}, Actual had ${aCmd.length}`;
        }
        let eVerb = eCmd[0], aVerb = aCmd[0];
        if (eVerb !== aVerb) {
            return `Command index ${idx} differs. Expected had ${eVerb}, Actual had ${aVerb}`;
        }
        for (let arg = 1; arg < eCmd.length; arg++) {
            if (Math.abs(eCmd[arg] - aCmd[arg]) > TOLERANCE) {
                return `Command index ${idx} has different argument for verb ${eVerb} at position ${arg}. Expected had ${eCmd[arg]}, Actual had ${aCmd[arg]}`
            }
        }
    }
    return null;
}

describe('PathKit\'s PathOps Behavior', function() {
    // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
    var PathKit = null;
    var PATHOP_MAP = {};
    var FILLTYPE_MAP = {};
    const LoadPathKit = new Promise(function(resolve, reject) {
        if (PathKit) {
            resolve();
        } else {
            PathKitInit({
                locateFile: (file) => '/pathkit/'+file,
            }).ready().then((_PathKit) => {
                PathKit = _PathKit;
                PATHOP_MAP = {
                    'kIntersect_SkPathOp':         PathKit.PathOp.INTERSECT,
                    'kDifference_SkPathOp':        PathKit.PathOp.DIFFERENCE,
                    'kUnion_SkPathOp':             PathKit.PathOp.UNION,
                    'kXOR_SkPathOp':               PathKit.PathOp.XOR,
                    'kXOR_PathOp':                 PathKit.PathOp.XOR,
                    'kReverseDifference_SkPathOp': PathKit.PathOp.REVERSE_DIFFERENCE,
                };
                FILLTYPE_MAP = {
                    'kWinding_FillType':        PathKit.FillType.WINDING,
                    'kEvenOdd_FillType':        PathKit.FillType.EVENODD,
                    'kInverseWinding_FillType': PathKit.FillType.INVERSE_WINDING,
                    'kInverseEvenOdd_FillType': PathKit.FillType.INVERSE_EVENODD,
                };
                resolve();
            });
        }
    });

    function getFillType(str) {
        let e = FILLTYPE_MAP[str];
        expect(e).toBeTruthy(`Could not find FillType Enum for ${str}`);
        return e;
    }

    function getPathOp(str) {
        let e = PATHOP_MAP[str];
        expect(e).toBeTruthy(`Could not find PathOp Enum for ${str}`);
        return e;
    }

    it('combines two paths with .op() and matches what we see from C++', function(done) {
        LoadPathKit.then(catchException(done, () => {
            // Test JSON created with:
            // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsOp.json -m PathOpsOp$
            fetch('/base/tests/PathOpsOp.json').then((r) => {
                r.json().then((json) => {
                    expect(json).toBeTruthy();
                    let testNames = Object.keys(json);
                    // Assert we loaded a non-zero amount of tests, i.e. the JSON is valid.
                    expect(testNames.length > 0).toBeTruthy();
                    testNames.sort();
                    for (testName of testNames) {
                        let test = json[testName];

                        let path1 = PathKit.FromCmds(test.p1);
                        expect(path1).not.toBeNull(`path1 error when loading cmds '${test.p1}'`);
                        path1.setFillType(getFillType(test.fillType1));

                        let path2 = PathKit.FromCmds(test.p2);
                        expect(path2).not.toBeNull(`path2 error when loading cmds '${test.p2}'`);
                        path2.setFillType(getFillType(test.fillType2));

                        let combined = path1.op(path2, getPathOp(test.op));

                        if (test.expectSuccess === 'no') {
                            expect(combined).toBeNull(`Test ${testName} should have not created output, but did`);
                        } else {
                            expect(combined).not.toBeNull();
                            let expected = PathKit.FromCmds(test.out);
                            // Do a tolerant match.
                            let diff = diffPaths(expected, combined);
                            if (test.expectMatch === 'yes'){
                                // Check fill type
                                expect(combined.getFillType().value).toEqual(getFillType(test.fillTypeOut).value);
                                // diff should be null if the paths are identical (modulo rounding)
                                if (diff) {
                                    expect(`[${testName}] ${diff}`).toBe('');
                                    addSVG('[PathOps] ' + testName, expected, combined, diff);
                                }
                            } else if (test.expectMatch === 'flaky') {
                                // Don't worry about it, at least it didn't crash.
                            } else {
                                if (!diff) {
                                    expect(`[${testName}] was expected to have paths that differed`).not.toBe('');
                                }
                            }
                            expected.delete();
                        }
                        // combined === path1, so we only have to delete one.
                        path1.delete();
                        path2.delete();
                    }
                    done();
                });
            });
        }));
    });

    it('simplifies a path with .simplify() and matches what we see from C++', function(done) {
        LoadPathKit.then(catchException(done, () => {
            // Test JSON created with:
            // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsSimplify.json -m PathOpsSimplify$
            fetch('/base/tests/PathOpsSimplify.json').then((r) => {
                r.json().then((json) => {
                    expect(json).toBeTruthy();
                    let testNames = Object.keys(json);
                    // Assert we loaded a non-zero amount of tests, i.e. the JSON is valid.
                    expect(testNames.length > 0).toBeTruthy();
                    testNames.sort();
                    for (testName of testNames) {
                        let test = json[testName];

                        let path = PathKit.FromCmds(test.path);
                        expect(path).not.toBeNull(`path1 error when loading cmds '${test.path}'`);
                        path.setFillType(getFillType(test.fillType));

                        let simplified = path.simplify();

                        if (test.expectSuccess === 'no') {
                            expect(simplified).toBeNull(`Test ${testName} should have not created output, but did`);
                        } else {
                            expect(simplified).not.toBeNull();
                            let expected = PathKit.FromCmds(test.out);
                            // Do a tolerant match.
                            let diff = diffPaths(expected, simplified);
                            if (test.expectMatch === 'yes'){
                                // Check fill type
                                expect(simplified.getFillType().value).toEqual(getFillType(test.fillTypeOut).value);
                                // diff should be null if the paths are identical (modulo rounding)
                                if (diff) {
                                    expect(`[${testName}] ${diff}`).toBe('');
                                    addSVG('[Simplify] ' + testName, expected, simplified, diff);
                                }
                            } else if (test.expectMatch === 'flaky') {
                                // Don't worry about it, at least it didn't crash.
                            } else {
                                if (!diff) {
                                    expect(`[${testName}] was expected to not match output`).not.toBe('');
                                }
                            }
                            expected.delete();
                        }
                        // simplified === path, so we only have to delete one.
                        path.delete();
                    }
                    done();
                });
            });
        }));
    });
});