return if global.window

_ = require './nodash' # imported for debounce/throttle methods
utility = require './utility'
fs = require 'fs'
np = require 'path'
ion = require '../'

# this doesn't seem to work with Object.observe callbacks
process.on 'uncaughtException', (e) ->
    console.error e.stack ? e

module.exports = exports =
    removeExtension: removeExtension = utility.removeExtension
    changeExtension: changeExtension = utility.changeExtension
    normalizePath: normalizePath = utility.normalizePath
    isPrivate: isPrivate = (path) ->
        return false unless path?
        path = normalizePath path
        result = path[0] is '_' or path.indexOf('/_') >= 0
        return result
    link: (object) ->
        # EXISTING_FILE : SYMBOLIC_LINK
        for key, value of object
            if not fs.existsSync key
                console.error "link source not found: #{key}"
                continue
            isDirectory = utility.isDirectory key
            # existing file path needs to be relative to the link path
            existingPath = np.relative value, key
            console.log "link EXISTING: #{existing}  LINK: #{value}"
    runFile: (file) ->
        if not fs.existsSync(file)
            console.warn("File not found: {{file}}")
            return

        code = utility.read(file)
        js = ion.compiler.compile(code)
        eval(js)
    runTests: do ->
        fn = (manifestFile) ->
            # convert the files to a name, moduleId map
            require('../browser/tester').spawnTests manifestFile
        return _.debounce(_.throttle(fn, 100), 2000)

    buildScriptIncludeFile: (files, base = '') ->
        files.map((x) -> "document.writeln(\"<script type='text/javascript' src='#{base}#{normalizePath x}'></script>\");").join('\n')

    getModuleId: getModuleId = (source, packageObject) ->
        # new getModuleId behavior
        if typeof source is 'string'
            root = source
            path = packageObject
            return normalizePath removeExtension np.join root, path
        # old getModuleId behavior
        if packageObject?
            srcDirectory = packageObject.directories.src
            if Array.isArray(srcDirectory)
                for dir in srcDirectory
                    if source.path.startsWith(dir)
                        srcDirectory = dir
                        break
                if Array.isArray(srcDirectory)
                    throw new Error("Couldn't find correct source directory for " + source)
            return normalizePath removeExtension np.join packageObject.name, np.relative(srcDirectory, source.path)
        else
            return null

    showPrettyError: showPrettyError = (e, filename, input) ->
        message = e.message = syntaxErrorToString e, filename, input
        beep = '\x07'
        console.error message + beep

    syntaxErrorToString: syntaxErrorToString = (e, filename, code) ->
        return e.toString() unless e.location?
        # lifted from https://github.com/jashkenas/coffee-script/blob/master/src/helpers.coffee
        repeat = (str, n) ->
            # Use clever algorithm to have O(log(n)) string concatenation operations.
            res = ''
            while n > 0
                res += str if n & 1
                n >>>= 1
                str += str
            res
        {first_line, first_column, last_line, last_column} = e.location
        last_line ?= first_line
        last_column ?= first_column

        codeLine = code.split('\n')[first_line]

        start    = first_column
        end      = if first_line is last_line then last_column + 1 else codeLine.length
        marker   = repeat(' ', start) + repeat('^', end - start)

        colorize = (str) -> "\x1B[1;31m#{str}\x1B[0m"
        codeLine = codeLine[...start] + colorize(codeLine[start...end]) + codeLine[end..]
        marker   = colorize marker

        """
        #{filename}:#{first_line + 1}:#{first_column + 1}: error: #{e.originalMessage ? e.message}

        #{codeLine}
        #{marker}
        """

    # this compiles coffeescript if needed, but does not actually write the result.
    compileCoffeeScript: compileCoffeeScript = (source, packageObject) ->
        return if source.modified is 0
        moduleId = if typeof packageObject is 'string' then packageObject else getModuleId source, packageObject

        input = source.read()
        filename = source.path

        cs = require 'coffeescript'
        try
            # console.log "Compile: #{filename}"
            compiled = cs.compile input, options = {bare: true}
            # console.log 'sourceMap: ' + typeof options.sourceMap
            compiled = addBrowserShim compiled, moduleId, input
            return compiled
        catch e
            showPrettyError e, filename, input
            return

    # this compiles a pegjs parser and returns the result.  Does not write to the target file.
    compilePegjs: compilePegjs = (source, packageObject) ->
        return if source.modified is 0
        moduleId = if typeof  packageObject is 'string' then packageObject else getModuleId source, packageObject
        filename = source.path
        try
            peg = require 'pegjs'
            # console.log "Building: #{filename}"
            input = source.read()
            parser = peg.buildParser input, {cache:true,output:"source"}
            source = "module.exports = " + parser
            source = addBrowserShim source, moduleId
            return source
        catch e
            console.error e

    # this compiles a pegs parser and returns the result.  Does not write to the target file.
    compilePegs: compilePegs = (source, packageObject) ->
        return if source.modified is 0
        moduleId = if typeof  packageObject is 'string' then packageObject else getModuleId source, packageObject
        filename = source.path
        try
            peg = require('pegs').bootstrap
            # console.log "Building: #{filename}"
            input = source.read()
            parser = peg.parse(input)
            source = parser
            source = addBrowserShim source, moduleId
            return source
        catch e
            console.error e

    compileIon: compileIon = (source, packageObject) -> compileIonWithSourceMap(source, packageObject)?[0]

    # this compiles ion and returns the result.  Does not write to the target file.
    compileIonWithSourceMap: compileIonWithSourceMap = (source, packageObject) ->
        if source.modified is 0
            return
        moduleId = if typeof  packageObject is 'string' then packageObject else getModuleId source, packageObject
        filename = source.path
        try
            # console.log "Compile: #{filename}"
            ionCompiler = require '../compiler'
            input = source.read()
            [source,map] = ionCompiler.compileWithSourceMap(input, {source:moduleId + ".ion" ,sourceMap:filename.split(/[\/\\]/).pop()})
            source = addBrowserShim source, moduleId, input
            return [source,map]
        catch e
            console.error(String(e))

    shimJavascript: shimJavascript = (source, packageObject) ->
        return if source.modified is 0
        moduleId = if typeof packageObject is 'string' then packageObject else getModuleId source, packageObject
        result = addBrowserShim source.read(), moduleId
        return result

    addBrowserShim: addBrowserShim = (sourceText, moduleId, originalSource) ->
        # don't shim if the source starts with a shebang
        if originalSource?.substring(0, 2) is "#!"
            stripValue = "#!browser"
            if sourceText.startsWith(stripValue)
                sourceText = sourceText.substring(stripValue.length)
            return sourceText
        # make sure the javascript isn't already shimmed, so we don't shim it twice.
        if moduleId?
            safeId = "_" + moduleId.replace(/[^a-zA-Z0-9]/g, '_') + "_"
            sourceText =
                """
                void (function(){var #{safeId} = function(module,exports,require){#{sourceText}
                  }
                  if (typeof require === 'function') {
                    if (require.register)
                      require.register('#{moduleId}',#{safeId});
                    else
                      #{safeId}.call(this, module, exports, require);
                  }
                  else {
                    #{safeId}.call(this);
                  }
                }).call(this)
                """
        return sourceText

for name in ["ModuleBuilder", "WebsiteBuilder", "File", "Directory", "utility"]
    do (name) ->
        Object.defineProperty(exports, name, {enumerable: true, get: -> require("./" + name)})

