text: 'use strict'
  
  // most of this code was written by Andrew Kelley
  // licensed under the BSD license: see
  // https://github.com/andrewrk/node-mv/blob/master/package.json
  
  // this needs a cleanup
  
  const u = require('universalify').fromCallback
  const fs = require('graceful-fs')
  const ncp = require('../copy/ncp')
  const path = require('path')
  const remove = require('../remove').remove
  const mkdirp = require('../mkdirs').mkdirs
  
  function move (source, dest, options, callback) {
    if (typeof options === 'function') {
      callback = options
      options = {}
    }
  
    const shouldMkdirp = ('mkdirp' in options) ? options.mkdirp : true
    const overwrite = options.overwrite || options.clobber || false
  
    if (shouldMkdirp) {
      mkdirs()
    } else {
      doRename()
    }
  
    function mkdirs () {
      mkdirp(path.dirname(dest), err => {
        if (err) return callback(err)
        doRename()
      })
    }
  
    function doRename () {
      if (path.resolve(source) === path.resolve(dest)) {
        setImmediate(callback)
      } else if (overwrite) {
        fs.rename(source, dest, err => {
          if (!err) return callback()
  
          if (err.code === 'ENOTEMPTY' || err.code === 'EEXIST') {
            remove(dest, err => {
              if (err) return callback(err)
              options.overwrite = false // just overwriteed it, no need to do it again
              move(source, dest, options, callback)
            })
            return
          }
  
          // weird Windows shit
          if (err.code === 'EPERM') {
            setTimeout(() => {
              remove(dest, err => {
                if (err) return callback(err)
                options.overwrite = false
                move(source, dest, options, callback)
              })
            }, 200)
            return
          }
  
          if (err.code !== 'EXDEV') return callback(err)
          moveAcrossDevice(source, dest, overwrite, callback)
        })
      } else {
        fs.link(source, dest, err => {
          if (err) {
            if (err.code === 'EXDEV' || err.code === 'EISDIR' || err.code === 'EPERM' || err.code === 'ENOTSUP') {
              moveAcrossDevice(source, dest, overwrite, callback)
              return
            }
            callback(err)
            return
          }
          fs.unlink(source, callback)
        })
      }
    }
  }
  
  function moveAcrossDevice (source, dest, overwrite, callback) {
    fs.stat(source, (err, stat) => {
      if (err) {
        callback(err)
        return
      }
  
      if (stat.isDirectory()) {
        moveDirAcrossDevice(source, dest, overwrite, callback)
      } else {
        moveFileAcrossDevice(source, dest, overwrite, callback)
      }
    })
  }
  
  function moveFileAcrossDevice (source, dest, overwrite, callback) {
    const flags = overwrite ? 'w' : 'wx'
    const ins = fs.createReadStream(source)
    const outs = fs.createWriteStream(dest, { flags })
  
    ins.on('error', err => {
      ins.destroy()
      outs.destroy()
      outs.removeListener('close', onClose)
  
      // may want to create a directory but `out` line above
      // creates an empty file for us: See #108
      // don't care about error here
      fs.unlink(dest, () => {
        // note: `err` here is from the input stream errror
        if (err.code === 'EISDIR' || err.code === 'EPERM') {
          moveDirAcrossDevice(source, dest, overwrite, callback)
        } else {
          callback(err)
        }
      })
    })
  
    outs.on('error', err => {
      ins.destroy()
      outs.destroy()
      outs.removeListener('close', onClose)
      callback(err)
    })
  
    outs.once('close', onClose)
    ins.pipe(outs)
  
    function onClose () {
      fs.unlink(source, callback)
    }
  }
  
  function moveDirAcrossDevice (source, dest, overwrite, callback) {
    const options = {
      overwrite: false
    }
  
    if (overwrite) {
      remove(dest, err => {
        if (err) return callback(err)
        startNcp()
      })
    } else {
      startNcp()
    }
  
    function startNcp () {
      ncp(source, dest, options, err => {
        if (err) return callback(err)
        remove(source, callback)
      })
    }
  }
  
  module.exports = {
    move: u(move)
  }
  