/*======================================================================
 *  Parsing
 *======================================================================
 */

MassEdit.Parsing = new Object();

MassEdit.Parsing.test = function(text1, text2) {
    try {
        console.log(
            MassEdit.Parsing._computeGenericTextEditingCost(
                MassEdit.Parsing._parse(text1),
                MassEdit.Parsing._parse(text2)));
    } catch(e) {
        console.log(e);
    }
};

MassEdit.Parsing.parse = function(text) {
    var result = MassEdit.Parsing.parseURL(text);
    if (result == null) {
        result = MassEdit.Parsing.parseEmailAddress(text);
    }
    if (result == null) {
        result = MassEdit.Parsing.parseGenericText(text);
    }
    return result;
};

MassEdit.Parsing.parseURL = function(text) {
    return null;
};

MassEdit.Parsing.parseEmailAddress = function(text) {
    if (/\s/.test(text) || text.indexOf("@") < 0 || text.indexOf(".") < 0) {
        return null;
    }
    
    var prefixes = [];
    var suffixes = [];
    
    var colon = text.indexOf(":");
    var prefixLength = 0;
    if (colon > 0) {
        if (text.substr(0, colon) != "mailto") {
            return null;
        }
    }
    
    var at = text.indexOf("@");
    var prefixString = text.substr(0, at);
    if (colon > 0) {
        prefixes.push({
            kind: "text",
            start: 0,
            text: "mailto"
        });
        prefixes.push({
            kind: "symbol",
            start: colon,
            text: ":"
        });
        prefixes.push({
            kind: "text",
            start: colon+1,
            text: prefixString.substr(colon+1)
        });
    } else {
        prefixes.push({
            kind: "text",
            start: 0,
            text: prefixString
        });
    }
    
    var suffixStrings = text.substr(at + 1).split(".");
    var lengthSoFar = prefixString.length;
    for (var i = 0; i < suffixStrings.length; i++) {
        if (i > 0) {
            suffixes.unshift({
                kind:  "symbol",
                start: lengthSoFar,
                text:  "."
            });
        }
        suffixes.unshift({
            kind: "text",
            start: lengthSoFar + 1,
            text:  suffixStrings[i]
        });
        lengthSoFar += suffixStrings[i].length + 1;
    }
    
    return {
        type:       "email",
        prefixes:   prefixes,
        suffixes:   suffixes
    };
};

MassEdit.Parsing.parseGenericText = function(text) {
    var segments = [];
    
    var scannerState = {
        current: 0
    };
    while (scannerState.current < text.length) {
        var c = scannerState.character = text.charAt(scannerState.current);
        
        if (MassEdit.Parsing._isDigit(c)) {
            segments.push(MassEdit.Parsing._parseDigitSequence(text, scannerState));
        } else if (MassEdit.Parsing._isWhitespace(c)) {
            segments.push(MassEdit.Parsing._parseWhitespaceSequence(text, scannerState));
        } else if (MassEdit.Parsing._isSymbol(c)) {
            segments.push({ 
                kind:   "symbol", 
                start:  scannerState.current,
                text:   c 
            });
            scannerState.current++;
        } else if (MassEdit.Parsing._isPunctuation(c)) {
            segments.push({ 
                kind: "punctuation", 
                start:  scannerState.current,
                text: c 
            });
            scannerState.current++;
        } else {
            segments.push(MassEdit.Parsing._parseAlphanumericSequence(text, scannerState));
        }
    };
    
    return {
        type:     "text",
        segments: segments
    };
};

MassEdit.Parsing._parseWhitespaceSequence = function(text, scannerState) {
    var start = scannerState.current;
    var end = text.substr(start).search(/\S/);
    if (end < 0) {
        end = text.length;
    } else {
        end += start;
    }
    
    scannerState.current = end;
    return {
        kind:   "whitespace",
        start:  start,
        text:   text.substring(start, end)
    };
};

MassEdit.Parsing._parseDigitSequence = function(text, scannerState) {
    var start = scannerState.current;
    
    while (scannerState.current < text.length && 
        MassEdit.Parsing._isDigit(text.charAt(scannerState.current))) {
        scannerState.current++;
    }
    
    return {
        kind:   "digits",
        start:  start,
        text:   text.substring(start, scannerState.current)
    };
};

MassEdit.Parsing._parseAlphanumericSequence = function(text, scannerState) {
    var start = scannerState.current;
    var end = text.substr(start + 1).search(/\W/);
    if (end < 0) {
        end = text.length;
    } else {
        end += start + 1;
    }
    
    scannerState.current = end;
    return {
        kind:   "alphanumeric",
        start:  start,
        text:   text.substring(start, end)
    };
};

MassEdit.Parsing._isDigit = function(c) {
    return /^\d/.test(c);
};

MassEdit.Parsing._isWhitespace = function(c) {
    return /\s/.test(c);
};

MassEdit.Parsing._isSymbol = function(c) {
    return "@#$%^&*-+=".indexOf(c) >= 0;
};

MassEdit.Parsing._isDelimiter = function(c) {
    return "()[]{}<>\"'`".indexOf(c) >= 0;
};

MassEdit.Parsing._isPunctuation = function(c) {
    return ".,!?:;".indexOf(c) >= 0;
};

MassEdit.Parsing.computeEditingCost = function(structureLeft, structureRight) {
    if (structureLeft.type != structureRight.type) {
        return Number.POSITIVE_INFINITY;
    }
    if (structureLeft.type == "email") {
        return MassEdit.Parsing.computeEmailEditingCost(structureLeft, structureRight);
    } else {
        return MassEdit.Parsing.computeGenericTextEditingCost(structureLeft, structureRight);
    }
};

MassEdit.Parsing.computeEmailEditingCost = function(structureLeft, structureRight) {
    return MassEdit.Parsing._computeSegmentsEditingCost(structureLeft.suffixes, structureRight.suffixes);
    //+ MassEdit.Parsing._computeSegmentsEditingCost(structureLeft.prefixes, structureRight.prefixes) / 10;
};

MassEdit.Parsing.computeGenericTextEditingCost = function(structureLeft, structureRight) {
    var leftSegments = structureLeft.segments;
    var rightSegments = structureRight.segments;
    return MassEdit.Parsing._computeSegmentsEditingCost(leftSegments, rightSegments);
};

MassEdit.Parsing._computeSegmentsEditingCost = function(leftSegments, rightSegments) {
    var positionalCostFactor = 0.7;
    var totalCost = 0;
    
    var leftIndex = 0;
    var rightIndex = 0;
    
    while (leftIndex < leftSegments.length && rightIndex < rightSegments.length) {
        var leftMinCost = Number.POSITIVE_INFINITY;
        var rightMinIndex = rightIndex;
        
        var right = rightIndex;
        var leftSegment = leftSegments[leftIndex];
        while (right < rightSegments.length) {
            var positionalCost = (right - rightIndex) * positionalCostFactor;
            if (positionalCost > leftMinCost) {
                break;
            }
            
            var leftMinCost2 = positionalCost + 
                MassEdit.Parsing._computeGenericTextSegmentEditingCost(leftSegment, rightSegments[right]);
                
            if (leftMinCost2 < leftMinCost) {
                leftMinCost = leftMinCost2;
                rightMinIndex = right;
            }
            
            right++;
        }
        
        var rightMinCost = Number.POSITIVE_INFINITY;
        var leftMinIndex = leftIndex;
        
        var left = leftIndex;
        var rightSegment = rightSegments[rightIndex];
        while (left < leftSegments.length) {
            var positionalCost = (left - leftIndex) * positionalCostFactor;
            if (positionalCost > rightMinCost) {
                break;
            }
            
            var rightMinCost2 = positionalCost + 
                MassEdit.Parsing._computeGenericTextSegmentEditingCost(rightSegment, leftSegments[left]);
                
            if (rightMinCost2 < rightMinCost) {
                rightMinCost = rightMinCost2;
                leftMinIndex = left;
            }
            
            left++;
        }
        
        if (leftMinCost < rightMinCost) {
            //console.log("Match " + leftIndex + " " + rightMinIndex + " at " + leftMinCost);
            
            totalCost += leftMinCost + rightMinIndex - rightIndex;
            
            leftIndex++;
            rightIndex = rightMinIndex + 1;
        } else {
            //console.log("Match " + leftMinIndex + " " + rightIndex + " at " + rightMinCost);
            
            totalCost += rightMinCost + leftMinIndex - leftIndex;
            
            rightIndex++;
            leftIndex = leftMinIndex + 1;
        }
    }
    
    totalCost += 
        (leftSegments.length - leftIndex) + 
        (rightSegments.length - rightIndex);
        
    return totalCost;
};

MassEdit.Parsing._computeGenericTextSegmentEditingCost = function(segmentLeft, segmentRight) {
    if (segmentLeft.type != segmentRight.type) {
        return 2;
    } else if (segmentLeft.text == segmentRight.text) {
        return 0;
    }
    
    var type = segmentLeft.type;
    if (type == "punctuation") {
        return 0.3;
    } else if (type == "symbol") {
        return 0.5;
    } else if (type == "delimiter") {
        return 0.5;
    } else if (type == "whitespace") {
        return 0;
    } else {
        var l1 = segmentLeft.text.length;
        var l2 = segmentRight.text.length;
        
        if (l1 == 1 && l2 > 1 || l2 == 1 && l1 > 1) {
            return 1;
        } else {
            //return 0.2 * (1 + Math.abs(l1 - l2)) / (1 + Math.max(l1, l2));
            return 0.1;
        }
    }
};

MassEdit.Parsing.createCharLocator = function(rawIndex, text, structure) {
    if (rawIndex == 0) {
        return {
            index: 0,
            segmentLocator: {
                index: 0
            }
        };
    } else if (rawIndex == text.length) {
        return {
            index: -1, // last
            segmentLocator: {
                index: -1 // last
            }
        };
    }
    
    var segments = structure.segments;
    for (var i = 0; i < segments.length; i++) {
        var segment = segments[i];
        if (rawIndex >= segment.start && rawIndex < segment.start + segment.text.length) {
            rawIndex -= segment.start;
            
            var charIndex = rawIndex < segment.text.length / 2 ? 
                rawIndex :
                rawIndex - segment.text.length - 1;
                
            if (i < segments.length / 2) {
                return {
                    index: charIndex,
                    segmentLocator: {
                        index: i
                    }
                }
            } else {
                return {
                    index: charIndex,
                    segmentLocator: {
                        index: i - segments.length - 1
                    }
                }
            }
        }
    }
    return {
        index: 0,
        segmentLocator: {
            index: 0
        }
    };
};

MassEdit.Parsing.isolateSelection = function(text, structure, startCharLocator, endCharLocator) {
    var startRawIndex = MassEdit.Parsing.charLocatorToRawIndex(text, structure, startCharLocator);
    var endRawIndex = MassEdit.Parsing.charLocatorToRawIndex(text, structure, endCharLocator);
    
    if (startRawIndex == -1) {
        if (endRawIndex == -1) {
            return [ text, "", "" ];
        } else {
            startRawIndex = 0;
        }
    } else if (endRawIndex == -1) {
        endRawIndex = text.length;
    }
    
    return [
        text.substring(0, startRawIndex),
        text.substring(startRawIndex, endRawIndex),
        text.substring(endRawIndex)
    ];
};

MassEdit.Parsing.charLocatorToRawIndex = function(text, structure, charLocator) {
    var segments = structure.segments;
    var segmentLocator = charLocator.segmentLocator;
    var segment;
    
    if (segments.length == 0 && (segmentLocator.index == 0 || segmentLocator.index == -1)) {
        return 0;
    }
    
    if (segmentLocator.index == -1) {
        return text.length;
    } else if (segmentLocator.index < 0) {
        var indexFromEnd = -(segmentLocator.index + 1);
        if (indexFromEnd > segments.length) {
            return -1;
        } else {
            segment = segments[segments.length - indexFromEnd];
        }
    } else {
        if (segmentLocator.index < segments.length) {
            segment = segments[segmentLocator.index];
        } else {
            return -1;
        }
    }
    
    var textLength = segment.text.length;
    if (charLocator.index == -1) {
        return segment.start + textLength;
    } else if (charLocator.index < 0) {
        var indexFromEnd = -(charLocator.index + 1);
        if (indexFromEnd > textLength) {
            return -1;
        } else {
            return segment.start + textLength - indexFromEnd;
        }
    } else {
        return segment.start + charLocator.index;
    }
};

MassEdit.Parsing.cloneSegment = function(a) {
    var b = {}
    for (var i in a) {
        if ((typeof a[i]) != "function") {
            b[i] = a[i]
        }
    }
    return b
}

MassEdit.Parsing.concatGenericText = function() {
    var v = {
        type : "text",
        segments : []
    }
    
    var baseStartIndex = 0
    for (var i = 0; i < arguments.length; i++) {
        var a = arguments[i]
        for (var ii = 0; ii < a.segments.length; ii++) {
            var segment = MassEdit.Parsing.cloneSegment(a.segments[ii])
            segment.start += baseStartIndex
            v.segments.push(segment)
        }
        if (a.segments.length > 0) {
            var segment = v.segments[v.segments.length - 1]
            baseStartIndex = segment.start + segment.text.length
        }
    }
    
    return v
}

MassEdit.Parsing.spliceGenericText = function(valueStructure, startIndex, endIndex, insertValueStructure) {
    var v = {
        type : "text",
        segments : [],
    }
    
    var inserted = false
    var adjustAmount = 0

    for (var i = 0; i < valueStructure.segments.length; i++) {
        var segment = valueStructure.segments[i]
        
        if (segment.start + segment.text.length <= startIndex) {
            v.segments.push(segment)
        } else if (segment.start < startIndex) {
            var temp = MassEdit.Parsing.cloneSegment(segment)
            temp.text = segment.text.substring(0, startIndex - segment.start)
            v.segments.push(temp)
        }
        
        if ((segment.start + segment.text.length >= startIndex) && !inserted) {
            inserted = true
            if (insertValueStructure) {
                v = MassEdit.Parsing.concatGenericText(v, insertValueStructure)
            }
            
            if (v.segments.length > 0) {
                var temp = v.segments[v.segments.length - 1]
                adjustAmount = (temp.start + temp.text.length) - endIndex
            } else {
                adjustAmount = -endIndex
            }
        }
        
        if (segment.start >= endIndex) {
            var temp = MassEdit.Parsing.cloneSegment(segment)
            temp.start += adjustAmount
            v.segments.push(temp)
        } else if (segment.start + segment.text.length > endIndex) {
            var temp = MassEdit.Parsing.cloneSegment(segment)
            temp.start = endIndex + adjustAmount
            temp.text = segment.text.substring(endIndex - segment.start, segment.text.length)
            v.segments.push(temp)
        }
    }

    return v
}
