context("MCFSolutions & co: S4 classes to encode min-cost-flow solutions")

test_that("Instantiation & validity", {
    expect_true(validObject(new("SubProbInfo"))) # test the prototype
    expect_silent(spi1  <- new("SubProbInfo", data.frame(groups=c('a','b'), flipped=logical(2), hashed_dist=c('a','b'),
                                           resolution=c(1,10), lagrangian_value=c(.5, 2), dual_value=c(0,1),
                                           feasible=c(TRUE, FALSE), exceedance=1, stringsAsFactors=F)
                               )
                  )
    expect_true(validObject(spi1))

    spi2  <- spi1
    colnames(spi2)[1]  <- "Subprob"
    expect_error(validObject(spi2), "Cols 1-8 should be")

    expect_true(validObject(new("NodeInfo"))) # test the prototype    
    expect_silent(ni1  <- new("NodeInfo",
                             data.frame(name='a', price=0.5, upstream_not_down=TRUE,
                                        supply=1L, groups = as.factor('b'),
                                        stringsAsFactors=F)
                             )
                  )
    expect_silent(ni1f  <- new("NodeInfo",
                               data.frame(name=c('b', 'c', 'd',
                                                 '(_Sink_)', '(_End_)'),
                                          price=0.5,
                                          upstream_not_down=c(TRUE, FALSE,
                                                              FALSE, NA, NA),
                                         supply=c(1L,0L,0L,-1L,-2L), groups = as.factor('b'),
                                         stringsAsFactors=F)
                              )
                  )
    expect_equivalent(node.labels(ni1f), as.character(1:5))
    expect_named(node.labels(ni1f),
                 c('b', 'c', 'd', '(_Sink_)', '(_End_)')
                 )
    expect_silent(node.labels(ni1f)  <- ni1f[['name']])
    expect_equivalent(node.labels(ni1f), ni1f[['name']])
    expect_error(new("NodeInfo",
                      data.frame(name='a', price='foo', upstream_not_down=FALSE,
                                 supply=1L, groups = as.factor('b'),
                                 stringsAsFactors=F)
                     ),
                 "should be a numeric" # Not sure it's necessary, but insisting
                  )                        # that 'price' be double not integer
    expect_error(new("NodeInfo",
                      data.frame(name=rep('a', 2), price=0, upstream_not_down=FALSE,
                                 supply=1L, groups = as.factor('b'),
                                 stringsAsFactors=F)
                     ),
                 "unique" 
                  )
    expect_true(validObject(new("ArcInfo"))) # test the prototype
    expect_silent(ai  <- new("ArcInfo",
                             matches=data.frame(groups = as.factor('a'), upstream = factor('b', levels=node.labels(ni1f)),
                                                downstream = factor(c('c','d'), levels=node.labels(ni1f)),
                                                stringsAsFactors=F),
                             bookkeeping=data.frame(groups = as.factor('a'),
                                                    start = factor(c('c','d'),
                                                                   levels=node.labels(ni1f)
                                                                   ),
                                                    end = factor('(_Sink_)',
                                                                 levels=node.labels(ni1f)
                                                                 ),
                                                    flow=1L, capacity=1L,
                                                    stringsAsFactors=F)
                             )
                  )
    expect_error(new("ArcInfo",
                     matches=data.frame(groups = as.factor('a'), upstream = as.factor('b'),
                                        downstream = as.factor(c('c','d')),stringsAsFactors=F),
                     bookkeeping=data.frame(groups = as.factor('a'), start = as.factor(c('c','d')),
                                            end = as.factor('(_Sink_)'), flow=1.5,
                                            capacity=1L, stringsAsFactors=F)
                     ), "should have type integer" # Not sure it's necessary, but insisting 
                  )                                # that 'flow' be integer not double
    expect_error(new("ArcInfo",
                     matches=data.frame(groups = as.factor('a'), upstream = as.factor('b'),
                                        downstream = as.factor(c('c','d')),stringsAsFactors=F),
                     bookkeeping=data.frame(groups = as.factor('a'), start = as.factor(c('c','d')),
                                            end = as.factor('(_Sink_)'), flow=-1L,
                                            capacity=1L, stringsAsFactors=F)
                     ), "should be nonnegative" 
                  )                                
    expect_error(new("ArcInfo",
                     matches=data.frame(groups = as.factor('a'),
                                        upstream = as.factor('b'),
                                        downstream=as.factor(c('c','d')),
                                        stringsAsFactors=F),
                     bookkeeping=data.frame(groups=as.factor('a'),
                                            start=as.factor(c('c','d')),
                                            end=as.factor('(_Sink_)'), flow=2L,
                                            capacity=1L, stringsAsFactors=F)
                     ), "flow can be no greater than capacity" 
                  )                                
    
    expect_silent(mcf1f  <- new("MCFSolutions", subproblems=spi1,nodes=ni1f,arcs=ai))
    expect_silent(as(mcf1f, "FullmatchMCFSolutions"))
    expect_equal(node.labels(mcf1f), node.labels(ni1f))
    expect_silent(node.labels(mcf1f) <- paste0(node.labels(ni1f),"_") )
    expect_equivalent(node.labels(mcf1f), paste0(node.labels(ni1f), "_") )
    expect_equivalent(node.labels(mcf1f@nodes), paste0(node.labels(ni1f), "_") )
})

expect_setequal(names(getSlots("MCFSolutions")),
                     c("subproblems", "nodes", "arcs")
                )# premise of c() method for MCFSolutions

test_that("c() methods", {
    spi1  <- new("SubProbInfo",
                 data.frame(groups=c('a','b'), flipped=logical(2), hashed_dist=c('a','b'),
                            resolution=c(1,10), lagrangian_value=c(.5, 2), dual_value=c(0,1),
                            feasible=c(TRUE, FALSE), exceedance=1, stringsAsFactors=F)
                 )
    spi2  <- new("SubProbInfo",
                 data.frame(groups=c('c'), flipped=logical(1), hashed_dist=c('a'),
                            resolution=c(1), lagrangian_value=c(.5), dual_value=c(0),
                            feasible=c(TRUE), exceedance=1, stringsAsFactors=F)
                 )
    spi3  <- new("SubProbInfo",
                 data.frame(groups=c('d'), flipped=logical(1), hashed_dist=c('a'),
                            resolution=c(1), lagrangian_value=c(.5), dual_value=c(0),
                            feasible=c(TRUE), exceedance=1, stringsAsFactors=F)
                 )
    
    expect_true(validObject(c(spi1, spi2)))
    expect_true(validObject(c(spi1, spi2, spi3)))
    expect_true(validObject(c(a=spi1, b=spi2))) # no confusion just b/c no `x=` arg!
    
    ni1f  <- new("NodeInfo",
                 data.frame(name=c('b', 'c', 'd',
                                   '(_Sink_)', '(_End_)'),
                            price=c(0.5, 0.5,
                                    NA_real_, # permissible for downstream nodes
                                    0.5, 0.5),
                            upstream_not_down=c(TRUE, FALSE,
                                                FALSE, NA, NA),
                            supply=c(1L,0L,0L,-1L,-2L), groups = as.factor('b'),
                            stringsAsFactors=F)
                 )
    node.labels(ni1f) <- ni1f[['name']]
    ni1f.a  <- ni1f.b <- ni1f.c  <- ni1f
    ni1f.a[,'groups']  <- factor(rep('a', nrow(ni1f)))
    ni1f.c[,'groups']  <- factor(rep('c', nrow(ni1f)))    
    expect_true(validObject(c(ni1f.a, ni1f.b)))
    expect_true(validObject(c(ni1f.a, ni1f.b, ni1f.c)))
    ni2  <- new("NodeInfo",
                data.frame(name=c(letters[2:5], '(_Sink_)', '(_End_)'), price=0.5,
                           upstream_not_down=c(TRUE, rep(FALSE,3), NA, NA),
                          supply=c(1L, rep(0L,3),-1L,-2L), groups=as.factor('c'),
                          stringsAsFactors=F)
                )
    node.labels(ni2) <- ni2[['name']]
    ni1ni2 <- c(ni1f, ni2)
    expect_equal(ni1ni2$name, c("b", "c", "d", "(_Sink_)", "(_End_)", letters[2:5], "(_Sink_)", "(_End_)"))
    expect_equal(levels(ni1ni2$groups), c("b", "c"))
    expect_named(node.labels(ni1ni2), c("b", "c", "d", "(_Sink_)", "(_End_)", letters[2:5], "(_Sink_)", "(_End_)") )
    expect_false( any(duplicated(node.labels(ni1ni2))) )

    some_levs  <- c(letters[2:4], '(_Sink_)', "(_End_)")
    ai1  <- new("ArcInfo",
                matches=data.frame(groups = factor('a'),
                                   upstream = factor('b', levels=some_levs),
                                   downstream = factor(c('c','d'), levels=some_levs),
                                   stringsAsFactors=F),
                bookkeeping=data.frame(groups = factor('a'),
                                       start = factor(c('c','d'), levels=some_levs),
                                       end = factor('(_Sink_)', levels=some_levs),
                                       flow=1L, capacity=1L,
                                       stringsAsFactors=F)
               )
    expect_true(validObject(ai1))
    expect_true(validObject(c(ai1, ai1)))
    expect_true(validObject(c(x=ai1, y=ai1, z=ai1)))
    some_levs  <- c(letters[2:5], '(_Sink_)', '(_End_)')    
    ai2  <- new("ArcInfo",
                matches=data.frame(groups = factor('c'),
                                   upstream = factor('b', levels=some_levs),
                                   downstream = factor(c('c','d', 'e'), levels=some_levs),
                                   stringsAsFactors = F),
                bookkeeping=data.frame(groups = factor('c'),
                                       start = factor(c('c','d'), levels=some_levs),
                                       end = factor('(_Sink_)', levels=some_levs),
                                       flow=1L, capacity=1L,
                                       stringsAsFactors=F)
               )
    expect_true(validObject(ai2))

    ai1ai2 <- c(ai1, ai2)
    expect_equal(levels(ai1ai2@matches$groups), c("a", "c"))
    expect_setequal(unique(as.character(ai1ai2@matches$upstream)), "b")
    expect_setequal(unique(as.character(ai1ai2@matches$downstream)),
                    c("c", "d", "e")
                    )
    expect_setequal(unique(as.character(ai1ai2@bookkeeping$end)), "(_Sink_)")


    mcf1  <- new("MCFSolutions", subproblems=spi1, nodes=ni1f, arcs=ai1)
    mcf2 <- new("MCFSolutions", subproblems=spi2,nodes=ni2,arcs=ai2)
    expect_true(validObject(mcf1))
    expect_true(validObject(mcf2))
    
    expect_error(c(mcf1, mcf1), "uplicates")
    expect_true(validObject(c(mcf1, mcf2)))
    expect_true(validObject(c(y=mcf1, z=mcf2)))

    mcf2f  <- as(mcf2, "FullmatchMCFSolutions")
    expect_is(c(mcf2f, mcf1), "FullmatchMCFSolutions")
    expect_is(c(mcf1, mcf2f), "MCFSolutions")
})
test_that("nodeinfo getter",{
    expect_silent(mcf  <-  new("MCFSolutions")) #prelim-
    expect_true(validObject(mcf, complete=TRUE))#inaries

    expect_is(nodeinfo(mcf@nodes), "NodeInfo")
    expect_is(nodeinfo(mcf), "NodeInfo")

    data <- data.frame(z = c(rep(0,10), rep(1,5)),
                       x = rnorm(15), fac=rep(c(rep("a",2), rep("b",3)),3))
    f1 <- fullmatch(z~x, min.c=1, max.c=1, omit.fraction=.5, data = data)
    expect_is(f1, "optmatch")
    expect_false(is.null(attr(f1, "MCFSolutions")))
    expect_is(nodeinfo(f1), "NodeInfo")
    expect_null(nodeinfo(10))
})
test_that("NodeInfo to tibble converter", {
    ni1f  <- new("NodeInfo",
                 data.frame(name=c('b', 'c', 'd',
                                   '(_Sink_)', '(_End_)'),
                            price=0.5,
                            upstream_not_down=c(TRUE, FALSE,
                                                FALSE, NA, NA),
                            supply=c(1L,0L,0L,-1L,-2L), groups = as.factor('b'),
                            stringsAsFactors=F)
                 )
    expect_silent(ni_tbl  <- as(ni1f, "tbl_df"))
    expect_is(ni_tbl$nodelabels, "factor")
    expect_equivalent(as.character(ni_tbl$nodelabels),
                      as.character(1:5))
    expect_null(names(ni_tbl$nodelabels))
    node.labels(ni1f) <- ni1f[['name']]
    expect_silent(ni_tbl  <- as(ni1f, "tbl_df"))
    expect_is(ni_tbl$nodelabels, "factor")
    expect_equivalent(as.character(ni_tbl$nodelabels), 
                      c('b', 'c', 'd', '(_Sink_)', '(_End_)')
                      )
    expect_equivalent(levels(ni_tbl$nodelabels), 
                      c('b', 'c', 'd', '(_Sink_)', '(_End_)')
                      ) # default encoding would start w/ "(_End_)", "(_Sink_)"
})
test_that("Preserve levels when filtering a node info tibble",{
    ni1f  <- new("NodeInfo",
                 data.frame(name=c('b', 'c', 'd',
                                   '(_Sink_)', '(_End_)'),
                            price=0.5,
                            upstream_not_down=c(TRUE, FALSE,
                                                FALSE, NA, NA),
                            supply=c(1L,0L,0L,-1L,-2L), groups = as.factor('b'),
                            stringsAsFactors=F)
                 )
    node.labels(ni1f) <- ni1f[['name']]
    ni_tbl  <- as(ni1f, "tbl_df")
    expect_silent(ni_tbl_s  <- dplyr::filter(ni_tbl, name %in% letters))
    expect_is(ni_tbl_s$nodelabels, "factor")
    expect_equivalent(levels(ni_tbl_s$nodelabels), 
                      c('b', 'c', 'd', '(_Sink_)', '(_End_)')
                      ) 
})
test_that("Node labels getter",{
    expect_silent(mcf  <-  new("MCFSolutions")) #prelim-
    expect_true(validObject(mcf, complete=TRUE))#inaries

    expect_is(node.labels(mcf@nodes), "character")
    expect_is(node.labels(mcf), "character")

    data <- data.frame(z = c(rep(0,10), rep(1,5)),
                       x = rnorm(15), fac=rep(c(rep("a",2), rep("b",3)),3))
    f1 <- fullmatch(z~x, min.c=1, max.c=1, omit.fraction=.5, data = data)
    expect_is(f1, "optmatch")
    expect_false(is.null(attr(f1, "MCFSolutions")))
    expect_is(node.labels(f1), "character")
    expect_false(is.null(names(node.labels(f1))))
    expect_null(nodeinfo(10))

})
test_that("filtering on groups/subproblem field", {

    spi1  <- new("SubProbInfo",
                 data.frame(groups=c('a','b'), flipped=logical(2), hashed_dist=c('a','b'),
                            resolution=c(1,10), lagrangian_value=c(.5, 2), dual_value=c(0,1),
                            feasible=c(TRUE, FALSE), exceedance=1, stringsAsFactors=F)
                 )
    expect_error(filter_by_subproblem(spi1, groups="a"), "implemented")

    ni1f  <- new("NodeInfo",
                 data.frame(name=c('b', 'c', 'd',
                                   '(_Sink_)', '(_End_)'),
                            price=0.5,
                            upstream_not_down=c(TRUE, FALSE,
                                                FALSE, NA, NA),
                            supply=c(1L,0L,0L,-1L,-2L), groups = as.factor('b'),
                            stringsAsFactors=F
                            )
                 )
    node.labels(ni1f)  <- ni1f[['name']]
    expect_silent(ni1a  <- filter_by_subproblem(ni1f, groups="b"))
    expect_identical(ni1f, ni1a)
    expect_silent(ni10  <- filter_by_subproblem(ni1f, groups="a"))
    expect_is(ni10, "NodeInfo")
    expect_equal(nrow(ni10), 0L)

    ni2  <- new("NodeInfo",
                data.frame(name=c('a', '(_Sink_)', '(_End_)'), price=0.5,
                           upstream_not_down=c(FALSE, NA, NA),
                          supply=c(0L,-1L,-2L), groups=as.factor('c'),
                          stringsAsFactors=F)
               )
    ni12  <- c(ni1f, ni2)
    expect_silent(ni1b  <- filter_by_subproblem(ni12, groups="b"))
    expect_is(ni1b, "NodeInfo")
    expect_equal(nrow(ni1b), 5L)
    expect_silent(ni12a  <- filter_by_subproblem(ni12, groups=c("b","c")))
    expect_is(ni12a, "NodeInfo")
    expect_equal(nrow(ni12a), 8L)

    some_levs  <- c(letters[2:4], '(_Sink_)', '(_End_)')
    ai1  <- new("ArcInfo",
                matches=data.frame(groups = factor('a'),
                                   upstream = factor('b', levels=some_levs),
                                   downstream = factor(c('c','d'), levels=some_levs),
                                   stringsAsFactors=F),
                bookkeeping=data.frame(groups = factor('a'),
                                       start = factor(c('c','d'), levels=some_levs),
                                       end = factor('(_Sink_)', levels=some_levs),
                                       flow=1L, capacity=1L,
                                       stringsAsFactors=F)
               )
    expect_error(filter_by_subproblem(ai1, groups="a"), "implemented")

    mcf1  <- new("MCFSolutions", subproblems=spi1, nodes=ni1f, arcs=ai1)
    expect_error(filter_by_subproblem(mcf1, groups="a"), "implemented")
})

test_that("Potentially unusual requirements of base functions",{
    ## de-duplication of row names when stacking data.frame-s
    expect_true(formals(base::rbind.data.frame)[['make.row.names']])
    df1 <- data.frame(x=1:2, y=3:4, row.names=c('a','b'))
    df2 <- data.frame(x=3:4, y=3:4, row.names=c('a','B'))
    df3 <- data.frame(x=5:6, y=3:4, row.names=c('b','a'))
    expect_equal(row.names(rbind(df1, df2)), c("a",  "b",  "a1", "B"))
    expect_equal(row.names(rbind(df1, df3)), c("a",  "b",  "b1", "a1"))
    ## char vecs can have repeats in row names
    cvec  <- letters
    names(cvec)  <- LETTERS
    names(cvec)[1]  <- "B"
    expect_is(cvec, "character")
    expect_named(cvec, c("B", LETTERS[-1L]))
    expect_true(any(duplicated(names(cvec))))
})

test_that("NodeInfo subsetting", {
    expect_silent(ni0_o  <- nodes_shell_fmatch(c(1,2), c(3,4)))
    expect_is(ni0_o, "NodeInfo")
    expect_equal(nrow(ni0_o), 6)
    expect_silent(ni0_n  <- filter(ni0_o, name!=2 & name!=4))
    expect_is(ni0_n, "NodeInfo")
    expect_equal(nrow(ni0_n), 4)    
})
test_that("Pull updated prices & supplies into a NodeInfo",{
    expect_silent(ni0_o  <- nodes_shell_fmatch(c(1,2), c(3,4)))
    expect_true(all(ni0_o[['price']]==0))
    ni0_n  <- filter(ni0_o, name!=2 & name!=4)
    expect_equal(nrow(ni0_n), 4) # unimportant in itself but presumed by next few lines
    ni0_n@.Data[[which(ni0_n@names=="price")]]  <- rep(1, 4)
    ni0_n@.Data[[which(ni0_n@names=="supply")]]  <- c(2, 0, -1, -1)
    ## (Now ni0_n has the form of an actual price/supply combo)
    expect_silent(ni0  <- update.NodeInfo(ni0_o, ni0_n))
    expect_equal(ni0[['price']], c(1, 0, 1, 0, 1, 1))
    expect_equal(ni0[['supply']], c(2, 0, 0, 0, -1, -1))

    expect_equal(update(ni0_o, ni0_n), ni0) # confirm `update()` dispatch
})
