 assert = require 'assert'
 { lstatSync, Stats } = require 'fs'
 _ = require 'lodash'
+{ execSync } = require 'child_process'
+isExe = require 'is-executable'
 
 logger = require './logger'
 styler = require './styler'

    try
        result = fn args...
    catch error
        printError error, description
        process.exit exitCode if exit is yes

    result

# Same as attempt, but calls the function with name 'fnName' of the module shelljs
@attemptShell = (fnName, args = [], { exit = yes, exitCode = 1, description = "" } = {}) ->
    assert _.isFunction(shelljs[fnName]), "Invalid shell function name" # SANITY-CHECK

    args = [args] unless _.isArray args

    result = shelljs[fnName](args...)

    if (error = shelljs.error())?
        printError error, description
        process.exit exitCode if exit is yes

    result

# Tells whether 'that' has a modification time older than 'other'
# Assumes arguments are Stat objects unless they're strings
@hasLaterModificationTime = (that, other) =>
    [that, other] = [that, other].map((file) =>
        if _.isString file
            @attempt lstatSync, file, description: "Getting #{file} info"
        else
            file
    )

    assert _.every([that, other], (elem) -> elem instanceof Stats) # SANITY-CHECK

    thatDate  = new Date(that.mtime)
    otherDate = new Date(other.mtime)
    thatDate.getTime() > otherDate.getTime()

# Testing Code
###


normalizeError = (err) -> err.toString().replace("Error: ", "")

handleError = (error, {
                        exit = yes, # Whether the process may exit on non-allowed error
                        allowedErrors = [] # List of allowed error codes,
                                           # returns null immediately if
                                           # error.code is in this array
                        exitCode = 0,  # if set to 0 defaults to (error.errno ? 1)
                        description = "" # String that will appear before the error text
                        printError = yes # Whether to print the error or not
                      } = {}) ->
    return null if error.code? and error.code in allowedErrors

    errorMsg =
        if description.length
            styler.id(description) + ": " + normalizeError(error)
        else
            normalizeError(error)

    logger.e errorMsg, showStack: no if printError

    exitCode = error.errno ? 1 if exitCode is 0
    process.exit exitCode if exit is yes

    error.toString = -> errorMsg
    error.isError = yes
    error


# Attemps to call a function with a given array of arguments.
# If the call throws a descriptive error is shown and the program exits unless
# otherwise specified
@attempt = (fn, args = [], options) =>
    assert _.isFunction(fn), "utils.attempt argument is not a function" # SANITY-CHECK

    args = [args] unless _.isArray args
