/*
 * Rejewski
 *
 * Copyright 2014 Jason Gerfen
 * All rights reserved.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

var crypto = require('./crypto')
  , tools = require('./tools');

/**
 * @object handshake
 * Handshake encapsulation/decapsulation
 */
module.exports = {

  /**
   * @function set
   * Alias for create()
   *
   * @param {String} key Key to use for HMAC
   * @param {String} pt The payload (encrypted format)
   * @param {String} nonce An optional nonce associated with current handshake
   * @param {String} pkey An optional Private key for signature
   * @param {Function} cb Callback
   *
   * @returns {Function}
   */
  set: function(key, pt, nonce, pkey, cb){
    var timestamp = tools.timestamp()
      , digest = false
      , payload = false
      , signature = false
      , ct = false;

    nonce = nonce || crypto.pseudo();

    try {

      if (typeof pt == 'object') {
        pt = JSON.stringify(pt);
      }

      if (256 > pt.length) {
        var pad = crypto.pseudo(256);
        pt = pt+'::'+pad;
      }

      crypto.encrypt(key, pt, false, false, function hsSet(err, enc){
        if (err) cb(true);
        ct = enc;
      });

      crypto.digest(key, ct, false, false, function hsSet(err, hmac){
        if (err) cb(true);
        digest = hmac;
      });

      this.create(timestamp, nonce, digest, ct, function hsSet(err, pld){
        if (err) cb(true);
        payload = pld;
      });

      if (pkey){
        crypto.sign(pkey, payload, false, false, function hsSet(err, sig){
          if (err) cb(true);
          signature = sig;
        });
      } else {
        crypto.hash(payload, false, false, function hsSet(err, hash){
          if (err) cb(true);
          signature = hash;
        });
      }
    } catch(e) {
      console.log('handshake.set -> '+e);
      cb(true);
    }

    cb(null, {
      payload: payload,
      signature: signature
    });
  },

  /**
   * @function get
   * Alias for extract()
   *
   * @param {String} key Key/passphrase used for encryption of payload
   * @param {String} payload Payload to be extracted, decrypted & checksummed
   * @param {Integer} skew The clock skew used for timestamp verification
   * @param {String} pkey Optional private key to verify signature
   * @param {Function} cb Callback
   *
   * @returns {Function}
   */
  get: function(key, payload, skew, pkey, cb){
    var timestamp = (Number(tools.timestamp()) - skew)
      , signature = false
      , digest = false
      , obj = false
      , pt = false;

    try {
      if (pkey){
        if (pkey != true) {
          crypto.verify(pkey, payload.payload, payload.signature, false, false,
                        function hsGet(err, sig){

            if (err) cb(true);

            if (sig != true) {
              console.log('handshake.get() -> Could not verify RSA signature');
              cb(true);
            }
          });
        }
      } else {
        crypto.hash(payload.payload, false, false, function hsGet(err, hash){

          if (err) cb(true);

          signature = hash;
        });

        if (signature != payload.signature){

          console.log('handshake.get() -> Could not verify signature');

          cb(true);
        }
      }

      this.extract(payload.payload, function hsGet(err, data){
        if (err) cb(true);
        if (!data.payload || !data.digest || !data.nonce || !data.timestamp) {
          cb(true);
        }
        obj = data;
      });

      if ((Number(obj.timestamp) - timestamp) > skew) {
        console.log('handshake.get() -> Clockskew too great');
        cb(true);
      }

      crypto.digest(key, obj.payload, false, false, function hsSet(err, hmac){
        if (err) cb(true);
        digest = hmac;
      });

      if (digest != obj.digest) {
        console.log('handshake.get() -> Could not verify digest ');
        cb(true);
      }

      crypto.decrypt(key, obj.payload, false, false, false,
                     function hsGet(err, p){
        if (err) cb(true);

        pt = /::/.test(p) ? p.split('::')[0] : p;
    }

    return cb(null, {
      payload: pt,
      digest: obj.digest,
      nonce: obj.nonce,
      timestamp: obj.timestamp  
    });
  },

  /**
   * @function create
   * Wrapper for creating an encapsulated payload containing
   * a timestamp, an nonce, a MAC digest & payload
   *
   * @param {String} timestamp An EPOCH
   * @param {String} nonce Forward secrecy random number
   * @param {String} digest The MAC digest of payload
   * @param {String} payload The payload of communication
   *
   * @returns {Function}
   */
  create: function(timestamp, nonce, digest, payload, cb){
    var one = this.encapsulate(nonce, timestamp)
      , two = this.encapsulate(digest, one)
      , three = this.encapsulate(payload, two);

    if (!three) return cb(false);
    return cb(null, three);
  },

  /**
   * @function extract
   * Wrapper for extracting an encapsulated payload
   *
   * @param {String} payload The encapsulated payload
   *
   * @returns {Object}
   */
  extract: function(payload, cb) {
    var one = this.decapsulate(payload)
      , two = this.decapsulate(one.digest)
      , three = this.decapsulate(two.digest);

    if (!one.payload || !two.payload || !three.payload || !three.digest) {
      cb(false);
    }

    return cb(null, {
      payload: one.payload,
      digest: two.payload,
      nonce: three.payload,
      timestamp: three.digest
    });
  },

  /**
   * @function encapsulate
   * Creates encapsulated payaload
   *
   * @param {String} payload The payload to be used
   * @param {String} digest The digest to be used
   *
   * @returns {String}
   */
  encapsulate: function(payload, digest) {
    var a = []
      , b = digest.split('')
      , ret = [];

    do {
      a.push(payload.substring(0, 2))
    } while((payload = payload.substring(2, payload.length)));

    for (var n = 0; n < a.length; n++) {
      if (b[n] && a[n]) {
        ret.push(a[n]+b[n]);
      } else if(b[n] && !a[n]) {
        ret.push(b[n]);
      } else if(!b[n] && a[n]) {
        ret.push(a[n]);
      }
    }
    return ret.join('')+'/'+digest.length;
  },

  /**
   * @function decapsulate
   * Decapsulates payload into an object
   *
   * @param {String} payload The payload to be used
   *
   * @returns {Object}
   */
  decapsulate: function(payload) {
    try {
      var r = new RegExp('.{1, 3}', 'g')
        , i = /\/(\d+)$/g.exec(payload)
        , s = payload.replace(/\/\d+$/g, '')
        , c = s.match(/(.){1,3}/g)
        , d = []
        , p = []
        , x = 0;
    } catch(e) {
      return false;
    }

    for (var n = 0; n < i[1]; n++) {
      if (c[n])
        d.push(c[n].substr(2, c[n].length))
    }

    c.forEach(function(item) {
      if (item) {
        if (x < i[1]) {
          p.push(item.substr(0, item.length - 1));
        } else {
          p.push(item);
        }
        x++;
      }
    });

    return {
      digest: d.join(''),
      payload: p.join('')
    }
  },

  /**
   * @function verify
   * Verify's payload with associated HMAC
   *
   * @param {Object} opts System configuration object
   * @param {Object} obj Object containing a secret & key
   * @param {String} payload The payload to be used
   * @param {Object} digest Object containing HMAC & signature
   * @param {Integer} ts The timestamp of associated payload
   *
   * @returns {Boolean}
   */
  verify: function(opts, obj, payload, digest, ts){
    if (Number(ts) < Number(tools.timestamp() - opts.clockskew)){
      return false;
    }

    if (obj.key && digest.sig){
      if (digest.sig != crypto.verify(obj.key, payload)){
        return false;
      }
    }

    if (obj.secret && digest.hmac){
      if (digest.hmac != crypto.digest(obj.secret, payload)){
        return false;
      }
    }

    return true;
  }
};
      });
    } catch(e) {
      console.log('handshake.get() -> '+e);
      cb(true);