import Rx from 'rxjs/Rx';
import { ManagerBase } from 'jupyter-js-widgets';
import { BackendSync } from './backend-sync';
import { ModelUpdater } from './model-updater';

export class WidgetManager extends ManagerBase {
  constructor(store, dispatch) {
    super();
    this.dispatch = dispatch;
    this._internalModels = {};

    // Create the mechanisms for syncing between redux state, backbone state,
    // and the backend.
    this.backendToRedux = new BackendSync(
      store,
      dispatch,
      this.createModel.bind(this),
      this.comm_target_name,
      this.version_comm_target_name
    );
    this.reduxToManager = new ModelUpdater(store, this);
  }

  createModel(id, data) {
    let modelLoaded;
    this._internalModels[id] = new Promise(resolve => (modelLoaded = resolve));

    const modelPromise = this.new_model({
      model_name: data._model_name,
      model_module: data._model_module,
      model_id: id,
    }, data).catch(err => console.error("Couldn't create a model.", err));

    // Listen for changes to the model so that they can be applied to the store
    // Create an observable from the current model state, and from the model
    // change event.  Merge them and use that observable to update the store.
    const modelInfoPromise = modelPromise.then(model => {
      modelLoaded(model);
      const initialState = Rx.Observable
        .of(this.getSerializedModelState(model));
      const stateChangeEvents = Rx.Observable
        .fromEvent(model, 'change')
        .map(() => this.getSerializedModelState(model));
      const stateChanges = initialState.merge(stateChangeEvents);
      return { model, stateChanges };
    });

    return modelInfoPromise;
  }

  createViewForModel(id, cellId) {
    return this._models[id].then(model => this.create_view(model, { cellId }));
  }

  getSerializedModelState(model) {
    const state = model.constructor._serialize_state(model.get_state(false), this);
    // TODO HACK
    // Here I'm lazy so I convert from JS to JSON and back to traverse the
    // object hierarcy serializing where needed.
    return state.then(js => JSON.parse(JSON.stringify(js)));
  }

  setModelState(id, serializedState) {
    return this._internalModels[id].then(model => {
      const statePromise = model.constructor._deserialize_state(serializedState, this);
      return statePromise.then(state => model.set_state(state));
    });
  }

  deleteModels(ids) {
    // To delete widgets make their "comms close" which will trigger the
    // natural cleanup subroutines of the widgets.
    ids.forEach(deletedId => {
      this._models[deletedId].then(model => {
        model._handle_comm_closed();
      });
    });
  }
}

@@ -162,16 +162,1042 @@
 pdater';
+%0Aimport %7B createMessage %7D from '../api/messaging';%0A%0Aclass PhonyComm %7B%0A  constructor(store, id) %7B%0A    this.id = id;%0A    this.store = store;%0A  %7D%0A%0A  send(data, callbacks, metadata, buffers) %7B%0A    // Only allow custom messages to be sent to the backend.  This prevents%0A    // the widget machinery from sending state updates directly to the backend.%0A    // Instead, the redux widget state management system should send the%0A    // updates.  See backend-sync.js for the state logic.%0A    if (!data %7C%7C data.method !== 'custom') return;%0A%0A    //  Construct comm msg%0A    const msg = createMessage('comm_msg');%0A    msg.content = %7B%0A      comm_id: this.id,%0A      data: data %7C%7C %7B%7D,%0A    %7D;%0A    msg.metadata = metadata %7C%7C msg.metadata;%0A    msg.buffers = buffers %7C%7C msg.buffers; // TODO: Make sure this works%0A%0A    // Subscribe, send msg, unsubscribe%0A    const shell = this.store.getState().app.channels.shell;%0A    const shellSubscription = shell.subscribe(() =%3E %7B%7D);%0A    shell.next(msg);%0A    shellSubscription.unsubscribe();%0A  %7D%0A%0A  close() %7B %7D%0A%7D
 %0A%0Aexport
@@ -1281,24 +1281,48 @@
    super();%0A
+    this.store = store;%0A
     this.dis
@@ -2291,16 +2291,16 @@
  store.%0A
-
     cons
@@ -2345,24 +2345,74 @@
 (model =%3E %7B%0A
+      model.comm = new PhonyComm(this.store, id);%0A
       modelL

Add support for frontend to backend custom msgs