 
   ## Examples
 
-    iex(1)> Wadler.nest(5, Wadler.concat(Wadler.line, Wadler.text("6")))
-    Wadler.NEST[indent: 5, rest: Wadler.CONCAT[left: LINE, right: Wadler.TEXT[string: "6"]]]
+    iex(1)> Wadler.nest(5, Wadler.concat(Wadler.break, Wadler.text("6")))
+    Wadler.DocNest[indent: 5, doc: Wadler.DocCons[left: Wadler.DocBreak, right: Wadler.DocText[str: "6"]]]
 
     iex(2)> IO.puts Wadler.pretty(80, v(1))
     

    :ok

  """
  @spec nest(non_neg_integer, docentity) :: NEST.t
  def nest(i, x) when is_integer(i), do: NEST[indent: i, rest: x]

  @doc """
  Document entity representation of a text.

    B
    :ok
  """
  @spec group(doc) :: DocGroup.t
  def group(d), do: DocGroup[doc: d]

  # Helpers
  @doc """
  Inserts a mandatory single space between two document entities.

  ## Examples
  
    iex(1)> Wadler.space Wadler.text("Hughes"), Wadler.text("Wadler")
    Wadler.DocCons[
      left:  Wadler.DocText[str: "Hughes"], 
      right: Wadler.DocCons[
        left:  Wadler.DocText[str: " "],
        right: Wadler.DocText[str: "Wadler"]
      ]
    ] 

  """
  @spec space(doc, doc) :: DocCons.t
  def space(x, y), do: concat(x, concat(text(" "), y))
  
  @doc """
  Inserts a mandatory linebreak between two document entities.

  ## Examples

    iex(1)> Wadler.line Wadler.text("Hughes"), Wadler.text("Wadler")
    Wadler.DocCons[
      left:  Wadler.DocText[str: "Hughes"], 
      right: Wadler.DocCons[
        left:  Wadler.DocText[str: "\n"],
        right: Wadler.DocText[str: "Wadler"]
      ]
    ]
  """
  @spec line(doc, doc) :: DocCons.t
  def line(x, y), do: concat(x, concat(text(newline), y))

  @doc """
  Folds a list of document entities into a document entity
  using a function that is passed as the first argument.

  ## Example

    iex(1)> [Wadler.text("A"), Wadler.text("B")] 
    [Wadler.DocText[string: "A"], Wadler.DocText[string: "B"]]
    
    iex(2)> Wadler.folddoc(fn(x,y) -> Wadler.concat(x, Wadler.concat(Wadler.text("!"), y)) end, v(1))
    Wadler.CONCAT[
      left: Wadler.DocText[str: "A"], 
      right: Wadler.DocCons[
        left: Wadler.DocText[string: "!"], 
        right: Wadler.DocText[string: "B"]
      ]
    ]
    
  """
  @spec folddoc( ((doc, [doc]) -> doc), [doc]) :: doc 
  def folddoc(_, []), do: empty
  def folddoc(_, [doc]), do: doc
  def folddoc(f, [d|ds]), do: f.(d, folddoc(f, ds))

  @doc """
  Folds a list of document entities with space/2 function.
  """
  @spec spread(doc) :: doc
  def spread(doc), do: folddoc(fn(x, d) -> space(x, d) end, doc)

  @doc """
  Folds a list of document entities with line/2 function.
  """
  @spec stack(doc) :: doc
  def stack(doc),  do: folddoc(fn(x, d) -> line(x, d) end, doc)

  @doc """
  Surrounds a document with characters.
  Puts the document between left and right enclosing and nests it using `group`.
  """
  @spec surround(binary, doc, binary) :: doc
  def surround(left, doc, right) do
    group(
      concat(
             text(left), 
             concat(
                    nest(2, concat(break, doc)),
                    concat(break, text(right))
             )
      )
    )
  end  

  @spec surround(doc, binary) :: doc
  def surround(doc, str) do
    surround(str, doc, str)
  end
  
  # Records representing __simple__ documents, already on a fixed layout
  # Those are generalized by `docfactor` type.
  @type sdoc :: SNil | SText.t | SLine.t
  defrecord SText, str: "", sdoc: SNil
  defrecord SLine, indent: 1, sdoc: SNil # newline + spaces

  @doc """
  Renders a simple document into a binary
  """
  @spec render(sdoc) :: binary
  def render(SNil), do: ""
  def render(SText[str: s, sdoc: d]), do: s <> render(d)
  def render(SLine[indent: i, sdoc: d]) do
    prefix = repeat " ", i
    newline <> prefix <> render d
  end


  @doc """
  The pretty printing function.
  Takes maximum width and document to print as its arguments and returns the string
  representation of the best layout for the document to fit in the given width.
  """
  @spec pretty(non_neg_integer, doc) :: binary
  def pretty(w, d) do
    sdoc = format w, 0, [{0, Flat, DocGroup[doc: d]}]
    render(sdoc)
  end

  # Record representing the document mode to be rendered: __flat__ or __broken__
  @type mode :: Flat | Break

  # the fits? and format methods have to deal explicitly with the document modes
  @spec fits?(integer, [{ integer, mode, doc }]) :: boolean
  defp fits?(w, _) when w < 0,                              do: false
  defp fits?(_, []),                                        do: true
  defp fits?(w, [{_, _, DocNil} | t]),                      do: fits? w, t
  defp fits?(w, [{i, m, DocCons[left: x, right: y]} | t]),  do: fits? w, [{i, m, x} | [{i, m, y} | t]]
  defp fits?(w, [{i, m, DocNest[indent: j, doc: x]} | t]),  do: fits? w, [{i + j, m, x} | t]
  defp fits?(w, [{_, _, DocText[str: s]} | t]),             do: fits? (w - strlen s), t
  defp fits?(w, [{_, Flat, DocBreak[str: s]} | t]),         do: fits? (w - strlen s), t
  defp fits?(_, [{_, Break, DocBreak[str: _]} | _]),        do: true # impossible (but why?)
  defp fits?(w, [{i, _, DocGroup[doc: x]} | t]),            do: fits? w, [{i, Flat, x} | t]

  @spec format(integer, integer, [{ integer, mode, doc }]) :: boolean
  defp format(_, _, []),                                       do: SNil
  defp format(w, k, [{_, _, DocNil} | t]),                     do: format w, k, t
  defp format(w, k, [{i, m, DocCons[left: x, right: y]} | t]), do: format w, k, [{i, m, x} | [{i, m, y} | t]]
  defp format(w, k, [{i, m, DocNest[indent: j, doc: x]} | t]), do: format w, k, [{i + j, m, x} | t]
  defp format(w, k, [{_, _, DocText[str: s]} | t]),            do: SText[str: s, sdoc: format w, (k + strlen s), t]
  defp format(w, k, [{_, Flat, DocBreak[str: s]} | t]),        do: SText[str: s, sdoc: format w, (k + strlen s), t]
  defp format(w, _, [{i, Break, DocBreak[str: _]} | t]),       do: SLine[indent: i, sdoc: format w, i, t]
  defp format(w, k, [{i, _, DocGroup[doc: x]} | t]) do
    if fits? (w - k), [{i, Flat, x} | t] do
      format w, k, [{i, Flat, x} | t]
    else
      format w, k, [{i, Break, x} | t]
    end
  end
end
