#' Convert a string to matrix\cr
#'  For those who miss the luxury of using spaces as delimitters.
#'
#' @param a string which contains the body of the matrix with \\nr.
#' @param header = 1 if the string has row/col names
#' @param missing a string which indicates missing values: "NA" or "."
#' @param rnameroot root of the row names when header=0
#' @param cnameroot root of the column names when header=0
#' @param nummat = 1 to force the result to be a numeric matrix. \cr
#' This may cause an error!
#' @param debug = 1 to dump intermediate result
#' @return a matrix or data.frame
#'
#' @details
#' Use space ( blank characters ) or a comma as delimiters. \cr
#' Two or more commas indicate the existence of  missing values in between.\cr
#' The last comma indicates one element is missing at the end.\cr
#' Blank lines will be neglected.
#'
#' \preformatted{
#' CARDS <- " v1 v2 v3
#' s1 11 12 13
#' s2 21 22 23
#' s3 31 32 33"
#' A <- cards( CARDS, header=1 )
#' }
#' A will be a 3 x 3 matrix with row and column names.
#'
#'  This module converts the following types of string:
#' \preformatted{
#'      a1="         , a2="11 12, 13  ,  a3="C1 C2 C3  ,  a4="C0 C1 C2 C3
#'          11 12 13       21, 22  23     R1 11 12 13         R1 11 12 13
#'          21 22 23       31 32 33       R2 21 22 23         R2 21 22 23
#'          31 32 33       41,, 43"       R3 31 32 33         R3 31 32 33
#'          41 42 43                      R4 41 42 43"        R4 41 42 43"
#'         "}
#'  into a matrix or data.frame. \cr
#'
#'  When the string contains both row and column names
#'  as in a3 or a4 above, header=1 must be specified. \cr
#'  If, as a3 above,
#'  the first line ending with \\n has one less elements than the rest,
#'  it is assumed that both row and col names are given. \cr
#'  Otherwise, as a4 above, C0 will be treated as the first column name
#'  of the resulting matrix. \cr
#'   \cr
#'  The row/col names cannot have commas in it. (for csv compatibility) \cr
#'  When the string does not have row and column names, characters specified
#'  in rnameroot or cnameroot will be used to generate the names. \cr
#'  Character elements of the matrix cannot have blanks in them. \cr
#'  The quotes will be treated as characters. \cr
#'
#'  \cr
#'   When a column contains both numeric and character string
#'   a data frame, not a matrix, will be created. \cr
#'   Scientific notation like 1e-9 will be mistaken as character. \cr
#'
#' \cr
#'  To input from the clip board, \cr
#'  copy the body of the matrix with row and column names and use \cr
#'    \code{ read.table(file("clipboard"),header=1) }
#' \cr
#'   \code{ write.table( string-such-as-a1-etc-above, "clipboard",sep=" ")}
#'    \cr
#'   may be used to improve this module.
#'
#'
#' @examples
#' CARDS=" v1 v2 v3
#' s1 11 12 13
#' s2 21 22 23
#'
#' s3 NA 32 33"
#' cards( CARDS, header=1 )
#'
#' CARDS=" v1 v2 v3
#' s1, 11, 12, 13
#' s2, 21, 22, 23
#' s3,,    32, 33"
#' cards( CARDS, header=1 )
#'
#' CARDS="v0 v1 v2 v3
#' s1 11 12 13
#' s2 21 22 23
#' s3 .  32 33"
#' cards( CARDS, header=1, missing="." )
#'
#' CARDS="
#'  11 12 13
#'  21 22 23
#'  NA  32 33
#'  "
#' cards( CARDS  )
#'
#' # result is a character matrix.
#' CARDS <- " cos(theta) -sin(theta)
#'            sin(theta) cos(theta)"
#' T <- cards(CARDS)
#'
#'
#' # result is a data frame.
#' CARDS <- "
#' num char num
#' 11 aa 12
#' 21 bb 22
#' "
#' df <- cards( CARDS, header=1 )
#' str(df)
#'
#' CARDS <- "
#'      ncat      a         b1        b2
#'  r1  3.00e+00  1.25e+00 -2.06e+00 .
#'  r2  3.00e+00  1.36e+00 -1.44e-16 .
#'  r3  3.00e+00  1.40e+00  1.97e+00 .
#'  r4  3.00e+00  1.02e+00 -2.04e+00 .
#'  r5  3.00e+00  1.06e+00 -1.46e-16 .
#'  r6  3.00e+00  1.07e+00  1.98e+00 .
#'  r7  4.00e+00  1.10e+00 -2.13e+00  1.05e-15
#'  r8  4.00e+00  7.19e-01 -2.03e+00 -3.27e-16
#'  "
#'  Ad <- cards(CARDS, header=1, missing=".")
#'  Print("This is a character matrix.", Ad)
#'
#'  Ad <- cards(CARDS, header=1, missing=".", nummat=1)
#'  Print("This is a numeric matrix.", Ad)
#'
#' @export
#'

cards <- function( a, rnameroot="r", cnameroot="c", header=0
                   , missing="NA", nummat=0, debug=0 ){
 # convert a string containing a matrix to a numeric matrix
 # Shin-ichi Mayekawa
 # 111112c,
 # missing values: 13c,14
 # T -> 1, F -> 0: 111121,120127
 # new algorithm: 111125
 # data frame: 120217,18
 # nummat: 120304
 # remove tab: 120307
 # drop=F -> drop=0: 130110
 # rownamesin: 20150528,29
 # new algorithm: 20150529,30
 # lazy.matrix -> lazy_matrix: 20150602
 # renamed as cards: 20150603
 # return character mat if possible: 20151107
 # above version added to lazy.tools as it is: 20161124
 #
 #
 #
 #  This module converts the following types of string:
 #
 #      a1="           a2="11 12, 13     a3="C1 C2 C3     a4="C0 C1 C2 C3
 #          11 12 13       21, 22  23     R1 11 12 13         R1 11 12 13
 #          21 22 23       31 32 33       R2 21 22 23         R2 21 22 23
 #          31 32 33       41,, 43"       R3 31 32 33         R3 31 32 33
 #          41 42 43                      R4 41 42 43"        R4 41 42 43"
 #         "
 #  into a matrix or data.frame.
 #
 #
 #  Args:
 #   a          string containing the body of a matrix (with or w/o names)
 #   rnameroot  root of the row names to be generated
 #   cnameroot  root of the col names to be generated
 #   missing    "." or "NA" to identify missing values
 #   header     = 1 if the string contains both row and col names
 #   nummat     = 1 to convert data frame to numeric matrix
 #              This may fail.
 #
 #
 #  When the string contains both row and column names
 #  as in a3 or a4 above, header=1 must be specified.
 #  If, as a3 above,
 #  the first line ending with \n has one less elements than the rest
 #  it is assumed that both row and col names are given.
 #  Otherwise, as a4 above, C0 will be treated as the first column name
 #  of the resulting matrix.
 #
 #  The row/col names cannot have commas in it. (for csv compatibility)
 #
 #  When the string does not have row and column names, characters specified
 #  in rnameroot or cnameroot will be used to generate the names.
 #
 #  Character elements of the matrix cannot have blanks in them.
 #  The quotes will be treated as characters.
 #
 #
 # Note:
 #
 #   When a column contains both numeric and character string
 #   a data frame, not a matrix, will be created.
 #
 #   Scientific notation like 1e-9 will be mistaken as character.
 #
 #
 #  To input from the clip board,
 #  copy the body of the matrix with row and column names and use
 #    read.table(file("clipboard"),header=1)
 #
 #   write.table( string-such-as-a1-etc-above, "clipboard",sep=" ")
 #   may be used to improve this module.
 #
 #


 if ( debug > 0 ){
  Print(rnameroot, cnameroot, header, missing, debug, sep=" ")
  cat("\nInput string as it is\n")
  print(list(a=a))
 }

 # mark . for grep: needs spaces on both sides!!
 #if ( missing == "." ) missing=" \\. "
 missing1=paste(" ",missing," ",sep="")

 # compress multiple blanks and \n
 a=gsub(" {2,}"," ",a)
 a=gsub("\n{2,}","\n",a)

 # split by \n
 a=strsplit(a, "\n")[[1]]

 a=gsub("^ *","",a) # leading blanks
 a=gsub(" *$","",a) # trailing blanks

 a=a[which(a != "")]

 if( debug ){
  cat("\nInput string after strsplitting by \\n.\n")
  print(a)
 }

 # mark the missing elements by unusual number.

 a=gsub("NA","-999999",a)
 a=gsub(",,", ",-999999,", a)
 a=gsub("\t\t", ",-999999,", a)
 a=gsub(", ,", ",-999999,", a)
 a=gsub("\t \t", ",-999999,", a)
 a=gsub("^,","-999999,",a)
 a=gsub(",$",",-999999",a)
 a=gsub(missing1, " -999999 ", a, fixed=1)  # needs spaces on both sides!!
 a=gsub(" \\.$", " -999999", a)  # blank + . at the end

 # translate commas and tabs to blanks
 a=chartr(",", " ", a)   # translate ,   to space  (csv compatibility)
 a=chartr("\t", " ", a)  # translate TAB to space  (csv compatibility)
 a=gsub(" {2,}"," ",a) # remove multiple blanks

 # below 2 lines are to compact the default names like [1, ]
 a=gsub("\\[ ","\\[",a)
 a=gsub(" \\]","\\]",a)

 f=a[which(a != "")]
 lenf=unlist(sapply(f,function(x){length(gregexpr(" ",x)[[1]])}))+1
 #lenf=sapply(regmatches(f, gregexpr(" ", f)), length)+1
 names(lenf)=NULL

 if( debug > 0){
  cat("\nAfter interpretation\n")
  Print(a,f,lenf)
  #Print(c); Print(f);Print(lenf)
 }

 # with names or not
 if( header == 1 ){

  # check if there is rowname
  if( lenf[1] == lenf[2]  &  all(lenf[-1] == lenf[2]) ){
   rownamesin=0; nc=lenf[2]
  }
  else if( lenf[1]+1 == lenf[2]  &  all(lenf[-1] == lenf[2]) ){
   rownamesin=1; nc=lenf[1]
  }
  else{
   cat("\n\nerror1:(lazy_matri) The input string does not seem to be")
   cat(" the elements of a matrix. \n")
   cat(" Below is the # of elements per line.\n")
   cat(" Something is wrong with the first line:\n")
   print(lenf)
   cat(" Below is the input string interpreted so far:\n")
   a=gsub("-999999","NA",a)
   print(a)
   return()
  }
  # case where the input contains row/col names
  # first element is the column names
  colname=f[1]
  colname=strsplit(colname," ")[[1]]
  #nc=length(colname)
  # g contains the 2nd row and below with row names at column 1
  g=f[-1]                 # remove colnames
  nr=length(g)
  # separate all the elements and make the result a matrix
  h=unlist(strsplit(g," "))
  h=h[which(h != "")]
  # row names are located at first column
  if( rownamesin == 1 ){
   res=matrix(h,,nc+1,byrow=1)
   rowname=res[,1]
   # remove the first row
   res=res[,-1,drop=0]
  }
  else{
   res=matrix(h,,nc,byrow=1)
   rowname=paste(rnameroot,1:nr,sep="")
  }

  if( debug > 0 ){
   cat("\nDecomposed string\n")
   Print(h)
   Print(nr,nc,rowname,colname)
  }

 } # end of header=1
 else{

  # case where the input does not contains row/col names
  if( length(which(lenf==lenf[1])) == length(lenf) ){
   nc=lenf[1]
  }
  else{
   cat("\n\nerror1:(lazy_matri) The input string does not seem to be\n")
   cat(" the elements of a matrix without row/col names. \n")
   cat(" Below is the # of elements per line.\n")
   print(lenf)
   cat(" Below is the input string interpreted so far:\n")
   a=gsub("-999999","NA",a)
   print(a)
   return()
  }
  nr=length(f)
  h=unlist(strsplit(f," "))
  h=h[which(h != "")]
  # nc=length(h)/nr
  res=matrix(h,nr,byrow=1)
  if( debug > 0 ){
   Print(h)
   Print(nr,nc,h)
  }

 } # end of header=0

 if (debug > 0 ){
  Print(nr,nc)
  Print(res)
  Print(was(res))
 }

 # generate names when the names are not in.
 if ( header == 0 ){
  colname=paste(cnameroot,1:nc,sep="")
  rowname=paste(rnameroot,1:nr,sep="")
 }

 # Here, res is the desired matrix of character type.

 # character, numeric or data frame?
 if( debug ) Print(class(res))
 nr=nrow(res); nc=ncol(res)
 loc=grep("[[:alpha:][:blank:]!#$%&'()*,/]",res)

 if( debug ){
  cat("loc is the location of non-digit elements: ", loc, "\n")
 }

 # mat of df
 if( length(loc) == 0 ){
  # make it a numeric matrix
  res=as.numeric(res)
  res=matrix(res,nr,nc)
  res[which(res == -999999)]=NA  # mark the missing elements by NAs.
 }
 else if( length(loc) < length(res) ){
  # make it a data frame
  res1=as.data.frame(res)
  for( j in 1:nc ){
   loc=grep("[[:alpha:][:blank:]!#$%&'()*,/]",res[,j])
   if( length(loc) == 0 ){
    res1[,j]=as.numeric(unlist(res[,j]))
    if( length(which(res1[,j] == -999999)) > 0 )
     res1[which(res1[,j] == -999999),j]=NA
   }
   else{
    res1[,j]=res[,j]
    if( length(which(res1[,j] == "-999999")) > 0 )
     res1[which(res1[,j] == "-999999"),j]=NA
   }
  }
  res=res1
 }

 # result matrix to be returned
 dimnames(res)[[1]]=rowname
 dimnames(res)[[2]]=colname

 # remove the names with [ or ]
 # if( length(grep("\\[",dimnames(res))) > 0 )   dimnames(res)=NULL

 if( nummat == 1 )
  res=matrix( as.numeric(unlist(res)),nrow(res)
              , dimnames=list(rownames(res),colnames(res)) )


 return( res )

} # end of cards



