(require! 'util 'path 'fs)

(var sibilant (#(...args) (sibilant.entry ...args))
     error    (#(str) (throw str))
     inspect  util.inspect)

(set module 'exports sibilant)
(set sibilant
     'dir (process.cwd)
     'dependencies {})

(def sibilant.relative-dir-and-file (file-name)
     (|> [ (path.dirname file-name) file-name ]
         (.map (#> (path.relative (process.cwd) #0)))))
(var relative-dir-and-file sibilant.relative-dir-and-file)

(def sibilant.record-dependency (from to)
     (default (get sibilant.dependencies from) [])
     (|> sibilant.dependencies
         (get from)
         (.push to)))

(def sibilant.flat-dependencies ()
     (|> sibilant.dependencies
         values
         flatten))

(def sibilant.entry (source options)
     (when (hash? source)
           (assign options source
                   source undefined))

     (default options {})
     (when (string? source)
           (set options 'source source))

     (var { map source file quote-keys json } options)
     (default map: false,
              quote-keys: json)

     (when (and (exists? file)
                (not (exists? source)))
           (var [relative-dir relative-file] (relative-dir-and-file file))
           (assign source (or (get sibilant.source-cache relative-file)
                              (|> file
                                  (fs.read-file-sync 'utf8)
                                  sibilant.strip-shebang))))

     (when file
           (var [relative-dir relative-file] (relative-dir-and-file file))
           (set sibilant.source-cache relative-file source))

     (with-file file
       (#>
        (var quote-state sibilant.quote-hash-keys)
        (when quote-keys
              (set sibilant 'quote-hash-keys true))

        (var ast (|> source parse restructure)
             output (transpile ast)
             sourcemap (when map (*sourcemapper output))
             js (output-formatter output)
             dependencies (sibilant.flat-dependencies))

        (when quote-keys
              (set sibilant 'quote-hash-keys quote-state))

        { ast: ast,
          output: output,
          js: js,
          map: sourcemap,
          dependencies: dependencies })))

(def sibilant.transpile-file (file-name)
     (with-file file-name

                (#>
                 (var source (|> file-name
                                 (fs.read-file-sync 'utf8)
                                 sibilant.strip-shebang)

                      [relative-dir relative-file] (relative-dir-and-file file))

                 (set sibilant.source-cache relative-file source)

                 (|> source parse restructure transpile))))

(def with-file (file-name fn)
     (if file-name
         (with-dir-and-file ...(sibilant.relative-dir-and-file file-name) (#> (fn file-name)))
         (fn)))

(def sibilant.sourcemap-file (file-name)
     (with-file file-name
                (#->
                 (fs.read-file-sync 'utf8)
                 sibilant.strip-shebang
                 sourcemap)))


(set require.extensions ".sibilant"
     (#(module filename)
       (|> (sibilant {file filename})
           (get 'js)
           (module._compile filename))))

(set require.extensions ".son"
     (#(module filename)
       (var content (|> (sibilant { file: filename,
                                    json: true })
                        (get 'js))
            json (try (JSON.parse content)
                      (do (console.error "could not parse:\n" content)
                          (throw e))))
       
       (set module 'exports json)))


(def sibilant.package-info ()
     (|> **dirname
         (concat "/../package.json")
         (fs.read-file-sync 'utf8)
         JSON.parse))

(def sibilant.version-string ()
     (var package (sibilant.package-info))
     (concat package.name " version " package.version
             "\n(at " (path.join **dirname "..") ")"))


(def sibilant.include (file)
     (unless (match-regex? file "\\.(sibilant|son)$")
           (assign file (concat file ".sibilant")))

     (when (match-regex? file "^\\.\\.?/")
           (assign file (path.resolve sibilant.dir file)))

     (var resolved-file
          (try (require.resolve file)
               (error ("Failed to resolve file for inclusion: " file))))

     (sibilant.record-dependency sibilant.file file)

     (|> (sibilant {file resolved-file})
         (get 'output)))
