# FUNCTIONS IN THIS FILE 
# saveWeka (name, bak=TRUE, noWarn=TRUE, env=parent.frame()) 
# parseWeka (txt=clipPaste()) 
# RecallPrecision (cm, labelTrue=NULL) 
# wekaMakeConfMat (txt) 
# getWekaName (txt) 
# savingobjBaksToClear () 
# rmObjsToClear () 



saveWeka <- function(name, bak=TRUE, noWarn=TRUE, env=parent.frame()) {
  # takes the clipboard and assigns to the variable of name name
  #  eg can be called by:
  #   saveWeka("Test") or saveWeka(Test)
  #   in both instances
  
  parsed <- parseWeka()

  if (missing(name)) {
      name <- colnames(parsed)[[1]]
  } else {
      name <- as.character(match.call()[[2]])
  }

  # if object already exists && flagged to backup, do so before overwriting
  if (bak && exists(name, envir=env)) {
    name.bak <- paste(name, timeStamp(), sep=".")
    assign(name.bak, get(name, envir=env), envir=env)
    if (!noWarn)
      warning("`", name, "`", " already exists. Backed up previous value to ", name.bak)
  }

  assign(name, parsed, envir=env)
  return(setNames(name, "saved.as"))
}

parseWeka <- function(txt=clipPaste()) {

  # get the confusion matrix
  RP <- RecallPrecision( wekaMakeConfMat(txt) )

  # flatten it
  ret <- do.call(rbind, RP)

  # add the name to the algorithm
  colnames(ret) <- getWekaName(txt)  

  return(ret)
}


RecallPrecision <- function(cm, labelTrue=NULL) {
  # cm is a confusion matrix
  #  type numeric matrix
  #  dimnames of cm should be named "Actual", "Predicted"

  # make everything lowercase
  colnames(cm) <- tolower(colnames(cm))
  rownames(cm) <- tolower(rownames(cm))
  names(dimnames(cm)) <- tolower(names(dimnames(cm)))


  dnms <- dimnames(cm)
  Pr   <- which(names(dnms) == "predicted")
  Ac   <- which(names(dnms) == "actual")

  # error check
  if (identical(integer(0), Ac) || identical(integer(0), Pr))
    stop ("dimnames of matrix not correctly labeled as 'Predicted' and 'Actual'")

  if (! all (sort(colnames(cm)) == sort(rownames(cm))))
    stop ("rownames and colnames do not match")

  if( all (dim(cm) != c(2, 2))) 
    stop("Confusion Matrix is not 2x2. Dont know how to handle")

  # FIND COLUMN FOR "TRUE"
  if (is.null(labelTrue) || !(labelTrue %in% colnames(cm)))  {
    # look for good, yes, true
    originalLT <- labelTrue

    if ("good" %in% colnames(cm))
      labelTrue <- "good"      
    if ("yes" %in% colnames(cm))
      labelTrue <- "yes"      
    if ("true" %in% colnames(cm))
      labelTrue <- "true"      
    # if couldn't find anything, take the first column to be "TRUE"
    if (!labelTrue %in% c("good", "yes", "true"))
      labelTrue <- colnames(cm)[[1]]

    # if something was originally given, but not found
    if (!is.null(originalLT))
      warning("'", originalLT, "' not found as column name.  Using '", labelTrue, "' instead.")
  }

  t.actual    <- which(dnms$actual    == labelTrue)
  f.actual    <- which(dnms$actual    != labelTrue)
  t.predicted <- which(dnms$predicted == labelTrue)
  f.predicted <- which(dnms$predicted != labelTrue)


  # "actual" should be the columns, if not, swap them now 
  if (all(c(Ac, Pr) == c(1, 2)))
    cm <- t(cm)

  DESC <- expand.grid("Pred (row)"=c("t.predicted", "f.predicted"), "Actu (col)" = c("t.actual", "f.actual"))
  INDS <- as.matrix(expand.grid("Pred (row)"=c(t.predicted, f.predicted), "Actu (col)" = c(t.actual, f.actual)))
  g(TP, FN, FP, TN) %=% cm[INDS]
  
  # to manually check my programming is correct
  overview <- cbind(names=c("TP", "FN", "FP", "TN"), values=c(TP, FN, FP, TN), DESC)
  
  
  Precision <- TP / (TP + FP)
  Recall    <- TP / (TP + FN)
  FMeasure  <- (2 * Recall * Precision) / (Recall + Precision)

  return(list("Precision"=Precision, "Recall"=Recall, "FMeasure"=FMeasure)) 

}


wekaMakeConfMat <- function(txt) { 

  library(stringr)

  # using this as indicator for where CM starts
  start <- str_locate(pattern="=== Confusion Matrix ===", txt)[,"end"] + 1
  if (!is.na(start))
    txt <- str_sub(txt, start, -1)

  # trim any whitespace
  txt <- str_trim(txt)


  # the 13-letters before the first line break should be "classifed as"
  firstBreak <- regexpr("\n", txt)[[1]]
  tester <- substr(txt, firstBreak-13, firstBreak-1)
  if (tester != "classified as") {
    stop ("I do not know how to make confustion matrix from input")
  }

  mydf <- data.frame(read.table(text=txt, header=TRUE, stringsAsFactors=FALSE))

  # Clean up the input
  #-------------------#

    # classes will be used for rownames & colnames
    classes <- mydf$as

    # the first column is incorrectly set as the rownames. 
    # Shift all columns over by one
    mydf <- cbind(as.numeric(rownames(mydf)), mydf[, 1:(ncol(mydf)-1)])

    # clean up col & row names
    rownames(mydf) <- classes
    colnames(mydf) <- classes

    # The numbe of total columns are more than just the confusion matrix
    # drop the superfluous col rows  (ie, those which didnt get assigned a name)
    mydf <- mydf[, classes]
  #-------------------#
  
  # Convert to matrix, with labeled names #
  #---------------------------------------#
    # convert to numbers
    mymat <- as.matrix(mydf)
          # # This might not be needed
          # as.matrix(sapply(mydf, as.numeric))
          # rownames(mymat) <- classes

  names(dimnames(mymat)) <- c("Predicted", "Actual")
  return (mymat)
}

getWekaName <- function(txt)  {

  # we are looking for something like 
  #   ....\nScheme:weka.classifiers.bayes.NaiveBayes \nRelation.....
  # and want to take out the first term after the last dot. 
  #  We will search for "Scheme" and then find the 
  #  location of the last dot following it
  Scheme.loc <- str_locate(txt, pattern="Scheme")[,"start"]
  txt  <- substr(txt, Scheme.loc, 300)
  dots <- regexpr(pattern="(\\.\\w+)+\\.", txt)

  # we want to start at the first letter following the last dot
  start <- dots[1] + attr(dots, "match.length")[1]
  # ... and end right before the first space
  substr(txt, start, regexpr("\\s", txt)-1)
}




#------------

# Cleanup from saving
objBaksToClear <- function() {
  today <- substr(timeStamp(), 1, 8) 
  ls(pattern=paste0("\\.", today), envir=.GlobalEnv)
}

rmObjsToClear <- function(){
  rm(list=objBaksToClear(), envir=.GlobalEnv)  
}
