import std.format;
import std.array;
import std.typecons;
import std.string;
import std.exception;
import std.stdio;

enum Justify {L,C,R}


string FormattedPrintFill(in int[] column_widths, uint between, string filler)
{
  auto a = appender!string;
  foreach (w;column_widths)
    a.put(repeat(filler,w)~repeat(" ",between));

  return a.data;
}

// all justified same way
string FormattedPrint(T...)(in int[] column_widths, in int between, in Justify justify, 
			    in string[] formats, in T args)
{
  enforce(column_widths.length == formats.length); // should this be an "in" contract?
  enforce(column_widths.length == args.length); // should this be an "in" contract?

  Justify[] j;
  j.length = column_widths.length;
  j[] = justify;
  return FormattedPrint(column_widths,between,j,formats,args); // forward to full version
}

// full version
string FormattedPrint(T...)(in int[] column_widths, in uint between, in Justify[] justify, 
			  in string[] formats,in T args) 
{
  enforce(column_widths.length == formats.length); // should this be an "in" contract?
  enforce(column_widths.length == justify.length); // should this be an "in" contract?
  enforce(column_widths.length == args.length); // should this be an "in" contract?
  
  auto a = appender!string;
  const int num_columns = formats.length;
  int k = 0;
  foreach (arg; args) {
    a.put(formatAndPlace(column_widths[k],justify[k],formats[k],arg));
    ++k;
    if (k<num_columns)
      a.put(repeat(" ",between));
  }
  return a.data;
}

// forward the formatting work to built-in formattedWrite function
private string formatted(T)(in string fmt, in T data) 
{
  auto a = appender!string;
  formattedWrite(a,fmt,data);
  return a.data;
}

// do the work for one item
private string formatAndPlace(T)(in uint width, in Justify justify, in string fmt, in T data) 
{
  debug (1) { writefln("fAP(%s,%s,%s,%s)",width,justify,fmt,data); }
  auto fd = formatted(fmt,data);
  auto spaces = width - fd.length;
	
  if (spaces < 0)
    throw new Exception("formatted data is larger than column width.");

  switch (justify) 
    {
    case Justify.R:
      return repeat(" ",spaces)~fd;
      break;
    case Justify.C:
       return repeat(" ",spaces/2) ~ fd ~ repeat(" ",(spaces - spaces/2));
      break;
    case Justify.L:
      return fd ~ repeat(" ",spaces);
    }
}

