var MassEdit = new Object();

MassEdit.startEdit = function(records, fCreateColumn, fClearAllColumns) {
	
    MassEdit._fCreateColumn = fCreateColumn;
    MassEdit._fClearAllColumns = fClearAllColumns;
    
    /*
     *  Parse values into structures
     */
    
    var valueStructures = [];
    for(var i = 0; i < records.length; i++) {
        var record = records[i];
        var value = record.text.toString();
        valueStructures.push({
            record:               record,
            structures:           [ MassEdit.Parsing.parseGenericText(value) ],
            values:               [ value ],
            typicalStructure:     MassEdit.Parsing.parse(value)
        });
    }
    
    /*
     *  Cluster value structures
     */
    clusters = [];
    var makeCluster = function(clusterValueStructures) {
        clusters.push({
            valueStructures:    clusterValueStructures,
            changes:            [],
            activeValueIndex:   -1,
            activeField:        null
        });
    };
    
    while (valueStructures.length > 1) {
        makeCluster(MassEdit.Clustering.extractFirstCluster(valueStructures));
    }
    if (valueStructures.length == 1) {
        makeCluster(valueStructures);
    }
    clusters.sort(function(c1, c2) { return c2.valueStructures.length - c1.valueStructures.length; });
    MassEdit._clusters = clusters;
    
    /*
     *  Construct UI
     */
    var makeColumn = function(cluster, clusterIndex) {
        var column = MassEdit._fCreateColumn();
        
        var valueStructures = cluster.valueStructures;
        for (var j = 0; j < valueStructures.length; j++) {
            var vs = valueStructures[j];
            var div = MassEdit._makeValueUI(vs, clusterIndex, j);
            column.appendChild(div);
            vs.div = div;
        }
    };
    
    for (var i = 0; i < clusters.length; i++) {
        makeColumn(clusters[i], i);
    }
    
    this._startEdit(0, 0);
};

MassEdit._makeValueUI = function(vs, clusterIndex, valueIndex) {
    var self = this;
    
    var div = document.createElement("div");
    div.baseClassName = "massEdit-valueUI"; 
    div.className = div.baseClassName;
    div.appendChild(document.createTextNode(vs.values[0] + "\u00a0"));
    
    div.onmouseover = function() { div.className = div.baseClassName + " massEdit-valueUI-hovered"; };
    div.onmouseout = function()  { div.className = div.baseClassName; };
    div.onmousedown = function(event)     { 
      self._startEdit(clusterIndex, valueIndex);
    }
    
    return div;
};

MassEdit._showValueWithoutSelection = function(vs) {
    vs.div.innerHTML = vs.values[0] + "\u00a0";
};

MassEdit._startEdit = function(clusterIndex, valueIndex) {

    var cluster = MassEdit._clusters[clusterIndex];
    if (cluster.activeValueIndex == valueIndex) {
        return;
    } else {
    	MassEditInterface.setSingleLineMode(false)
        for (var c = 0; c < MassEdit._clusters.length; c++) {
            var cluster2 = MassEdit._clusters[c];
            if (cluster2.activeValueIndex >= 0) {
                var valueStructures = cluster2.valueStructures;
                for (var i = 0; i < valueStructures.length; i++) {
                    MassEdit._showValueWithoutSelection(valueStructures[i]);
                }
                cluster2.activeValueIndex = -1;
                cluster2.activeField = null;
            }
        }
    }
    
    var vs = cluster.valueStructures[valueIndex];
    var div = vs.div;
    div.innerHTML = "";
    
    var textField = new MassEdit.SmartTextField(vs.values[0], div);
    textField.onSelectionChange = function(
        selectionStart, 
        selectionEnd
    ) { 
        MassEdit._onSelectionChange(clusterIndex, selectionStart, selectionEnd); 
    }
    textField.onChange = function(
        oldValue, 
        oldSelectionStart, 
        oldSelectionEnd,
        newValue,
        newSelectionStart,
        newSelectionEnd
    ) {
        MassEdit._onChange(
            clusterIndex,
            oldValue, 
            oldSelectionStart, 
            oldSelectionEnd,
            newValue,
            newSelectionStart,
            newSelectionEnd
        );
    }
    
    var input = textField.getInput();
    input.style.width = "100%";
    input.style.border = "none";

    div.appendChild(MassEdit._makeSingleLineButton(vs))
    
    vs.textField = textField
    
    cluster.activeValueIndex = valueIndex;
    cluster.activeField = textField;
    
    input.focus();
    
    MassEdit._showSelections(clusterIndex, input.selectionStart, input.selectionEnd);
};


MassEdit._findActiveCluster = function() {
    var clusters = this._clusters;
    for (var i = 0; i < clusters.length; i++) {
        var cluster = clusters[i];
        if (cluster.activeField != null) {
            return i;
        }
    }
    return -1;
};

// Returns SmartTextField for the cluster currently being
// edited, or null if no cluster is being editing
MassEdit._findActiveField = function() {
    var i = MassEdit._findActiveCluster();
    if (i < 0) return null;
    return clusters[i].activeField;
}

MassEdit._onChange = function(
    clusterIndex,
    oldValue, 
    oldSelectionStart, 
    oldSelectionEnd,
    newValue,
    newSelectionStart,
    newSelectionEnd
) {
    var cluster = MassEdit._clusters[clusterIndex];
    var activeValueStructure = cluster.valueStructures[cluster.activeValueIndex];
    
    var oldSpans = [];
    oldSpans.push(oldValue.substring(0, oldSelectionStart));
    oldSpans.push(oldValue.substring(oldSelectionStart, oldSelectionEnd));
    oldSpans.push(oldValue.substring(oldSelectionEnd));
    
    var newSpans = [];
    newSpans.push(newValue.substring(0, newSelectionStart));
    newSpans.push(newValue.substring(newSelectionStart, newSelectionEnd));
    newSpans.push(newValue.substring(newSelectionEnd));
    
    var processChange = function(oldText, newText, offset) {
        var x = oldText.indexOf(newText);
        if (x == 0) {
            MassEdit._processValueInputDelete(clusterIndex, offset + newText.length, offset + oldText.length);
        } else if (x > 0) {
            MassEdit._processValueInputDelete(clusterIndex, offset, offset + x);
            if (x + newText.length < oldText.length) {
                MassEdit._processValueInputDelete(clusterIndex, offset + newText.length, offset + (oldText.length - x));
            }
        } else {
            x = newText.indexOf(oldText);
            if (x == 0) {
                MassEdit._processValueInputInsert(clusterIndex, offset + oldText.length, newText.substring(oldText.length));
            } else if (x > 0) {
                MassEdit._processValueInputInsert(clusterIndex, offset, newText.substring(0, x));
                if (x + oldText.length < newText.length) {
                    MassEdit._processValueInputInsert(clusterIndex, offset + x + oldText.length, newText.substring(x + oldText.length));
                }
            } else { // replace
                MassEdit._processValueInputDelete(clusterIndex, offset, offset + oldText.length);
                MassEdit._processValueInputInsert(clusterIndex, offset, newText);
            }
        }
    };
    
    if (oldSpans[0] != newSpans[0]) {
        processChange(oldSpans[0], newSpans[0], 0);
    }
    if (oldSpans[1] != newSpans[1]) {
        processChange(oldSpans[1], newSpans[1], newSpans[0].length);
    }
    if (oldSpans[2] != newSpans[2]) {
        processChange(oldSpans[2], newSpans[2], newSpans[0].length + newSpans[1].length);
    }
    
    MassEdit._onSelectionChange(clusterIndex, newSelectionStart, newSelectionEnd);
};

MassEdit._processValueInputDelete = function(clusterIndex, rawFromIndex, rawToIndex) {
    var cluster = MassEdit._clusters[clusterIndex];
    
    var activeValueStructure = cluster.valueStructures[cluster.activeValueIndex];
    var activeValue = activeValueStructure.values[0];
    var activeStructure = activeValueStructure.structures[0];
    
    //console.log("delete " + rawFromIndex + " " + rawToIndex);
    var change = {
        type: "delete",
        startCharLocator: MassEdit.Parsing.createCharLocator(rawFromIndex, activeValue, activeStructure),
        endCharLocator: MassEdit.Parsing.createCharLocator(rawToIndex, activeValue, activeStructure)
    }
    MassEdit._processChange(cluster, change);
    
};

MassEdit._processValueInputInsert = function(clusterIndex, rawIndex, insertText) {
    var cluster = MassEdit._clusters[clusterIndex];
    var activeValueStructure = cluster.valueStructures[cluster.activeValueIndex];
    var charLocator = MassEdit.Parsing.createCharLocator(
        rawIndex, 
        activeValueStructure.values[0], 
        activeValueStructure.structures[0]
    );
    
    var changes = cluster.changes;
    for (var i = 0; i < changes.length; i++) {
        var change = changes[i];
        if (change.type == "delete") {
            var valueBeforeDelete = activeValueStructure.values[i + 1];
            var structureBeforeDelete = activeValueStructure.structures[i + 1];
            
            var startRawIndex = MassEdit.Parsing.charLocatorToRawIndex(valueBeforeDelete, structureBeforeDelete, change.startCharLocator);
            var endRawIndex = MassEdit.Parsing.charLocatorToRawIndex(valueBeforeDelete, structureBeforeDelete, change.endCharLocator);

            if (startRawIndex == -1) {
                if (endRawIndex == -1) {
                    // do nothing
                    continue;
                } else {
                    startRawIndex = 0;
                }
            } else if (endRawIndex == -1) {
                endRawIndex = text.length;
            }
            
            if (valueBeforeDelete.substring(startRawIndex, endRawIndex) == insertText) { // looks like a smart paste
                //console.log("smart-paste " + rawIndex + " " + insertText);
                MassEdit._processChange(
                    cluster,
                    {   type:           "smart-paste",
                        editOffset:     i,
                        charLocator:    charLocator
                    }
                );
                return;
            }
        }
    }
    
    //console.log("insert " + rawIndex + " " + insertText);
    MassEdit._processChange(
        cluster,
        {   type:           "insert",
            insertText:     insertText,
            charLocator:    charLocator
        }
    );
};

// Applies a transformer function to every selection, 
//   where  transformer: function(String selected, String prefix, String suffix)
//                         returns a string that will replace the selected string
MassEdit.transformText = function(transformer) {
    var clusterIndex = MassEdit._findActiveCluster();
    if (clusterIndex >= 0) {
        var cluster = MassEdit._clusters[clusterIndex];
        
        var activeValueStructure = cluster.valueStructures[cluster.activeValueIndex];
        var activeValue = activeValueStructure.values[0];
        var activeStructure = activeValueStructure.structures[0];
        var textField = cluster.activeField;
        var selectionStart = textField.getSelectionStart();
        var selectionEnd = textField.getSelectionEnd();
        
        var change = {
            type:               "transform",
            transformer:        transformer,
            startCharLocator:   MassEdit.Parsing.createCharLocator(selectionStart, activeValue, activeStructure),
            endCharLocator:     MassEdit.Parsing.createCharLocator(selectionEnd, activeValue, activeStructure)
        }
        
        MassEdit._processChange(cluster, change);
        
        textField.setText(activeValueStructure.values[0]);

        textField.focus();
    }
};

MassEdit._processChange = function(cluster, change) {
    var valueStructures = cluster.valueStructures;
    if (change.type == "insert") {
        var charLocator = change.charLocator;
        var insertText = change.insertText;
        
        for (var i = 0; i < valueStructures.length; i++) {
            var vs = valueStructures[i];
            if ((cluster.activeValueIndex != i) && MassEditInterface.getSingleLineMode()) {
                vs.values.unshift(vs.values[0]);
                vs.structures.unshift(vs.structures[0]);
                continue;
            }
            
            var rawIndex2 = MassEdit.Parsing.charLocatorToRawIndex(vs.values[0], vs.structures[0], charLocator);
            
            if (rawIndex2 == -1) {
                vs.values.unshift(vs.values[0]);
                vs.structures.unshift(vs.structures[0]);
                continue;
            }
            
            vs.structures.unshift(MassEdit.Parsing.spliceGenericText(
                vs.structures[0], rawIndex2, rawIndex2,
                MassEdit.Parsing.parseGenericText(insertText)))
            vs.values.unshift(vs.values[0].substring(0, rawIndex2) + insertText + vs.values[0].substring(rawIndex2));
        }
    } else if (change.type == "delete") {
        var startCharLocator = change.startCharLocator;
        var endCharLocator = change.endCharLocator;
        
        for (var i = 0; i < valueStructures.length; i++) {
            var vs = valueStructures[i];
            if ((cluster.activeValueIndex != i) && MassEditInterface.getSingleLineMode()) {
                vs.values.unshift(vs.values[0]);
                vs.structures.unshift(vs.structures[0]);
                continue;
            }
            
            var value = vs.values[0];
            var structure = vs.structures[0];
            
            var startRawIndex = MassEdit.Parsing.charLocatorToRawIndex(value, structure, startCharLocator);
            var endRawIndex = MassEdit.Parsing.charLocatorToRawIndex(value, structure, endCharLocator);
            
            if (startRawIndex == -1) {
                if (endRawIndex == -1) {
                    vs.values.unshift(vs.values[0]);
                    vs.structures.unshift(vs.structures[0]);
                    continue;
                } else {
                    startRawIndex = 0;
                }
            } else if (endRawIndex == -1) {
                endRawIndex = value.length;
            }
            
            vs.structures.unshift(MassEdit.Parsing.spliceGenericText(
                vs.structures[0], startRawIndex, endRawIndex))
            vs.values.unshift(value.substring(0, startRawIndex) + value.substr(endRawIndex));
        }
    } else if (change.type == "transform") {
        var startCharLocator = change.startCharLocator;
        var endCharLocator = change.endCharLocator;
        
        for (var i = 0; i < valueStructures.length; i++) {
            var vs = valueStructures[i];
            if ((cluster.activeValueIndex != i) && MassEditInterface.getSingleLineMode()) {
                vs.values.unshift(vs.values[0]);
                vs.structures.unshift(vs.structures[0]);
                continue;
            }
            
            var value = vs.values[0];
            var structure = vs.structures[0];
            
            var startRawIndex = MassEdit.Parsing.charLocatorToRawIndex(value, structure, startCharLocator);
            var endRawIndex = MassEdit.Parsing.charLocatorToRawIndex(value, structure, endCharLocator);
            
            if (startRawIndex == -1) {
                if (endRawIndex == -1) {
                    vs.values.unshift(vs.values[0]);
                    vs.structures.unshift(vs.structures[0]);
                    continue;
                } else {
                    startRawIndex = 0;
                }
            } else if (endRawIndex == -1) {
                endRawIndex = value.length;
            }
            
            var textPrefix = value.substring(0, startRawIndex);
            var textToTransform = value.substring(startRawIndex, endRawIndex);
            var textSuffix = value.substr(endRawIndex);
            
            var textTransformed = change.transformer(textToTransform, textPrefix, textSuffix);
            
            vs.structures.unshift(MassEdit.Parsing.spliceGenericText(
                vs.structures[0], startRawIndex, endRawIndex,
                MassEdit.Parsing.parseGenericText(textTransformed)))
            vs.values.unshift(textPrefix + textTransformed + textSuffix);
        }
    } else if (change.type == "smart-paste") {
        var charLocator = change.charLocator;
        
        var editOffset = change.editOffset;
        var change2 = cluster.changes[change.editOffset];
        
        var startCharLocator = change2.startCharLocator;
        var endCharLocator = change2.endCharLocator;
        
        for (var i = 0; i < valueStructures.length; i++) {
            var vs = valueStructures[i];
            if ((cluster.activeValueIndex != i) && MassEditInterface.getSingleLineMode()) {
                vs.values.unshift(vs.values[0]);
                vs.structures.unshift(vs.structures[0]);
                continue;
            }
            
            var rawIndex = MassEdit.Parsing.charLocatorToRawIndex(vs.values[0], vs.structures[0], charLocator);
            if (rawIndex == -1) {
                vs.values.unshift(vs.values[0]);
                vs.structures.unshift(vs.structures[0]);
                continue;
            }
            
            var valueBeforeEdit = vs.values[editOffset + 1];
            var structureBeforeEdit = vs.structures[editOffset + 1];
            
            var startRawIndex = MassEdit.Parsing.charLocatorToRawIndex(valueBeforeEdit, structureBeforeEdit, startCharLocator);
            var endRawIndex = MassEdit.Parsing.charLocatorToRawIndex(valueBeforeEdit, structureBeforeEdit, endCharLocator);
            
            if (startRawIndex == -1) {
                if (endRawIndex == -1) {
                    vs.values.unshift(vs.values[0]);
                    vs.structures.unshift(vs.structures[0]);
                } else {
                    startRawIndex = 0;
                }
            } else if (endRawIndex == -1) {
                endRawIndex = valueBeforeEdit.length;
            }
            
            var textPrefix = vs.values[0].substr(0, rawIndex);
            var textToPaste = valueBeforeEdit.substring(startRawIndex, endRawIndex);
            var textSuffix = vs.values[0].substr(rawIndex);
            
            var prefixSegments = MassEdit.Parsing.parseGenericText(textPrefix).segments;
            var pasteSegments = MassEdit.Parsing.parseGenericText(textToPaste).segments;
            var suffixSegments = MassEdit.Parsing.parseGenericText(textSuffix).segments;
            
            for (var n = 0; n < pasteSegments.length; n++) {
                var segment = pasteSegments[n];
                segment.start += textPrefix.length;
            }
            for (var n = 0; n < suffixSegments.length; n++) {
                var segment = suffixSegments[n];
                segment.start += textPrefix.length + textToPaste.length;
            }
            
            vs.values.unshift(textPrefix + textToPaste + textSuffix);
            vs.structures.unshift({
                type:       "text",
                segments:   prefixSegments.concat(pasteSegments).concat(suffixSegments)
            });
        }
    }
    cluster.changes.unshift(change);
    
    MassEdit.onChangeCallback();
};

MassEdit._undoLastChange = function(clusterIndex) {
    //console.log("undo");
    var cluster = MassEdit._clusters[clusterIndex];
    if (cluster.changes.length > 0) {
        cluster.changes.shift();
        
        var valueStructures = cluster.valueStructures
        for (var i = 0; i < valueStructures.length; i++) {
            var vs = valueStructures[i];
            vs.values.shift();
            vs.structures.shift();
            
            if (i != cluster.activeValueIndex) {
                MassEdit._showValueWithoutSelection(vs);
            }
        }
        cluster.activeField.setText(cluster.valueStructures[cluster.activeValueIndex].values[0]);
    }
    
    MassEdit.onChangeCallback();
};

MassEdit._onSelectionChange = function(clusterIndex, selectionStart, selectionEnd) {
    var cluster = MassEdit._clusters[clusterIndex];
    MassEdit._showSelections(clusterIndex, selectionStart, selectionEnd);
};

MassEdit.setInvalidSelection = function(yes) {
	var cluster = MassEdit._clusters[MassEdit._findActiveCluster()]
	var vs = cluster.valueStructures[cluster.activeValueIndex]
	if (yes) {
		vs.div.baseClassName = "massEdit-valueUI invalidSelection"
		vs.div.className = vs.div.baseClassName
		cluster.activeField.setReadOnly(true)
	} else {
		vs.div.baseClassName = "massEdit-valueUI"
		vs.div.className = vs.div.baseClassName
		cluster.activeField.setReadOnly(false)
	}
}

MassEdit.callback_onKeyDownWhileReadOnly = function() {
	var cluster = MassEdit._clusters[MassEdit._findActiveCluster()]
	var vs = cluster.valueStructures[cluster.activeValueIndex]
	
	vs.div.className = "massEdit-valueUI invalidSelectionHighlight"
	window.setTimeout(function() {
		vs.div.className = vs.div.baseClassName
	}, 100)
}

MassEdit._showSelections = function(/*optional*/ clusterIndex, 
                                    /*optional*/ selectionStart, 
                                    /*optional*/ selectionEnd) {
    if (MassEditInterface.getSingleLineMode()) {
    	return
    }
    
    if (clusterIndex === undefined) {
      clusterIndex = MassEdit._findActiveCluster()
      if (clusterIndex < 0) return // can't show selections
      var textfield = clusters[clusterIndex].activeField
      var input = textfield.getInput()
      selectionStart = input.selectionStart
      selectionEnd = input.selectionEnd
      if (selectionStart == -1 || selectionEnd == -1) return
    }
      
    var cluster = MassEdit._clusters[clusterIndex];
    
    var valueStructures = cluster.valueStructures;
    var activeValueStructure = valueStructures[cluster.activeValueIndex];
    var activeValue = activeValueStructure.values[0];
    var activeStructure = activeValueStructure.structures[0];
    
    //console.log(selectionStart + "," + selectionEnd);
    var invalidSelection = false
    if (selectionStart == selectionEnd) {
        var charLocator = MassEdit.Parsing.createCharLocator(selectionStart, activeValue, activeStructure);
        
        for (var i = 0; i < valueStructures.length; i++) {
            if (i != cluster.activeValueIndex) {
                var vs = valueStructures[i];
                var value = vs.values[0];
                var rawIndex = MassEdit.Parsing.charLocatorToRawIndex(value, vs.structures[0], charLocator);
                if (rawIndex == -1) {
                    MassEdit._showValueWithoutSelection(vs);
                    invalidSelection = true
                } else {
                    vs.div.innerHTML = "";//"\u00a0";
                    vs.div.appendChild(document.createTextNode(value.substr(0, rawIndex)));
                    
                    var span = document.createElement("span");
                    span.className = "massEdit-cursor";
                    vs.div.appendChild(span);
                    
                    vs.div.appendChild(document.createTextNode(value.substr(rawIndex)));
                }
            }
        }
    } else {
        var startCharLocator = MassEdit.Parsing.createCharLocator(selectionStart, activeValue, activeStructure);
        var endCharLocator = MassEdit.Parsing.createCharLocator(selectionEnd, activeValue, activeStructure);
        
        for (var i = 0; i < valueStructures.length; i++) {
            if (i != cluster.activeValueIndex) {
                var vs = valueStructures[i];
                var a = MassEdit.Parsing.isolateSelection(vs.values[0], vs.structures[0], startCharLocator, endCharLocator);
                
                if (a[1] == "") {
                    MassEdit._showValueWithoutSelection(vs);
                    invalidSelection = true
                } else {                
	                vs.div.innerHTML = "";//"\u00a0";
	                vs.div.appendChild(document.createTextNode(a[0]));
	                
	                var span = document.createElement("span");
	                span.className = "massEdit-valueSelection";
	                span.innerHTML = a[1];
	                vs.div.appendChild(span);
	                
	                vs.div.appendChild(document.createTextNode(a[2] + "\u00a0"));
	            }
            }
        }
    }
    MassEdit.setInvalidSelection(invalidSelection)    
};

MassEdit._showNoSelections = function(cluster) {
	if (!cluster) {
        for (var c = 0; c < MassEdit._clusters.length; c++) {
            var cluster = MassEdit._clusters[c];
            if (cluster.activeValueIndex >= 0) {
            	MassEdit._showNoSelections(cluster)
            }
        }
		return
	}

    var valueStructures = cluster.valueStructures;
    for (var i = 0; i < valueStructures.length; i++) {
        if (i != cluster.activeValueIndex) {
            MassEdit._showValueWithoutSelection(valueStructures[i]);
        }
    }
};

MassEdit._makeSingleLineButton = function(vs) {
    var div = document.createElement("div")
    div.innerHTML = "<a title='Edit only this line'><input type='checkbox' class='singleLineButton'/></a>"
    var button = div.firstChild
    button.onclick = function() { 
        MassEditInterface.toggleSingleLineMode()
        button.checked = MassEditInterface.getSingleLineMode()
        if (button.checked) {
        	vs.div.baseClassName = "massEdit-valueUI singleLineEditMode"
        	vs.div.className = vs.div.baseClassName
			vs.textField.setReadOnly(false)
        } else {
        	// we have to check, since it may have been changed to "massEdit-valueUI invalidSelection"
        	if (vs.div.baseClassName == "massEdit-valueUI singleLineEditMode") {
	        	vs.div.baseClassName = "massEdit-valueUI"
	        	vs.div.className = vs.div.baseClassName
	        }
        }
    }
    return button
}
