import docopt, json, md5, nre, os, osproc, sequtils, sets, strutils, tempfile

proc doStep(step: string, verbose: bool, cmd: string): tuple[output: TaintedString, exitCode: int] =
  if verbose: echo "> " & cmd
  result = execCmdEx(cmd, {poUsePath})
  if result.exitCode != 0:
    writeLine(stderr, "Failed step [" & step &
      "] Received error code: " & $result.exitCode)
    quit(1)
  if verbose: echo ""

when isMainModule:
  let doc = """
Usage:
  deploy_plans_via_ftp [options]

Options:
  -c --config <cfgFile>  Use <cfgFile> as the source of configuration.
  -h --help              Print this usage information.
  -v --verbose           Print verbose information about operations.
  -V --version           Print version information.
"""

  let args = docopt(doc, version = "deploy_plans_via_ftp 0.2.0")

  if args["--help"]: echo doc; quit()

  let verbose = args["--verbose"]
  let cfgFilePaths = @[
    $getEnv("HOME") & "/.personal-planning.config.json",
    if args["--config"]: $args["--config"] else:""]

  let cfgFilePath =
    foldl(cfgFilePaths, if len(a) > 0: a elif existsFile(b): b else: "not-exists")

  if not existsFile(cfgFilePath):
    if args["--config"]:
      quit("Cannot find config file: " & $args["--config"], 2)
    else: quit("Cannot find config file: " & cfgFilePaths[0], 2)

  var cfg: JsonNode
  try: cfg = parseFile(cfgFilePath)
  except: quit("Could not parse as json: " & cfgFilePath, 3)

  let repoUrl =
    if cfg.hasKey("repo"): cfg["repo"].str
    else: "_git@git.jdb-labs.com:jdb/personal-planning.git"

  let ftpRoot =
    if cfg.hasKey("ftp") and cfg["ftp"].hasKey("root"): cfg["ftp"]["root"].str
    else: "ftp://ftp.fastmail.com/jonathan.jdbernard.com/files/personal-planning"

  let ftpUsername =
    if cfg.hasKey("ftp") and cfg["ftp"].hasKey("username"):
      cfg["ftp"]["username"].str
    else: "jonathan@jdbernard.com"

  if not (cfg.hasKey("ftp") and cfg["ftp"].hasKey("password")):
    quit("Could not find ftp.password in config file: " & cfgFilePath, 4)

  let ftpPassword = cfg["ftp"]["password"].str

  let htmlFileRe =
    if cfg.hasKey("outputPattern"): re(cfg["outputPattern"].str)
    else: re".* (\S+)\.html?$"

  let mdFileRe =
    if cfg.hasKey("inputPattern"): re(cfg["inputPattern"].str)
    else: re".*/(\S+-plan)\.md?$"

  const subDirs = ["daily", "weekly", "monthly", "yearly"]

  var output: string
  var exitCode: int

  # Make a temprary directory
  let tempdir = mkdtemp("personal-planning-")

  # Checkout personal development repository (local clone)
  discard doStep("clone repo", verbose, "git clone " & repoUrl & " " & tempdir)

  for curDir in subDirs:
    let fullDirPath = tempdir & "/" & curDir
    let userOption = " --user '" & ftpUsername & ":" & ftpPassword & "' "
    let remoteOptions = userOption & " '" & ftpRoot & "/" & curDir & "/' "

    # Get the file manifest from the server.
    (output, exitCode) = doStep("read remote files manifest (" & curDir & ")", verbose,
      "curl " & userOption & " '" & ftpRoot & "/" & curDir & "/mf.json'")

    var manifestUpdated = false

    var manifest: JsonNode
    try: manifest = parseJson(output)
    except: manifest = newJObject()
    
    # List the daily files locally.
    let localFiles =
      sequtils.toSeq(walkDir(fullDirPath))
        .filterIt(it.kind == pcFile and
                  it.path.find(mdFileRe).isSome)
        .mapIt(it.path.find(mdFileRe).get().captures[0])
        .toSet

    # ID the files that have changed or are missing.
    for fileName in localFiles:
      let tempPath = fullDirPath & "/temp.html"
      let filePath = fullDirPath & "/" & fileName
      let localMd5 = getMD5(readFile(filePath & ".md"))
      let remoteMd5 =
        if manifest.hasKey(fileName): manifest[fileName].getStr
        else: getMD5("")

      if localMd5 == remoteMd5: continue

      # Compile the markdown into HTML
      discard doStep("compile plan file (" & fileName & ")", verbose,
        "markdown " & filePath & ".md" & " > " & tempPath)
        
      # Concatenate the HTML template to create the final HTML
      discard doStep("concatenate HTML template (" & fileName & ")", verbose,
        "cat " & tempdir & "/code/start.html " &  tempPath & " " &
                 tempdir & "/code/end.html > " & filePath & ".html")
      # Upload the new file to FastMail
      discard doStep("upload file to FastMail (" & fileName & ")", verbose,
        "curl -T '" & filePath & ".html' " & remoteOptions)

      manifest[fileName] = %($localMd5)
      manifestUpdated = true

    # Upload the new manifest
    if manifestUpdated:
      let manifestFilePath = fullDirPath & "/mf.json"
      writeFile(manifestFilePath, manifest.pretty)
      discard doStep("upload updated manifest", verbose,
        "curl -T '" & manifestFilePath & "' " & remoteOptions)

  # Delete local temp repo
  if verbose: echo "Deleting " & tempdir
  removeDir(tempdir)