Source: stateMachine/stateMachine.js

/*
* @Author: amitshah
* @Date:   2018-04-09 12:58:48
* @Last Modified by:   amitshah
* @Last Modified time: 2018-04-28 23:41:07
*/

const machina = require('machina');
const message = require('../message');
const channel = require('../channel');
const util = require('ethereumjs-util');

/** @namespace stateMachine */

/** @class encapsulate state machine transitions */
class MessageState{
  constructor(state,stateMachine){
    this.state = state;//message.*
    this.stateMachine = stateMachine; //statemachine.*
  }

  applyMessage(stateChange,message){
    this.stateMachine.handle(this.state,stateChange,message);
  }

}

/** @class Internal encapsulate mediated transfer state*/ 
class MediatedTransferState extends message.MediatedTransfer{
  constructor(options){
    super(options);
    this.secret;
    this.hash;
  }

  toRevealSecret(){

  }

}

/** @class factory handles the initiators lifecycle events for a mediated transfer.
*State changes can only occur after a mutating action has taken place in the engine.  the transitions merely emit further actions.
* @memberof stateMachine
* @returns {machina.BehavioralFsm}
* @see Engine.handleEvent*/
const InitiatorFactory = function(){ return new machina.BehavioralFsm( {

    initialize: function() {
      //a shared event emitter between all the state machines

    },

    namespace: "mediated-transfer",

    initialState: "init",

    states: {

        init:{
          _onEnter:function (state) {
          },
          //we have already "sent" and handled the transfer locally, now we await if
          //our channel partner responds
          "*":function(state){
            this.emit("GOT.sendMediatedTransfer",state);
            this.transition(state,"awaitRequestSecret")
          },
          _onExit:function () {
          }
        },
        awaitRequestSecret: {
            receiveRequestSecret: function( state, requestSecret ) {
                //we dont care if you request the secret after expiration
                //this also means we can NEVER reuse a secret

                if(state.target.compare(requestSecret.from)===0 &&
                  state.lock.hashLock.compare(requestSecret.hashLock)===0 &&
                  state.msgID.eq(requestSecret.msgID))
                {
                  //now you have to assume that money is gone
                  this.emit("GOT.sendRevealSecret",Object.assign(state,{revealTo:state.target}));
                  this.transition(state,"awaitRevealSecret");
                }

            },

        },
        awaitRevealSecret: {
            _onEnter: function(state) {

            },
            receiveRevealSecret:function(state,secretReveal){
              //we only unlock if the partner state learned the secret
              //not just anybody, channel can handle multiple reveals of the same secret
              if(secretReveal.from.compare(state.to)===0
                && state.lock.hashLock.compare(util.sha3(secretReveal.secret))===0){
                this.emit("GOT.sendSecretToProof",state);
                this.transition(state, 'completedTransfer');
              }
            },
            _onExit: function(state  ) {

            }

        },
        completedTransfer:{

        },
        failedTransfer:{

        },
        expiredTransfer:{

        }

    },

} );

}

/** @class factory handles the targets lifecycle events for a mediated transfer
* @memberof stateMachine
* @returns {machina.BehavioralFsm}
* @see Engine.handleEvent
*/
const TargetFactory = function(){ return new machina.BehavioralFsm( {

    initialize: function( ) {
        // your setup code goes here...
    },

    namespace: "mediated-transfer",

    initialState: "init",

    states: {

        init:{
          "*":function (state,transition,currentBlock) {
            //see if its safe to wait or dont request the secret
            //and let the lock expire by itself
            //we cant reject a lockedtransfer, it will put our locksroot out of sync
            //instead we require silent fails

            if(state.lock.expiration.lte(currentBlock.add(channel.REVEAL_TIMEOUT))){
              this.transition(state, "expiredTransfer");
            }else{
              console.log("Safe to process lock, lets request it:"+state.initiator.toString('hex'));
              this.emit("GOT.sendRequestSecret",state)
              //this.eventEmitter.emit('sendSecretRequest',state,currentBlock,revealTimeout);
              this.transition(state,"awaitRevealSecret");

            }


          },
          _onExit:function (state) {

          }

        },
        awaitRevealSecret: {
            _onEnter: function(state) {

            },
            receiveRevealSecret:function(state,revealSecret){
                //reveal secret can come from anywhere including the blockchain

                if(state.lock.hashLock.compare(util.sha3(revealSecret.secret))===0 &&
                  state.initiator.compare(revealSecret.from)===0){
                    //in memory "states" object on the target and initator statemachines are now synced
                    state = Object.assign(state,{secret:revealSecret.secret,revealTo:state.from});
                    //send this backwards to state.from
                    this.emit('GOT.sendRevealSecret',state);
                    this.transition(state,"awaitSecretToProof");

                }

            },
            handleBlock:function (state,currentBlock) {
              if(state.lock.expiration.lte(currentBlock.add(channel.REVEAL_TIMEOUT))){
                this.transition(state,"expiredTransfer");
              }else{
                //not expired
              }
            },
            _onExit: function(state  ) {

            }

        },
        awaitSecretToProof:{
          receiveSecretToProof:function(state,secretToProof){
            if(secretToProof.from.compare(state.from)===0){ // this shouldnt happen... the handleTransfer would have errored
              this.emit('GOT.receiveSecretToProof',state);
              this.transition(state,"completedTransfer");
            };

          },
          handleBlock:function (state,currentBlock) {
            if(state.lock.expiration.lte(currentBlock.add(channel.REVEAL_TIMEOUT))){
              this.emit('GOT.closeChannel',state);
              this.transition(state, "completedTransfer");
            }
          }
        },
        completedTransfer:{

        },
        expiredTransfer:{

        }

    },

} );
};

module.exports = {
  MessageState,InitiatorFactory,TargetFactory
}