
var Proteus = require("proteus"),
    FS      = require("fs"),
    Path    = require("path"),
    Glob    = require("glob"),
    CP      = require("child_process"),
    log     = require("sake/logger"),
    sake    = require("sake"),
    Task    = require("sake/task"),

    DEFAULT_FILE_MODE       = "0644",
    DEFAULT_DIRECTORY_MODE  = "0777",
    
    futil   = {}
;

module.exports = Proteus.merge(futil, {
    sh: function (cmd, fn) {
        log.info("sh: " + cmd);
        CP.exec(cmd, function (error, stdout, stderr) {
            if (!error && fn) {
                fn(error, stdout);
            }
            else if (error && fn) {
                fn(error, stderr);
            }
            else if (error) {
                log(stderr);
            }
        });
    },

    readdir: FS.readdir.bind(FS),
    
    mkdir: function (path, mode) {
        log.info("mkdir " + path);
        FS.mkdirSync(path, mode);
    },
    
    mkdir_p: function (path, mode) {
        var cpath = path,
            missing = []
        ;
        
        log.info("mkdir -p " + path);
        mode = mode || DEFAULT_DIRECTORY_MODE;
        while (!Path.existsSync(cpath) && !(cpath === "." || cpath === "/")) {
            missing.push(Path.basename(cpath));
            cpath = Path.dirname(cpath);
        }
        
        missing.reduceRight(function (t, c) {
            var p = Path.join(t, c);
            futil.mkdir(p, mode);
            return p;
        }, cpath);
    },

    rm: function () {
        Proteus.slice(arguments, 0).forEach(function (path) {
            log.info("rm " + path);
            FS.unlinkSync(path);
        });
    },

    rm_rf: function () {
        Proteus.slice(arguments).forEach(function (path) {
            var files, dirs;
            
            if (FS.statSync(path).isFile()) {
                return futil.rm(path);
            }

            log.info("rm -rf " + path);

            files = [];
            dirs = [];
            
            Glob.sync(Path.join(path, "**/*")).forEach(function (path) {
                if (FS.statSync(path).isDirectory()) {
                    dirs.push(path);
                }
                else {
                    files.push(path);
                }
            });

            dirs.push(path);
            
            // files first
            files.forEach(function (path) {
                futil.rm(path);
            });
            
            // directories next
            dirs.forEach(function (path) {
                // log.info(path);
                FS.rmdirSync(path);
            });
            
        });
    },

    cp: function (from, to) {
        log.info("cp " + from + " " + to);
        futil.write(to, futil.read(from));
    },
    
    cp_r: function (from, to) {
        var fromContents = from.lastIndexOf("/") === from.length-1,
            toContents   = to.lastIndexOf("/") === to.length-1,
            here         = process.cwd(),
            srcRoot      = Path.resolve(from),
            destRoot     = Path.resolve(to),
            sources      = Glob.sync(Path.join(srcRoot, "**/*")),
            toExists     = Path.existsSync(to)
        ;
        
        log.info("cp -R " + from + " " + to);
        
        if (!fromContents && toExists) {
            destRoot = Path.join(
                destRoot,
                Path.relative(Path.dirname(srcRoot), srcRoot)
            );
            sources.unshift(from);
        }
        
        futil.mkdir_p(destRoot);
        
        // Create the directories first...
        sources.filter(function (path) {
            return FS.statSync(path).isDirectory();
        }).map(function (path) {
            return Path.relative(
                here,
                Path.resolve(destRoot, Path.relative(srcRoot, path))
            );
        }).forEach(function (path) {
            futil.mkdir_p(path);
        });
        
        // Then copy over the files
        sources.filter(function (path) {
            return FS.statSync(path).isFile();
        }).map(function (path) {
            return [
                Path.relative( // src
                    here,
                    Path.resolve(destRoot, path)
                ),
                Path.relative( // dest
                    here,
                    Path.resolve(destRoot, Path.relative(srcRoot, path))
                )
            ];
        }).forEach(function (fromTo) {
            futil.cp(fromTo[0], fromTo[1]);
        });
    },

    mv: function (from, to) {
        log.info("mv " + from + " " + to);
        FS.renameSync(from, to);
    },
    
    ln: function (from, to) {
        log.info("ln " + from + " " + to);
        FS.linkSync(from, to);
    },

    ln_s: function (from, to) {
        log.info("ln -s " + from + " " + to);
        FS.symlinkSync(from, to);
    },
    
    cat: function () {
        var args = Proteus.slice(arguments);
        log.info("cat " + args.join(" "));
        return args.map(function (path) {
            if (Array.isArray(path)) {
                return futil.cat.apply(futil, path);
            }
            return futil.read(path, "utf8");
        }).join("");
    },
    
    read: function (path, enc) {
        return FS.readFileSync.apply(FS, arguments);
    },
    
    slurp: Proteus.aliasMethod("read", futil),
    
    write: function (/*path, data, enc, mode*/) {
        var args = Proteus.slice(arguments, 0, 3),
            mode = arguments[3] || "w"
        ;
        if (mode === "a") {
            args[1] = args[1] + futil.read(args[0]);
        }
        FS.writeFileSync.apply(FS, args);
    },
    
    spit: Proteus.aliasMethod("write", futil)
});

@@ -2484,16 +2484,98 @@
    dirs.
+sort(function (a, b) %7B%0A                return b.length - a.length;%0A            %7D).
 forEach(
@@ -2607,19 +2607,16 @@
         
- //
  log.inf

Remove directories in order of longest path to shortest.