Source: types/circuit.js

'use strict';

// External dependencies
const crypto = require('crypto');
const parser = require('dotparser');
const visualize = require('javascript-state-machine/lib/visualize');
const StateMachine = require('javascript-state-machine');

// Fabric Types
const Machine = require('./machine');
const Actor = require('./actor');

/**
 * The {@link Circuit} is the mechanism through which {@link Fabric}
 * operates, a computable directed graph describing a network of
 * {@link Peer} components and their interactions (side effects).
 * See also {@link Swarm} for deeper *inspection of {@link Machine}
 * mechanics.
 */
class Circuit extends Actor {
  constructor (config = {}) {
    super(config);

    this.settings = Object.assign({
      edges: [],
      gates: [],
      loops: [],
      nodes: [],
      wires: [],
      methods: {},
      state: {
        graph: {
          edges: [],
          nodes: []
        }
      }
    }, config);

    this['@data'] = this.settings;

    this.gates = [];
    this.transitions = [];
    this.methods = {};

    for (let i in this.settings.gates) {
      this.transitions.push({
        name: `step`,
        from: 'cycle()',
        to: `${this.settings.gates[i]}`
      });
    }

    for (let i in this.settings.wires) {
      let wire = this.settings.wires[i];
      this.transitions.push({ name: wire.name, from: wire.from, to: wire.to });
    }

    // Internal State
    this._state = {
      steps: [
        'load', // load from storage
        'bootstrap', // configure memory
        'step', // single cycle before start
        'start', // create services
        'listen' // listen for input
      ],
      content: this.settings.state
    };

    this.graph = new StateMachine({
      init: 'start()',
      data: this.state,
      transitions: this.transitions,
      methods: this.methods
    });

    return this;
  }

  get hash () {
    return crypto.createHash('sha256').update(this.dot).digest('hex');
  }

  get dot () {
    // TODO: generate polynomial for circuit
    return visualize(this.graph, {
      orientation: 'horizontal'
    });
  }

  _registerMethod (name, method) {
    this.methods[name] = method;
  }

  toObject () {
    return parser(this.dot);
  }

  parse (input) {
    return parser(input);
  }

  compute (input) {
    let output = input;

    // empty resolves to Identity function f(x) = x

    return output;
  }

  scramble () {
    let key = crypto.randomBytes(32);
    let machine = new Machine({ seed: key });
    let seed = machine.sip();
    let gates = [];

    for (let i = 0; i < this._state.steps.length; i++) {
      gates.push({
        name: this._state.steps[i],
        seed: machine.sip()
      });
    }

    gates.sort((a, b) => {
      return a.seed - b.seed;
    });

    return gates;
  }

  render () {
    let hash = crypto.createHash('sha256').update(this.dot).digest('base64');
    return `<fabric-circuit>
  <fabric-code-snippet data-bind="${this.hash}" data-integrity="sha256-${hash}">${this.dot}</fabric-code-snippet>
  <fabric-canvas>
    <fabric-grid>
      <fabric-grid-row>
        <textarea data-bind="${this.hash}" placeholder="Purity contract here...">${this.dot}</textarea>
      </fabric-grid-row>
      <fabric-grid-row>
        <button data-action="_step">step</button>
      </fabric-grid-row>
      <fabric-grid-row>
        <fabric-svg id="svg"></fabric-svg>
      </fabric-grid-row>
      <fabric-grid-row>
        <canvas id="output" data-bind="output"></canvas>
        <canvas id="canvas" data-bind="${this.hash}"></canvas>
      </fabric-grid-row>
  </fabric-canvas>
</fabric-circuit>`;
  }

  _draw () {
    return this;
  }

  async _step () {
    let circuit = this;
    let origin = circuit.hash + '';

    console.log('[CIRCUIT:STEP]', this.graph);
    console.log('[CIRCUIT:STEP]', 'woo:', this.toObject());
    console.log('[CIRCUIT:STEP]', 'zkc:', this.toObject().map((x) => {
      return x.children.filter(x => {
        return x.type === 'edge_stmt';
      }).map((y) => {
        return y.edge_list.map(z => {
          return z.id;
        });
      });
    })[0]);

    console.log('[CIRCUIT:STEP]', 'fsm:', this.graph._fsm);
    console.log('[CIRCUIT:STEP]', 'origin:', origin);
    console.log('[CIRCUIT:STEP]', 'origin hash:', this.hash);
    console.log('[CIRCUIT:STEP]', 'origin data:', this.dot);
    console.log('[CIRCUIT:STEP]', 'current:', this.graph.state);

    switch (this.graph.state) {
      default:
        console.error('unhandled state:', this.graph.state);
        break;
      case 'init':
        this.graph.ready();
        break;
      case 'ready':
        this.graph.step1();
        break;
      case '1':
        this.graph.step2();
        break;
      case '2':
        this.graph.step3();
        break;
      case '3':
        this.graph.done();
        break;
      case 'complete':
        console.log('#winning');
        this.emit('complete');
        break;
    }

    console.log('[CIRCUIT:STEP]', 'after:', this.graph.state);

    this.emit(origin, this.dot);

    await this._PUT(`/output`, Buffer.alloc((256 ** 3) / 8), false);
    await this._PUT(`/source`, this.dot, false);
    await this._PUT(`/status`, this.graph.state, false);

    let commit = await this.commit();

    console.log('commit:', commit);

    return commit;
  }

  async start () {
    return this;
  }
}

module.exports = Circuit;