/**
 * Support for implementing tools that have multiple inputs and outputs.
 *
 * @author Martin Bravenboer <martin@cs.uu.nl>
 */
module dryad/lib-ext/multi-options
strategies

  /**
   * Wraps a strategy into a command-line tool that allows a list of
   * inputs and outputs.
   *
   * @param s  List(a) -> List(b)
   */
  multi-io-wrap(s) =
    multi-io-wrap(fail, s)

  multi-io-wrap(extra-opts, s) =
    multi-io-wrap(extra-opts, system-usage, system-about, s)

  multi-io-wrap(extra-opts, usage, about, s) =
    option-wrap(extra-opts <+ multi-io-options,  usage, about, id, multi-io(s))

rules

  /**
   * Handles a command-line option that allows multiple values.
   *
   * This strategy is similar to ArgOption, but allows multiple
   * values. e.g. -i A.java B.java. The values are not allowed to
   * start with a -, since the - distinguishes values from option 
   * flags.
   *
   * Usage is identical to ArgOption. The handle-value will get
   * a list of values.
   *
   * @type  handle-value  List(String) -> _
   * @type  is-flag       String ->?
   */
  MultiArgOption(is-flag, handle-value, usage) :
    [flag | options] -> [() | rest]
    where
      <is-flag> flag

      ; let splitter =
              split-fetch-keep(where(explode-string => ['-' | _]))
              ; \ (args, next-opt, tail) -> (args, [next-opt | tail]) \
              <+ !(<id>, [])

         in <splitter> options => (args, rest)
          ; <handle-value> args
        end

  MultiArgOption(is-flag, label, usage) =
    "register-usage-info"
    ; register-usage(usage)

/**
 * Strategies that support multi-io-wrap
 */
strategies

  multi-io(s) =
    multi-input(multi-output(s))

  multi-output(s) =
    let
     in abstract-multi-output(s, single-output-to-file, single-output-to-stream)
    end
    
  single-output-to-file =
    if <get-config> "-b" then
      WriteToBinaryFile
    else
      WriteToTextFile
    end
    
  single-output-to-stream =
    if <get-config> "-b" then
      write-in-baf-to-stream
    else
      write-in-text-to-stream; <fputc> ('\n', <id>)
    end

  /**
   * @type                  List(a) -> List(b)
   * @param write-to-file   (String, b) -> ...
   * @param write-to-stream (Stream, b) -> ...
   */
  abstract-multi-output(s, write-to-file, write-to-stream) =
      s
    ; ?result
    ; if not(is-list) then
        say(!"internal error: result of main strategy is not a list")
        ; <exit> 1
      end

    ; let
          create-output-list =
            <get-config> "--multi-output"
            ; if <not(eq)> (<length> result, <length>) then
                say(!"error: you must specify the same number of outputs as inputs.")
                ; <exit> 1
              end

       in if <get-config> "--multi-output" then
            <zip> (<create-output-list>, <id>)
            ; map(write-to-file)
          else
            where(stdout-stream => stream)
            ; map(<write-to-stream> (stream, <id>))
            ; <fclose> stream
          end
       end

  multi-input(s) =
      ( <get-config> "--multi-input" <+ ![stdin()] )
    ; map(ReadFromFile)
    ; s

strategies

  multi-io-options =
      multi-input-option
    + multi-output-option
    + general-options
    + output-format-option

  multi-input-option =
    MultiArgOption("-i"
    , <set-config> ("--multi-input", <id>)
    , ( <get-config> "input-description" < id + !"Read input from files")
      ; <concat-strings> ["-i      ", <id>, "  [stdin]"]
    )

  multi-output-option =
    MultiArgOption("-o"
    , <set-config> ("--multi-output", <id>)
    , ( <get-config> "output-description" < id + !"Write output to files")
      ; <concat-strings> ["-o      ", <id>, "  [stdout]"]
    )

  output-format-option =
    Option("--binary-output"
    , where(<set-config> ("-b",()))
    , !"--binary-output     Write output in binary aterm format (baf)"
    )