/** @jsx React.DOM */

var Todo = Backbone.Model.extend({
  // Default attributes for the todo
  // and ensure that each todo created has `title` and `completed` keys.
  defaults: {
    title: '',
    completed: false
  },

  // Toggle the `completed` state of this todo item.
  toggle: function() {
    this.save({
      completed: !this.get('completed')
    });
  }
});

var TodoList = Backbone.Collection.extend({

  // Reference to this collection's model.
  model: Todo,

  // Save all of the todo items under the `"todos"` namespace.
  localStorage: new Store('todos-react-backbone'),

  // Filter down the list of all todo items that are finished.
  completed: function() {
    return this.filter(function( todo ) {
      return todo.get('completed');
    });
  },

  remaining: function() {
    return this.without.apply(this, this.completed());
  },

  // We keep the Todos in sequential order, despite being saved by unordered
  // GUID in the database. This generates the next order number for new items.
  nextOrder: function () {
    if (!this.length) {
      return 1;
    }
    return this.last().get('order') + 1;
  },

  // Todos are sorted by their original insertion order.
  comparator: function (todo) {
    return todo.get('order');
  }
});

var Utils = {
  pluralize: function( count, word ) {
    return count === 1 ? word : word + 's';
  },

  stringifyObjKeys: function(obj) {
    var s = '';
    for (var key in obj) {
      if (!obj.hasOwnProperty(key)) {
        continue;
      }
      if (obj[key]) {
        s += key + ' ';
      }
    }
    return s;
  }
};

// Begin React stuff

var TodoItem = React.createClass({
  handleSubmit: function(event) {
    var val = this.refs.editField.getDOMNode().value.trim();
    if (val) {
      this.props.onSave(val);
    } else {
      this.props.onDestroy();
    }
    return false;
  },

  onEdit: function() {
    this.props.onEdit();
    this.refs.editField.getDOMNode().focus();
  },

  render: function() {
    var classes = Utils.stringifyObjKeys({
      completed: this.props.todo.get('completed'), editing: this.props.editing
    });
    return (
      <li className={classes}>
        <div className="view">
          <input
            className="toggle"
            type="checkbox"
            checked={this.props.todo.get('completed')}
            onChange={this.props.onToggle}
            key={this.props.key}
          />
          <label onDoubleClick={this.onEdit}>
            {this.props.todo.get('title')}
          </label>
          <button className="destroy" onClick={this.props.onDestroy} />
        </div>
        <form onSubmit={this.handleSubmit}>
          <input
            ref="editField"
            className="edit"
            defaultValue={this.props.todo.get('title')}
            onBlur={this.handleSubmit}
            autoFocus="autofocus"
          />
        </form>
      </li>
    );
  }
});

var TodoFooter = React.createClass({
  render: function() {
    var activeTodoWord = Utils.pluralize(this.props.count, 'todo');
    var clearButton = null;

    if (this.props.completedCount > 0) {
      clearButton = (
        <button id="clear-completed" onClick={this.props.onClearCompleted}>
          Clear completed ({this.props.completedCount})
        </button>
      );
    }

    return (
      <footer id="footer">
        <span id="todo-count">
          <strong>{this.props.count}</strong>{' '}
          {activeTodoWord}{' '}left
        </span>
        {clearButton}
      </footer>
    );
  }
});

// An example generic Mixin that you can add to any component that should react
// to changes in a Backbone component. The use cases we've identified thus far
// are for Collections -- since they trigger a change event whenever any of
// their constituent items are changed there's no need to reconcile for regular
// models. One caveat: this relies on getBackboneModels() to always return the
// same model instances throughout the lifecycle of the component. If you're
// using this mixin correctly (it should be near the top of your component
// hierarchy) this should not be an issue.
var BackboneMixin = {
  componentDidMount: function() {
    // Whenever there may be a change in the Backbone data, trigger a reconcile.
    this.getBackboneModels().forEach(function(model) {
      model.on('add change remove', this.forceUpdate.bind(this, null), this);
    }, this);
  },

  componentWillUnmount: function() {
    // Ensure that we clean up any dangling references when the component is
    // destroyed.
    this.getBackboneModels().forEach(function(model) {
      model.off(null, null, this);
    }, this);
  }
};

var TodoApp = React.createClass({
  mixins: [BackboneMixin],
  getInitialState: function() {
    return {editing: null};
  },

  componentDidMount: function() {
    // Additional functionality for todomvc: fetch() the collection on init
    this.props.todos.fetch();
    this.refs.newField.getDOMNode().focus();
  },

  componentDidUpdate: function() {
    // If saving were expensive we'd listen for mutation events on Backbone and
    // do this manually. however, since saving isn't expensive this is an
    // elegant way to keep it reactively up-to-date.
    this.props.todos.forEach(function(todo) {
      todo.save();
    });
  },

  getBackboneModels: function() {
    return [this.props.todos];
  },

  handleSubmit: function(event) {
    event.preventDefault();
    var val = this.refs.newField.getDOMNode().value.trim();
    if (val) {
      this.props.todos.create({
        title: val,
        completed: false,
        order: this.props.todos.nextOrder()
      });
      this.refs.newField.getDOMNode().value = '';
    }
  },

  toggleAll: function(event) {
    var checked = event.nativeEvent.target.checked;
    this.props.todos.forEach(function(todo) {
      todo.set('completed', checked);
    });
  },

  edit: function(todo) {
    this.setState({editing: todo.get('id')});
  },

  save: function(todo, text) {
    todo.set('title', text);
    this.setState({editing: null});
  },

  clearCompleted: function() {
    this.props.todos.completed().forEach(function(todo) {
      todo.destroy();
    });
  },

  render: function() {
    var footer = null;
    var main = null;
    var todoItems = this.props.todos.map(function(todo) {
      return (
        <TodoItem
          key={todo.cid}
          todo={todo}
          onToggle={todo.toggle.bind(todo)}
          onDestroy={todo.destroy.bind(todo)}
          onEdit={this.edit.bind(this, todo)}
          editing={this.state.editing === todo.get('id')}
          onSave={this.save.bind(this, todo)}
        />
      );
    }, this);

    var activeTodoCount = this.props.todos.remaining().length;
    var completedCount = todoItems.length - activeTodoCount;
    if (activeTodoCount || completedCount) {
      footer =
        <TodoFooter
          count={activeTodoCount}
          completedCount={completedCount}
          onClearCompleted={this.clearCompleted}
        />;
    }

    if (todoItems.length) {
      main = (
        <section id="main">
          <input id="toggle-all" type="checkbox" onChange={this.toggleAll} />
          <ul id="todo-list">
            {todoItems}
          </ul>
        </section>
      );
    }

    return (
      <div>
        <section id="todoapp">
          <header id="header">
            <h1>todos</h1>
            <form onSubmit={this.handleSubmit}>
              <input
                ref="newField"
                id="new-todo"
                placeholder="What needs to be done?"
              />
            </form>
          </header>
          {main}
          {footer}
        </section>
        <footer id="info">
          <p>Double-click to edit a todo</p>
          <p>
            Created by{' '}
            <a href="http://github.com/petehunt/">petehunt</a>
          </p>
          <p>Part of{' '}<a href="http://todomvc.com">TodoMVC</a></p>
        </footer>
      </div>
    );
  }
});

React.renderComponent(
  <TodoApp todos={new TodoList()} />, document.getElementById('container')
);
