/**
 * @private
 * Adds hit testing and path intersection points methods to the Ext.draw.Path.
 * Included by the Ext.draw.PathUtil.
 */
Ext.define('Ext.draw.overrides.hittest.Path', {
    override: 'Ext.draw.Path',
 
    // An arbitrary point outside the path used for hit testing with ray casting method.
    rayOrigin: {
        x: -10000,
        y: -10000
    },
 
    /**
     * Tests whether the given point is inside the path.
     * @param {Number} x 
     * @param {Number} y 
     * @return {Boolean} 
     * @member Ext.draw.Path
     */
    isPointInPath: function(x, y) {
        var me = this,
            commands = me.commands,
            solver = Ext.draw.PathUtil,
            origin = me.rayOrigin,
            params = me.params,
            ln = commands.length,
            firstX = null,
            firstY = null,
            lastX = 0,
            lastY = 0,
            count = 0,
            i, j;
 
        for (= 0, j = 0; i < ln; i++) {
            switch (commands[i]) {
                case 'M':
                    if (firstX !== null) {
                        // eslint-disable-next-line max-len
                        if (solver.linesIntersection(firstX, firstY, lastX, lastY, origin.x, origin.y, x, y)) {
                            count += 1;
                        }
                    }
 
                    firstX = lastX = params[j];
                    firstY = lastY = params[+ 1];
                    j += 2;
                    
                    break;
                
                case 'L':
                    // eslint-disable-next-line max-len
                    if (solver.linesIntersection(lastX, lastY, params[j], params[+ 1], origin.x, origin.y, x, y)) {
                        count += 1;
                    }
 
                    lastX = params[j];
                    lastY = params[+ 1];
                    j += 2;
                    
                    break;
                
                case 'C':
                    count += solver.cubicLineIntersections(
                        lastX, params[j], params[+ 2], params[+ 4],
                        lastY, params[+ 1], params[+ 3], params[+ 5],
                        origin.x, origin.y, x, y
                    ).length;
                    lastX = params[+ 4];
                    lastY = params[+ 5];
                    j += 6;
                    
                    break;
                
                case 'Z':
                    if (firstX !== null) {
                        // eslint-disable-next-line max-len
                        if (solver.linesIntersection(firstX, firstY, lastX, lastY, origin.x, origin.y, x, y)) {
                            count += 1;
                        }
                    }
                    
                    break;
            }
        }
 
        return count % 2 === 1;
    },
 
    /**
     * Tests whether the given point is on the path.
     * @param {Number} x 
     * @param {Number} y 
     * @return {Boolean} 
     * @member Ext.draw.Path
     */
    isPointOnPath: function(x, y) {
        var me = this,
            commands = me.commands,
            solver = Ext.draw.PathUtil,
            params = me.params,
            ln = commands.length,
            firstX = null,
            firstY = null,
            lastX = 0,
            lastY = 0,
            i, j;
 
        for (= 0, j = 0; i < ln; i++) {
            switch (commands[i]) {
                case 'M':
                    if (firstX !== null) {
                        if (solver.pointOnLine(firstX, firstY, lastX, lastY, x, y)) {
                            return true;
                        }
                    }
 
                    firstX = lastX = params[j];
                    firstY = lastY = params[+ 1];
                    j += 2;
                    
                    break;
                    
                case 'L':
                    if (solver.pointOnLine(lastX, lastY, params[j], params[+ 1], x, y)) {
                        return true;
                    }
 
                    lastX = params[j];
                    lastY = params[+ 1];
                    j += 2;
                    
                    break;
                    
                case 'C':
                    if (solver.pointOnCubic(
                        lastX, params[j], params[+ 2], params[+ 4],
                        lastY, params[+ 1], params[+ 3], params[+ 5], x, y)) {
                        return true;
                    }
 
                    lastX = params[+ 4];
                    lastY = params[+ 5];
                    j += 6;
                    
                    break;
                    
                case 'Z':
                    if (firstX !== null) {
                        if (solver.pointOnLine(firstX, firstY, lastX, lastY, x, y)) {
                            return true;
                        }
                    }
                    
                    break;
            }
        }
 
        return false;
    },
 
    /**
     * Calculates the points where the given segment intersects the path.
     * If four parameters are given then the segment is considered to be a line segment,
     * where given parameters are the coordinates of the start and end points.
     * If eight parameters are given then the segment is considered to be
     * a cubic Bezier curve segment, where given parameters are the
     * coordinates of its edge points and control points.
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
     * @param x4
     * @param y4
     * @return {Array} 
     * @member Ext.draw.Path
     */
    getSegmentIntersections: function(x1, y1, x2, y2, x3, y3, x4, y4) {
        var me = this,
            count = arguments.length,
            solver = Ext.draw.PathUtil,
            commands = me.commands,
            params = me.params,
            ln = commands.length,
            firstX = null,
            firstY = null,
            lastX = 0,
            lastY = 0,
            intersections = [],
            i, j, points;
 
        for (= 0, j = 0; i < ln; i++) {
            switch (commands[i]) {
                case 'M':
                    if (firstX !== null) {
                        switch (count) {
                            case 4:
                                points = solver.linesIntersection(firstX, firstY, lastX, lastY,
                                                                  x1, y1, x2, y2);
 
                                if (points) {
                                    intersections.push(points);
                                }
 
                                break;
 
                            case 8:
                                points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3,
                                                                       y4, firstX, firstY, lastX,
                                                                       lastY);
                                intersections.push.apply(intersections, points);
                                break;
                        }
                    }
 
                    firstX = lastX = params[j];
                    firstY = lastY = params[+ 1];
                    j += 2;
                    break;
 
                case 'L':
                    switch (count) {
                        case 4:
                            points = solver.linesIntersection(lastX, lastY, params[j],
                                                              params[+ 1], x1, y1, x2, y2);
 
                            if (points) {
                                intersections.push(points);
                            }
 
                            break;
 
                        case 8:
                            points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4,
                                                                   lastX, lastY, params[j],
                                                                   params[+ 1]);
                            intersections.push.apply(intersections, points);
                            break;
                    }
 
                    lastX = params[j];
                    lastY = params[+ 1];
                    j += 2;
                    break;
 
                case 'C':
                    switch (count) {
                        case 4:
                            points = solver.cubicLineIntersections(
                                lastX, params[j], params[+ 2], params[+ 4],
                                lastY, params[+ 1], params[+ 3], params[+ 5],
                                x1, y1, x2, y2);
                            intersections.push.apply(intersections, points);
                            break;
 
                        case 8:
                            points = solver.cubicsIntersections(
                                lastX, params[j], params[+ 2], params[+ 4],
                                lastY, params[+ 1], params[+ 3], params[+ 5],
                                x1, x2, x3, x4, y1, y2, y3, y4);
                            intersections.push.apply(intersections, points);
                            break;
                    }
 
                    lastX = params[+ 4];
                    lastY = params[+ 5];
                    j += 6;
                    break;
 
                case 'Z':
                    if (firstX !== null) {
                        switch (count) {
                            case 4:
                                points = solver.linesIntersection(firstX, firstY, lastX, lastY, x1,
                                                                  y1, x2, y2);
 
                                if (points) {
                                    intersections.push(points);
                                }
 
                                break;
 
                            case 8:
                                points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3,
                                                                       y4, firstX, firstY, lastX,
                                                                       lastY);
                                intersections.push.apply(intersections, points);
                                break;
                        }
                    }
 
                    break;
            }
        }
 
        return intersections;
    },
 
    getIntersections: function(path) {
        var me = this,
            commands = me.commands,
            params = me.params,
            ln = commands.length,
            firstX = null,
            firstY = null,
            lastX = 0,
            lastY = 0,
            intersections = [],
            i, j, points;
 
        for (= 0, j = 0; i < ln; i++) {
            switch (commands[i]) {
                case 'M':
                    if (firstX !== null) {
                        points = path.getSegmentIntersections.call(path, firstX, firstY, lastX,
                                                                   lastY);
                        intersections.push.apply(intersections, points);
                    }
 
                    firstX = lastX = params[j];
                    firstY = lastY = params[+ 1];
                    j += 2;
                    break;
 
                case 'L':
                    points = path.getSegmentIntersections.call(path, lastX, lastY, params[j],
                                                               params[+ 1]);
                    intersections.push.apply(intersections, points);
                    lastX = params[j];
                    lastY = params[+ 1];
                    j += 2;
                    break;
 
                case 'C':
                    points = path.getSegmentIntersections.call(path, lastX, lastY, params[j],
                                                               params[+ 1], params[+ 2],
                                                               params[+ 3], params[+ 4],
                                                               params[+ 5]);
                    
                    intersections.push.apply(intersections, points);
                    lastX = params[+ 4];
                    lastY = params[+ 5];
                    j += 6;
                    break;
 
                case 'Z':
                    if (firstX !== null) {
                        points = path.getSegmentIntersections.call(path, firstX, firstY, lastX,
                                                                   lastY);
                        intersections.push.apply(intersections, points);
                    }
 
                    break;
            }
        }
 
        return intersections;
    }
});