{ stdenv
, lib
, fetchurl
, clojure
, gnutar
, nodejs
, jre
, unzip
, nodePackages
, xcbuild
, python
, openssl
, pkgs
, fetchgit
, darwin
}:
let
  version = "1.10.1";
  nodeVersion = "11.13.0";
  nodeSources = fetchurl {
    url = "https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}.tar.gz";
    sha256 = "1cjzjbshxnysxkvbf41p3m8298cnhs9kfvdczgvvvlp6w16x4aac";
  };
  lumo-internal-classpath = "LUMO__INTERNAL__CLASSPATH";

  # as found in cljs/snapshot/lumo/repl.cljs
  requireDeps = '' \
      cljs.analyzer \
      cljs.compiler \
      cljs.env \
      cljs.js \
      cljs.reader \
      cljs.repl \
      cljs.source-map \
      cljs.source-map.base64 \
      cljs.source-map.base64-vlq \
      cljs.spec.alpha \
      cljs.spec.gen.alpha \
      cljs.tagged-literals \
      cljs.tools.reader \
      cljs.tools.reader.reader-types \
      cljs.tools.reader.impl.commons \
      cljs.tools.reader.impl.utils \
      clojure.core.rrb-vector \
      clojure.core.rrb-vector.interop \
      clojure.core.rrb-vector.nodes \
      clojure.core.rrb-vector.protocols \
      clojure.core.rrb-vector.rrbt \
      clojure.core.rrb-vector.transients \
      clojure.core.rrb-vector.trees \
      clojure.string \
      clojure.set \
      clojure.walk \
      cognitect.transit \
      fipp.visit \
      fipp.engine \
      fipp.deque \
      lazy-map.core \
      lumo.pprint.data \
      lumo.repl \
      lumo.repl-resources \
      lumo.js-deps \
      lumo.common '';

  compileClojurescript = (simple: ''
    (require '[cljs.build.api :as cljs])
    (cljs/build \"src/cljs/snapshot\"
      {:optimizations      ${if simple then ":simple" else ":none"}
       :main               'lumo.core
       :cache-analysis     true
       :source-map         false
       :dump-core          false
       :static-fns         true
       :optimize-constants false
       :npm-deps           false
       :verbose            true
       :closure-defines    {'cljs.core/*target*       \"nodejs\"
                            'lumo.core/*lumo-version* \"${version}\"}
       :compiler-stats     true
       :process-shim       false
       :fn-invoke-direct   true
       :parallel-build     false
       :browser-repl       false
       :target             :nodejs
       :hashbang           false
       ;; :libs               [ \"src/cljs/bundled\" \"src/js\" ]
       :output-dir         ${if simple
  then ''\"cljstmp\"''
  else ''\"target\"''}
       :output-to          ${if simple
  then ''\"cljstmp/main.js\"''
  else ''\"target/deleteme.js\"'' }})
  ''
  );


  cacheToJsons = ''
    (import [java.io ByteArrayOutputStream FileInputStream])
    (require '[cognitect.transit :as transit]
             '[clojure.edn :as edn]
             '[clojure.string :as str])

    (defn write-transit-json [cache]
      (let [out (ByteArrayOutputStream. 1000000)
            writer (transit/writer out :json)]
        (transit/write writer cache)
        (.toString out)))

    (defn process-caches []
      (let [cache-aot-path      \"target/cljs/core.cljs.cache.aot.edn\"
            cache-aot-edn       (edn/read-string (slurp cache-aot-path))
            cache-macros-path   \"target/cljs/core\$macros.cljc.cache.json\"
            cache-macros-stream (FileInputStream. cache-macros-path)
            cache-macros-edn    (transit/read (transit/reader cache-macros-stream :json))
            caches              [[cache-aot-path cache-aot-edn]
                                 [cache-macros-path cache-macros-edn]]]
        (doseq [[path cache-edn] caches]
          (doseq [key (keys cache-edn)]
            (let [out-path (str/replace path #\"(\.json|\.edn)\$\"
                             (str \".\" (munge key) \".json\"))
                  tr-json  (write-transit-json (key cache-edn))]
              (spit out-path tr-json))))))

    (process-caches)
  '';

  trimMainJsEnd = ''
    (let [string (slurp \"target/main.js\")]
      (spit \"target/main.js\"
        (subs string  0 (.indexOf string \"cljs.nodejs={};\"))))
  '';


  cljdeps = import ./deps.nix { inherit pkgs; };
  classp = cljdeps.makeClasspaths {
    extraClasspaths = [ "src/js" "src/cljs/bundled" "src/cljs/snapshot" ];
  };


  getJarPath = jarName: (lib.findFirst (p: p.name == jarName) null cljdeps.packages).path.jar;
in
stdenv.mkDerivation {
  inherit version;
  pname = "lumo";

  src = fetchgit {
    url = "https://github.com/anmonteiro/lumo.git";
    rev = "${version}";
    sha256 = "12agi6bacqic2wq6q3l28283badzamspajmajzqm7fbdl2aq1a4p";
  };

  buildInputs = [
    nodejs
    clojure
    jre
    unzip
    python
    openssl
    gnutar
    nodePackages."lumo-build-deps-../interpreters/clojurescript/lumo"
  ]
  ++ lib.optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [
    ApplicationServices
    xcbuild
  ]
  );

  patches = [ ./no_mangle.patch ./mkdir_promise.patch ];

  postPatch = ''
    substituteInPlace $NIX_BUILD_TOP/lumo/vendor/nexe/exe.js \
      --replace 'glob.sync(dir + "/*")' 'glob.sync(dir + "/../*")'
  '';

  buildPhase = ''
       # Copy over lumo-build-deps environment
    rm yarn.lock
    cp -rf ${nodePackages."lumo-build-deps-../interpreters/clojurescript/lumo"}/lib/node_modules/lumo-build-deps/* ./

    # configure clojure-cli
    mkdir ./.cpcache
    export CLJ_CONFIG=`pwd`
    export CLJ_CACHE=`pwd`/.cpcache

    # require more namespaces for cljs-bundle
    sed -i "s!ns lumo.core! \
              ns lumo.core  \
               (:require ${requireDeps}) \
               (:require-macros [clojure.template :as temp] \
                                [cljs.test :as test])!g" \
              ./src/cljs/snapshot/lumo/core.cljs

    # Step 1: compile clojurescript with :none and :simple
    ${clojure}/bin/clojure -Scp ${classp} -e "${compileClojurescript true}"
    ${clojure}/bin/clojure -Scp ${classp} -e "${compileClojurescript false}"
    cp -f cljstmp/main.js target/main.js
    ${clojure}/bin/clojure -Scp ${classp} -e "${trimMainJsEnd}"

    # Step 2: sift files
    unzip -o ${getJarPath "org.clojure/clojurescript"} -d ./target
    unzip -j ${getJarPath "org.clojure/clojure"} "clojure/template.clj" -d ./target/clojure
    unzip -o ${getJarPath "org.clojure/google-closure-library"} -d ./target
    unzip -o ${getJarPath "org.clojure/google-closure-library-third-party"} -d ./target
    unzip -o ${getJarPath "org.clojure/tools.reader"} -d ./target
    unzip -o ${getJarPath "org.clojure/test.check"} -d ./target
    cp -rf ./src/cljs/bundled/lumo/* ./target/lumo/
    cp -rf ./src/cljs/snapshot/lumo/repl.clj ./target/lumo/
    # cleanup
    mv ./target/main.js ./target/main
    rm ./target/*\.js
    mv ./target/main ./target/main.js
    rm ./target/AUTHORS
    rm ./target/LICENSE
    rm ./target/*.edn
    rm ./target/*.md
    rm -rf ./target/css
    rm -rf ./target/META-INF
    rm -rf ./target/com
    rm -rf ./target/cljs/build
    rm -rf ./target/cljs/repl
    rm  ./target/cljs/core\.cljs\.cache.aot\.json
    rm  ./target/cljs/source_map\.clj
    rm  ./target/cljs/repl\.cljc
    rm  ./target/cljs/externs\.clj
    rm  ./target/cljs/closure\.clj
    rm  ./target/cljs/util\.cljc
    rm  ./target/cljs/js_deps\.cljc
    rm  ./target/cljs/analyzer/utils\.clj
    rm  ./target/cljs/core/macros\.clj
    rm  ./target/cljs/compiler/api.clj
    rm  ./target/goog/test_module*
    rm  ./target/goog/transpile\.js
    rm  ./target/goog/base_*
    find ./target -type f -name '*.class' -delete
    find ./target -type d -empty -delete

    # Step 3: generate munged cache jsons
    ${clojure}/bin/clojure -Scp ${classp} -e "${cacheToJsons}"
    rm  ./target/cljs/core\$macros\.cljc\.cache\.json


    # Step 4: Bunde javascript
    NODE_ENV=production node scripts/bundle.js
    node scripts/bundleForeign.js

    # Step 5: Backup resources
    cp -R target resources_bak

    # Step 6: Package executeable 1st time
    # fetch node sources and copy to palce that nexe will find
    mkdir -p tmp/node/${nodeVersion}
    cp ${nodeSources} tmp/node/${nodeVersion}/node-${nodeVersion}.tar.gz
    tar -C ./tmp/node/${nodeVersion} -xf ${nodeSources} --warning=no-unknown-keyword
    mv ./tmp/node/${nodeVersion}/node-v${nodeVersion}/* ./tmp/node/${nodeVersion}/
    rm -rf ${lumo-internal-classpath}
    cp -rf target ${lumo-internal-classpath}
    node scripts/package.js ${nodeVersion}
    rm -rf target
    mv ${lumo-internal-classpath} target

    # Step 7: AOT Macros
    sh scripts/aot-bundle-macros.sh

    # Step 8: Package executeable 2nd time
    node scripts/package.js ${nodeVersion}
  '';

  dontStrip = true;

  installPhase = ''
    mkdir -p $out/bin
    cp build/lumo $out/bin
  '';

  meta = {
    description = "Fast, cross-platform, standalone ClojureScript environment";
    longDescription = ''
      Lumo is a fast, standalone ClojureScript REPL that runs on Node.js and V8.
      Thanks to V8's custom startup snapshots, Lumo starts up instantaneously,
      making it the fastest Clojure REPL in existence.
    '';
    homepage = "https://github.com/anmonteiro/lumo";
    license = stdenv.lib.licenses.epl10;
    maintainers = [ stdenv.lib.maintainers.hlolli ];
    platforms = stdenv.lib.platforms.linux ++ stdenv.lib.platforms.darwin;
  };
}
