text: 'use strict';
  var flocore = require('flocore-lib');
  var BufferUtil = flocore.util.buffer;
  var async = require('async');
  
  function Reorg(node, block) {
    this.node = node;
    this.block = block;
    this.db = block.db;
  }
  
  Reorg.prototype.handleReorg = function(newBlockHash, callback) {
    var self = this;
  
    self.handleConcurrentReorg(function(err) {
      if(err) {
        return callback(err);
      }
  
      self.findCommonAncestorAndNewHashes(self.block.tip.hash, newBlockHash, function(err, commonAncestor, newHashes) {
        if(err) {
          return callback(err);
        }
  
        self.rewindBothTips(commonAncestor, function(err) {
          if(err) {
            return callback(err);
          }
          self.fastForwardBothTips(newHashes, callback);
        });
      });
    });
  };
  
  Reorg.prototype.handleConcurrentReorg = function(callback) {
    var self = this;
  
    if(self.block.concurrentTip.hash === self.block.tip.hash) {
      return callback();
    }
  
    self.findCommonAncestorAndNewHashes(
      self.block.concurrentTip.hash,
      self.block.tip.hash,
      function(err, commonAncestor, newHashes) {
      if(err) {
        return callback(err);
      }
  
      self.rewindConcurrentTip(commonAncestor, function(err) {
        if(err) {
          return callback(err);
        }
  
        self.fastForwardConcurrentTip(newHashes, callback);
      });
    });
  };
  
  Reorg.prototype.rewindConcurrentTip = function(commonAncestor, callback) {
    var self = this;
  
    async.whilst(
      function() {
        return self.block.concurrentTip.hash !== commonAncestor;
      },
      function(next) {
        self.block.getBlockOperations(self.block.concurrentTip, false, 'concurrent', function(err, operations) {
          if(err) {
            return next(err);
          }
  
          operations.push(self.block.getTipOperation(self.block.concurrentTip, false, 'concurrentTip'));
          self.db.batch(operations, function(err) {
            if(err) {
              return next(err);
            }
  
            var prevHash = BufferUtil.reverse(self.block.concurrentTip.header.prevHash).toString('hex');
  
            self.block.getBlocks([prevHash], function(err, blocks) {
              if(err) {
                return next(err);
              }
  
              self.block.concurrentTip = blocks[0];
              next();
            });
          });
        });
  
  
      },
      callback
    );
  };
  
  Reorg.prototype.fastForwardConcurrentTip = function(newHashes, callback) {
    var self = this;
  
    async.eachSeries(newHashes, function(hash, next) {
      self.block.getBlocks([hash], function(err, blocks) {
        if(err) {
          return next(err);
        }
  
        self.block.getBlockOperations(blocks[0], true, 'concurrent', function(err, operations) {
          if(err) {
            return next(err);
          }
  
          operations.push(self.block.getTipOperation(blocks[0], true, 'concurrentTip'));
          self.db.batch(operations, function(err) {
            if(err) {
              return next(err);
            }
  
            self.block.concurrentTip = blocks[0];
            next();
          });
        });
      });
    }, callback);
  };
  
  Reorg.prototype.rewindBothTips = function(commonAncestor, callback) {
    var self = this;
  
    async.whilst(
      function() {
        return self.block.tip.hash !== commonAncestor;
      },
      function(next) {
        async.parallel(
          [
            function(next) {
              self.block.getBlockOperations(self.block.concurrentTip, false, 'concurrent', function(err, operations) {
                if(err) {
                  return next(err);
                }
                operations.push(self.block.getTipOperation(self.block.concurrentTip, false, 'concurrentTip'));
                next(null, operations);
              });
            },
            function(next) {
              self.block.getBlockOperations(self.block.tip, false, 'serial', function(err, operations) {
                if(err) {
                  return next(err);
                }
  
                operations.push(self.block.getTipOperation(self.block.tip, false));
                next(null, operations);
              });
            }
          ],
          function(err, results) {
            if(err) {
              return callback(err);
            }
  
            var operations = results[0].concat(results[1]);
            self.db.batch(operations, function(err) {
              if(err) {
                return next(err);
              }
  
              var prevHash = BufferUtil.reverse(self.block.tip.header.prevHash).toString('hex');
  
              self.block.getBlocks([prevHash], function(err, blocks) {
  
                if(err) {
                  return next(err);
                }
  
                self.block.concurrentTip = blocks[0];
                self.block.tip = blocks[0];
                next();
              });
            });
          }
        );
      },
      callback
    );
  };
  
  Reorg.prototype.fastForwardBothTips = function(newHashes, callback) {
    var self = this;
  
    async.eachSeries(newHashes, function(hash, next) {
      self.block.getBlocks([hash], function(err, blocks) {
        if(err) {
          return next(err);
        }
  
        async.parallel(
          [
            function(next) {
              self.block.getBlockOperations(blocks[0], true, 'concurrent', function(err, operations) {
                if(err) {
                  return next(err);
                }
  
                operations.push(self.block.getTipOperation(blocks[0], true, 'concurrentTip'));
                next(null, operations);
              });
            },
            function(next) {
              self.block.getBlockOperations(blocks[0], true, 'serial', function(err, operations) {
                if(err) {
                  return next(err);
                }
  
                operations.push(self.block.getTipOperation(blocks[0], true));
                next(null, operations);
              });
            }
          ],
          function(err, results) {
            if(err) {
              return next(err);
            }
  
            var operations = results[0].concat(results[1]);
  
            self.db.batch(operations, function(err) {
              if(err) {
                return next(err);
              }
  
              self.block.concurrentTip = blocks[0];
              self.block.tip = blocks[0];
              next();
            });
          }
        );
      });
    }, callback);
  };
  
  Reorg.prototype.findCommonAncestorAndNewHashes = function(oldTipHash, newTipHash, callback) {
    var self = this;
  
    var mainPosition = oldTipHash;
    var forkPosition = newTipHash;
  
    var mainHashesMap = {};
    var forkHashesMap = {};
  
    mainHashesMap[mainPosition] = true;
    forkHashesMap[forkPosition] = true;
  
    var commonAncestor = null;
    var newHashes = [forkPosition];
  
    async.whilst(
      function() {
        return !commonAncestor;
      },
      function(next) {
        async.parallel(
          [
            function(next) {
              if(!mainPosition) {
                return next();
              }
  
              self.block.getBlockHeader(mainPosition, function(err, mainBlockHeader) {
                if(err) {
                  return next(err);
                }
  
                if(mainBlockHeader && mainBlockHeader.prevHash) {
                  mainHashesMap[mainBlockHeader.prevHash] = true;
                  mainPosition = mainBlockHeader.prevHash;
                } else {
                  mainPosition = null;
                }
                next();
              });
            },
            function(next) {
              if(!forkPosition) {
                return next();
              }
  
              self.block.getBlockHeader(forkPosition, function(err, forkBlockHeader) {
                if(err) {
                  return next(err);
                }
  
                if(forkBlockHeader && forkBlockHeader.prevHash) {
                  forkHashesMap[forkBlockHeader.prevHash] = true;
                  forkPosition = forkBlockHeader.prevHash;
                  newHashes.unshift(forkPosition);
                } else {
                  forkPosition = null;
                }
  
                next();
              });
            }
          ],
          function(err) {
            if(err) {
              return next(err);
            }
  
            if(forkPosition && mainHashesMap[forkPosition]) {
              commonAncestor = forkPosition;
            }
  
            if(mainPosition && forkHashesMap[mainPosition]) {
              commonAncestor = mainPosition;
            }
  
            if(!mainPosition && !forkPosition) {
              return next(new Error('Unknown common ancestor'));
            }
  
            next();
          }
        );
      },
      function(err) {
        if(err) {
          return callback(err);
        }
  
        // New hashes are those that are > common ancestor
        var commonAncestorFound = false;
        for(var i = newHashes.length - 1; i >= 0; i--) {
          if(newHashes[i] === commonAncestor) {
            commonAncestorFound = true;
          }
  
          if(commonAncestorFound) {
            newHashes.shift();
          }
        }
  
        callback(null, commonAncestor, newHashes);
      }
    );
  };
  
  module.exports = Reorg;
  