/** 
 * This is a simple Box to text converter.
 * It formats H, HV, A/R and ALT boxes.
 *
 * Authors:
 * - Merijn de Jonge <mdjonge@cwi.nl>
 * - Eelco Visser <visser@acm.org>
 * - Joost Visser <jvisser@cwi.nl>
 * - Martin Bravenboer <martin.bravenboer@gmail.com>
 *
 * Eelco Visser (April 5, 2001)
 * - repaired bug in handling of H boxes
 * - refactored handling of H case using thread-map
 *
 * Martin Bravenboer (December 26/27/28, 2002)
 * - implemented HV and A/R boxes
 * - refactored many strategy defs
 */
module stratego/gpp/BoxToText
imports
  Box
  libstratego-lib
  stratego/gpp/Util

strategies

  /**
   * @todo Dump the exit
   */
  try-abox2text(|pos, width) =
    let local-abox2text(|pos) =
          abox2text(local-abox2text | pos, width)
          <+ log(|Error(), "invalid box: ", <id>)
             ; <exit> 1

     in local-abox2text(|0)
    end
    ; Fst

  abox2text(| width) =
    let local-abox2text(|pos) =
          abox2text(local-abox2text | pos, width)

     in local-abox2text(|0)
    end
    ; Fst

strategies

  /**
   * Use different names to make profiling easier.
   */
  abox2text(rec : t * t -> t | xpos, width) =
      abox2text-HV(rec | xpos, width)
    + abox2text-HV(rec | xpos, width)
    + abox2text-H(rec | xpos, width)
    + abox2text-V(rec | xpos, width)
    + abox2text-A(rec | xpos, width)
    + abox2text-R(rec | xpos, width)
    + abox2text-S(rec | xpos, width)
    + abox2text-C(rec | xpos, width)
    + abox2text-FBOX(rec | xpos, width)
    + abox2text-R(rec | xpos, width)
    + abox2text-ALT(rec | xpos, width)
    + abox2text-list(rec | xpos, width)

/**
 * This encoding of HV boxes is exponential; not so bad for formatting
 * paragraphs, but not tractable for formatting nested expressions.
 */
strategies

  /**
   * HV
   *
   * @todo Handle vs and is options
   * @todo Substract last hspace from xpos
   * @type (Box, Int) -> (Strs, Int)
   */
  abox2text-HV(rec : t * t -> t | xpos, width) =
    ?HV(sopt, bs)

    ; <gpp-hs-chars> sopt => hs-chars
    ; <implode-string> hs-chars => hs
    ; <gpp-vs-text> sopt => vs
    ; <length> hs-chars => hsl
    ; <add> (xpos, <gpp-is-length> sopt) => left-xpos

    ; !(bs, [([], xpos)])
    ; foldl(
        gpp-hv-add-to-hbox(rec | hsl, width)
        <+ gpp-hv-in-new-vbox(rec | hsl, left-xpos, width)
      )
    ; reverse; map((reverse, id))
    ; map((<separate-by> (hs, <id>), id))
    ; gpp-format-vbox(|sopt, xpos)

  /**
   * @type Box * [Int * [String]] -> [Int * [String]]
   */
  gpp-hv-add-to-hbox(rec : t * t -> t | hsl, width) :
    (box, [(strs, xpos) | vbs ]) -> [([ str | strs ], <add> (new-xpos, hsl)) | vbs]
    where
      <rec(|xpos)> box => (str, new-xpos)
      ; (<leq> (new-xpos, width) + (!width => 0))
        
  gpp-hv-in-new-vbox(rec : t * t -> t | hsl, left-xpos, width) :
    (box, vbs) -> [([str], <add> (new-xpos, hsl)) | vbs]
    where
      <rec(|left-xpos)> box => (str, new-xpos)

strategies

  /**
   * H
   *
   * @type (Box, Int) -> (Strs, Int)
   */       
  abox2text-H(rec : t * t -> t | xpos, width) =
    ?H(sopt, bs)

    ; <gpp-hs-chars> sopt => hschars
    ; <implode-string> hschars => hs
    ; <length> hschars => hsl
    ; <filter(gpp-is-real-hbox)> bs

    ; let rec-add(|pos) =
            rec(|pos); (id, <add> (hsl, <id>))

       in gpp-thread-map(rec-add | xpos)
        ; (separate-by(|hs), id)
        ; try((not([]), <subt> (<id>, hsl)))
      end

  gpp-thread-map(s: t * t -> t | t) :
    [] -> ([], t)

  gpp-thread-map(s : t * t -> t | t) :
    [x | xs] -> ([y | ys], t'')
    where
      <s(|t)> x => (y, t')
      ; <gpp-thread-map(s | t')> xs => (ys, t'')

strategies

  /**
   * V
   *
   * @type (Box, Int) -> (Strs, Int)
   */
  abox2text-V(rec : t * t -> t | xpos, width) =
    ?V(sopt, bs)

    ; <add> (xpos, <gpp-is-length> sopt) => left-xpos
    ; <filter(gpp-is-real-vbox)> bs
    ; if ?[] then
        !([], xpos)
      else
        gpp-do-vbox-children(rec | xpos, left-xpos)
        ; gpp-format-vbox(|sopt, xpos)
      end

  /**
   * @type Int * Int * [BOX] -> [Strings * Int]
   */
  gpp-do-vbox-children(rec : t * t -> t | xpos, left-xpos) :
    [b | bs] -> [<rec(|xpos)> b | <map(rec(|left-xpos))> bs]

strategies

  /**
   * A
   * 
   * The first two rules are to handle empty alignment operators, i.e., A
   * boxses with [] or [[]] as third argument.
   * Without subboxes ([] as third argument)
   */
  abox2text-A(rec : t * t -> t | xpos, width) :
    A(AOPTIONS(aopt), sopt, []) -> ([], xpos)

  /**
   * Without subboxes ([[]] as third argument)
   */
  abox2text-A(rec : t * t -> t | xpos, width) :
    A(AOPTIONS(aopt), sopt, [[]]) -> ([], xpos)

  abox2text-A(rec : t * t -> t | xpos, width) =
    ?A(AOPTIONS(aopt), sopt, bs)

    ; !bs
    ; gpp-construct-rows
    ; map(?R(_, <id>))
    ; gpp-to-matrix(|<length> aopt)
    ; matrix-transpose

    ; reverse
    ; add-indices
    ; reverse
    ; ?columns

    ; <zip> (aopt, columns) => column-tuples
    ; <foldl(gpp-do-A-column(rec))> (column-tuples, (xpos, [])) => (last-xpos, str-columns)

    ; <reverse; matrix-transpose> str-columns
    ; map(!(<id>, last-xpos))
    ; gpp-format-vbox(|sopt, xpos)

  /**
   * (A-OPTION * (Int * [BOX])) * (Int * [[Strs]]) -> Int * [[Strs]]
   */
  gpp-do-A-column(rec : t * t -> t) :
    ((aopt, (index, bs)), (xpos, columns)) -> (<add> (<string-length> hs, max-xpos), [ new-column | columns ])
     where
         < map(rec(|xpos))
         ; unzip
         ; (map(try(flatten-list; concat-strings)), list-max)
         > bs => (strings, max-xpos)
       ; <subt> (max-xpos, xpos) => max-width
       ; (<gt> (index, 1) < <gpp-SOpts-of-alignment; gpp-hs-text> aopt + !"") => hs
       ; <map(
            <gpp-align-column> (index, aopt, (' ', <id>, max-width))
          ; <conc-strings> (<id>, hs)
         )> strings => new-column

  gpp-align-column : (_, AR(_), t) -> <align-right>  t
  gpp-align-column : (_, AC(_), t) -> <align-center> t
  gpp-align-column : (1, AL(_), (_, s, _)) -> s
  gpp-align-column : (i, AL(_), t) -> <align-left>   t
    where <gt> (i, 1)

      // TODO: align cannot handle newlines in cells
      // TODO: remove flatten-list and implement our own align strategies

  /**
   * Make sure that all R boxes have the same number of arguments. If
   * not, add empty boxes ([]) to make equal lengths.
   */
  gpp-to-matrix(|nr-cols) =
    map(gpp-FitToMax(|nr-cols))
  
  gpp-FitToMax(|nr-cols) =
    ?lst
    ; where(length => my-length)
    ; <subt> (nr-cols, my-length) => diff
    ; ( <gt> (<id>, 0)

        // Create new (empty) columns
        ; <copy> (diff, []) => l

        // Create row with correct number of columns
        ; <conc> (lst, l)

     <+ ?0
        ; !lst

     <+ // A row with to many columns cause a fatal error, because
        // since formatting information is missing, we do not know
        // how to format these extra columns
        log(|Error(), "Too many columns in row: ", lst)
        ; <exit> 1
      )

  // R, not as child of an A
  abox2text-R(rec : t * t -> t | xpos, width) :
    R(sopt, bs) -> <rec(|xpos)> HV(sopt, bs)

  /**
   * S
   */
  abox2text-S(rec : t * t -> t | xpos, width) :
    S(s) -> (s, <add> (xpos, <gpp-rel-string-length(|xpos)> s))

  abox2text-C(rec : t * t -> t | xpos, width) :
    C(_,[S(s)]) -> (s, xpos)

  abox2text-FBOX(rec : t * t -> t | xpos, width) :
    FBOX(_,b) -> <rec(|xpos)> b

  abox2text-R(rec : t * t -> t | xpos, width) :
    R(sopt, bs) -> <rec(|xpos)> HV(sopt, bs)
      
  abox2text-ALT(rec : t * t -> t | xpos, width) =
    ?ALT(a1, a2)
    ; <rec(|xpos)> a1 => r1@(s, xpos2)
    ; if <leq> (xpos2, width) then
        !r1
      else
        <rec(|xpos)> a2
      end
         
  abox2text-list(rec : t * t -> t | xpos, width) :
    [b] -> <rec(|xpos)> b

  abox2text-list(rec : t * t -> t | xpos, width) :
    [] -> ("", xpos)

strategies

  /**
   * @type List(S-Option) -> String
   */
  gpp-hs-text =
    gpp-hs-chars
    ; implode-string

  gpp-hs-chars =
    gpp-hs-length
    ; gpp-copy(|' ')

  /**
   * @type List(S-Option) -> String
   */
  gpp-vs-text =
    gpp-vs-chars
    ; implode-string

  gpp-vs-chars =
    gpp-vs-chars(|[])

  gpp-vs-chars(|cont) =
    gpp-vs-length
    ; inc
    ; gpp-copy(|'\n', cont)

  /**
   * @type List(S-Option) -> String
   */
  gpp-is-text =
    gpp-is-chars
    ; implode-string

  gpp-is-chars =
    gpp-is-chars(|[])

  gpp-is-chars(|cont) =
    gpp-is-length
    ; gpp-copy(|' ', cont)

strategies

  gpp-copy(|t) =
    gpp-copy(|t, [])

  gpp-copy(|t, ts) =
    \ 0 -> ts \
    <+ ?n
       ; <subt> (n, 1)
       ; gpp-copy(|t, [t | ts])

strategies

  /**
   * @type Box ->? Box
   */
  gpp-is-real-vbox =
    not(
      ?[]
    + ?_#([_,[]])
    + ?_#([_,[[]]])
    )

  /**
   * @type Box ->? Box
   */
  gpp-is-real-hbox =
    not(?[] + ?H([],[]))

strategies

  /**
   * @param List(S-Option)
   * @param Int
   * @type List((Strings, Int)) -> (List(Strings), Int)
   */
  gpp-format-vbox(|sopt, xpos) :
    strs -> (result, last-xpos)
    where
      <gpp-vertical-sep(|xpos)> sopt => sep
      ; <last> strs => (_, last-xpos)
      ; !strs
      // separate-by + Fst
      ; ([] + [Fst |
          rec x(
            []
          + [Fst | x]
            ; ![sep | <id>]
          )])
      ; ?result

  /**
   * @param Int
   * @type List(S-Option) -> String
   */
  gpp-vertical-sep(|xpos) =
    ?sopt
    ; <gpp-vs-chars(|
         <gpp-is-chars(|
            <gpp-copy(|' ')> xpos
         )> sopt
      )> sopt
    ; implode-string