define('jls/html/DataGrid', ['jls/lang/Class', 'jls/lang/Exception', 'jls/lang/Logger', 'jls/gui/Element', 'jls/html/HtmlElement', 'jls/html/Label', 'jls/html/Edit', 'jls/html/CheckBox'],
function (Class, Exception, Logger, Element, HtmlElement, Label, Edit, CheckBox)
{

    var Cell = Class.create(HtmlElement,
    {
        initialize : function($super, parameters) {
            this._selected = false;
            $super(parameters);
        },
        onCreate : function($super) {
            this.setAttribute('htmlTagName', 'td');
            $super();
            this._cellContent = this.createCellContent();
            if (this._cellContent) {
                this.addChild(this._cellContent);
            }
        },
        createCellContent : Class.emptyFunction,
        getCellContent : function() {
            return this._cellContent;
        },
        getValue : function() {
            return this.getTextContent();
        },
        setValue : function(value) {
            this.setTextContent(value);
            return this;
        },
        isSelected : function() {
            return this._selected;
        },
        setSelected : function(value) {
            this._selected = value;
            return this;
        },
        edit : function(editor) {
            while (this.getChildCount() > 1) {
                this.removeChild(this.getChildCount() - 1);
            }
            this._cellContent.getStyle().setProperty('display', editor ? 'none' : '');
            if (editor) {
                this.addChild(editor);
            }
        }
    });
            
    var BooleanCellRenderer = Class.create(Cell,
    {
        createCellContent : function() {
            return new CheckBox({attributes: {disabled: true}});
        },
        getValue : function() {
            return this.getCellContent().getChecked();
        },
        setValue : function(value) {
            this.getCellContent().setChecked(value);
            return this;
        }
    });
                    
    var BooleanCellEditor = Class.create(BooleanCellRenderer,
    {
        createCellContent : function() {
            return new CheckBox();
        }
    });
                            
    var TextCellRenderer = Class.create(Cell,
    {
        createCellContent : function() {
            return new Label();
        },
        getValue : function() {
            return this.getCellContent().getText();
        },
        setValue : function(value) {
            this.getCellContent().setText(value);
            return this;
        }
    });
                            
    var TextEditor = Class.create(Edit,
    {
        getValue : function() {
            return this.getText();
        },
        setValue : function(value) {
            this.setText(value);
            return this;
        }
    });
                            
    var Row = Class.create(HtmlElement,
    {
        initialize : function($super, parameters) {
            this._selected = false;
            $super(parameters);
        },
        onCreate : function($super) {
            this.setAttribute('htmlTagName', 'tr');
            $super();
        },
        onAddChild : function(child) {
            if (child instanceof Cell) {
                this.getHtmlElement().appendChild(child.getHtmlElement());
            }
        },
        isSelected : function() {
            return this._selected;
        },
        setSelected : function(value) {
            this._selected = value;
            return this;
        }
    });
    
    var ModelEvent = Class.create({
        initialize : function(model, firstRow, lastRow, column, type) {
            this.model = model;
            this.firstRow = firstRow;
            this.lastRow = lastRow || firstRow;
            this.column = column || ModelEvent.ALL_COLUMNS;
            this.type = type || ModelEvent.UPDATE;
        },
        toString : function() {
            return '{' + ModelEvent.formatEventType(this.type) + ', ' + this.firstRow + '-' + this.lastRow + ', ' + this.column + '}';
        }
    });
    Object.extend(ModelEvent,
    {
        formatEventType : function(type) {
            switch (type) {
            case ModelEvent.UPDATE:
                return 'UPDATE';
            case ModelEvent.DELETE:
                return 'DELETE';
            case ModelEvent.INSERT:
                return 'INSERT';
            }
        },
        DELETE : 1,
        INSERT : 2,
        UPDATE : 0,
        HEADER_ROW : -1,
        ALL_COLUMNS : -1
    });
    
    var Model = Class.create({
        initialize : function(parameters) {
            parameters = parameters || {};
            this._data = parameters.data || [];
            this._columnNames = parameters.columnNames || [];
            this._isColumnEditable = parameters.isColumnEditable || false;
            this._listeners = [];
            if (typeof this._isColumnEditable == 'boolean') {
                var isEditable = this._isColumnEditable;
                this._isColumnEditable = [];
                for (var i = this.getColumnCount() - 1; i >= 0; i--) {
                    this._isColumnEditable.push(isEditable);
                }
            }
        },
        addModelListener : function(listener) {
            this._listeners.push(listener);
        },
        removeModelListener : function(listener) {
            for (var i = 0; i < this._listeners.length; i++) {
                if (listener === this._listeners[i]) {
                    this._listeners.splice(i, 1);
                    break;
                }
            }
            return this;
        },
        fireModelChanged : function(event) {
            for (var i = 0; i < this._listeners.length; i++) {
                this._listeners[i].modelChanged(event);
            }
        },
        getColumnCount : function() {
            return this._columnNames.length;
        },
        getColumnName : function(column) {
            return this._columnNames[column];
        },
        getRowCount : function() {
            return this._data.length;
        },
        getRow : function(row) {
            return this._data[row];
        },
        addRow : function(rowData) {
            Logger.getInstance().trace('addRow()');
            this._data.push(rowData);
            this.fireModelChanged(new ModelEvent(this, this._data.length - 1, undefined, undefined, ModelEvent.INSERT));
        },
        insertRow : function(row, rowData) {
            Logger.getInstance().trace('insertRow(' + row + ')');
            this._data.splice(row, 0, rowData);
            this.fireModelChanged(new ModelEvent(this, row, undefined, undefined, ModelEvent.INSERT));
        },
        removeRow : function(row) {
            Logger.getInstance().trace('removeRow(' + row + ')');
            this._data.splice(row, 1);
            this.fireModelChanged(new ModelEvent(this, row, undefined, undefined, ModelEvent.DELETE));
        },
        getValueAt : function(row, column) {
            return this._data[row][column];
        },
        setValueAt : function(value, row, column) {
            this._data[row][column] = value;
            return this;
        },
        isCellEditable : function(row, column) {
            return this._isColumnEditable[column];
        },
        removeAll : function() {
            Logger.getInstance().trace('removeAll()');
            var rowCount = this.getRowCount();
            if (rowCount <= 0) {
                return;
            }
            this._data = [];
            this.fireModelChanged(new ModelEvent(this, 0, rowCount - 1, undefined, ModelEvent.DELETE));
        }
    });

    var BeanModel = Class.create(Model,
    {
        initialize : function($super, parameters) {
            parameters = parameters || {};
            $super(parameters);
            this._keys = parameters.keys;
            this._getFunctionName = parameters.getFunctionName || null;
            this._setFunctionName = parameters.setFunctionName || null;
            this._useGetterSetter = (parameters.useGetterSetter === true);
        },
        getValueAt : function(row, column) {
            var rowObject = this.getRow(row);
            var key = this._keys[column];
            if (this._useGetterSetter) {
                var getter = key.camelize('get');
                if (! Object.isFunction(rowObject[getter])) {
                    throw new Exception('No getter (' + getter + ')');
                }
                return rowObject[getter]();
            } else if (this._getFunctionName != null) {
                return rowObject[this._getFunctionName](key);
            }
            return rowObject[key];
        },
        setValueAt : function(value, row, column) {
            Logger.getInstance().trace('setValueAt(' + value + ', ' + row + ', ' + column + ')');
            var rowObject = this.getRow(row);
            var key = this._keys[column];
            if (this._useGetterSetter) {
                var setter = key.camelize('set');
                if (! Object.isFunction(rowObject[setter])) {
                    throw new Exception('No setter (' + setter + ')');
                }
                rowObject[setter](value);
                return this;
            } else if (this._setFunctionName != null) {
                return rowObject[this._setFunctionName](key, value);
            }
            rowObject[key] = value;
            return this;
        }
    });

    /*
     * TODO separate the table view and the cell content view
     */
    var View = Class.create({
        initialize : function(parameters) {
            parameters = parameters || {};
            this._columnTypes = parameters.columnTypes;
            this._cellParameters = parameters.cellParameters || null;
            this._rowParameters = parameters.rowParameters || null;
            this._altCellStyle = parameters.altCellStyle || {
                selected: {borderStyle: 'double'},
                notSelected: {borderStyle: 'dotted'}
            };
        },
        createRow : function(row) {
            return new Row(this._rowParameters);
        },
        createHeaderCellRenderer : function(column) {
            return new TextCellRenderer(this._cellParameters);
        },
        createCellRenderer : function(row, column, editable) {
            switch (this._columnTypes[column]) {
            case 'boolean':
                if (editable) {
                    return new BooleanCellEditor(this._cellParameters);
                } else {
                    return new BooleanCellRenderer(this._cellParameters);
                }
            case 'number':
            case 'string':
                return new TextCellRenderer(this._cellParameters);
            case 'date':
            default:
                return new Cell(this._cellParameters);
            }
        },
        createEditor : function(row, column) {
            switch (this._columnTypes[column]) {
            case 'number':
            case 'string':
                return new TextEditor(this._cellParameters);
            case 'boolean':
            case 'date':
            default:
                return null;
            }
        },
        changeCellSelection : function(cell, select) {
        },
        changeRowSelection : function(row, select) {
            for (var j = row.getChildCount() - 1; j >= 0; j--) {
                var cell = row.getChild(j);
                cell.getStyle().setProperties(this._altCellStyle[select ? 'selected' : 'notSelected']);
            }
        }
    });
            
    var DataGrid = Class.create(HtmlElement,
    {
        initialize : function($super, parameters, parent) {
            this._tableBody = null;
            this._model = null;
            this._view = null;
            this._selectedRow = -1;
            this._selectedColumn = -1;
            this._editingRow = -1;
            this._editingColumn = -1;
            this._editor = null;
            parameters = parameters || {};
            if (parameters.model) {
                this.setModel(parameters.model);
            }
            if (parameters.view) {
                this.setView(parameters.view);
            }
            $super(parameters, parent);
            if (this._model && this._view) {
                this.render();
            }
            this.observe('click', (function (event) {
                var rowCount = this.getChildCount();
                var i, j;
                for (i = 0, j = -1; i < rowCount; i++) {
                    var row = this.getChild(i);
                    for (j = row.getChildCount() - 1; j >= 0; j--) {
                        var cell = row.getChild(j);
                        if (cell.containsNode(event.target)) {
                            break;
                        }
                    }
                    if (j >= 0) {
                        this.selectCell(i - 1, j);
                        break;
                    }
                }
            }).bind(this));
        },
        onCreate : function($super) {
            this.setAttribute('htmlTagName', 'table');
            $super();
            this._tableBody = document.createElement('tbody');
            this.getHtmlElement().appendChild(this._tableBody);
        },
        onAddChild : function(child) {
            if (child instanceof Row) {
                this._tableBody.appendChild(child.getHtmlElement());
            }
        },
        onInsertChild : function(index, child) {
            if (child instanceof Row) {
                this._tableBody.insertBefore(child.getHtmlElement(), this._tableBody.childNodes[index]);
            }
        },
        selectCell : function(rowIndex, columnIndex) {
            Logger.getInstance().trace('selectCell(' + rowIndex + ', ' + columnIndex + ')');
            this.changeSelection(rowIndex, columnIndex);
        },
        changeSelection : function(rowIndex, columnIndex, toggle, extend) {
            if ((this._selectedRow == rowIndex) && (this._selectedColumn == columnIndex)) {
                // for check box
                var row = this.getChild(rowIndex + 1);
                var cell = row.getChild(columnIndex);
                var value = cell.getValue();
                this._model.setValueAt(value, rowIndex, columnIndex);
                return;
            }
            if ((this._selectedRow >= 0) && (this._selectedRow + 1 < this.getChildCount())) {
                var selectedRow = this.getChild(this._selectedRow + 1);
                //var selectedCell = selectedRow.getChild(this._selectedColumn);
                selectedRow.setSelected(false);
                this._view.changeRowSelection(selectedRow, selectedRow.isSelected());
                this._selectedRow = -1;
                this._selectedColumn = -1;
            }
            if ((rowIndex < 0) || (columnIndex < 0)) {
                this.stopEditing();
                return;
            }
            Logger.getInstance().trace('changeSelection(' + rowIndex + ', ' + columnIndex + ')');
            var row = this.getChild(rowIndex + 1);
            //var cell = row.getChild(columnIndex);
            if (! row.isSelected()) {
                row.setSelected(true);
                this._view.changeRowSelection(row, row.isSelected());
            }
            Logger.getInstance().trace('changeSelection() selectedRow: ' + rowIndex);
            this._selectedRow = rowIndex;
            this._selectedColumn = columnIndex;
            if (this._model.isCellEditable(rowIndex, columnIndex)) {
                this.editCellAt(rowIndex, columnIndex);
            }
        },
        editCellAt : function(rowIndex, columnIndex) {
            Logger.getInstance().trace('editCellAt(' + rowIndex + ', ' + columnIndex + ')');
            var row = this.getChild(rowIndex + 1);
            var cell = row.getChild(columnIndex);
            this.stopEditing();
            this._editor = this._view.createEditor(rowIndex, columnIndex);
            if (! this._editor) {
                // for checkbox
                var value = cell.getValue();
                this._model.setValueAt(value, rowIndex, columnIndex);
                return;
            }
            var value = this._model.getValueAt(rowIndex, columnIndex);
            this._editor.setValue(value);
            cell.edit(this._editor);
            this._editingRow = rowIndex;
            this._editingColumn = columnIndex;
        },
        stopEditing : function() {
            if ((this._editingRow < 0) || (this._editingColumn < 0)) {
                return;
            }
            Logger.getInstance().trace('stopEditing(' + this._editingRow + ', ' + this._editingColumn + ')');
            var row = this.getChild(this._editingRow + 1);
            var cell = row.getChild(this._editingColumn);
            cell.edit(null);
            var value = this._editor.getValue();
            this._model.setValueAt(value, this._editingRow, this._editingColumn);
            cell.setValue(value);
            this._editor = null;
            this._editingRow = -1;
            this._editingColumn = -1;
        },
        getCellSelectionEnabled : function() {
        },
        getRowSelectionAllowed : function() {
        },
        getSelectedColumn : function() {
            return this._selectedColumn;
        },
        getSelectedColumnCount : function() {
        },
        getSelectedRow : function() {
            return this._selectedRow;
        },
        getSelectedRowCount : function() {
        },
        getSelectedRows : function() {
        },
        setView : function(view) {
            this._view = Element.wrap(View, view);
        },
        getView : function() {
        	return this._view;
        },
        setModel : function(model) {
            this._model = Element.wrap(Model, model);
            this._model.addModelListener(this);
        },
        getModel : function() {
        	return this._model;
        },
        render : function() {
            var headerRow = this._view.createRow(-1);
            this.addChild(headerRow);
            var columnCount = this._model.getColumnCount();
            for (var j = 0; j < columnCount; j++) {
                var headerCell = this._view.createHeaderCellRenderer(j);
                headerRow.addChild(headerCell);
                headerCell.setValue(this._model.getColumnName(j));
            }
            var rowCount = this._model.getRowCount();
            for (var i = 0; i < rowCount; i++) {
                var row = this._view.createRow(i);
                this.addChild(row);
                for (var j = 0; j < columnCount; j++) {
                    var cell = this._view.createCellRenderer(i, j, this._model.isCellEditable(i, j));
                    row.addChild(cell);
                    cell.setValue(this._model.getValueAt(i, j));
                }
            }
        },
        modelChanged : function(event) {
            Logger.getInstance().trace('modelChanged(' + event + ')');
            switch (event.type) {
            case ModelEvent.DELETE:
                for (var i = event.lastRow; i >= event.firstRow; i--) {
                    this.removeChild(i + 1); // For header
                }
                break;
            case ModelEvent.INSERT:
                var columnCount = this._model.getColumnCount();
                for (var i = event.firstRow; i <= event.lastRow; i++) {
                    var row = this._view.createRow(i);
                    //this.addChild(row); // TODO Fix
                    this.insertChild(i + 1, row);
                    for (var j = 0; j < columnCount; j++) {
                        var cell = this._view.createCellRenderer(i, j, this._model.isCellEditable(i, j));
                        row.addChild(cell);
                        cell.setValue(this._model.getValueAt(i, j));
                    }
                }
                break;
            }
        }
    });

    Object.extend(DataGrid,
    {
        Row : Row,
        Cell : Cell,
        View : View,
        Model : Model,
        BeanModel : BeanModel
    });

    return DataGrid;

});
