ewing Demos
  • Home
  • Gallery
  • sysetholApp
  • IsleRoyaleApp
  • fivePlotApp
  • fiveShowApp
  • tempApp
  • triangleApp
  • hexmapApp
  • hexmoveApp

sysetholApp (Systems Ethology Platform)

The central Systems Ethology platform orchestrating host-parasite individual-based simulations, age class distributions over time, spatial substrate networks, and parameter evaluations.
Author

Brian S. Yandell

← Back to Demos Gallery

The live application below is running completely client-side in your browser using serverless Shinylive (WebAssembly). It provides a streamlined Systems Ethology platform (sysetholApp()) with clean sidebar controls (sysetholInput), dynamic conditional panels (Steps per click log-scale geometric slider when nsim = 1, Confidence Band when nsim > 1), sidebar Run Engine & Reset buttons, and modular tabs (sysetholOutput: Dist Plots fine time-granularity step plots, Substrate Plots, Envelope Plots, and Input Data via inputApp).

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 880
#| components: [viewer]

library(shiny)
library(bslib)
library(ggplot2)
library(cowplot)
library(dplyr)
library(tidyr)
library(tibble)
library(rlang)
library(splines)
library(stats)
library(graphics)

# --- Auto-Included Data Table: future.host ---
future.host <- structure(list(current = c("crawler", "first.instar", "first.molt", 
"second.1", "second.2", "second.3", "second.3", "female", "male", 
"second.molt", "third.1", "third.2", "third.3", "virgin", "gravid", 
"death", "starved"), future = c("first.instar", "first.molt", 
"second.1", "second.2", "second.3", "female", "male", "second.molt", 
"death", "third.1", "third.2", "third.3", "virgin", "gravid", 
"gravid", "death", "death"), fid = c(2L, 3L, 4L, 5L, 6L, 8L, 
9L, 10L, 16L, 11L, 12L, 13L, 14L, 15L, 15L, 16L, 16L), time = c(55L, 
92L, 48L, 30L, 30L, 52L, 52L, 0L, 0L, 48L, 55L, 55L, 95L, 90L, 
10L, 0L, 0L), pch = c("0", "1", "1", "2", "2", "2", "2", "2", 
"2", "2", "3", "3", "3", "V", "G", "D", "D"), color = c("brown", 
"green", "green", "turquoise", "turquoise", "turquoise", "turquoise", 
"turquoise", "turquoise", "turquoise", "blue", "blue", "blue", 
"violet", "black", "red", "red"), ageclass = c("crawler", "host", 
"host", "host", "host", "host", "host", "host", "host", "host", 
"host", "host", "host", "host", "gravid", NA, "gravid"), event = c("future", 
"future", "future", "future", "future", "future", "future", "future", 
"future", "future", "future", "future", "future", "future", "birth", 
"death", "future"), init = c(10L, 4L, 4L, 1L, 1L, 1L, 1L, 0L, 
0L, 1L, 1L, 1L, 1L, 1L, 2L, 0L, 0L)), class = "data.frame", row.names = c("1", 
"2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", 
"14", "15", "16", "17"))


# --- Auto-Included Data Table: future.parasite ---
future.parasite <- structure(list(current = c("egg", "larvae", "prepupae", "pupae", 
"adult", "adult", "feed", "ovip", "death", "male", "starved"), 
    future = c("larvae", "prepupae", "pupae", "adult", "feed", 
    "ovip", "adult", "adult", "death", "death", "death"), fid = c(2L, 
    3L, 4L, 5L, 7L, 8L, 5L, 5L, 9L, 9L, 9L), time = c(48L, 60L, 
    24L, 144L, 12L, 12L, 12L, 12L, 0L, 0L, 0L), pch = c("E", 
    "L", "p", "P", "F", "H", "F", "H", "D", "D", "D"), color = c("brown", 
    "green", "turquoise", "blue", "orange", "purple", "orange", 
    "purple", "red", "red", "red"), ageclass = c("young", "young", 
    "young", "young", "adult", "adult", "adult", "adult", NA, 
    "adult", "adult"), event = c("future", "future", "future", 
    "future", "future", "future", "attack", "attack", "death", 
    "future", "future"), init = c(10L, 5L, 3L, 2L, 1L, 0L, 0L, 
    0L, 0L, 0L, 0L)), class = "data.frame", row.names = c("1", 
"2", "3", "4", "5", "6", "7", "8", "9", "10", "11"))


# --- Auto-Included Data Table: host.parasite ---
host.parasite <- structure(list(ovip = c(0L, 0L, 0L, 0L, 3L, 4L, 2L, 3L, 5L, 5L, 
2L, 0L, 0L, 0L, 0L, 0L), feed = c(0L, 5L, 2L, 5L, 2L, 1L, 0L, 
0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L), offspring = c(0, 0, 0, 0, 
0, 0, 0, 1.3, 1.3, 1.3, 2.7, 0, 0, 0, 0, 0), male = c(NA, 5L, 
5L, 5L, 5L, 5L, 4L, 2L, 1L, 1L, 0L, NA, NA, NA, NA, NA)), class = "data.frame", row.names = c("crawler", 
"first.instar", "first.molt", "second.1", "second.2", "second.3", 
"second.molt", "third.1", "third.2", "third.3", "virgin", "gravid", 
"death", "parasite", "male", "female"))


# --- Auto-Included Data Table: organism.features ---
organism.features <- structure(list(units = c("DD", "hr", NA), offspring = c("20", 
"host", NA), attack = c(NA, "host", NA), birth = c("gravid", 
NA, NA), substrate = c("substrate", "substrate", NA), deplete = c(100L, 
48L, NA), subclass = c("host", "adult", NA), parasite = c(NA, 
"ecto", NA), move = c("crawler", "adult", NA)), class = "data.frame", row.names = c("host", 
"parasite", "substrate"))


# --- Auto-Included Data Table: redscale ---
redscale <- structure(list(DD = c(-5L, 0L, 0L, 55L, 147L, 195L, 225L, 255L, 
307L, 355L, 410L, 465L, 560L, 650L, 651L), aphytis.feed = c(0L, 
0L, 0L, 5L, 2L, 5L, 2L, 1L, 0L, 0L, 0L, 0L, 0L, 0L, 0L), aphytis = c(0L, 
0L, 0L, 0L, 0L, 0L, 3L, 4L, 2L, 3L, 5L, 5L, 2L, 0L, 0L), comperiella = c(0, 
0, 0, 3, 3, 2, 2, 3, 4, 5, 5, 5, 3.5, 0, 0), encarsia = c(0, 
0, 0, 4, 3.5, 5, 5, 5, 4.5, 4.5, 4, 3, 3, 0, 0), gender = c("either", 
"either", "neuter", "neuter", "neuter", "neuter", "neuter", "neuter", 
"female", "female", "female", "female", "female", "female", "female"
), pch = c("P", "P", "0", "1", "1", "2", "2", "2", "2", "3", 
"3", "3", "V", "G", "D"), color = c("white", "white", "brown", 
"green", "green", "turquoise", "turquoise", "turquoise", "turquoise", 
"blue", "blue", "blue", "violet", "black", "red"), stage = c("parasite", 
"parasite", "crawler", "first.instar", "first.molt", "second.1", 
"second.2", "second.3", "second.molt", "third.1", "third.2", 
"third.3", "virgin", "gravid", "death")), class = "data.frame", row.names = c(NA, 
-15L))


# --- Auto-Included Data Table: substrate.host ---
substrate.host <- structure(list(substrate = c("fruit", "fruit", "fruit", "fruit", 
"twig", "leaf", "leaf"), side = c("1", "2", "3", "4", NA, "top", 
"bottom"), init = c(1L, 1L, 1L, 1L, 1L, 5L, 0L), find = c(5L, 
5L, 5L, 5L, 1L, 3L, 2L), move = c(1L, 1L, 1L, 1L, 5L, 3L, 4L), 
    fruit = c(3L, 3L, 3L, 3L, 3L, 3L, 3L), twig = c(2L, 2L, 2L, 
    2L, 1L, 2L, 1L), leaf = c(1L, 1L, 1L, 1L, 2L, 1L, 2L)), class = "data.frame", row.names = c("fr1", 
"fr2", "fr3", "fr4", "twig", "lftop", "lfbot"))


# --- Auto-Included Data Table: substrate.parasite ---
substrate.parasite <- structure(list(substrate = c("fruit", "fruit", "fruit", "fruit", 
"twig", "leaf", "leaf"), side = c("1", "2", "3", "4", NA, "top", 
"bottom"), init = c(1L, 1L, 1L, 1L, 1L, 5L, 0L), find = c(5L, 
5L, 5L, 5L, 1L, 3L, 2L), move = c(1L, 1L, 1L, 1L, 5L, 3L, 4L), 
    fruit = c(3L, 3L, 3L, 3L, 3L, 3L, 3L), twig = c(2L, 2L, 2L, 
    2L, 1L, 2L, 1L), leaf = c(1L, 1L, 1L, 1L, 2L, 1L, 2L)), class = "data.frame", row.names = c("fr1", 
"fr2", "fr3", "fr4", "twig", "lftop", "lfbot"))


# --- Auto-Included Data Table: substrate.substrate ---
substrate.substrate <- structure(list(fr1 = c(0L, 1L, 1L, 1L, 1L, 1L, 1L), fr2 = c(1L, 
0L, 1L, 1L, 1L, 1L, 1L), fr3 = c(1L, 1L, 0L, 1L, 1L, 1L, 1L), 
    fr4 = c(1L, 1L, 1L, 0L, 1L, 1L, 1L), twig = c(1L, 1L, 1L, 
    1L, 1L, 1L, 1L), lftop = c(1L, 1L, 1L, 1L, 1L, 1L, 1L), lfbot = c(1L, 
    1L, 1L, 1L, 1L, 1L, 1L)), class = "data.frame", row.names = c("fr1", 
"fr2", "fr3", "fr4", "twig", "lftop", "lfbot"))


# --- Auto-Included Data Table: temperature.base ---
temperature.base <- structure(list(Day = c(0L, 0L, 0L, 0L, 0L, 30L, 30L, 30L, 30L, 
30L, 30L), Time = c(0L, 8L, 12L, 18L, 20L, 0L, 8L, 12L, 15L, 
18L, 20L), Base = c(0L, 0L, 100L, 75L, 0L, 0L, 0L, 80L, 100L, 
80L, 60L)), class = "data.frame", row.names = c(NA, -11L))


# --- Auto-Included Data Table: temperature.par ---
temperature.par <- structure(list(value = c(24L, 200L, 52L, 70L, 75L, 85L, 90L, 
8L), description = c("hours in day", "number of days", "minimum temperature for degree-day calculation (Fahrenheit)", 
"begining daily low temperature", "ending daily low temperature", 
"begining daily high temperature", "ending daily high temperature", 
"number of nodes for interpolating spline")), class = "data.frame", row.names = c("Unit", 
"Days", "Min", "LowBeg", "LowEnd", "HighBeg", "HighEnd", "Length"
))


# --- Source: spline.R ---
## $Id: spline.R,v 1.0 2002/12/09 yandell@stat.wisc.edu Exp $
##
## Functions for Bland Ewing's modeling.
##
##     Copyright (C) 2000,2001,2002 Brian S. Yandell.
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by the
## Free Software Foundation; either version 2, or (at your option) any
## later version.
##
## These functions are distributed in the hope that they will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## The text of the GNU General Public License, version 2, is available
## as http://www.gnu.org/copyleft or by writing to the Free Software
## Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
###########################################################################################
## Support routines for future.meanvalue
###########################################################################################
spline.rate <- function( meanvalue, x )
{
  coeff <- stats::coef( meanvalue )
  for( i in 2:ncol( coeff ))
    coeff[,i-1] <- i * coeff[,i]
  coeff[,ncol( coeff ) ] <- 0
  meanvalue$coefficients <- coeff
  if( missing( x ))
    stats::predict( meanvalue )
  else
    stats::predict( meanvalue, x )
}
###########################################################################################
spline.deriv <- function( s )
{
  s$coefficients <- s$coefficients[,-1]
  for( i in seq( 2, ncol( s$coefficients )))
    s$coefficients[,i] <- s$coefficients[,i] * i
  s
}
###########################################################################################
splinesum <- function( xy, fit = splines::interpSpline( xy$x, xy$y ),
                       log2 = log( 2 ), tol = 1e-5 )
{
  n <- nrow( xy )
  ## mean time to future event (assume linear off end )
  maxx <- xy[n,"x"]
  mean.y <- maxx * mean( exp( - stats::predict( fit )$y )) + xy[1,"x"]
  rate <- spline.rate( fit, maxx )$y
  if( rate > tol )
    mean.y <- mean.y + exp( - xy[n,"y"] ) / rate

  ## median time to future event
  adiff <- diff( stats::coef( fit )[,1] )
  ## undefined if mean value function not monotone
  if( !( all( adiff < 0) || all( adiff > 0 )))
    median.y <- NA
  else {
    invmvalue <- splines::backSpline( fit )
    median.y <- stats::predict( invmvalue, log2 )$y
    if( is.na( median.y ))
      median.y <- spline.extrapolate( fit, invmvalue, log2 )
  }
  c( mean = mean.y, median = median.y )
}
###########################################################################################
summaryshow <- function( xy, fit, col = "black", sums = splinesum( xy, fit ))
{
  tmpar <- graphics::par( col = col )
  graphics::mtext( paste( "mean =", round( sums[1], 2 )), 3, at = graphics::par("usr")[1], adj = 0 )
  medianshow <- if( is.na( sums[2] ))
    "curve not monotone"
  else
    paste( "median =", round( sums[2], 2 ))
  graphics::mtext( medianshow, 3, at = graphics::par("usr")[2] / 1.25, adj = 1 )
  graphics::par( tmpar )
  invisible( sums )
}
###########################################################################################
curve.plot <- function( xy = seq(0,1,by=.25), y = c(0,.1,.5,.9,1),
  z = graphics::locator(1,"n"), n=5, action="add",
  fit = splines::interpSpline( xy$x, xy$y ),
  backfit = TRUE, save.ends = 3, col = c("blue","red"), lwd = 4,
  f = function( x ) x, finv = function( x ) x )
{
  if( !is.list( xy )) {
    xy <- data.frame( x = xy )
    xy$y <- y
  }
  else
    xy <- as.data.frame( xy )
  if( !match( action, c("refresh","finish"), nomatch = 0 )) {
    z <- as.data.frame(z)
    tmp <- z$x > max( xy$x )
    if( any( tmp )) {
      if( all( tmp ))
        return( xy )
      z$x <- z$x[!tmp]
      z$y <- z$y[!tmp]
    }
  }
  remove.points <- function( xy, finv, save.ends = TRUE ) {
    ## find closest point after standardizing distances
    usr <- graphics::par( "usr" )
    tmp <- (( xy$x - z$x ) / diff( usr[1:2] )) ^ 2 +
      (( xy$y - z$y ) / diff( usr[3:4] )) ^ 2
    if( save.ends )
      tmp <- tmp[ - c( 1, nrow( xy )) ]
    tmp <- save.ends + min( seq( tmp )[ tmp == min( tmp ) ] )
    tmpd <- xy[tmp,"y"]
    tmpd <- finv( tmpd )
    graphics::points( xy[tmp,"x"], tmpd, lwd = lwd, col = "white" ) 
    tmp
  }
  for( i in 1:n) {
    switch( action,
      noaction =
        return( xy )
      ,
      add = {
        graphics::points(z$x,z$y, lwd = lwd )
        z$y <- f( z$y )
        xy <- rbind(xy,z)
        xy <- xy[order(xy$x),]
        fit <- splines::interpSpline( xy$x, xy$y )
      },
      replace = {
        graphics::points(z$x,z$y, lwd = lwd )
        z$y <- f( z$y )
        tmp <- remove.points( xy, finv, save.ends == 3 )
        tmpp <- ( tmp > 1 & tmp < length( xy$x ))
        if( save.ends != 2 | tmpp )
          xy$x[tmp] <- z$x
        if( save.ends != 1 | tmpp )
          xy$y[tmp] <- z$y
        fit <- splines::interpSpline( xy$x, xy$y )
      },
      delete = {
        z$y <- f( z$y )
        tmp <- remove.points( xy, finv, save.ends > 0 )
        xy <- xy[-tmp,]
        fit <- splines::interpSpline( xy$x, xy$y )
      },
      refresh =, finish = {
        xy
      }
    )
    tmpp <- stats::predict( fit )
    graphics::lines( tmpp$x, finv( tmpp$y ), col = col[1], lwd = lwd )

    adiff <- diff( stats::coef( fit )[,1] )
    if( backfit & ( all( adiff < 0) || all( adiff > 0 ))) {
      ## backspline (not quite a spline) fit to inverse
      tmpback <- stats::predict( splines::backSpline( fit ))
      graphics::lines( tmpback$y, finv( tmpback$x ), col = col[2], lwd = lwd )
    }
  }
  list( xy = xy[order(xy$x),], fit = fit )
}
###########################################################################################
cdf.lines <- function( data, fig = "mean value", nspline = 8,
  conf = c(50,80,90,95), rescale = 1,
  col = c("green","blue","red","orange") )
{
  rate <- fig == "mean value"
  n <- length( data )
  data <- sort( data )
  prob <- seq( n ) / ( n + 1 )
  f <- - rescale * log( 1 - prob )
  ylab <- "prob"
  if( rate )
    ylab <- "cum rate"
  else
    f <- 1 - exp( -f )
  graphics::lines( data, f, lwd = 2 )
  conf <- conf / 100
  for( i in seq( length( conf ))) {
    ## lower confidence
    tmp <- stats::qbinom( conf[i], n, prob, lower.tail = TRUE ) / ( n + 1 )
    tmp <- 1 - exp( rescale * log( 1 - tmp ))
    tmpna <- is.na( tmp )
    if( any( tmpna ))
      tmp[tmpna] <- f[1]
    if( rate )
      tmp <- - log( 1 - tmp )
    graphics::lines( data, tmp, lty = 2, col = col[i] )
    ## upper confidence
    tmp <- stats::qbinom( conf[i], n, prob, lower.tail = FALSE ) / ( n + 1 )
    tmp <- 1 - exp( rescale * log( 1 - tmp ))
    tmpna <- is.na( tmp )
    if( any( tmpna ))
      tmp[tmpna] <- f[n]
    if( rate )
      tmp <- - log( 1 - tmp )
    graphics::lines( data, tmp, lty = 2, col = col[i] )
  }
}
###########################################################################################
rspline <- function( meantime = 1,
                    fivepar = c(dispersion = 1, location = 0, intensity = 1, truncation = 0,
                      rejection = Inf ),
                    fit = NULL,
                    meanvalue = fit$meanvalue, invmvalue = fit$invmvalue,
                    span = Inf )
{
  ## dispersion = a, location = b, intensity = c
  ## truncation = -log(1-d), rejection = -log(1-e)

  ## y = a M^-1( G(d)+cV ) + b  if 1 - exp( -cV ) < G(e)
  ## y = span        if 1 - exp( -cV ) >= G(e)

  ## this is not quite right for truncation, as we know event happened before b
  ## if 1-exp(-cV) < d, but I am not sure how to pass that information along yet

  default <- is.null( meanvalue )

  ##          V ~ exp(1)
  ## intensity:      V/c
  ## truncation:      (G(d)+V)/c
  rate <- ( fivepar["truncation"] + rexp( 1 )) / fivepar["intensity"]

  ## mean value inverse:    M^-1( G(d)+V/c )
  if( default )
    y <- rate
  else {
    y <- stats::predict( invmvalue, rate )$y
    ## kludge to linearly extrapolate beyond cubic spline fit
    if( is.na( y ))
      y <- spline.extrapolate( meanvalue, invmvalue, rate )
  }

  ## dispersion and location:  y = a M^-1( cV ) + b
  y <- meantime * fivepar["dispersion"] * y + fivepar["location"]

  ## rejection:      y >= e? then set y to span
  if( y > fivepar["rejection"] )
    y <- span

  y
}
###########################################################################################
spline.extrapolate <- function( meanvalue, invmvalue, x )
{
  ## linear extrapolation of inverse spline beyond upper end
  coeff <- stats::coef( invmvalue )
  nr <- nrow( coeff ) - 1
  xknot <- splines::splineKnots( invmvalue )[nr+(0:1)]
  yknot <- splines::splineKnots( meanvalue )[nr+1]
  tmpr <- ( xknot[2] - xknot[1] )
  slope <- coeff[nr,2] + tmpr * ( 2 * coeff[nr,3] + tmpr * 3 * coeff[nr,4] )
  yknot + slope * ( x - xknot[2] )
}
###########################################################################################
### spline.design() is a prototype for designing spline curves
### Ultimately, pieces of spline.design, spline.temp() and spline.meanvalue()
### will be pulled out as subroutines to reduce code overlap
###########################################################################################
spline.design <- function (y = yinit, x = xinit, nspline = 8, xy = data.frame(x = x, 
    y = y), n = 1, horizontal = FALSE) 
{
    is.data <- !missing(y)
    if (is.data) {
        data <- y
        if (missing(x)) 
            x <- as.numeric(names(y))
        datax <- x
        ndata <- length(data)
        choose <- round(seq(1, ndata, length = nspline))
        xinit <- x <- x[choose]
        yinit <- y <- y[choose]
    }
    else {
        tmp <- seq(0, nspline - 1)
        if (missing(x)) 
            xinit <- tmp
        else xinit <- x
        if (missing(y)) 
            yinit <- rep(50, nspline)
        else yinit <- y
    }

  ## plot curve and surrounding axes
  graphics::par( mfrow = c(1,1), mar = rep(4.1,4))
  plotit <- function( xy, fig = "temp", fit = splines::interpSpline( xy$x, xy$y ),
    horizontal = FALSE, strip = .25, margin = .1 )
  {
    switch( fig, {
        y <- xy$y
        ylim <- range(y)
      }
    )
    xlim <- range(xy$x)
    xlim <- xlim + c(-1,1) * margin * diff( xlim )
    if( horizontal ) {
      if( diff( ylim ) == 0 )
        ylim <- ylim * c(.75,1.25)
      separator <- ylim[2]
      ylim[2] <- ylim[2] + strip * diff( ylim )
    }
    else {
      separator <- xlim[2]
      xlim[2] <- xlim[2] + strip * diff( xlim )
    }
    axt <- c("n","s")
    tmpar <- graphics::par( xaxt = axt[1+horizontal], yaxt = axt[2-horizontal] )
    plot( xy$x, y, xlim = xlim, ylim = ylim, type="n", xlab = "", ylab = "" )
    graphics::par( xaxt = "s", yaxt = "s" )
    graphics::points( xy$x, y, lwd = 4 )
    graphics::title( fig )
    graphics::mtext( "time", 1, 2 )
    graphics::mtext( fig, 2, 2 )
    if( horizontal ) {
      p <- pretty( c(ylim[1],separator) )
      graphics::axis( 2, p[ p <= separator ] )
      graphics::abline( h = separator, lty = 2 )
    }
    else {
      p <- pretty( c(xlim[1],separator) )
      graphics::axis( 1, p[ p <= separator ] )
      graphics::abline( v = separator, lty = 2 )
    }
    curve.plot( xy, n = n, action = "refresh", fit = fit, backfit = FALSE,
      save.ends = 0 )
    separator
  }
  ## place commands along right strip of plot, highlighting current command
  plotcmd <- function( ans, fig, cmds, cmdlocs, usr, col = "green", rest = "black",
    horizontal = TRUE )
  {
    ans <- c( ans, fig )
    tmp <- is.na( match( cmds, ans ))
    if( any( tmp )) for( i in unique( cmdlocs$adj )) {
      tmpi <- tmp & i == cmdlocs$adj
      if( any( tmpi ))
        graphics::text( cmdlocs$x[tmpi], cmdlocs$y[tmpi], cmds[tmpi], col = rest, adj = i )
    }
    if( any( !tmp )) for( i in unique( cmdlocs$adj )) {
      tmpi <- !tmp & i == cmdlocs$adj
      if( any( tmpi ))
        graphics::text( cmdlocs$x[tmpi], cmdlocs$y[tmpi], cmds[tmpi], col = col, adj = i )
    }
  }
  cmds <- c("add","delete","replace","","finish","restart","refresh","rescale",
    "","data","temp")
  newlocs <- if( horizontal )
    function( cmds, data = FALSE, usr )
    {
      if( !data )
        cmds <- cmds[ cmds != "data" ]
      n <- length( cmds )
      blank <- seq( n )[ cmds == "" | cmds == " " ]
      tmp <- diff(usr[3:4]) / 20
      m <- mean( usr[1:2] )
      y <- usr[4] + 0.5 * tmp - c( tmp * seq( blank[1] - 1 ), 0,
        tmp * seq( blank[2] - blank[1] - 1 ), 0,
        tmp * seq( n - blank[2] ))
      x <- c( rep( usr[1], blank[1] - 1 ), mean( m, usr[1] ),
        rep( m, blank[2] - blank[1] - 1 ), mean( m, usr[2] ),
        rep( usr[2], n - blank[2] ))
      adj <- c( rep( 0, blank[1] ),
        rep( 0.5, blank[2] - blank[1] ),
        rep( 1, n - blank[2] ))
      tmp <- data.frame( x = x, y = y, adj = adj )
      cmds[blank[2]] <- " "
      row.names( tmp ) <- cmds
      tmp
    }
  else
    function( cmds, data = FALSE, usr )
    {
      if( !data )
        cmds <- cmds[ cmds != "data" ]
      n <- length( cmds )
      blank <- seq( n )[ cmds == "" ]
      tmp <- diff(usr[3:4]) / 20
      m <- mean( usr[3:4] )
      tmp <- c( usr[4] - tmp * seq( blank[1] - 1 ),
        mean( m, usr[4] ),
        m + tmp * ( seq( blank[1] + 1, blank[2] - 1 ) - mean( blank )),
        mean( m, usr[3] ),
        usr[3] + tmp * seq( n - blank[2] ))
      tmp <- data.frame( x = rep( usr[2], n ), y = tmp, adj = rep( 1, n ))
      cmds[blank[2]] <- " "
      row.names( tmp ) <- cmds
      tmp
    }

  fig <- "temp"
  newans <- ans <- "replace"

  graphics::par( mar = c(4.1,4.1,3.1,4.1),omi=rep(.25,4))
  fit <- splines::interpSpline( xy$x, xy$y )
  separator <- plotit( xy, fig, fit, horizontal )

  usr <- graphics::par("usr")
  cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
  cmds <- row.names( cmdlocs )
  plotcmd( ans, fig, cmds, cmdlocs, usr )
  use.data <- FALSE
  rescale.data <- 1
  repeat {
    ## get command from plot using cursor
    z <- graphics::locator(1,"n")
    if(( !horizontal & z$x > separator ) | ( horizontal * z$y > separator )) {
      if( horizontal ) { # need to look at both z&y 
        x <- abs( z$x - cmdlocs$x )
        x <- x == min( x )
        newans <- cmds[x]
        z <- abs( z$y - cmdlocs$y )[x]
        newans <- newans[ z == min( z ) ][1]
      }
      else {
        z <- abs(z$y - cmdlocs$y )
        newans <- cmds[ z == min( z ) ][1]
      }
      switch( newans,
        finish =, refresh = {
          separator <- plotit( xy, fig, fit, horizontal )
          usr <- graphics::par("usr")
          cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
        },
        data = {
          use.data <- is.data & !use.data
          if( is.data & !use.data )
            plotit( xy, fig, fit, horizontal )
        },
        temp = {
          fig <- newans
          separator <- plotit( xy, fig, fit, horizontal )
          usr <- graphics::par("usr")
          cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
        },
        rescale = {
          cat( "enter new values followed by RETURN key\n" )
          tmpy <- readline( paste( "maximum ", fig, "(",
            round( max( xy$y ), 2 ), "):", sep = "" ))
          if( tmpy != "" ) {
            tmpy <- suppressWarnings(as.numeric( tmpy )) / max( xy$y )
            xy$y <- tmpy * xy$y
            if( is.data )
              rescale.data <- rescale.data * tmpy
          }
          tmpx <- readline( paste( "maximum time(", 
            round( max( xy$x ), 2 ), "):", sep = "" ))
          if( tmpx != "" ) {
            tmpx <- suppressWarnings(as.numeric( tmpx )) / max( xy$x )
            xy$x <- tmpx * xy$x
          }
          fit <- splines::interpSpline( xy$x, xy$y )
          separator <- plotit( xy, fig, fit, horizontal )
          usr <- graphics::par("usr")
          cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
        },
        restart = {
          if( is.data )
            rescale.data <- 1
          xy <- data.frame( x = xinit, y = yinit )
          fit <- splines::interpSpline( xy$x, xy$y )
          separator <- plotit( xy, fig, fit, horizontal )
          usr <- graphics::par("usr")
          cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
        },
        add =, delete =, replace = {
          ans <- newans
        }
      )
      if( use.data ) {
        rx <- range( xy$x )
        dx <- range( datax )        
        graphics::lines( rx[1] + ( datax - dx[1] ) * diff( rx ) / diff( dx ), data * rescale.data )
      }
      plotcmd( ans, fig, cmds, cmdlocs, usr )
    }
    else {
      fit <- curve.plot( xy, n = n, action = ans, z = z, fit = fit, backfit = FALSE,
        save.ends = 0 )
      xy <- fit$xy
      fit <- fit$fit
      ans <- "replace"
      plotcmd( ans, fig, cmds, cmdlocs, usr )
    }
    if( newans == "finish" )
      break
  }
  plotcmd( newans, fig, cmds, cmdlocs, "red" )
  tmp <- curve.plot( xy, n = n, action = "refresh", backfit = FALSE, save.ends = 0 )
  tmp
}

# --- Source: triangle.R ---
## $Id: triangle.R,v 0.9 2002/12/09 yandell@stat.wisc.edu Exp $
##
## Functions for Bland Ewing's modeling.
##
##     Copyright (C) 2000,2001,2002 Brian S. Yandell.
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by the
## Free Software Foundation; either version 2, or (at your option) any
## later version.
##
## These functions are distributed in the hope that they will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## The text of the GNU General Public License, version 2, is available
## as http://www.gnu.org/copyleft or by writing to the Free Software
## Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
###########################################################################################
## rtri( n, width )
##
## plot.current( x, species )
## text.current( x, species )
##
###########################################################################################
###########################################################################################
### Tridiagonal Coordinate System S3 Classes & Algebra
###########################################################################################
tricoord <- function(a, b = NULL, c = NULL) {
  if (is.data.frame(a) && all(c("a", "b", "c") %in% names(a))) {
    res <- a
  } else if (is.matrix(a) && ncol(a) == 3) {
    res <- as.data.frame(a)
    names(res) <- c("a", "b", "c")
  } else if (!is.null(b) && !is.null(c)) {
    res <- data.frame(a = a, b = b, c = c)
  } else if (is.numeric(a) && length(a) == 3) {
    res <- data.frame(a = a[1], b = a[2], c = a[3])
  } else {
    stop("Invalid tricoord input format")
  }
  class(res) <- c("tricoord", "data.frame")
  res
}

`+.tricoord` <- function(e1, e2) {
  # Handle vector offsets cleanly
  if (is.numeric(e2) && length(e2) == 3) {
    tricoord(e1$a + e2[1], e1$b + e2[2], e1$c + e2[3])
  } else if (inherits(e2, "tricoord")) {
    tricoord(e1$a + e2$a, e1$b + e2$b, e1$c + e2$c)
  } else {
    stop("Invalid right hand operand for tricoord addition")
  }
}

`-.tricoord` <- function(e1, e2) {
  if (is.numeric(e2) && length(e2) == 3) {
    tricoord(e1$a - e2[1], e1$b - e2[2], e1$c - e2[3])
  } else if (inherits(e2, "tricoord")) {
    tricoord(e1$a - e2$a, e1$b - e2$b, e1$c - e2$c)
  } else {
    stop("Invalid right hand operand for tricoord subtraction")
  }
}

###########################################################################################
rtri <- function( n, width, tri = matrix(0,3,n), roundoff = TRUE )
{
  tri <- as.matrix( tri )
  if( n == 1 ) {
    xy <- stats::runif( 2, 0, width )
    if( roundoff )
      xy <- round( xy )

    i <- sample( 3, 1  )
    i1 <- 1 + i%%3
    i2 <- 1 + (i+1)%%3
    tri[i1,] <- tri[i1,] + xy[1]
    tri[i2,] <- tri[i2,] - xy[2]
    tri[i,] <- - ( tri[i1,] + tri[i2,] )
    return( tri )
  }
  else {
    xy <- data.frame( x = stats::runif( n, 0, width ),
      y = - stats::runif( n, 0, width ))
    if( roundoff )
      xy <- round( xy )

    out <- sample( 3, n, replace = TRUE )
    for( i in 1:3 ) {
      outi <- out == i
      if( any( outi )) {
        i1 <- 1 + i%%3
        i2 <- 1 + (i+1)%%3
        tri[i1,outi] <- tri[i1,outi] + xy$x[outi]
        tri[i2,outi] <- tri[i2,outi] + xy$y[outi]
        tri[i,outi] <- - ( tri[i1,outi] + tri[i2,outi] )
      }
    }
  }
  tri
}
###########################################################################################
car2tri.default <- function(x,y)
  car2tri( cbind( x, y ))
car2tri <- function( xy, xmult = ( 2 + sq3 ) / 4, ymult = ( 3 + 2 * sq3 ) / 12,
  sq3 = sqrt( 3 ))
{
#  if( !is.matrix( xy ))
#    xy <- t( as.matrix( xy ))
  aa <- xmult * xy[,1] - ymult * xy[,2]
  bb <- - xmult * xy[,1] - ymult * xy[,2]
  cc <- -( aa + bb )
  rbind( a = aa, b = bb, c = cc )
}
###########################################################################################
tri2car.default <- function(aa,bb,cc=-(aa+bb))
  tri2car( rbind( aa, bb, cc ))
tri2car <- function(tri, xmult = 2 / ( 2 + sq3 ), ymult = 6 / ( 3 + 2 * sq3 ),
  sq3 = sqrt( 3 ))
{
  if( inherits(tri, "tricoord") ) {
    # If the user passes our S3 tricoord dataframe, map it correctly natively.
    x <- ( tri$a - tri$b ) * xmult
    y <- - ( tri$a + tri$b ) * ymult
  } else {
    if( !is.matrix( tri ))
      tri <- as.matrix( tri )
    x <- ( tri[1,] - tri[2,] ) * xmult
    y <- - ( tri[1,] + tri[2,] ) * ymult
  }
  data.frame( x = x, y = y )
}
###########################################################################################
cardist <- function( xy )
  sqrt( xy[,1]^2 + xy[,2]^2 )
###########################################################################################
tridist <- function( tri )
  apply( tri, 1, max )
###########################################################################################
gasket <- function( aa, bb )
{
  n <- length( aa )
  pp <- c(-1,1,0,1)
  dda <- diff( aa )
  ddb <- diff( bb )
  ss <- sign( sign( dda ) - sign( ddb ))
  dda <- 2 - abs( dda )
  ddb <- 2 - abs( ddb )
  aa <- 2 * aa
  aa <- c( aa[1], rbind( aa[-n] + pp[dda+1+ss], aa[-1] + pp[dda+1-ss], aa[-1] ))
  bb <- 2 * bb
  bb <- c( bb[1], rbind( bb[-n] + pp[ddb+1-ss], bb[-1] + pp[ddb+1+ss], bb[-1] ))
  data.frame( aa = aa, bb = bb )
}

# --- Source: substrate_triangle.R ---
get_substrate_grid <- function(width, step = 1, orientation = "up") {
  pts <- expand.grid(a = seq(0, width - step, by = step), 
                     b = seq(0, width - step, by = step))
  
  if (orientation == "up") {
    pts <- subset(pts, a + b <= width - step)
    pts$c <- -(pts$a + pts$b)
  } else {
    pts <- subset(pts, a + b <= width - step)
    pts$a <- -pts$a
    pts$b <- -pts$b
    pts$c <- -(pts$a + pts$b)
  }
  return(pts)
}

substrate_topology <- function(width = 10, step = 1) {
  W <- width - step
  
  # Topology adjacency offsets
  list(
    fr2   = list(offset = c(0, 0, 0), dir = "down"),
    fr1   = list(offset = c(-W, -W, 2*W), dir = "up"),
    fr3   = list(offset = c(-W, 0, W), dir = "up"),
    fr4   = list(offset = c(0, -W, W), dir = "up"),
    tw1   = list(offset = c(-W, 0, W), dir = "down"),
    twig  = list(offset = c(-W, 0, W), dir = "down"),
    tw2   = list(offset = c(-2*W, 0, 2*W), dir = "up"),
    lftop = list(offset = c(-2*W, W, W), dir = "down"),
    lfbot = list(offset = c(-3*W, W, 2*W), dir = "up")
  )
}

create_substrate <- function(topology, width = 10, step = 1) {
  W <- width - step
  all_points <- data.frame()
  labels_df <- data.frame()
  poly_df <- data.frame()
  
  for (sub in names(topology)) {
    cfg <- topology[[sub]]
    grid <- get_substrate_grid(width, step, cfg$dir)
    
    o_a <- cfg$offset[1]
    o_b <- cfg$offset[2]
    o_c <- cfg$offset[3]

    # Needs tricoord and tri2car which are presumably exported/available from R/triangle.R
    grid_tri <- tricoord(grid$a, grid$b, grid$c)
    grid_tri <- grid_tri + cfg$offset
    
    car_pts <- tri2car(grid_tri)
    car_pts$substrate <- sub
    all_points <- rbind(all_points, car_pts)
    
    # Determine bounds and midpoints for side labels 1,2,3
    if (cfg$dir == "up") {
      v_top <- c(o_a, o_b, o_c)
      v_br  <- c(o_a + W, o_b, o_c - W)
      v_bl  <- c(o_a, o_b + W, o_c - W)
      
      m1 <- (v_top + v_bl) / 2
      m2 <- (v_top + v_br) / 2
      m3 <- (v_bl + v_br)  / 2
      centroid <- (v_top + v_br + v_bl) / 3
      p_mat <- cbind(v_top, v_br, v_bl)
    } else {
      v_bot <- c(o_a, o_b, o_c)
      v_tr  <- c(o_a, o_b - W, o_c + W)
      v_tl  <- c(o_a - W, o_b, o_c + W)
      
      m1 <- (v_bot + v_tr) / 2
      m2 <- (v_bot + v_tl) / 2
      m3 <- (v_tl + v_tr)  / 2
      centroid <- (v_bot + v_tr + v_tl) / 3
      p_mat <- cbind(v_bot, v_tr, v_tl)
    }
    
    # Interpolate slightly towards the centroid to put text "just inside" the edges
    w_in <- 0.25 # weight towards centroid
    l1 <- m1 * (1 - w_in) + centroid * w_in
    l2 <- m2 * (1 - w_in) + centroid * w_in
    l3 <- m3 * (1 - w_in) + centroid * w_in
    
    mat_l <- cbind(l1, l2, l3)
    car_l <- tri2car(mat_l)
    car_l$label <- c("1", "2", "3")
    car_l$substrate <- sub
    labels_df <- rbind(labels_df, car_l)
    
    car_p <- tri2car(p_mat)
    car_p$substrate <- sub
    poly_df <- rbind(poly_df, car_p)
  }
  
  centers <- stats::aggregate(cbind(x,y) ~ substrate, data=all_points, mean)
  
  obj <- list(
    points = all_points, 
    labels = labels_df, 
    poly = poly_df, 
    centers = centers,
    topology = topology
  )
  class(obj) <- "substrate"
  return(obj)
}

autoplot.substrate <- function(object, ...) {
  ggplot2::ggplot() +
    # Draw the black boundary lines outlining the substrates exactly over outer dots
    ggplot2::geom_polygon(data=object$poly, ggplot2::aes(x=x, y=y, group=substrate), fill=NA, color="black", linewidth=0.7) +
    # Plot grid dots
    ggplot2::geom_point(data=object$points, ggplot2::aes(x=x, y=y, color=substrate), size=1.5) +
    # Plot Substrate Labels (Centers)
    ggplot2::geom_text(data=object$centers, ggplot2::aes(x=x, y=y, label=substrate), color="black", fontface="bold", size=5) +
    # Axis side numbers
    ggplot2::geom_text(data=object$labels, ggplot2::aes(x=x, y=y, label=label), color="darkred", fontface="bold", size=4) +
    ggplot2::theme_void() +
    ggplot2::coord_fixed() +
    ggplot2::ggtitle("Ewing Tridiagonal Substrate Network Mapping")
}

create_hex_overlay <- function(object, step = 1) {
  pts <- if (inherits(object, "substrate")) object$points else object
  if (is.null(pts) || nrow(pts) == 0) return(data.frame())
  
  xmult <- 2 / (2 + sqrt(3))
  ymult <- 6 / (3 + 2 * sqrt(3))
  d <- step * sqrt(xmult^2 + ymult^2)
  r <- d / sqrt(3)
  
  angles <- (seq(0, 5) * 60 + 30) * pi / 180
  dx <- r * cos(angles)
  dy <- r * sin(angles)
  
  n_pts <- nrow(pts)
  hex_list <- vector("list", n_pts)
  
  for (i in seq_len(n_pts)) {
    px <- pts$x[i] + dx
    py <- pts$y[i] + dy
    sub <- pts$substrate[i]
    hex_list[[i]] <- data.frame(
      x = px,
      y = py,
      cell_id = i,
      substrate = sub,
      stringsAsFactors = FALSE
    )
  }
  
  do.call(rbind, hex_list)
}


# --- Source: ewing_substrate.R ---
ewing_substrate <- function( community,
                             species,
                             headstuff = c( 0, "start", sum( to.plot )),
                             units = getOrgFeature( community, species[1], "units" ),
                             right = species[1], adj = c(0,.5,1),
                             show_sub = NULL,
                             step = 0,
                             layout = c("facet", "hex"),
                             width = 10,
                             step_density = 1,
                             rescale = TRUE,
                             x_var = c("step", "time"),
                             ...)
{
  x_var <- match.arg(x_var)
  
  if (inherits(community, "isle_royale_sim")) {
    p_map <- autoplot(community$habitat_overlay, show_landmarks = TRUE)
    moose_sf <- sf::st_as_sf(community$moose_pop, coords = c("lon", "lat"), crs = sf::st_crs(community$habitat_overlay$layer))
    wolf_sf  <- sf::st_as_sf(community$wolf_pop, coords = c("lon", "lat"), crs = sf::st_crs(community$habitat_overlay$layer))
    
    hdr_str <- if (x_var == "step") paste0("Step ", community$nstep) else paste0("Time Units ", community$nstep)
    
    p_map <- p_map +
      ggplot2::geom_sf(data = moose_sf, color = "#27ae60", shape = 21, fill = NA, stroke = 1.0, size = 0.8, alpha = 0.85) +
      ggplot2::geom_sf(data = wolf_sf, color = "#e74c3c", shape = 21, fill = NA, stroke = 1.4, size = 1.5, alpha = 0.95) +
      ggplot2::ggtitle(paste0("Isle Royale Substrate Plot (", hdr_str, ")"))
    
    return(p_map)
  }

  layout <- match.arg(layout)
  
  if (length(species) > 1) {
    res_list <- lapply(species, function(sp) {
      df <- ewing_substrate(community = community, species = sp, headstuff = headstuff,
                            units = units, right = right, adj = adj, show_sub = show_sub,
                            step = step, layout = layout, width = width, step_density = step_density,
                            rescale = rescale, ...)
      if (!is.null(df) && nrow(df) > 0) {
        df$species <- sp
      }
      df
    })
    res_list <- res_list[!sapply(res_list, is.null)]
    if (length(res_list) == 0) return(NULL)
    combined <- do.call(rbind, res_list)
    attr(combined, "species") <- paste(species, collapse = " & ")
    step_val <- if (!is.null(community$step)) community$step else if (!is.null(community$count$step)) community$count$step else if (!is.null(attr(community, "nstep"))) attr(community, "nstep") else step
    attr(combined, "step") <- step_val
    attr(combined, "layout") <- layout
    attr(combined, "width") <- width
    attr(combined, "step_density") <- step_density
    class(combined) <- c("ewing_substrate", class(combined))
    return(combined)
  }
  
  ## plot current stages for species (except random parasites)
  organism <- get.species( community, species )[,-1]
  if(is.null(organism)) # species is not in community
    return(NULL)
  
  future <- getOrgFuture( community, species, c("color","pch") )
  
  # Substrate names mapping (e.g. fr1, fr2, fr3, fr4, twig, lftop, lfbot)
  substrate_feat <- getOrgFeature( community, species, "substrate")
  sub_interact <- getOrgInteract(community, substrate_feat, species)
  substrates <- rownames(sub_interact)
  if (is.null(substrates) || length(substrates) == 0) {
    substrates <- names(getOrgInteract(community, substrate_feat, substrate_feat))
  }
  if (is.null(show_sub)) show_sub <- substrates
  
  position <- paste( "pos", letters[1:3], sep = "." )
  
  if (layout == "hex") {
    topo <- substrate_topology(width = width, step = step_density)
    n_org <- ncol(organism)
    gx <- numeric(n_org)
    gy <- numeric(n_org)
    
    sub_indices <- organism["sub.stage", ]
    org_sub_names <- substrates[sub_indices]
    
    # Process organisms per substrate patch to rescale local coordinates into substrate surface triangle
    unique_subs <- unique(org_sub_names)
    
    for (sub in unique_subs) {
      idx <- which(org_sub_names == sub)
      
      target_name <- sub
      if (!target_name %in% names(topo)) {
        if (target_name == "twig") target_name <- "tw1"
        if (target_name == "tw1") target_name <- "twig"
      }
      
      cfg <- topo[[target_name]]
      
      pa <- organism["pos.a", idx]
      pb <- organism["pos.b", idx]
      pc <- organism["pos.c", idx]
      
      if (!is.null(cfg)) {
        # Determine substrate surface width (allows substrates of different sizes in future)
        W_sub <- if (!is.null(cfg$width)) (cfg$width - step_density) else (width - step_density)
        
        if (rescale) {
          amin <- min(pa); amax <- max(pa)
          bmin <- min(pb); bmax <- max(pb)
          
          u <- if (amax > amin) (pa - amin) / (amax - amin) else rep(0.5, length(idx))
          v <- if (bmax > bmin) (pb - bmin) / (bmax - bmin) else rep(0.5, length(idx))
          
          # 15% inner padding to ensure symbols sit comfortably inside substrate polygon borders
          u_m <- 0.15 + 0.70 * u
          v_m <- 0.15 + 0.70 * v
          
          a_p <- u_m * W_sub
          b_p <- v_m * (W_sub - a_p)
          c_p <- -(a_p + b_p)
        } else {
          a_p <- pa
          b_p <- pb
          c_p <- pc
        }
        
        off <- cfg$offset
        if (cfg$dir == "up") {
          ga <- a_p + off[1]
          gb <- b_p + off[2]
          gc <- c_p + off[3]
        } else {
          ga <- -a_p + off[1]
          gb <- -b_p + off[2]
          gc <- -c_p + off[3]
        }
        car <- tri2car(tricoord(ga, gb, gc))
        gx[idx] <- car$x
        gy[idx] <- car$y
      } else {
        car <- tri2car(organism[position, idx, drop = FALSE])
        gx[idx] <- car$x
        gy[idx] <- car$y
      }
    }
    
    xy <- data.frame(x = gx, y = gy)
  } else {
    xy <- tri2car( organism[position,] )
  }
  
  dat <- dplyr::filter(
    dplyr::mutate(
      tibble::tibble(xy), 
      stage = organism["stage",],
      substrate = substrates[organism["sub.stage",]],
      pchar = factor(as.character( future$pch[.data$stage + 1] ), levels = unique(as.character(future$pch))),
      color = as.character( future$color[.data$stage + 1] ),
      species = species),
    .data$substrate %in% show_sub)
  
  attr(dat, "species") <- species
  step_val <- if (!is.null(community$step)) community$step else if (!is.null(community$count$step)) community$count$step else if (!is.null(attr(community, "nstep"))) attr(community, "nstep") else step
  attr(dat, "step") <- step_val
  attr(dat, "layout") <- layout
  attr(dat, "width") <- width
  attr(dat, "step_density") <- step_density
  class(dat) <- c("ewing_substrate", class(dat))
  dat
}

ggplot_ewing_substrate <- function(object,
                                   xlab = "horizontal", ylab = "vertical",
                                   layout = attr(object, "layout"),
                                   width = attr(object, "width"),
                                   step_density = attr(object, "step_density"),
                                   layers = c("poly", "hex", "organisms", "centers", "labels"),
                                   ...)
{
  if (inherits(object, "ggplot")) return(object)
  if (is.null(layout)) layout <- "facet"
  if (is.null(width)) width <- 10
  if (is.null(step_density)) step_density <- 1
  
  species <- attr(object, "species")
  step <- attr(object, "step")
  
  # Allows same color for different pchar, but only one color per pchar.
  tmp <- dplyr::arrange(
    dplyr::distinct(
      dplyr::distinct(object, .data$pchar, .data$color),
      .data$pchar, .keep_all = TRUE),
    .data$pchar)
  col.palate <- tmp$color
  names(col.palate) <- as.character(tmp$pchar)
  
  if (layout == "hex") {
    topo <- substrate_topology(width = width, step = step_density)
    sub_obj <- create_substrate(topo, width = width, step = step_density)
    hex_overlay <- create_hex_overlay(sub_obj, step = step_density)
    
    p <- ggplot2::ggplot()
    if ("poly" %in% layers && nrow(sub_obj$poly) > 0) {
      p <- p + ggplot2::geom_polygon(data = sub_obj$poly, ggplot2::aes(x = x, y = y, group = substrate), 
                                     fill = NA, color = "black", linewidth = 0.7)
    }
    if ("hex" %in% layers && nrow(hex_overlay) > 0) {
      p <- p + ggplot2::geom_polygon(data = hex_overlay, ggplot2::aes(x = x, y = y, group = cell_id), 
                                     fill = NA, color = "gray75", linewidth = 0.3)
    }
    if ("organisms" %in% layers && nrow(object) > 0) {
      p <- p + ggplot2::geom_text(data = object, ggplot2::aes(x = x, y = y, label = pchar, color = pchar), 
                                  fontface = "bold", size = 4) +
        ggplot2::scale_color_manual(name = "Stage", values = col.palate) +
        ggplot2::guides(color = ggplot2::guide_legend(override.aes = list(label = names(col.palate))))
    }
    if ("centers" %in% layers && nrow(sub_obj$centers) > 0) {
      p <- p + ggplot2::geom_text(data = sub_obj$centers, ggplot2::aes(x = x, y = y, label = substrate), 
                                  color = "black", fontface = "bold", size = 4.5)
    }
    if ("labels" %in% layers && nrow(sub_obj$labels) > 0) {
      p <- p + ggplot2::geom_text(data = sub_obj$labels, ggplot2::aes(x = x, y = y, label = label), 
                                  color = "darkred", fontface = "bold", size = 3.5)
    }
    
    return(p + ggplot2::theme_void() + 
             ggplot2::theme(plot.margin = ggplot2::margin(2, 2, 2, 2, "pt")) +
             ggplot2::coord_fixed() + 
             ggplot2::ggtitle(paste(species, "on Hex Substrate Grid at", step, "steps")))
  }
  
  # Default Facet View by Substrate Component
  ggplot2::ggplot(object) +
    ggplot2::aes(.data$x, .data$y, label = .data$pchar, col = .data$pchar) +
    ggplot2::geom_text() +
    ggplot2::facet_wrap(~ substrate) +
    ggplot2::xlab(xlab) +
    ggplot2::ylab(ylab) +
    ggplot2::scale_color_manual(name = "Stage", values = col.palate) + 
    ggplot2::guides(color = ggplot2::guide_legend(override.aes = list(label = names(col.palate)))) +
    ggplot2::ggtitle(paste(species, "on substrate at", step, "steps"))
}
autoplot.ewing_substrate <- function(object, ...)
  ggplot_ewing_substrate(object, ...)

# --- Source: ewing_ageclass.R ---
ewing_ageclass <- function(community, substrate = TRUE, total = TRUE,
                           normalize = TRUE, ...) {
  nsim_val <- NULL
  nstep_val <- NULL
  
  if (inherits(community, "isle_royale_sim")) {
    if (is.null(community$history) || nrow(community$history) == 0) return(NULL)
    out <- community$history
    if (!substrate) {
      out <- dplyr::filter(out, .data$Type != "substrate")
    }
    if (total) {
      tot <- dplyr::mutate(
        dplyr::ungroup(
          dplyr::summarize(
            dplyr::group_by(out, .data$Species, .data$step, .data$time, .data$Type),
            Count = sum(.data$Count),
            .groups = "drop"
          )
        ),
        State = "total"
      )
      out <- dplyr::bind_rows(out, tot)
    }
    if (normalize) {
      out <- dplyr::ungroup(
        dplyr::mutate(
          dplyr::group_by(out, .data$Species, .data$State, .data$Type),
          Count = {
            m <- max(.data$Count, na.rm = TRUE)
            if (!is.na(m) && m > 0) .data$Count / m else 0
          }
        )
      )
    }
    ordered_levels <- unique(c("calf", "yearling", "adult", "senior", "pup", "subadult", "total"))
    out$State <- factor(out$State, levels = ordered_levels[ordered_levels %in% unique(out$State)])
    attr(out, "nstep") <- community$nstep
    attr(out, "units") <- "days"
    class(out) <- c("ewing_ageclass", class(out))
    return(out)
  }
  
  if (inherits(community, "ewing_discrete")) {
    nsim_val <- attr(community, "nsim")
    nstep_val <- attr(community, "nstep")
    community <- community[[1]]
  }
  count <- readCount(community)
  if(!length(count)) return(NULL)
  species <- names(count)
  if(is.null(species)) return(NULL)
  
  ageclass <- list()
  for( i in species ) {
    ageclass[[i]] <- levels( getOrgFuture( community, i, "ageclass" ))
  }
  substrates <- list()
  for( i in species ) {
    substrates[[i]] <- levels( getOrgInteract( community,, i, "substrate" ))
  }
  out <- list()
  for(i in species) {
    out[[i]] <- dplyr::mutate(
      tidyr::pivot_longer(
        tibble::tibble(
          as.data.frame(count[[i]])),
        dplyr::any_of(c(ageclass[[i]], substrates[[i]])), 
        names_to = "State",
        values_to = "Count"),
      Type = ifelse(.data$State %in% substrates[[i]], "substrate", "ageclass"))
  }
  out <- dplyr::bind_rows(out, .id = "Species")
  if(!substrate) {
    out <- dplyr::filter(out, .data$Type != "substrate")
  }
  if(total) {
    tot <- dplyr::mutate(
      dplyr::ungroup(
        dplyr::summarize(
          dplyr::group_by(
            out,
            .data$Species, .data$step, .data$time, .data$future, .data$Type),
          Count = sum(.data$Count),
          .groups = "drop")
      ),
      State = "total")
    out <- dplyr::bind_rows(out, tot)      
  }
  if(normalize) {
    out <- dplyr::ungroup(
      dplyr::mutate(
        dplyr::group_by(
          out,
          .data$Species, .data$State, .data$Type),
        Count = {
          m <- max(.data$Count, na.rm = TRUE)
          if (!is.na(m) && m > 0) .data$Count / m else 0
        }))
  }
  subs <- if (substrate) unlist(substrates) else NULL
  if (length(species) > 1) {
    ordered_levels <- unique(c(ageclass[[species[1]]], "total", unlist(ageclass[species[-1]]), subs))
  } else {
    ordered_levels <- unique(c(unlist(ageclass), "total", subs))
  }
  out$State <- factor(out$State, levels = ordered_levels)
  
  attr(out, "nstep") <- if (!is.null(nstep_val)) nstep_val else attr(community, "nstep")
  if (is.null(attr(out, "nstep")) && !is.null(out$step)) {
    attr(out, "nstep") <- max(out$step, na.rm = TRUE)
  }
  attr(out, "nsim") <- if (!is.null(nsim_val)) nsim_val else attr(community, "nsim")
  attr(out, "units") <- if (inherits(community, "isle_royale_sim")) "days" else tryCatch(getOrgFeature(community, species[1], "units"), error = function(e) "time")
  
  class(out) <- c("ewing_ageclass", class(out))
  out
}
ggplot_ewing_ageclass <- function(object, main = NULL, title = NULL, x_var = c("step", "time"), time_unit = NULL, ... )
{
  x_var <- match.arg(x_var)
  
  if (is.null(time_unit)) {
    time_unit <- attr(object, "units")
  }
  if (is.null(time_unit) || is.na(time_unit) || time_unit == "NA") {
    time_unit <- "time"
  }
  
  if (is.null(title)) title <- main
  if (is.null(title)) {
    nstep <- attr(object, "nstep")
    if (is.null(nstep) && !is.null(object$step)) {
      nstep <- max(object$step, na.rm = TRUE)
    }
    nsim <- attr(object, "nsim")
    unit_str <- if (x_var == "step") "steps" else time_unit
    time_hdr <- if (x_var == "step") "Steps" else paste0(toupper(substring(time_unit, 1, 1)), substring(time_unit, 2))
    if (!is.null(nstep)) {
      if (!is.null(nsim) && nsim > 1) {
        title <- paste0("Age Classes over ", time_hdr, " (", nstep, " ", unit_str, ", nsim = ", nsim, ")")
      } else {
        title <- paste0("Age Classes over ", time_hdr, " (", nstep, " ", unit_str, ")")
      }
    } else {
      title <- paste0("Age Classes over ", time_hdr)
    }
  }
  
  species_vec <- unique(as.character(object$Species))
  if (length(species_vec) == 0) species_vec <- "Organism"
  
  p_list <- list()
  
  for (sp in species_vec) {
    df_sp <- object[object$Species == sp, , drop = FALSE]
    if (is.factor(df_sp$State)) {
      df_sp$State <- droplevels(df_sp$State)
    }
    
    x_col <- if (x_var == "step" && "step" %in% names(df_sp)) "step" else "time"
    x_lbl <- if (x_var == "step") "steps" else time_unit
    
    sp_title <- paste(toupper(substring(sp, 1, 1)), substring(sp, 2), " Age Classes", sep = "")
    
    p_sub <- ggplot2::ggplot(df_sp, ggplot2::aes(x = .data[[x_col]], y = .data$Count, col = .data$State, group = .data$State)) +
      ggplot2::geom_step(na.rm = TRUE, linewidth = 0.8) +
      ggplot2::geom_point(size = 2, na.rm = TRUE) +
      ggplot2::theme_minimal() +
      ggplot2::labs(
        title = sp_title,
        x = x_lbl,
        y = "Count",
        color = "Age Class"
      ) +
      ggplot2::theme(
        plot.title = ggplot2::element_text(size = 11, face = "bold", hjust = 0.5),
        legend.position = "right"
      )
      
    p_list[[sp]] <- p_sub
  }
  
  if (length(p_list) == 1) {
    return(p_list[[1]] + ggplot2::ggtitle(title))
  }
  
  grid_plots <- cowplot::plot_grid(plotlist = p_list, ncol = length(p_list), align = "h")
  title_widget <- cowplot::ggdraw() + 
    cowplot::draw_label(title, fontface = 'bold', x = 0.5, hjust = 0.5, size = 13)
  
  cowplot::plot_grid(title_widget, grid_plots, ncol = 1, rel_heights = c(0.12, 1))
}
autoplot.ewing_ageclass <- function(object, x_var = c("step", "time"), ...) {
  x_var <- match.arg(x_var)
  ggplot_ewing_ageclass(object, x_var = x_var, ...)
}


# --- Source: ewing_envelope.R ---
ewing_envelope <- function(object, species, item, ordinate = "time", increment = 0.5) {
  # Pull out `ordinate` and `item` for each run 
  pulled <-  
    tidyr::fill(
      dplyr::arrange(
        tidyr::pivot_wider(
          dplyr::bind_rows(
            purrr::map(
              object,
              function(x) {
                dplyr::distinct(
                  as.data.frame(x[[species]][,c(ordinate, item)]),
                  .data[[ordinate]],
                  .keep_all = TRUE)
              }),
            .id = "run"),
          names_from = "run",
          values_from = item),
        .data[[ordinate]]),
      -dplyr::matches(ordinate))
  
  out <- GET::create_curve_set(list(r = as.matrix(pulled)[,1], 
                                    obs = as.matrix(pulled[,-1])))
  class(out) <- c("ewing_envelope", class(out))
  attr(out, "count") <- attr(object, "count")
  attr(out, "nstep") <- attr(object, "nstep")
  attr(out, "nsim") <- attr(object, "nsim")
  
  attr(out, "species") <- species
  attr(out, "item") <- item
  attr(out, "ordinate") <- ordinate
  out
}
ewing_envelopes <- function(object) {
  species <- attr(object, "species")
  items <- attr(object, "items")
  ordinate <- attr(object, "ordinate")
  nstep <- attr(object, "nstep")
  count <- attr(object, "count")
  nsim <- attr(object, "nsim")
  confidence <- (nsim > 2)
  
  envs <- as.list(species)
  names(envs) <- species
  if(confidence) {
    confs <- envs
  } else {
    confs <- NULL
  }
  for(specy in species) {
    env1 <- as.list(items[[specy]])
    if(confidence) {
      conf1 <- env1
    }
    for(item in items[[specy]]) {
      env1[[item]] <- ewing_envelope(object, specy, item, ordinate)
      if(confidence) {
        conf1[[item]] <- GET::fBoxplot(env1[[item]], type = 'area')
      }
    }
    envs[[specy]] <- env1
    if(confidence) {
      confs[[specy]] <- conf1
    }
  }
  
  object <- list(env = envs, conf = confs)
  class(object) <- c("ewing_envelopes", class(object))
  attr(object, "species") <- species
  attr(object, "items") <- items
  attr(object, "ordinate") <- ordinate
  attr(object, "nstep") <- nstep
  attr(object, "count") <- count
  attr(object, "nsim") <- nsim
  attr(object, "confidence") <- confidence
  object
}
summary.ewing_envelopes <- function(object, species = NULL, verbose = TRUE, ...) {
  # object$conf[[specy]][[item]] is time by 6-num boxplot summary
  if(verbose) {
    nstep <- attr(object, "nstep")
    count <- attr(object, "count")
    nsim <- attr(object, "nsim")
    cat(nsim, "Runs of ",
        nstep, "Steps for", 
        paste(names(count), count, sep = "=", collapse = ", "), "\n")
  }
  out <- print(object, species, ...)
  if(!is.null(out)) {
    out <- dplyr::ungroup(
      dplyr::filter(
        dplyr::group_by(
          out,
          .data$species, .data$item),
        (.data$r == 0) | (.data$r == max(.data$r))))
  }
  out
}
print.ewing_envelopes <- function(x, species = NULL, ...) {
  # x$conf[[specy]][[item]] is time by 6-num boxplot summary
  if(is.null(x$conf)) {
    return(NULL)
  }
  out <- dplyr::bind_rows(
    purrr::map(
      x$conf,
      function(x) {
        # somehow get summary across species and items using as.data.frame
        x <- x[names(x) != ""]
        dplyr::bind_rows(
          purrr::map(
            x,
            as.data.frame),
          .id = "item")
      }),
    .id = "species")
  if(!is.null(species)) {
    if(species %in% unique(out$species)) {
      sp <- species
      out <- dplyr::filter(out, species == sp)
    }
  }
  dplyr::mutate(out, dplyr::across(where(is.numeric), function(x) pmax(x,0)))
  out
}
ggplot_ewing_envelopes <- function(object, confidence = FALSE, main = "", ...) {
  if(inherits(object, "ewing_discrete")) {
    object <- ewing_envelopes(object)
  }
  species <- attr(object, "species")
  items <- attr(object, "items")
  ordinate <- attr(object, "ordinate")
  nstep <- attr(object, "nstep")
  count <- attr(object, "count")
  nsim <- attr(object, "nsim")
  confidence <- confidence & attr(object, "confidence")
  
  patch <- list()
  for(specy in species) {
    p <- list()
    for(item in items[[specy]]) {
      if(confidence) {
        p[[item]] <- plot(object$conf[[specy]][[item]], main = main) + 
          ggplot2::labs(x = "time", y = item) +
          ggplot2::ggtitle(main) +
          ggplot2::ylim(0, NA)
        
      } else {
        p[[item]] <- ggplot_ewing_envelope(object$env[[specy]][[item]])
      }
    }
    patch[[specy]] <- cowplot::plot_grid(plotlist = p, nrow = length(p))
  }
  
  # NEED TO get attribute count and nstep in here
  patch <- cowplot::plot_grid(plotlist = patch, ncol = length(patch)) 
  # Add a title. <https://wilkelab.org/cowplot/articles/plot_grid.html>
  title <- cowplot::ggdraw() + 
    cowplot::draw_label(
      paste(nsim, "Runs of ", nstep, "Steps for", 
            paste(species, count, sep = "=", collapse = ", ")),
      x = 0, hjust = 0
    ) +
    ggplot2::theme(
      # add margin on the left of the drawing canvas,
      # so title is aligned with left edge of first plot
      plot.margin = ggplot2::margin(0, 0, 0, 7)
    )
  cowplot::plot_grid(title, patch, ncol = 1, rel_heights = c(0.1, 1))
}

ggplot_ewing_envelope <- function(object, cols = c("#21908CFF", "#440154FF", "#5DC863FF"), 
                                  main = "", ...) {
  # Kludge. GET::forder needs at least 3 points; cols can be at most length(object).
  lcols <- length(cols)
  nsim <- ncol(object$funcs)
  if(nsim >= min(3, lcols)) {
    A <- GET::forder(object, measure = 'area')
    lcols <- min(lcols, length(object))
    idx <- order(A)[seq_len(lcols)]
    cols <- cols[seq_len(lcols)]
  } else {
    lcols <- nsim
    idx <- seq_len(lcols)
    cols <- cols[seq_len(lcols)]
  }
  
  item <- attr(object, "item")
  species <- attr(object, "species")
  ordinate <- attr(object, "ordinate")
  
  if(length(object) >= 50) {
    p <- plot(object, idx = idx, col_idx = cols, main = main)
  } else {
    p <- plot(object)
  }
  p + 
    ggplot2::labs(x = ordinate, y = item) +
    ggplot2::ggtitle(paste(species, item))
}
autoplot.ewing_envelope <- function(object, ...) {
  ggplot_ewing_envelope(object, ...)
}

# --- Source: ewing_snapshot.R ---
ewing_snapshot <- function(object, step = 0, ...)
{
  out <- list(step = step,
              ageclass = ewing_ageclass(object, ...))

  species <- get.species(object)
  subs <- list()
  for(j in species) {
    subs[[j]] <- ewing_substrate(object, j, step = step, ...)
  }
  out$substrate <- subs
  
  class(out) <- c("ewing_snapshot", "ewing", "list")
  out
}

# --- Source: temp.R ---
## $Id: temp.R,v 1.0 2002/12/09 yandell@stat.wisc.edu Exp $
##
## Functions for Bland Ewing's modeling.
##
##     Copyright (C) 2000,2001,2002 Brian S. Yandell.
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by the
## Free Software Foundation; either version 2, or (at your option) any
## later version.
##
## These functions are distributed in the hope that they will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## The text of the GNU General Public License, version 2, is available
## as http://www.gnu.org/copyleft or by writing to the Free Software
## Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
###########################################################################################
## initTemp( community, lo.hour, hi.hour )  ### creates Temperature object
## activeTemp( community, lo.hour, hi.hour )    ### updates Temperature object
## checkTime( ) # check if activeTemp needs updating to cover time interval
##
## temp.design( )
## temp.plot( )
###########################################################################################
rescale.temp <- function( low, high, newry = ry, newrx = rx,
  ry = range( c( stats::predict( low, low$knots )$y, stats::predict( high, high$knots )$y )))
{
  if( max( abs( ry - newry )) > 0 ) {
    tmpy <- diff( newry ) / diff( ry )
    low$coefficients <- low$coefficients * tmpy
    high$coefficients <- high$coefficients * tmpy
    tmpy <- newry[1] - ry[1] * tmpy
    low$coefficients[,1] <- tmpy + low$coefficients[,1]
    high$coefficients[,1] <- tmpy + high$coefficients[,1]
  }
  rx <- range( low$knots, high$knots )
  if( max( abs( rx - newrx )) > 0 ) {
    newx <- diff( newrx ) / diff( rx )
    low$knots <- newrx[1] + newx * low$knots
    high$knots <- newrx[1] + newx * high$knots
    tmp <- 1
    for( i in seq( 2, ncol( low$coefficients ))) {
      tmp <- tmp * newx
      low$coefficients[,i] <- low$coefficients[,i] / tmp
      high$coefficients[,i] <- high$coefficients[,i] / tmp
    }
  }
  list( low = low, high = high )
}
###########################################################################################
### Temperature data structure
### Min minimum temperature for degree-day computations
### Time    list of hours when temperature changes through a day
### Base    list of temperature shifts through a day
###     list names indicate first day Hour and Base applies
### Low spline fit for daily low temperatures
### High    spline fit for daily high temperatures
### DegreeDay   spline fit (hour->DD) for currently active days
### Hour        ramped backspline fit (DD->hour) for currently active days
### Idea is that DegreeDay and Hour are updated whenever a future event is scheduled
### past the latest future event already scheduled. At that time, the first
### and last time of fit are both adjusted. This can be done without rebuilding
### the whole spline fit by appropriate adjustment of knots and coefficents.
### Steps:  (1) combine Hour, Low and High to get knots
###     (2) build spline coefficients
###########################################################################################
temp.spline <- function( community, hour, temp, start = 0,
  mintemp = getTemp( community, "Min" ), cumulative = TRUE,
  mult = getTemp( community, "Unit" ) )
{
  if( is.data.frame( hour ) & missing( temp )) {
    temp <- hour$temp
    hour <- hour$hour
  }
  ## drop low value if ones on either side are low
  low <- temp <= mintemp
  toolow <- low & c(FALSE,low[-length(low)]) & c(low[-1],FALSE)
  hour <- hour[!toolow]
  temp <- temp[!toolow]
  lh <- length( hour )
  ## expand single low in middle to two
  low <- temp <= mintemp
  toolow <- low & c(FALSE,!low[-length(low)]) & c(!low[-1],FALSE)
  if( any( toolow )) {
    hour <- c(hour,hour[toolow])
    temp <- c(temp,temp[toolow])
    temp <- temp[ order( hour ) ]
    hour <- sort( hour )
  }
  ## truncate on left
  low <- temp <= mintemp & c(temp[-1] > mintemp,FALSE)
  low1 <- c(FALSE,low[-lh])
  if( any( low ))
    hour[low] <- hour[low] + ( hour[low1] - hour[low] ) * ( mintemp - temp[low] ) /
      ( temp[low1] - temp[low] )
  ## truncate on right
  low <- temp <= mintemp & c(FALSE,temp[-lh] > mintemp)
  low1 <- c(low[-1],FALSE)
  if( any( low ))
    hour[low] <- hour[low] + ( hour[low1] - hour[low] ) * ( mintemp - temp[low] ) /
      ( temp[low1] - temp[low] )
  temp[ temp < mintemp ] <- mintemp
  temp <- temp[ !duplicated( hour ) ]
  hour <- unique( hour )

  ## linear interpolating spline for temp
  lh <- length( hour ) - 1
  h0 <- 1:lh
  aa <- ( temp[1+h0] - temp[h0] ) / ( hour[1+h0]-hour[h0] )
  aa <- c( aa, aa[lh] )
  bb <- temp - mintemp
  s <- list( knots = hour, coefficients = cbind( bb,aa,0,0) )
  dimnames( s$coefficients ) <- list( paste( floor( hour / mult ), round( hour %% mult ),
    sep = "." ), c("const","linear","quad","cubic") )
  attr(s,"formula") <- temp ~ hour
  class( s ) <- c("npolySpline","polySpline","spline")
  if( !cumulative )
    return( s )

  ## quadratic spline for cumulative temp
  aa <- s$coefficients[,3] <- s$coefficients[,2] / 2 / mult / mult
  lh <- length( aa ) - 1
  h0 <- 1:lh
  h1 <- 1 + h0
  hour <- s$knots
  bb <- s$coefficients[,2] <- s$coefficients[,1] / mult
  cc <- ( hour[h1] - hour[h0] ) * ( bb[h0] + aa[h0] *
    ( hour[h1] - hour[h0] ))
  s$coefficients[,1] <- start + c( 0, cumsum( cc ))
  s
}
###########################################################################################
temp.repeat <- function( community, period = range( days ))
{
  lodays <- range( getTemp( community, "Low" )$knots )
  hidays <- range( getTemp( community, "High" )$knots )
  days <- c( max( lodays[1], hidays[1] ), min( lodays[2], hidays[2] ))
  lodays <- period[1] >= days[1] & period[1] <= days[2]
  if( lodays )
    days[1] <- period[1]
  hidays <- period[2] <= days[2] & period[2] >= days[1]
  if( hidays )
    days[2] <- period[2]
  if( !( lodays & hidays )) {
    stop( paste( "\nSimulation period is outside of Temperature days:\n period =",
      paste( round( period ), collapse = "," ), "; days =",
      paste( round( days ), collapse = "," ), "\nNeed to run temp.design() and start over!" ))
  }
  period <- days

  days <- seq( days[1], days[2] )
  low <- stats::predict( getTemp( community, "Low" ), days )$y
  high <- stats::predict( getTemp( community, "High" ), days )$y
  periods <- c( as.numeric( names( getTemp( community, "Time" ))), Inf )
  temps <- hours <- numeric( )
  period[2] <- period[2] + 1
  for( i in seq( length( periods ) - 1 )) {
    day <- max( period[1], periods[i] )
    this.period <- days >= day & days < min( period[2], periods[i+1] )
    n <- sum( this.period )
    if( n ) {
      if( !is.null( getTemp( community, "Time", i ))) {
        time <- getTemp( community, "Time", i )
        base <- getTemp( community, "Base", i )
      }
      h <- length( time )
      lotemp <- rep( low[this.period], rep(h,n) )
      hitemp <- rep( high[this.period], rep(h,n) )
      temp <- ( base - min( base )) / diff( range( base ))
      temps <- c( temps, rep( temp, n ) * ( hitemp - lotemp ) + lotemp )

      hours <- c( hours, rep( time, n ) + rep( getTemp( community, "Unit" ) *
        seq( day, day + n - 1 ), rep(h,n) ))
    }
  }
  day <- days == period[2]
  if( any( day )) {
    temps <- c( temps, low[day] + temp[1] * ( high[day] -
      getTemp( community, "Low" )[day] ))
    hours <- c( hours, getTemp( community, "Unit" ) * period[2] )
  }
  data.frame( hour = hours, temp = temps )
}
##########################################################################################
showTemp <- function( community )
{
  cat( "Temperature set for days",
      paste( range( getTemp( community, "Low" )$knots ), collapse = " to " ), "\n" )
  cat( "Daily low temperature range:",
      paste( round( range( stats::predict( getTemp( community, "Low" ))$y )),
            collapse = " to " ), "\n" )
  cat( "Daily high temperature range:",
      paste( round( range( stats::predict( getTemp( community, "High" ))$y )),
            collapse = " to " ), "\n" )
  if( !is.null( getTemp( community, "DegreeDay" ) ))
    cat( "Active temperature range:",
        paste( round( range( getTemp( community, "DegreeDay", "knots" ) )),
              collapse = " to " ), "\n" )
  cat( "Run temp.design() to adjust temperature range\n" )
}
##########################################################################################
activeTemp <- function( community,
                       lo.hour = min( getTemp( community, "DegreeDay", "knots" ) ),
                       hi.hour = max( getTemp( community, "DegreeDay", "knots" ) ),
                       degreeday, messages = TRUE )
{
  unit <- getTemp( community, "Unit" )
  if( missing( hi.hour )) {
    hi.hour <- if( length( lo.hour ) > 1 )
      lo.hour[2]
    else
      lo.hour + unit
  }
  ## period is in units of hours
  period <- c( floor( lo.hour[1] / unit ), ceiling( hi.hour / unit ))
  if( missing( degreeday )) {
    degreeday <- if( is.null( getTemp( community, "DegreeDay" ) ))
      0
    else
      getDegreeDay( community, lo.hour[1] )
  }
  if( is.na( degreeday ))
    degreeday <- 0
  community <- setTemp( community, "DegreeDay",
                       temp.spline( community, temp.repeat( community, period ),
                                   start = degreeday ))
  community <- setTemp( community, "Hour",
                       break.backSpline( getTemp( community, "DegreeDay" )))

  if(messages) {
    tmp <- period * unit
    names( tmp ) <- c("lo.hour","hi.hour")
    print( tmp )
  }
  community
}
###########################################################################################
updateTemp <- function( community,
                       period = range( getTemp( community, "DegreeDay", "knots" ) ))
{
  ## period is in units of hours
  first <- period[1]
  last <- period[2]

  s <- getTemp( community, "DegreeDay" )
  knots <- s$knots
  ## drop earlier times that are now in the past
  drop <- sum( knots < first ) - 1
  nk <- length( knots )
  change <- drop > 0
  if( change ) {
    s$knots <- knots[ - seq( drop ) ]
    s$coefficients <- s$coefficients[ - seq( drop ), ]
  }
  ## add new days to include last
  period <- ceiling( c( knots[nk], last ) / getTemp( community, "Unit" ) )
  if( period[1] <= period[2] ) {
    change <- TRUE
    news <- temp.spline( community, temp.repeat( community, period ),
      start = getHour( getTemp( community, "Unit" ) * period[1] ))
    nk <- length( s$knots )
    if( s$knots[nk] == news$knots[1] ) {
      s$knots <- s$knots[-nk]
      s$coefficients <- s$coefficients[-nk,]
    }
    else
      s$coefficients[nk,] <- news$coefficients[1,]
    s$knots <- c( s$knots, news$knots )
    s$coefficients <- rbind( s$coefficients, news$coefficients )    
  }
  if( change ) {
    community <- setTemp( community, "DegreeDay", s )
    community <- setTemp( community, "Hour", break.backSpline( s ))
  }
  community
}
###########################################################################################
ramp.backSpline <- function( s )
{
  ## ramped backspline is a trick to get backSpline when curve is flat in spots
  ## if tmp <- stats::predict( ramp.backSpline( s ))
  ## then plot tmp$y versus tmp$x-tmp$y to "recover" original curve.
  ## problem is that one cannot recover particular x this way!

  s$coefficients[,1] <- s$coefficients[,1] + s$knots
  s$coefficients[,2] <- s$coefficients[,2] + 1
  splines::backSpline( s )
}
###########################################################################################
break.backSpline <- function( tmp )
{
  ## alternative to ramped backSpline that first removes flat regions (slope 0)
  ## problem remains that leftover may still have slope 0 at a point
  ## this can cause anomolous results!

  ## find flat regions and cut out
  tmpc <- diff(tmp$coefficients[,1]) == 0 
  tmpk <- cumsum( diff(tmp$knots) * ( tmpc ))
  tmpk <- c(tmpk,tmpk[length(tmpk)])
  tmp$knots <- tmp$knots - tmpk
  tmpc <- c(!tmpc,TRUE)
  tmp$knots <- tmp$knots[tmpc]
  tmp$coefficients <- tmp$coefficients[tmpc,]
  tmp$breaks
  nb <- dim( tmp$coefficients )

  ## kludge for backspline: cannot handle slope of zero
  tmpn <- tmp$coefficients[,2] == 0
  if( any( tmpn )) {
    tmpn <- seq( tmpn )[ tmpn ]
    tmp$coefficients[ tmpn, 2:nb[2] ] <-
      ( tmp$coefficients[ pmin( nb[1], tmpn + 1 ), 2:nb[2] ] +
       tmp$coefficients[ pmax(     1, tmpn - 1 ), 2:nb[2] ] ) / 2
  }

  ## back spline
  tmpb <- splines::backSpline(tmp )

  if( any( tmpc )) {
    ## shift back spline based on breaks
    tmpb$coefficients[,1] <- tmpb$coefficients[,1] + tmpk[tmpc]
  }
  tmpb
}
###########################################################################################
getDegreeDay <- function( community, hour )
{
  stats::predict( getTemp( community, "DegreeDay" ), hour )$y
}
###########################################################################################
getHour <- function( community, dd )
{
  stats::predict( getTemp( community, "Hour" ), dd )$y
}
###########################################################################################
getTime <- function( community, species, x )
{
  if( is.na(x)){
    cat("getTime missing value\n")
    browser()
  }
  switch( getOrgFeature( community, species, "units" ),
    ## organisms on hour basis assumed to be active only 6am-6pm
    hr = getDegreeDay( community,
      ( getTemp( community, "Unit" ) / 2 + x + floor( x / getTemp( community, "Unit" ) )) / 2 ),
    DD = x )
}
###########################################################################################
checkTime <- function( community, x, base, units )
{
## NOTE: Sometimes base can be negative!! (reset to 0)
  if( units == "hr" ) {
    x <- max( x )
#    print( c( base = base, x = x, knots = range( getTemp( community, "DegreeDay", "knots" ))))
    if( x > max( getTemp( community, "DegreeDay", "knots" )))
      community <- activeTemp( community, max( base, 0 ),
                              x + getTemp( community, "Unit" ), messages = FALSE )
  }
  community
}
###########################################################################################
transTime <- function( community, org1name, org2name, x,
                      unit1 = getOrgFeature( community, org1name, "units" ),
                      unit2 = getOrgFeature( community, org2name, "units" ))
{
  if( is.na(x)){
    cat("transTime missing value\n")
    browser()
  }
  if( unit1 == unit2 )
    return( x )
  switch( unit1,
    ## organisms on hour basis assumed to be active only 6am-6pm
    hr = getDegreeDay( community,
      ( getTemp( community, "Unit" ) / 2 + x + floor( x / getTemp( community, "Unit" ))) / 2 ),
    DD = getHour( x ))
}
###########################################################################################
### To do:
### 1. interactive designer for hourly temp fluctation over one day
### 2. interactive designer for daily lows and highs over season: temp.design() DONE
### 3. check future event trees hour vs. DD
### 4. schedule interaction events with hour-DD translation
### 5. make aphytis dormant at night
###########################################################################################

# --- Source: initTemp.R ---
##########################################################################################
### simulation temperature administration
##########################################################################################
initTemp <- function( community, lo.hour = 0, hi.hour = getTemp( community, "Unit" ),
                      days = TemperaturePar["Days"], 
                      messages = TRUE, datafile = "", ... )
{
  if(messages) {
    cat( "Initializing Temperature Profile ...\n" )
  }
  Temperature <- list()
  
  TemperaturePar <- getOrgData(community, "temperature", "par", messages, datafile)
  #  mydata( "TemperaturePar", getOrgInfo( community, "package" ), messages = messages)
  TemperaturePar <- array( TemperaturePar[,"value"],
                           dimnames = list( row.names( TemperaturePar )))
  Temperature$Unit <- TemperaturePar["Unit"]
  Temperature$Min <- TemperaturePar["Min"]
  
  ## set up daily temperature base
  
  TemperatureBase <- getOrgData(community, "temperature", "base", messages, datafile)
  #  mydata( "TemperatureBase", getOrgInfo( community, "package" ), messages = messages)
  Temperature$Time <- split( TemperatureBase$Time, TemperatureBase$Day )
  Temperature$Base <- split( TemperatureBase$Base, TemperatureBase$Day )
  
  community$temp <- Temperature
  
  tmp <- seq( lo.hour / Temperature$Unit,
              days + 1 + ( hi.hour / Temperature$Unit ),
              length = TemperaturePar["Length"] )
  tmp0 <- seq( 0, 1, length = TemperaturePar["Length"] )
  tmp1 <-  0.25 * ( TemperaturePar["HighBeg"] - TemperaturePar["LowBeg"] )
  Temperature$Low <- splines::interpSpline( tmp, TemperaturePar["LowBeg"] * ( 1 - tmp0 ) +
                                              TemperaturePar["LowEnd"] * tmp0 +
                                              sin( pi * 4 * tmp0 ) * tmp1 )
  Temperature$High <- splines::interpSpline( tmp, TemperaturePar["HighBeg"] * ( 1 - tmp0 ) +
                                               TemperaturePar["HighEnd"] * tmp0 +
                                               sin( pi * ( 0.125 + 4 * tmp0 )) * tmp1 )
  
  Temperature$DegreeDay <- NULL
  
  if(messages) {
    cat( "Base daily temperature fluctuation:\n" )
  }
  for( i in names( Temperature$time )) {
    cat( "From day", i, ":\n" )
    tmp <- Temperature$Base[[i]]
    names( tmp ) <- Temperature$Time[[i]]
    print( tmp )
  }
  community$temp <- Temperature
  
  if(messages) {
    showTemp( community )
    cat( "Initial active temperature:\n" )
  }
  activeTemp( community, lo.hour, hi.hour, getTemp( community, "Time", 1 )[1],
              messages = messages)
}
###########################################################################################
getTemp <- function( community, element, sub )
{
  tempelem <- community$temp[[element]]
  if( !missing( sub ))
    tempelem <- tempelem[[sub]]
  tempelem
}
###########################################################################################
setTemp <- function( community, element, value )
{
  community$temp[[element]] <- value
  community
}

# --- Source: temp.design.R ---
temp.design <- function( community, nspline = 8, n = 1, horizontal = TRUE,
                         col = c(low="blue",high="red") )
{
  low <- getTemp( community, "Low" )
  high <- getTemp( community, "High" )
  is.data <- !is.null( low )
  if( !is.data ) {
    tmp <- seq( 0, 60, length = nspline )
    low <- interpSpline( tmp, 60 + 0.125 * tmp + sin( 0.25 * tmp ))
    high <- interpSpline( tmp, 70 + 0.15 * tmp + sin(( pi / 8 ) + 0.225 * tmp ))
  }
  ## plot curve and surrounding axes
  par( mfrow = c(1,1), mar = rep(4.1,4))
  cat( "Switch to Graphic Screen to Adjust High and Low Temperatures\n" )
  
  plotit <- function( low, high, fig = fig, horizontal = FALSE, strip = .25, margin = 0 )
  {
    lowpred <- stats::predict( low, low$knots )
    highpred <- stats::predict( high, high$knots )
    ylim <- range( c( lowpred$y, highpred$y ))
    xlim <- range( c( lowpred$x, highpred$x))
    xlim <- xlim + c(-1,1) * margin * diff( xlim )
    if( horizontal ) {
      if( diff( ylim ) == 0 )
        ylim <- ylim * c(.75,1.25)
      separator <- ylim[2]
      ylim[2] <- ylim[2] + strip * diff( ylim )
    }
    else {
      separator <- xlim[2]
      xlim[2] <- xlim[2] + strip * diff( xlim )
    }
    axt <- c("n","s")
    tmpar <- par( xaxt = axt[1+horizontal], yaxt = axt[2-horizontal] )
    plot( xlim, ylim, xlim = xlim, ylim = ylim, type="n", xlab = "", ylab = "" )
    par( xaxt = "s", yaxt = "s" )
    title( fig )
    mtext( "day", 1, 2 )
    mtext( "temp", 2, 2 )
    if( horizontal ) {
      p <- pretty( c(ylim[1],separator) )
      axis( 2, p[ p <= separator ] )
      abline( h = separator, lty = 2 )
    }
    else {
      p <- pretty( c(xlim[1],separator) )
      axis( 1, p[ p <= separator ] )
      abline( v = separator, lty = 2 )
    }
    points( highpred$x, highpred$y, lwd = 4 )
    curve.plot( highpred, n = n, action = "refresh", fit = high, backfit = FALSE,
                save.ends = 3, col = "red", lwd = 2 * ( 1 + ( fig == "high" )))
    points( lowpred$x, lowpred$y, lwd = 4 )
    curve.plot( lowpred, n = n, action = "refresh", fit = low, backfit = FALSE,
                save.ends = 3, col = "blue", lwd = 2 * ( 1 + ( fig == "low" )))
    separator
  }
  ## place commands along right strip of plot, highlighting current command
  plotcmd <- function( ans, fig, cmds, cmdlocs, usr, col = "green", rest = "black",
                       horizontal = TRUE, data = FALSE )
  {
    ans <- c( ans, fig )
    if( data )
      ans <- c( ans, "data" )
    tmp <- is.na( match( cmds, ans ))
    if( any( tmp )) for( i in unique( cmdlocs$adj )) {
      tmpi <- tmp & i == cmdlocs$adj
      if( any( tmpi ))
        text( cmdlocs$x[tmpi], cmdlocs$y[tmpi], cmds[tmpi], col = rest, adj = i )
    }
    if( any( !tmp )) for( i in unique( cmdlocs$adj )) {
      tmpi <- !tmp & i == cmdlocs$adj
      if( any( tmpi ))
        text( cmdlocs$x[tmpi], cmdlocs$y[tmpi], cmds[tmpi], col = col, adj = i )
    }
  }
  cmds <- c("add","delete","replace","rescale","","finish","refresh","restart",
            "","data","high","low")
  newlocs <- if( horizontal )
    function( cmds, data = FALSE, usr )
    {
      if( !data )
        cmds <- cmds[ cmds != "data" ]
      n <- length( cmds )
      blank <- seq( n )[ cmds == "" | cmds == " " ]
      tmp <- diff(usr[3:4]) / 20
      m <- mean( usr[1:2] )
      y <- usr[4] + 0.5 * tmp - c( tmp * seq( blank[1] - 1 ), 0,
                                   tmp * seq( blank[2] - blank[1] - 1 ), 0,
                                   tmp * seq( n - blank[2] ))
      x <- c( rep( usr[1], blank[1] - 1 ), mean( m, usr[1] ),
              rep( m, blank[2] - blank[1] - 1 ), mean( m, usr[2] ),
              rep( usr[2], n - blank[2] ))
      adj <- c( rep( 0, blank[1] ),
                rep( 0.5, blank[2] - blank[1] ),
                rep( 1, n - blank[2] ))
      tmp <- data.frame( x = x, y = y, adj = adj )
      cmds[blank[2]] <- " "
      row.names( tmp ) <- cmds
      tmp
    }
  else
    function( cmds, data = FALSE, usr )
    {
      if( !data )
        cmds <- cmds[ cmds != "data" ]
      n <- length( cmds )
      blank <- seq( n )[ cmds == "" | cmds == " " ]
      tmp <- diff(usr[3:4]) / 20
      m <- mean( usr[3:4] )
      tmp <- c( usr[4] - tmp * seq( blank[1] - 1 ),
                mean( m, usr[4] ),
                m + tmp * ( seq( blank[1] + 1, blank[2] - 1 ) - mean( blank )),
                mean( m, usr[3] ),
                usr[3] + tmp * seq( n - blank[2] ))
      tmp <- data.frame( x = rep( usr[2], n ), y = tmp, adj = rep( 1, n ))
      cmds[blank[2]] <- " "
      row.names( tmp ) <- cmds
      tmp
    }
  
  newans <- ans <- "replace"
  par( mar = c(4.1,4.1,3.1,4.1),omi=rep(.25,4))
  fig <- "low"
  fit <- low
  separator <- plotit( low, high, fig, horizontal )
  
  usr <- par("usr")
  cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
  cmds <- row.names( cmdlocs )
  use.data <- FALSE
  plotcmd( ans, fig, cmds, cmdlocs, usr, data = use.data )
  rescale.data <- c( range( stats::predict( low, low$knots )$y,
                            stats::predict( high, high$knots )$y ), range( low$knots, high$knots ))
  repeat {
    ## get command from plot using cursor
    z <- locator(1,"n")
    if(( !horizontal & z$x > separator ) | ( horizontal & z$y > separator )) {
      if( horizontal ) { # need to look at both z&y 
        x <- abs( z$x - cmdlocs$x )
        x <- x == min( x )
        newans <- cmds[x]
        z <- abs( z$y - cmdlocs$y )[x]
        newans <- newans[ z == min( z ) ][1]
      }
      else {
        z <- abs(z$y - cmdlocs$y )
        newans <- cmds[ z == min( z ) ][1]
      }
      switch( newans,
              finish =, refresh = {
                separator <- plotit( low, high, fig, horizontal )
                usr <- par("usr")
                cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
              },
              data = {
                use.data <- is.data & !use.data
                if( is.data & !use.data )
                  plotit( low, high, fig, horizontal )
              },
              high =, low = {
                if( newans != fig )
                  fig <- newans
                separator <- plotit( low, high, fig, horizontal )
                usr <- par("usr")
                cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
              },
              rescale = {
                tmpcmds <- cmds
                tmpans <- tmpcmds[ cmds == "rescale" ] <-
                  "rescale: Switch to Character Screen"
                plotcmd( tmpans, fig, tmpcmds, cmdlocs, usr, data = use.data )
                ry <- range( c( stats::predict( low, low$knots )$y, stats::predict( high, high$knots )$y ))
                cat( "\nEnter new minimum/maximum followed by RETURN key\n" )
                newry <- ry
                show <- c("minimum","maximum")
                change <- FALSE
                for( i in 1:2 ) {
                  tmpy <- readline( paste( show[i], " temp (",
                                           round( ry[i], 2 ), "):", sep = "" ))
                  tmpy <- if( tmpy == "" ) NA
                  else as.numeric( tmpy )
                  if( is.na( tmpy ))
                    tmpy <- ry[i]
                  else
                    change <- TRUE
                  rescale.data[i] <- tmpy
                }
                tmp <- range( c( low$knots, high$knots ))
                for( i in 1:2 ) {
                  tmpx <- readline( paste( show[i], " time (", 
                                           round( tmp[i], 2 ), "):", sep = "" ))
                  tmpx <- if( tmpx == "" ) NA
                  else as.numeric( tmpx )
                  if( is.na( tmpx ))
                    tmpx <- tmp[i]
                  else
                    change <- TRUE
                  rescale.data[2+i] <- tmpx
                }
                cat( "Switch to Graphic Screen to Adjust High and Low Temperatures\n" )
                if( change ) {
                  tmp <- rescale.temp( low, high, rescale.data[1:2],
                                       rescale.data[3:4], ry )
                  low <- tmp$low
                  high <- tmp$high
                }
                separator <- plotit( low, high, fig, horizontal )
                usr <- par("usr")
                cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
              },
              restart = {
                low <- getTemp( community, "Low" )
                high <- getTemp( community, "High" )
                if( is.data )
                  rescale.data <- c( range( stats::predict( low, low$knots )$y,
                                            stats::predict( high, high$knots )$y ), range( low$knots, high$knots ))
                separator <- plotit( low, high, fig, horizontal )
                usr <- par("usr")
                cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
              },
              add =, delete =, replace = {
                ans <- newans
              }
      )
      if( use.data ) {
        tmp <- rescale.temp( getTemp( community, "Low" ), getTemp( community, "High" ),
                             rescale.data[1:2], rescale.data[3:4] )
        for( i in c("low","high") ) {
          datax <- tmp[[i]]$knots
          lines( datax, stats::predict( tmp[[i]], datax )$y )
        }
      }
      plotcmd( ans, fig, cmds, cmdlocs, usr, data = use.data )
    }
    else { # modify the curve knots
      fit <- get( fig )
      if( z$x >= min( fit$knots ) & z$x <= max( fit$knots )) {
        fit <- curve.plot( as.data.frame( stats::predict( fit, fit$knots )), n = n, action = ans,
                           z = z, fit = fit, backfit = FALSE, save.ends = 2, col = col[fig] )
        assign( fig, fit$fit )
      }
      ans <- "replace"
      plotcmd( ans, fig, cmds, cmdlocs, usr, data = use.data )
    }
    if( newans == "finish" )
      break
  }
  plotcmd( newans, fig, cmds, cmdlocs, "red", data = use.data )
  community <- setTemp( community, "Low", low )
  community <- setTemp( community, "High", high )
  activeTemp( community )
}

# --- Source: temp.plot.R ---
temp.plot <- function( community, lo.hour = s$knots[1], hi.hour = max( s$knots ),
                       length = 201,
                       col = NULL, derivative = FALSE, ..., printit = FALSE )
{
  s <- getTemp( community, "DegreeDay" )
  x <- seq( lo.hour, hi.hour, length = length )
  ## make sure to pick up knots to plot in this region
  x <- unique( sort( c( x, s$knots[ s$knots >= lo.hour & s$knots <= hi.hour ] )))
  ylab <- "degree-days"
  if( derivative ) {
    ylab <- "degrees above min"
    s$coefficients <- s$coefficients[,-1]
    for( i in seq( 2, ncol( s$coefficients )))
      s$coefficients[,i] <- s$coefficients[,i] * i
  }
  y <- stats::predict( s, x )$y
  plot( x / getTemp( community, "Unit" ), y, type = "l", xlab = "day",
        ylab = ylab, ... )
  if( printit )
    print( cbind( hi.hour, stats::predict( s, hi.hour )$y ))
  if( !is.null( col ))
    points( s$knots, coef(s)[,1], col = col )
  if( !derivative ) {
    s <- getTemp( community, "Hour" )
    x <- seq( min( y ), max( y ), length = length )
    tmp <- stats::predict( s, x )
    lines( tmp$y / getTemp( community, "Unit" ), tmp$x, col = "blue" )
  }
}
###########################################################################################
temp.lines <- function( s, mult = 24, col = "red" )
{
  x <- seq( s$knots[1], max( s$knots ), length = 51 )
  x <- unique( sort( c( x, s$knots )))
  p <- stats::predict( s, x )
  lines( p$x, p$y, col = col )
  if( !is.null( col ))
    points( s$knots / mult, coef(s)[,1], col = col )
}

# --- Source: my.R ---
###########################################################################################
## System files
###########################################################################################
my.read <- function(dataname, stringsAsFactors = TRUE)
{
  switch(tools::file_ext(dataname),
         "txt" =, "tsv" = read.table(dataname, stringsAsFactors = stringsAsFactors),
         "csv" = read.csv(dataname, stringsAsFactors = stringsAsFactors),
         "xls" = readxl::read_xls(dataname),
         "xlsx" = readxl::read_xlsx(dataname))
}
###########################################################################################
my.eval <- function(species, extension, element, checkdata = FALSE )
{
  if( !missing( extension ))
    species <- paste( species, extension, sep = ".")
  if( exists( species )) 
    organism <- get( species )
  else {
    if( checkdata ) {
      organism <- utils::data( list = species )
      if( organism == species )
        organism <- NULL
    }
    else
      organism <- NULL
  }
  if(!( missing(element) | is.null( organism )))
    organism <- organism[[element]]
  organism
}
###########################################################################################
mydata <- function( dataname, package, restart = FALSE, messages = TRUE )
{
  edata <- exists( dataname )
  if( restart & edata ) {
    remove( list = dataname, pos = 1 )
    edata <- !edata
  }
  if( !edata ) {
    utils::data( list = dataname, package = eval( package ))
    if(messages) {
      cat( "Data", dataname, "loaded\n" )
    }
  }
  else
    if(messages) {
      cat( "Data", dataname, "already loaded\n" )
    }
} 

# --- Source: Org.R ---
###########################################################################################
### Organism Features
###########################################################################################
getOrgFeature <- function( community, species, feature = names( OrgFeature ))
{
  OrgFeature <- getOrgInfo( community, "Feature" )
  if (!is.null(OrgFeature)) {
    if( missing( species ))
      return( row.names( OrgFeature ))
    f <- OrgFeature[ species, feature ]
    if( length( feature ) == 1 ) {
      if( any( is.na( f )))
        return( NA )
      f <- as.character( f )
    }
    else {
      f <- apply( f, 2, as.character )
    }
    f <- c( unlist( f ))
    numf <- suppressWarnings(as.numeric( f ))
    if( all( !is.na( numf )))
      f <- numf
    return(f)
  }
  
  # Fallback for webR standalone adapter objects
  if (missing(species)) return(c("host", "parasite"))
  if (missing(feature) || is.null(feature)) return(c(substrate = "substrate", units = "units"))
  if (length(feature) == 1) {
    if (feature == "substrate") return("substrate")
    if (feature == "units") return("units")
    return(NA)
  }
  res <- rep("substrate", length(feature))
  names(res) <- feature
  res
}
##########################################################################################
getOrgHosts <- function( community, species,
                         feature = c("offspring","attack","substrate") ###HOST SPECIFIC###
)
{
  # This seems overly complicated and adds substrate; maybe function name is wrong
  f <- unique( getOrgFeature( community, species, feature ))
  # f[match(species, f, nomatch = 0)] # this would only get species
  o <- getOrgFeature( community )
  o[ match( f, o, nomatch = 0 ) ]
}
###########################################################################################
getOrgFuture <- function( community, species, feature, current,
                          future = OrgFuture[[species]] )
{
  OrgFuture <- getOrgInfo( community, "Future" )
  if (!is.null(OrgFuture) && !is.null(OrgFuture[[species]])) {
    future <- OrgFuture[[species]]
    if( missing( current )) {
      if( missing( feature ))
        return( future )
      future <- future[,feature]
    }
    else {
      if( !is.numeric( current ))
        current <- match( current, future$current, nomatch = 0 )
      if( missing( feature ))
        future <- future[ current, ]
      else
        future <- future[ current, feature ]
    }
    if( is.null( future ))
      return( NA )
    if(is.character( future ))
      future <- as.factor(future)
    return(future)
  }
  
  # Fallback for webR standalone adapter objects
  if (!is.null(community$pop[[species]])) {
    pch_vec <- community$pop[[species]]$pch
    col_vec <- community$pop[[species]]$col
    fut <- data.frame(pch = pch_vec, color = col_vec, stringsAsFactors = FALSE)
    if (missing(feature)) return(fut)
    if (length(feature) == 1) return(fut[[feature]])
    return(fut[, feature, drop = FALSE])
  }
  NULL
}
###########################################################################################
get.interact <- function( community, species, host, avail, event )
{
  id <- get.species.element( community, host, "stage", avail )
  interact <- getOrgInteract( community, host, species, event )[id]
  interact[ is.na( interact ) ] <- 0
  interact
}
###########################################################################################
getOrgInteract <- function( community,
                            org1name = getOrgFeature( community, org2name, "substrate" ),
                            org2name, event = NULL )
{
  OrgInteract <- getOrgInfo( community, "Interact" )
  if (!is.null(OrgInteract) && !is.null(OrgInteract[[org1name]][[org2name]])) {
    tmp <- OrgInteract[[org1name]][[org2name]]
    if( is.null( event ))
      return( tmp )
    event <- as.character( event )
    inter <- tmp[,event]
    if( length( event ) == 1 )
      names( inter ) <- row.names( tmp )
    if(is.character(inter))
      inter <- factor(inter)
    return(inter)
  }
  
  # Fallback for webR standalone adapter objects
  sub_names <- if (!is.null(community$sub_names)) community$sub_names else c("fr1", "fr2", "fr3", "fr4", "twig", "lftop", "lfbot")
  mat <- matrix(1, nrow = length(sub_names), ncol = 1, dimnames = list(sub_names, "substrate"))
  as.data.frame(mat)
}
###########################################################################################
getOrgMeanValue <- function( community, species )
{
  OrgMeanValue <- getOrgInfo( community, "MeanValue" )
  ## The global org$MeanValue[[species]] contains mean value information.
  OrgMeanValue[[species]]
}
###########################################################################################
copyOrgInfo <- function( fromname, toname )
{
  out <- list()
  for( i in c("sim")) {
    from <- paste( fromname, i, sep = "." )
    if( exists( from )) {
      toto <- paste( toname, i, sep = "." )
      out[[toto]] <- get( from )
      cat( "copied", from, "to", toto, "into list\n" )
    }
  }
  return(out)
}
###########################################################################################
get.alive <- function( community, species, substrate )
{
  alive <- getOrgAlive( community, species )
  alive <- seq_along( alive )[alive]
  alive[ substrate == get.species.element( community, species, "sub.stage", alive ) ]
}
###########################################################################################
getOrgAlive <- function( community, species, element )
{
  organism <- get.species( community, species )
  ## identify dead organisms (free nodes for leftist tree)
  tmp <- c( FALSE, apply( organism[c("dist","up","left","right"),-1], 2,
                          function( x ) any( x > 1 )))
  if( !any( tmp )) {
    tmpp <- organism["up",1]
    if( tmpp > 1 )
      tmp[tmpp] <- TRUE
  }
  if( !missing( element ))
    tmp <- organism[ element, tmp ]
  tmp
}
###########################################################################################
getOrgAgeClass <- function( community, species, stage = seq_len( nrow( future )),
                            future = getOrgFuture( community, species ))
{
  ageclass <- future$ageclass[stage]
  tmp <- !is.na( ageclass )
  if( any( tmp ))
    ageclass[ !is.na( ageclass ) ]
  else
    NA
}
###########################################################################################
getOrgSubstrate <- function( community, species, elements = seq_len( nrow( inter )),
                             substrate = getOrgFeature( community, species, "substrate" ),
                             inter = getOrgInteract( community, substrate, species ))
{
  sites <- inter$substrate[elements]
  tmp <- !is.na( sites )
  if( any( tmp ))
    sites[ !is.na( sites ) ]
  else
    NA
}
###########################################################################################
sampleOrgSubstrate <- function( community, species, elements = seq_len( nrow( inter )),
                                substrate.name = getOrgFeature( community, species, "substrate" ),
                                inter = getOrgInteract( community, substrate.name, species ))
{
  if( is.na( substrate.name ))
    return( elements )
  newsub <- as.matrix( cbind( elements, inter[ elements, levels( factor(inter$substrate) ) ] ))
  apply( newsub, 1, function( x, is ) {
    ns <- sample( levels( factor(is) ), 1, prob = x[-1] / sum( x[-1] ))
    sub <- seq_len( nrow( inter ))[ ns == is ]
    if( length( sub ) > 1 ) {
      newsub <- getOrgInteract( community, substrate.name, substrate.name )[x[1],sub]
      sample( sub, 1, prob = newsub / sum( newsub ))
    }
    else
      sub
  }, inter$substrate )
} 
##########################################################################################
### simulation organism administration
##########################################################################################
initOrgInfo <- function( package, messages = TRUE, datafile = "", ... )
{
  community <- list( pop = list( ))
  community$org <- list( )
  community$org$package <- package
  ## Get data
  community$org$Feature <- getOrgData(community, "organism", "features",
                                      messages, datafile)
  
  community$pop <- list()
  community
}
##########################################################################################
setOrgInfo <- function( community, species, hosts, package, messages = TRUE,
                        datafile = "", ... )
{
  Organism <- community$org
  
  if( is.null( Organism$Future ))
    Organism$Future <- list( )
  if( is.null( Organism$Interact ))
    Organism$Interact <- list( )
  
  for( j in hosts )
    if( is.null( Organism$Interact[[j]] ))
      Organism$Interact[[j]] <- list( )
  
  ## Do not reset MeanValue as it may have important spline fits!
  if( is.null( Organism$MeanValue ))
    Organism$MeanValue <- list( )
  
  for( i in species ) {
    future <- getOrgData(community, "future", i,
                         messages, datafile)
    
    # Check that future agrees with organism.feature information
    subclass <- Organism$Feature[i,"subclass"]
    if(!(subclass %in% unique(future$ageclass))) {
      stop(paste("Future table", paste("future", i, sep = "."),
                 "does not include", subclass))
    }
    
    level.ageclass <- unique( future$ageclass )
    level.ageclass <- as.character( level.ageclass[ !is.na( level.ageclass ) ] )
    future$ageclass <- ordered( future$ageclass, level.ageclass )
    Organism$Future[[i]] <- future
    for( j in hosts )
      if( i != j ) {
        Organism$Interact[[j]][[i]] <- getOrgData(community, j, i,
                                                  messages, datafile)
        
        # Check that interaction agrees with host current stage information
        # This is messy!
        if(j %in% species) {
          if(!all(row.names(Organism$Interact[[j]][[i]]) %in%
                  c(as.character(Organism$Future[[j]]$current), i))) {
            stop(paste("Interaction table", paste(j, i, sep = "."),
                       "does not match", j, "current stages"))
          }
        }
      }
    if( is.null( Organism$MeanValue[[i]] ))
      Organism$MeanValue[[i]] <- list( )
    else
      cat( "Keeping Mean Value information for", i, "if any\n" )
  }
  for( i in unique( getOrgFeature( community, species, "substrate" ))) {
    Organism$Interact[[i]][[i]] <- getOrgData(community, i, i,
                                              messages, datafile)
  }
  community$org <- Organism
  community
}
###########################################################################################
getOrgData <- function(community, left, right,
                       messages = TRUE, datafile = "")
{
  # Get Organism Data from
  #     package data
  #     global data supplied by user
  #     external data file supplied by user
  sheet <- paste( left, right, sep = "." )
  data_exists <- FALSE
  if (is.character(datafile) && length(datafile) == 1 && nzchar(datafile)) {
    if (dir.exists(datafile)) {
      extensions <- c(".txt", ".tsv", ".csv", ".xls", ".xlsx")
      datafile_paths <- file.path(datafile, paste0(sheet, extensions))
      exist_idx <- file.exists(datafile_paths)
      if (any(exist_idx)) {
        datafile <- datafile_paths[exist_idx][1]
        data_exists <- TRUE
      } else {
        data_exists <- FALSE
      }
      sheet <- ""
    } else if (file.exists(datafile)) {
      data_exists <- TRUE
    }
  }
  if(!data_exists) {
    # Load package data or get user-provided global data.
    mydata( sheet, getOrgInfo( community, "package" ), messages = messages)
    my.eval( sheet )
  } else {
    # Read data file from user if provided.
    if(sheet == "")
      my.read(datafile)
    else {
      out <- as.data.frame(readxl::read_excel(datafile, sheet = sheet, .name_repair = "none"))
      if(names(out)[1] == "") { # first column is actual row names
        rownames(out) <- out[[1]]
        out[[1]] <- NULL
      }
      out
    }
  }
}
###########################################################################################
getOrgNames <- function(datafile = "") {
  if(datafile == "") {
    c("organism.features", "future.host", "future.parasite",
      "substrate.host", "substrate.parasite", "substrate.substrate",
      "temperature.base", "temperature.par")
  } else {
    readxl::excel_sheets(datafile)
  }
}
###########################################################################################
getOrgDataSimple <- function(community, dataname, datafile = ""){
  out <- getOrgData(
    community,
    left = stringr::str_remove(dataname, "\\..*"),
    right = stringr::str_remove(dataname, ".*\\."),
    messages = FALSE, datafile = datafile)
  # Kludge to reinstate rownames as a column
  if(!identical(rownames(out), as.character(seq_len(nrow(out))))) {
    out <- data.frame(rownames = rownames(out), out)
  }
  out
}
###########################################################################################
getOrgInfo <- function( community, element )
{
  community$org[[element]]
}
###########################################################################################
setOrgMeanValue <- function( community, species, stage, mvalue )
{
  ## The global Organism$MeanValue[[species]] contains mean value information.
  community$org$MeanValue[[species]][[stage]] <- mvalue
  community
}

# --- Source: organism.features.R ---
NULL

# --- Source: future.R ---
## $Id: future.R,v 1.0 2002/12/11 yandell@stat.wisc.edu Exp $
##
## Functions for Bland Ewing's modeling.
##
##     Copyright (C) 2000,2001,2002 Brian S. Yandell.
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by the
## Free Software Foundation; either version 2, or (at your option) any
## later version.
##
## These functions are distributed in the hope that they will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## The text of the GNU General Public License, version 2, is available
## as http://www.gnu.org/copyleft or by writing to the Free Software
## Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
###############################################################################
##
## future.events( community )
##
###############################################################################




###############################################################################
### Get birth and future event
###############################################################################
get.future <- function (community, species,
                        individuals = get.individual(community, species, id),
                        id = get.base(community, species))
{
  ## NOTE: This is the slow routine. For every event, it has to check if there
  ## is a mean value function and then call rspline.

  ## the structure future.species is set up to handle competing risks!
  future <- getOrgFuture(community, species, c("current", "future", "time"))
  future$fid <- match(future$future, future$current)
  individuals <- as.matrix(individuals)

  rownames <- dimnames(individuals)[[1]]
  for (i in seq(ncol(individuals))) {
    individual <- individuals[,i]
    current <- individual["stage"]
    individual["location"] <- individual["time"]

    ## competing risks based on potential future event times
    futures <- future$current == future$current[current]
    times <- rep( individual["time"], sum( futures ))
    cur <- seq(nrow(future))[futures]
    for( j in seq( sum( futures ))) {
      meantime <- future[cur[j], "time"]
      if( meantime > 0 ) {
        for.stage <- as.character(future$current[future$fid[ cur[j] ]])
        times[j] <- rspline( meantime, individual,
                              getOrgMeanValue(community, species)[[for.stage]])
      }
    }
    individual["time"] <- min( times )
    current <- cur[ times == individual["time"] ][1]
    individual["future"] <- future$fid[current]
    if (individual["time"] == individual["rejection"])
        individual["future"] <- future$fid[future$current == "death"]
    individuals[,i] <- individual
  }
  individuals
}
###############################################################################
event.death <- function( community, species,
                        id = get.base( community, species ))
{
  ## remove dead individual from leftist tree
  community <- put.species( community, species,
                           leftist.remove( get.species( community, species ), id ))
  ## free up individual for reuse
  community <- put.base( community, species, id )
  community
}
###############################################################################
update_mintime <- function( object, species, ... )
{
  base <- get.base( object, species )
  mintime <- max( getCount( object, species, "mintime" ),
                 getTime( object, species,
                         get.species.element( object, species, "time", base )))
  setCount( object, species, list( base = base, mintime = mintime ))
}
###############################################################################
set.birth <- function( community, species, neworg )
{
  ## merge immediate new births (if any)
  newbirths <- ncol( neworg )
  if( newbirths > 0 ) {
    neworg[c("dist","left","right","up"),] <- 1
    community <- checkTime( community, neworg["time",],
              getCount( community, species, "mintime" ),
              getOrgFeature( community, species, "units" ))
    oldbase <- getCount( community, species, "base" )
    tmp <- leftist.birth( get.species( community, species ), neworg,
                         getCount( community, species, "free" ))
    
    community <- put.species( community, species, tmp$tree )
    community <- put.base( community, species, free = tmp$free )
    community <- updateCounts( community, species, newbirths )
  }
  community
}



# --- Source: future.host.R ---
NULL

# --- Source: future.meanvalue.R ---
###########################################################################################
## init.meanvalue( organism, stage )
## spline.meanvalue( x, y )
## spline.meanvalue( data = data )
##
## five.show( )
## five.plot( )
##
###########################################################################################
# Curve Designing routines -- under development
###########################################################################################
future.meanvalue <- function( community, species, event = future$current[1],
                              data )
{
  future <- getOrgFuture( community, species )
  mvalue <- getOrgMeanValue( community, species )[[event]]
  if( missing( data )) {
    if( !is.null( mvalue )) {
      mvalue <- stats::predict( mvalue$meanvalue, mvalue$meanvalue$knots )
      mvalue <- spline.meanvalue( mvalue$x, mvalue$y )$fit
    }
    else
      mvalue <- spline.meanvalue( )$fit
  }
  else
    mvalue <- spline.meanvalue( data = data )$fit
  setOrgMeanValue( community, species, event, mvalue )
}
###########################################################################################
spline.meanvalue <- function( x = xinit, y = yinit, data, nspline = 8,
                              xy = data.frame( x = x, y = y ),
                              tol = 1e-5, n = 1 )
{
  is.data <- !missing( data )
  if( !is.data ) {
    tmp <- - log( 1 - seq( 0, 1 - exp( -5 ), length = nspline ))
    if( missing( x ))
      xinit <- tmp
    else
      xinit <- x
    if( missing( y ))
      yinit <- tmp
    else
      yinit <- y
    data <- NULL
  }
  else {
    xinit <- sort( data )
    ndata <- length( data )
    yinit <- seq( ndata ) / ( 1 + ndata )
    choose <- round( seq( 1, ndata, length = nspline ))
    xinit <- xinit[choose]
    yinit <- - log( 1 - yinit[choose] )
  }
  fs <- list( probability = function( x ) { - log( 1 - x ) } )
  finvs <- list( probability = function( x ) { 1 - exp( - x ) } )
  for( i in c("mean value","rate","density") )
    fs[[i]] <- finvs[[i]] <- function( x ) x
  
  ## plot curve and surrounding axes
  graphics::par( mfrow = c(1,1), mar = rep(4.1,4))
  plotit <- function( xy, fig = "mean value", fit = splines::interpSpline( xy$x, xy$y ))
  {
    switch( fig, {
      y <- xy$y
      ylim <- range(c(0,y))
    },
    probability = {
      y <- 1 - exp( - xy$y )
      ylim <- range(c(0,y))
    },
    rate = {
      y <- spline.rate( fit, xy$x )$y
      tmp <- spline.rate( fit )
      ylim <- range(c(0,tmp$y))
    },
    density = {
      y <- spline.rate( fit, xy$x )$y * exp( - stats::predict( fit, xy$x )$y )
      tmp <- spline.rate( fit )
      tmp$y <- tmp$y * exp( - stats::predict( fit )$y )
      ylim <- range(c(0,tmp$y))
    }
    )
    plot(xy$x,y,xlim=1.25*range(c(0,xy$x)), ylim = ylim,
         type="n", xlab = "", ylab = "" )
    graphics::points( xy$x, y, lwd = 4 )
    graphics::title( fig )
    graphics::mtext( "time", 1, 2 )
    graphics::mtext( fig, 2, 2 )
    graphics::abline( v = max( xy$x ), lty = 2 )
    
    switch( fig, {
      if( fig == "probability" ) {
        tmp <- c(.1,.2,.5,1:10)
        ltmp <- 1-exp(-tmp)
        graphics::mtext( "mean value", 4, 2 )
      }
      else {  
        tmp <- c(seq(0,.9,,by=.1),.95,.98,.99,.999)
        ltmp <- -log(1-tmp)
        graphics::mtext( "probability", 4, 2 )
      }
      usr <- graphics::par("usr")
      s <- ltmp <= usr[4] & ltmp >= usr[3]  
      tmpar <- graphics::par( cex = .75 )
      graphics::axis(4,ltmp[s],tmp[s])
      graphics::par( tmpar )
      summaryshow( xy, fit, "white" )
      tmp <- curve.plot( xy, n = n, action = "refresh", fit = fit,
                         f = fs[[fig]], finv = finvs[[fig]] )
      summaryshow( xy, tmp$fit )
    },
    rate =, density = {
      graphics::lines( tmp$x, tmp$y )
      summaryshow( xy, fit )
    }
    )
  }
  ## place commands along right strip of plot, highlighting current command
  plotcmd <- function( ans, fig, cmds, cmdlocs, usr, col = "green", rest = "black",
                       data = FALSE )
  {
    ans <- c( ans, fig )
    if( data )
      ans <- c( ans, "data" )
    tmp <- is.na( match( cmds, ans ))
    if( any( tmp ))
      graphics::text( rep(usr[2],sum(tmp)), cmdlocs[tmp], cmds[tmp], col = rest, adj = 1 )
    if( any( !tmp ))
      graphics::text( rep(usr[2],sum(!tmp)), cmdlocs[!tmp], cmds[!tmp], col = col, adj = 1 )
  }
  
  fig <- "mean value"
  newans <- ans <- "replace"
  
  graphics::par( mar = c(4.1,4.1,3.1,4.1),omi=rep(.25,4))
  fit <- splines::interpSpline( xy$x, xy$y )
  sums <- plotit( xy, fig, fit )
  
  cmds <- c("refresh","add","delete","replace","rescale","shrink to 1","finish","restart",
            "","data","mean value","probability","rate","density")
  newlocs <- function( cmds, data = FALSE, usr )
  {
    if( !data )
      cmds <- cmds[ cmds != "data" ]
    n <- length( cmds )
    blank <- seq( n )[ cmds == "" ]
    tmp <- diff(usr[3:4]) / 20
    tmp <- c( usr[4] - tmp * seq( blank - 1 ), mean( usr[3:4] ),
              usr[3] + tmp * seq( n - blank ))
    names( tmp ) <- cmds
    tmp
  }
  usr <- graphics::par("usr")
  cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
  cmds <- names( cmdlocs )
  use.data <- FALSE
  plotcmd( ans, fig, cmds, cmdlocs, usr )
  rescale.data <- 1
  repeat {
    ## get command from plot using cursor
    z <- graphics::locator(1,"n")
    if( z$x > max( xy$x )) {
      z <- abs(z$y - cmdlocs )
      newans <- cmds[z==min(z)][1]
      switch( newans,
              finish =, refresh = {
                sums <- plotit( xy, fig, fit )
              },
              data = {
                use.data <- is.data & !use.data
                if( is.data & !use.data & match( fig, c("mean value","probability"),
                                                 nomatch = 0 ))
                  sums <- plotit( xy, fig, fit )
              },
              "mean value" =, probability =, rate =, density = {
                fig <- newans
                sums <- plotit( xy, fig, fit )
                usr <- graphics::par("usr")
                cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
              },
              "shrink to 1" = {
                while( abs( sums[1] - 1 ) > tol ) {
                  xy$y <- xy$y * sums[1]
                  if( is.data )
                    rescale.data <- rescale.data * sums[1]
                  fit <- splines::interpSpline( xy$x, xy$y )
                  sums <- splinesum( xy, fit, tol )
                }
                sums <- plotit( xy, fig, fit )
                usr <- graphics::par("usr")
                cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
              },
              rescale = {
                cat( "enter new values followed by RETURN key\n" )
                tmpy <- readline( paste( "maximum mean value(",
                                         round( max( xy$y ), 2 ), "):", sep = "" ))
                if( tmpy != "" ) {
                  tmpy <- as.numeric( tmpy ) / max( xy$y )
                  xy$y <- tmpy * xy$y
                  if( is.data )
                    rescale.data <- rescale.data * tmpy
                }
                tmpx <- readline( paste( "maximum time(", 
                                         round( max( xy$x ), 2 ), "):", sep = "" ))
                if( tmpx != "" ) {
                  tmpx <- as.numeric( tmpx ) / max( xy$x )
                  xy$x <- tmpx * xy$x
                  if( is.data )
                    data <- data * tmpx
                }
                fit <- splines::interpSpline( xy$x, xy$y )
                sums <- plotit( xy, fig, fit )
                usr <- graphics::par("usr")
                cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
              },
              restart = {
                if( is.data )
                  rescale.data <- 1
                xy <- data.frame( x = xinit, y = yinit )
                fit <- splines::interpSpline( xy$x, xy$y )
                sums <- plotit( xy, fig, fit )
                usr <- graphics::par("usr")
                cmdlocs <- newlocs( cmds, data = is.data, usr = usr)
              },
              add =, delete =, replace = {
                ans <- newans
              }
      )
      if( use.data & match( fig, c("mean value","probability"), nomatch = 0 ))
        cdf.lines( data, fig, rescale = rescale.data )
      plotcmd( ans, fig, cmds, cmdlocs, usr, data = use.data )
    }
    else {
      if( is.na( match( fig, c("rate","density") ))) {
        summaryshow( xy, fit, "white", sums )
        fit <- curve.plot( xy, n = n, action = ans, z = z, fit = fit,
                           f = fs[[fig]], finv = finvs[[fig]] )
        xy <- fit$xy
        fit <- fit$fit
        sums <- summaryshow( xy, fit )
        ans <- "replace"
        plotcmd( ans, fig, cmds, cmdlocs, usr, data = use.data )
      }
    }
    if( newans == "finish" )
      break
  }
  plotcmd( newans, fig, cmds, cmdlocs, "red", data = use.data )
  summaryshow( xy, fit, "white", sums )
  tmp <- curve.plot( xy, n = n, action = "refresh",
                     f = fs[[fig]], finv = finvs[[fig]] )
  tmp$meanvalue <- tmp$fit
  tmp$fit <- NULL
  tmp$invmvalue <- splines::backSpline( tmp$meanvalue )
  sums <- summaryshow( xy, tmp$meanvalue )
  tmp$mean <- sums[1]
  tmp$median <- sums[2]
  tmp
}

# --- Source: substrate.host.R ---
NULL

# --- Source: host.parasite.R ---
NULL

# --- Source: simdata.R ---
NULL

# --- Source: redscale.R ---
NULL

# --- Source: init.simulation.R ---
init.simulation <- function( package = "ewing", 
                             count = 200,
                             interact = FALSE,
                             messages = TRUE,
                             ...)
{
  community <- initOrgInfo( package, messages = messages, ... )
  community <- initTemp( community, messages = messages, ... )
  
  species <- getOrgFeature( community )[1:2]
  hosts <- getOrgHosts( community, species )
  
  if(messages) {
    cat( "Creating simulation organism set using species:\n",
         paste( species, collapse = ", " ), "\n\n" )
  }
  
  community <- setOrgInfo( community, species, hosts, package,
                           messages = messages, ... )
  
  if(messages) {
    cat( "\n" )
  }
  species <- unique( species )
  num <- numeric( length( species ))
  names( num ) <- species
  
  count <- rep_len(count, length(species))
  names(count) <- species
  
  for( i in species ) {
    num[i] <- reuse <- count[i]
    
    if(messages | (interact & interactive())) {
      cat( paste( "Initialize ", i, " at size ", reuse, sep = "" ))
    }
    if(interact & interactive()) {
      cat(" :")
      r <- readline( )
      if( r != "" & is.na( pmatch( substring( r, 1, 1 ), c("y","Y") )))
        reuse <- suppressWarnings(as.numeric( r ))
      if( is.na( reuse ))
        reuse <- count[i]
    }
    if( reuse ) {
      if(messages) {
        cat( "...\n" )
      }
      community <- init.population( community, i, n = reuse, messages = messages, ... )
      num[i] <- reuse
    }
  }
  class( community ) <- c("ewing", "list")
  attr(community, "count") <- count
  community
}

# --- Source: init.population.R ---
init.population <- function( community, species, n = 200, width = 100,
                             units = getOrgFeature( community, species, "units" ),
                             timeit = FALSE,
                             reject = Inf,
                             position = rtri( n, width ),
                             colnames = c(leftistnames,paramnames,posnames,eventnames),
                             init.stage = istage,
                             init.weight = getOrgFuture( community, species, "init" ),
                             messages = TRUE,
                             ...)
{
  leftistnames <- c("dist","left","right","up")
  paramnames <- c("dispersion","location","intensity","truncation","rejection")
  posnames <- paste("pos",letters[1:3], sep = ".")
  eventnames <- c("time","stage","future","offspring","sex","sub.stage","sub.future")
  
  organism <- matrix( 0, length( colnames ), n+1,
                      dimnames = list( colnames, NULL ))
  organism["time",1] <- Inf
  
  ## 5-parameter initialization
  organism[c("dispersion","intensity"),-1] <- 1
  organism["rejection",-1] <- if( reject == Inf )
    rep( Inf, n )
  else
    reject * stats::rexp( n )
  
  ## triangular coordinates
  organism[posnames,-1] <- position
  
  ## substrate
  substrate.name <- getOrgFeature( community, species, "substrate" )
  if( !is.na( substrate.name )) {
    substrate <- getOrgInteract( community, substrate.name, species, "init" )
    organism["sub.stage",-1] <- organism["sub.future",-1] <- sample( length( substrate ),
                                                                     n, replace = TRUE, prob = substrate / sum( substrate ))
  }
  ## randomly generate events proportional to future time units
  nstage <- length( init.weight )
  istage <- sample( nstage, n, replace = TRUE, prob = init.weight / sum( init.weight ))
  init.stage <- array( init.stage, n )
  organism["stage",-1] <- init.stage
  
  ## schedule future events
  if( timeit )
    proctime <- proc.time()
  organism[,-1] <- get.future( community, species, organism[,-1] )
  if( timeit ) {
    tmp <- proc.time() - proctime
    cat( "future time: user=", tmp[1], "system=", tmp[2], "total=", tmp[3], "\n" )
  }
  ## create leftist tree
  if( timeit )
    proctime <- proc.time()
  community <- put.species( community, species, leftist.create( organism ))
  if( timeit ) {
    tmp <- proc.time() - proctime
    cat( "leftist time: user=", tmp[1], "system=", tmp[2], "total=", tmp[3], "\n" )
  }
  ## mean number of offspring
  if( timeit )
    proctime <- proc.time()
  #***This is where parasite is crashing--no offspring?**
  community <- initOffspring( community, species )
  if( timeit ) {
    tmp <- proc.time() - proctime
    cat( "offspring time: user=", tmp[1], "system=", tmp[2], "total=", tmp[3], "\n" )
  }
  
  if(messages) {
    cat( "Initializing events for", species, "with", ncol( organism ) - 1, "individuals\n" )
  }
  community
}

# --- Source: init.timing.R ---
###########################################################################################
### Timing of simulation run
###########################################################################################
init.timing <- function( community )
{
  ## initialize timing
  events <- NULL
  for( species in get.species( community ))
    events <- c( events, levels( getOrgFuture( community, species, "event" )))
  events <- sort( unique( events ))
  tmp <- c("total",events,"refresh","other")
  cpu <- matrix( 0, 3, length( tmp ),
                 dimnames = list( c("user","system","total"), tmp ))
  community$cpu <- cpu
  community <- set.timing( community, "total" )
  community
}
###########################################################################################
set.timing <- function( community, string, flag = -1 ) {
  if( !is.null( community$cpu ))
    community$cpu[,string] <- community$cpu[,string] + flag * proc.time()[1:3]
  community
}
###########################################################################################
fini.timing <- function( community )
{
  if( !is.null( community$cpu )) {
    community <- set.timing( community, "total", 1 )
    community$cpu[,"other"] <- community$cpu[,"total"] - apply( community$cpu[,-1], 1, sum )
  }
  community
}

# --- Source: initCount.R ---
###########################################################################################
### Simulation count object administration
###########################################################################################
initCount <- function( community, species, debugit = FALSE, file = NULL, append = FALSE,
                       messages = TRUE, ... )
{
  if(messages) {
    cat( "initial" )
    for( i in species)
      cat( ":", i, sum( apply( get.species( community, i ), 2,
                               function(x) !all(x[c("dist","left","right","up")]==1))) - 1 )
    cat( "\n" )
  }
  
  old_counts <- if (append) getCount( community, , "counts" ) else NULL
  old_step <- if (append) getCount( community, , "step" ) else 0

  count <- list()
  ## leftist tree counters
  count$mintime <- numeric( length( species ))
  count$base <- numeric( length( species ))
  names( count$base ) <- names( count$mintime ) <- species
  count$free <- list()
  
  ## initialize lists to keep track of events
  count$events <- count$countage <- count$countsub <- count$nameage <- count$namesub <- list()
  
  ## Set up hour to degreeday spline based on range of hours if any
  simmin <- c(hr=Inf,DD=Inf)
  for( i in species ) {
    if( is.null( get.species( community, i )))
      stop( paste( "Missing species", i ))
    count$free[[i]] <- 1
    count$base[i] <- get.base( community, i )
    units <- getOrgFeature( community, i, "units" )
    tmp <- get.individual( community, i )["time"]
    if( tmp < simmin[units] )
      simmin[units]<- tmp
  }
  if( max( simmin ) < Inf ) {
    community <- activeTemp( community, simmin["hr"], , simmin["DD"], messages = messages )
  }
  esums <- c("initial","during","final")
  tmpfn <- function( counter )
  {
    rownames <- levels( counter )
    array( 0, length( rownames ), dimnames = list( rownames ))
  }
  subclass <- getOrgFeature( community, species, "subclass" )
  names( subclass ) <- species
  for( i in species ) {
    species.time <- get.individual( community, i )["time"]
    count$mintime[i] <- getTime( community, i, species.time )
    future <- getOrgFuture( community, i )
    ## possible future events
    count$events[[i]] <- matrix( 0, nrow( future ), length( esums ),
                                 dimnames = list( as.character( future$current ), esums ))
    ## current record of future events
    count$countage[[i]] <- tmpfn( getOrgFuture( community, i, "ageclass" ))
    count$countsub[[i]] <- tmpfn( getOrgInteract( community,, i, "substrate" ))
    
    if( species.time < Inf ) {
      ## count by age groups
      stage <- getOrgAlive( community, i, "stage" )
      if( length( stage )) {
        classes <- getOrgAgeClass( community, i, stage )
        tmp <- table( classes )
        count$countage[[i]][ names( tmp ) ] <- tmp
      }
      ## count by substrate
      substage <- getOrgAlive( community, i, "sub.stage" )
      ## only for individuals of class = subclass[i]
      substage <- substage[ subclass[i] == getOrgAgeClass( community, i, stage ) ]
      if( length( substage )) {
        classes <- getOrgInteract( community,, i, "substrate" )
        tmp <- table( classes[substage] )
        count$countsub[[i]][ names( tmp ) ] <- tmp
      }
    }
  }
  count$debug <- debugit
  
  # If file is NULL, then don't write to file; keep counts internal
  count$file <- file
  if (append && !is.null(old_counts)) {
    count$counts <- old_counts
    count$step <- old_step
  }
  
  community$count <- count
  
  ## Put counts in file
  community <- putCount( community, append )
  
  ## tally events at start of simulation
  setEvents( community, "initial" )
}
###########################################################################################
getCount <- function( community, species, element )
{
  count <- community$count[[element]]
  if( !missing( species ))
    count <- count[[species]]
  count
}
###########################################################################################
set.step <- function( community, step )
  setCount( community,, list( step = step ))
###########################################################################################
setCount <- function( community, species, elements )
{
  count <- community$count
  for( i in names( elements )) {
    if( missing( species ))
      count[[i]] <- elements[[i]]
    else
      count[[i]][[species]] <- elements[[i]]
  }
  community$count <- count
  community
}

# --- Source: future.events.R ---
future.events <- function( community,
                           nstep = 4000,
                           species = get.species( community ),
                           
                           refresh = nstep / 20, cex = 0.5,
                           substrate.plot = TRUE, extinct = TRUE,
                           timeit = TRUE, debugit = FALSE,
                           messages = TRUE, append = NULL, ... )
  
{
  ## Integrity check of dataset, and initialization of tallies.
  if( missing( community ))
    stop( "Must specify a community." )
  
  if (is.null(append)) {
    append <- !is.null(community$count$counts) && nrow(community$count$counts) > 0
  }
  
  if( debugit ) cat( "initialization\n" )
  community <- initCount( community, species, debugit = debugit, file = NULL,
                           append = append, messages = messages )
  if( timeit )
    community <- init.timing( community )
  
  mintime <- getCount( community, , "mintime" )
  species.now <- species[ mintime == min( mintime ) ][1]
  future <- getOrgFuture( community, species.now )
  
  # Set up list for plot information.
  p <- list()
  pstep <- 0
  if (!is.null(community$plot)) {
    p <- community$plot
    pstep <- length(p)
  }
  
  start_step <- if (append && !is.null(community$count$step)) community$count$step else 0
  
  ## for nstep steps schedule future events and process immediate events
  for( istep in seq( nstep )) {
    ## stop if any extinct and extinct flag on, or all extinct
    omintime <- mintime
    mintime <- getCount( community, , "mintime" )
    if( debugit ) print( mintime )
    if( min( mintime ) < min( omintime )) {
      cat( "time reversal!\n" ) # should not happen
      browser()
    }
    tmp <- mintime == Inf
    if( any( tmp )) {
      if( extinct | all( tmp )) {
        for( i in names( mintime )[tmp] )
          cat( "***", i, "is extinct ***\n" )
        if( plotit )
          plot.ewing( community, substrate = substrate.plot, cex = cex, ...)
        break
      }
    }
    ## each species is always sorted so 1st element is next future event
    species.prev <- species.now
    species.now <- species[ mintime == min( mintime ) ][1]
    individual <- get.individual( community, species.now )
    if( is.na( individual["time"] ) | individual["time"] == Inf ) {
      cat( "No more finite future events. End of simulation.\n" )
      break
    }
    if( all( individual[c("dist","left","right","up")] == 1 )) {
      cat( individual["time"], ": last", species.now, "alive",
           getCount( community, species.now, "base" ), "\n" )
    }
    if( species.now != species.prev )
      future <- getOrgFuture( community, species.now )
    ## make future event the current stage
    current <- individual["stage"]
    stage <- as.character( future$current[current] )
    if(!length(stage))
      stop(paste("no stage", current))
    community <- updateCount( community, species.now, individual, stage == "death",
                              start_step + istep )
    individual["stage"] <- current
    individual["sub.stage"] <- individual["sub.future"]
    community <- put.individual( community, species.now, individual )
    
    if( debugit ) {
      cat( species.now, istep, "base",
           getCount( community, species.now, "base" ), "\n" )
      print( c( step = istep, 
                countage = sum( getCount( community, species.now, "countage" )),
                countsub = sum( getCount( community, species.now, "countsub" )),
                round( individual["time"], 2 ), current, stage ))
    }
    
    ## processing of immediate, pending and future events
    ## this is the main show--all the rest is setup
    event.type <- as.character( future[ current, "event" ] )
    if( debugit ) cat( "do", event.type, istep, stage,
                       as.character( future$current[ individual["future"] ] ),
                       individual["time"], "\n" )
    community <- set.timing( community, event.type )
    if( event.type == "death" )
      community <- event.death( community, species.now )
    else {
      if( event.type != "future" ) {
        ## this routine could be user supplied
        ## generic routines are event.birth, event.attack
        event.parsed <- get( paste( "event", event.type, sep = "." ))
        community <- event.parsed( community, species.now )
      }
      community <- event.future( community, species.now )
    }
    community <- set.timing( community, event.type, 1 )
    
    ## refresh plot
    if( refresh & ! ( istep %% refresh )) {
      community <- set.timing( community, "refresh" )
      # Save the ewing_ageclass and ewing_substrate objects.
      pstep <- pstep + 1
      p[[pstep]] <- ewing_snapshot(community, start_step + istep, ...)
      
      if(messages) {
        cat( "refresh", istep )
        for( j in get.species( community ))
          cat( ":", j, sum( getCount( community, j, "countage" ), na.rm = TRUE ))
        cat( "\n" )
      }
      community <- set.timing( community, "refresh", 1 )
    }
    ## periodic browser if in debug mode
    if( debugit ) {
      cat( "done", istep, "\n" )    
      if( refresh & !( istep %% refresh )) {
        cat( "Type \"c\" to continue or \"Q\" to quit.\n" )
        browser()
      }
    }
  }
  community <- set.timing( community, "refresh" )
  ## end of main loop on future events
  if( debugit ) cat("done\n")
  
  if( sum( getOrgAlive( community, species.now )) > 1 ) {
    ## tally events at end of simulation
    community <- setEvents( community, "final" )
  }
  community <- set.timing( community, "refresh", 1 )
  
  community <- fini.timing( community )
  community$step <- start_step + nstep
  attr(community, "nstep") <- start_step + nstep
  
  if( !refresh | (nstep%%refresh)) {
    pstep <- pstep + 1
    p[[pstep]] <- ewing_snapshot(community, start_step + istep, ...)
  }
  community$plot <- p
  
  community
}

# --- Source: event.R ---
## $Id: event.R,v 1.0 2002/12/11 yandell@stat.wisc.edu Exp $
##
## Functions for Bland Ewing's modeling.
##
##     Copyright (C) 2000,2001,2002 Brian S. Yandell.
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by the
## Free Software Foundation; either version 2, or (at your option) any
## later version.
##
## These functions are distributed in the hope that they will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## The text of the GNU General Public License, version 2, is available
## as http://www.gnu.org/copyleft or by writing to the Free Software
## Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
###############################################################################
##
## event.birth( community, species )
## event.attack( community, species )
##
## Issues to resolve:
## 2. search strategy for predator/parasite/parasitoid
## 3. generic calls to do.x
###############################################################################
event.birth <- function( community, species )
{
  ## now only one offspring at a time,
  ## but could depend on individual
  offspring <- get.offspring( community, species)
  ## update parent based on anticipated offspring
  community <- parent.birth( community, species, offspring )
  if( offspring ) {
    ## get new births
    newbirths <- get.birth( community, species, offspring )
    ## merge births into community
    community <- set.birth( community, species, newbirths )
  }
  community
}
###############################################################################
parent.birth <- function( community, species, offspring )
{
  ## get individual record
  individual <- get.individual( community, species )
  individual["offspring"] <- individual["offspring"] - offspring
  ## female starves when egg load depleted (less than or equal to 0)
  if( individual["offspring"] <= 0 )
    individual["stage"] <- set.future( community, species, "starved" )
  community <- put.individual( community, species, individual )
}
###############################################################################
get.birth <- function( community, species, offspring )
{
  ## get individual record
  individual <- get.individual( community, species )
  ## matrix of new offspring for community
  newbirths <- matrix( individual, length( individual ), offspring )
  dimnames( newbirths ) <- list( names( individual ), NULL )
  if( offspring ) {
    ## assumes newborn is stage 1, and next stage is 2
    newbirths["stage",] <- 1
    newbirths["future",] <- 2
    ## set up as unlinked node for leftist tree
    newbirths[c("dist","left","right","up"),] <- 1
    ## disperse offspring across substrate types
    newbirths <- event.move( community, species, newbirths )
  }
  if( getCount( community,, "debug" ))
    cat( round( individual["time"] ),
        getOrgFeature( community, species, "units" ),
        ":", species, "offspring", individual["offspring"], "\n" )
  if( offspring ) {
    ## get future events for new organisms
    get.future( community, species, newbirths)
  }
  else
    community
}
###############################################################################
get.deplete <- function( community, species )
{
  ## get individual record of attacker
  individual <- get.individual( community, species )
  ## Deplete reserves based on time spent searching for host.
  individual["offspring"] <- individual["offspring"] - 
    ( individual["time"] - individual["location"] ) /
      getOrgFeature( community, species, "deplete" )
  community <- put.individual( community, species, individual )
}
###############################################################################
set.future <- function( community, species, stage )
{
  current <- getOrgFuture( community, species, "current" )
  seq( length( current ))[ current == stage ]
}
###############################################################################
### Interaction Events (only attack for now)
###############################################################################
event.attack <- function( community, species )
{
  ## dyadic event: attack of host by adult parasitoid
  
  ## deplete individual based on time spent searching for host
  community <- get.deplete( community, species )
  ## get individual record of attacker
  individual <- get.individual( community, species )

  ## find a host if parasite has offspring reserve left
  if( individual["offspring"] > 0 ) {
    ## get name of host for attacker
    host <- getOrgFeature( community, species, "attack" )
    ## get attack parasite and event types
    attack <- get.attack( community, species, individual )
    ## find host located on the same substrate
    found <- event.find( community, species, host, attack["event"] )
    if( length( found )) {
      ## host-parasite interaction
      event.parsed <- get( paste( "event", attack["event"], sep="." ))
      community <- event.parsed( community, species, host, found )
      event.parsed <- get( paste( "host", attack["parasite"], sep="." ))
      community <- event.parsed( community, species, host, found )
      individual <- get.individual( community, species )
    }
  }
  if( individual["offspring"] > 0 ) {
    ## parasite moves along substrate
    individual <- event.move( community, species, individual )
  }
  else {
    ## parasite dies if it does not feed enough
    individual["stage"] <- set.future( community, species, "starved" )
  }
  ## put updated individual back in community
  community <- put.individual( community, species, individual )
}
###############################################################################
get.attack <- function( community, species, individual )
{
  ## get parasite type ("ecto" or "endo") and current event ("feed" or "ovip")
  parasite <- getOrgFeature( community, species, "parasite" )
  event <- getOrgFuture( community, species, "current", individual["future"] )
  event <- as.character( event )
  if( parasite=="endo" )
    event <- "ovip"
  else if( individual["offspring"] < 1 ) {
    ## must feed if depleted
    event <- "feed"
  }
  c( event = event, parasite = parasite )
}
###############################################################################
host.ecto <- function( community, species, host, dead )
{
  ## ectoparasites effectively kill their host

  ## get individual doing the attack
  individual <- get.individual( community, species )
  ## get host individual that is attacked
  hostindiv <- get.individual( community, host, dead )
  ## set host time to now, which may involve hr-DD translation
  hostindiv["time"] <- transTime( community, species, host, individual["time"] )
  ## schedule immediate death of host
  hostindiv["future"] <- set.future( community, host, "death" )
  ## update host record in community
  community <- put.individual( community, host, hostindiv, dead )
  ## update leftist tree and mintime
  community <- put.species( community, host,
                           leftist.update( get.species( community, host ), dead ))
  update_mintime( community, host )
}
###############################################################################
host.endo <- function( community, species, host, dead, harm )
{
  ## endoparasites reduces capacity of host (assumed by half here)
  
  ## get host individual that is attacked
  hostindiv <- get.individual( community, host, harm )
  ## schedule harm for hosts (reduce egg capacity by half)
  hostindiv["offspring"] <- floor( hostindiv["offspring"] / 2 )
  if( hostindiv["offspring"] == 0 ) {
    ## get individual doing the attack
    individual <- get.individual( community, species )
    ## set host time to now, which may involve hr-DD translation
    hostindiv["time"] <- transTime( community, species, host, individual["time"] )
    ## schedule immediate death of host
    hostindiv["future"] <- set.future( community, host, "death" )
  }
  ## update host record in community
  community <- put.individual( community, host, hostindiv, harm )
  if( hostindiv["offspring"] == 0 ) {
    ## update leftist tree and mintime
    community <- put.species( community, host,
                             leftist.update( get.species( community, host ), dead ))
    community <- update_mintime( community, host )
  }
  community
}
###############################################################################
event.feed <- function( community, species, host, dead )
{
  ## feed: adult parasite feeds on host

  interact <- get.interact( community, species, host, dead, "feed" )
  ## host-parasite interaction: feeding
  if( interact ) {
    individual <- get.individual( community, species )
    individual["offspring"] <- individual["offspring"] + interact
    community <- put.individual( community, species, individual )
  }
  community
}
###############################################################################
event.ovip <- function( community, species, host, dead, gender=TRUE )
{
  ## ovip: adult lays egg in the host to emerge later as adult
  offspring <- get.offspring( community, species )

  interact <- get.interact( community, species, host, dead, "ovip" )
  ## host-parasite interaction: feeding
  if( interact ) {
    ## update parent individual, depleting energy after egg laying
    community <- parent.birth( community, species, offspring )

    ## get new births
    newbirths <- get.birth( community, species, offspring )
    ## gender preference for offspring
    if( get.interact( community, species, host, dead, "male" ) < stats::runif( 1 ) |
      !gender ){
      ## set offspring for female eggs based on dead host
      newbirths["offspring", ] <- set.offspring( community, species, host, dead )
    }
    else {
      ## produce male and put on queue for immediate death
      newbirths <- set.male( community, species, host, newbirths )
    }
    community <- set.birth( community, species, newbirths )
  }
  community
}
###############################################################################
set.male <- function( community, species, host, newbirths )
{
  ## produce male offspring, which is queued for immediate death
  newbirths["future",1] <- set.future( community, species, "male" )
  newbirths["time",1] <- get.individual( community, species )["time"]
  newbirths
}

# --- Source: event.future.R ---
event.future <- function( community, species )
{
  ## schedule future event based on current stage
  individual <- get.future( community, species )
  ## move if appropriate
  individual <- event.move( community, species, individual )
  ## update time translation if needed
  community <- checkTime( community, individual["time"],
                          getCount( community, species, "mintime"),
                          getOrgFeature( community, species, "units" ))
  ## put updated individual back in community
  community <- put.individual( community, species, individual )
  
  ## reprioritize the leftist tree if time has changed
  if( individual["time"] > individual["location"] ) {
    ## and time is longer than next scheduled time
    if( individual["time"] >
        min( get.species.element( community, species, "time",
                                  individual[c("left","right")] ))) {
      ## remove individual from leftist tree
      community <- put.species( community, species,
                                leftist.update( get.species( community, species )))
    }
    community <- update_mintime( community, species )
  }
  community
}

# --- Source: Events.R ---
###########################################################################################
updateEvents <- function( community, species, event, increment = 1 )
{
  community$count$events[[species]][event,"during"] <-
    community$count$events[[species]][event,"during"] + increment
  community
}
###########################################################################################
setEvents <- function( community, period )
{
  count <- community$count
  for( species in get.species( community )) {
    current <- getOrgFuture( community, species, "current" )
    events <- rep( 0, length( current ))
    names( events ) <- as.character( current )
    
    stage <- getOrgAlive( community, species, "stage" )
    if( length( stage )) {
      tmp <- tapply( stage, current[stage], length )
      tmp[ is.na( tmp ) ] <- 0
      events[ names( tmp ) ] <- tmp
    }
    
    count$events[[species]][,period] <- events
  }
  community$count <- count
  community
}

# --- Source: leftist.R ---
## $Id: leftist.R,v 1.0 2002/12/11 yandell@stat.wisc.edu Exp $
##
## Functions for Bland Ewing's modeling.
##
##     Copyright (C) 2000,2001,2002 Brian S. Yandell.
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by the
## Free Software Foundation; either version 2, or (at your option) any
## later version.
##
## These functions are distributed in the hope that they will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## The text of the GNU General Public License, version 2, is available
## as http://www.gnu.org/copyleft or by writing to the Free Software
## Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
###########################################################################################
## This version has list structure for individuals.
## In addition it will keep species separate.
###########################################################################################
## Triply linked leftist trees
###########################################################################################
leftist.tree <- function( x,
  tree = data.frame( root = empty ),
  key = "time",
  empty = c( time = NA, dist = 1, left = 1, right = 1, up = 1 ))
{
  node <- empty
  node[key] <- Inf
  node[c("right","left")] <- 2
  node["dist"] <- 0
  if( missing( tree ))
    names( tree ) <- NULL

  node <- empty
  node[key] <- x[1]
  base <- 2
  tree[[base]] <- node

  for( i in 1 + seq( 2, length( x ))) {
    node <- empty
    node[key] <- x[i-1]
    if( node[key] <= tree[[base]][key] ) {
      ## insert at root
      node["left"] <- base
      tree[[base]]["up"] <- i
      base <- i
      tree[[base]] <- node
    }
    else {
      tree[[i]] <- node
      ## insert in tree (e.g. merge two trees)
      tree <- as.data.frame( leftist.merge( as.matrix( tree ), i, base ))
      base <- tree["up",1]
    }
  }
  tree[[1]][c("right","left","up")] <- base
  tree
}
###########################################################################################
leftist.create <- function( tree, key = "time" )
{
  ## assume 1st element of tree is for base
  ## and keys are in place already
  base <- 2
  tree[c("right","left","up"),] <- 1
  tree["dist",-1] <- 1
  ntree <- ncol( tree )

  for( i in seq( 3, ntree )) {
    if( tree[key,i] <= tree[key,base] ) {
      ## insert at root
      tree["left",i] <- base
      tree["up",base] <- i
      base <- i
    }
    else {
      ## insert in tree (e.g. merge two trees)
      tree <- leftist.merge( tree, i, base )
      base <- tree["up",1]
    }
  }
  ## set 1st element to point to base
  tree[c("right","left","up"),1] <- base
  tree
}
###########################################################################################
leftist.merge <- function( tree, P = 1, Q = 1, R = 1, key = "time" )
{
  while( TRUE ) {
    if( P == 1 ) {
      P <- Q
      Q <- 1
    }
    if( Q == 1 ) {
      D <- tree["dist",P]
      while( R > 1 ) {
        Q <- tree["right",R]
        temp <- tree["left",R]
        if( tree["dist",temp] < D ) {
          D <- tree["dist",temp] + 1
          tree["right",R] <- tree["left",R]
          tree["left",R] <- P
        }
        else {
          D <- D + 1
          tree["right",R] <- P
        }
        tree["up",P] <- R
        tree["dist",R] <- D
        P <- R
        R <- Q
      }
      tree["up",P] <- 1
      tree[c("left","right","up"),1] <- P
      return( tree )
    }
    ## merge two right lists
    if( tree[key,P] <= tree[key,Q] ) {
      temp <- tree["right",P]
      tree["right",P] <- R
      tree["up",R] <- P
      R <- P
      P <- temp
    }
    else {
      temp <- tree["right",Q]
      tree["right",Q] <- R
      tree["up",R] <- Q
      R <- Q
      Q <- temp
    }
  }
  tree["up",1] <- P
  tree[c("left","right","up"),P] <- 1
  tree
}
###########################################################################################
leftist.remove <- function( tree, P )
{
  oldbase <- tree["up",1]
  up <- tree["up",P]

  ## merge the subtrees below P
  tree <- leftist.merge( tree, tree["left",P], tree["right",P] )

  ## reset node to empty
  tree[c("up","left","right","dist"),P] <- 1

  ## return if base node removed
  if( oldbase == P )
    return( tree )

  ## make up node leftist
  if( tree["left",up] == P )
    tree["left",up] <- tree["right",up]
  tree["right",up] <- 1
  tree["dist",up] <- 1

  ## traverse back up the tree to make sure it is leftist to base
  upup <- tree["up",up]
  left <- tree["left",upup]
  right <- tree["right",upup]
  while( tree["dist",left] < tree["dist",right] ) {
    tree["dist",upup] <- tree["dist",left] + 1
    tree["left",upup] <- right
    tree["right",upup] <- left
    upup <- tree["up",upup]
    left <- tree["left",upup]
    right <- tree["right",upup]
  }
  ## merge down and up trees
  leftist.merge( tree, oldbase, tree["up",1] )
}
###########################################################################################
leftist.birth <- function( organism, neworg, free )
{
  newbase <- organism["up",1]
  norganism <- ncol( neworg )
  j <- norganism
  nfree <- free[1]
  while( j > 0 & nfree > 1 ) {
    newbabe <- free[nfree]
    organism[,newbabe] <- neworg[,j]
    organism <- leftist.merge( organism, newbase, newbabe )
    newbase <- organism["up",1]
    nfree <- nfree - 1
    free[1] <- nfree
    j <- j - 1
  }
  if( j > 0 ) {
    norganism <- ncol( organism )
    js <- 1:j
    organism <- cbind( organism, neworg[,js] )
    for( i in norganism + js ) {
      organism <- leftist.merge( organism, newbase, i )
      newbase <- organism["up",1]
    }
  }
  list( tree = organism, base = newbase, free = free )
}
###########################################################################################
leftist.free <- function( free, id )
{
  free[1] <- tmp <- free[1] + 1
  free[tmp] <- id
  free
}
###########################################################################################
leftist.update <- function( tree, P = tree["up",1] )
{
  tree <- leftist.remove( tree, P )
  leftist.merge( tree, tree["up",1], P )
}

# --- Source: community.R ---
get.organisms <- function(datafile = "") {
  org <- list(species = c("host", "parasite"), substrates = "substrate")
  if(datafile != "") {
    if(tools::file_ext(datafile) %in% c("xls","xlsx")){
      sheets <- readxl::excel_sheets(datafile)
      species <- stringr::str_remove(
        sheets[stringr::str_detect(sheets, "future\\.")],
        "future\\.")
      substrates <- unique(stringr::str_remove(
        sheets[stringr::str_detect(sheets, paste("\\.", species, sep = "", collapse = "|")) &
        !stringr::str_detect(sheets, paste(c("future", species), "\\.", sep = "", collapse = "|"))],
        "\\..*"))
      org <- list(species = species, substrates = substrates)
    }
  }
  org
}
###########################################################################################
get.species <- function( community, species ) {
  if( missing( species ))
    return( names( community$pop ))
  if( is.numeric( species ))
    species <- names( community$pop )[species]
  if( is.null( species ) || !species %in% names( community$pop ))
    return( NULL )
  
  ans <- community$pop[[species]]
  if (is.list(ans) && !is.matrix(ans) && !is.data.frame(ans) && !is.null(ans$org)) {
    ans <- ans$org
  }
  if (is.matrix(ans) || is.data.frame(ans)) {
    if (!"up" %in% rownames(ans) && (is.null(colnames(ans)) || colnames(ans)[1] != "dummy")) {
      dummy <- ans[, 1, drop = FALSE]
      colnames(dummy) <- "dummy"
      return(cbind(dummy, ans))
    }
  }
  ans
}
###########################################################################################
get.species.element <- function( community, species, rows, cols )
  community$pop[[species]][rows,cols]
###########################################################################################
put.species <- function( community, species, value )
{
  community$pop[[species]] <- value
  community
}
###############################################################################
put.individual <- function( community, species, individual,
                           id = get.base( community, species ))
{
  community$pop[[species]][,id] <- individual
  community
}  
###############################################################################
get.individual <- function( community, species,
                           id = get.base( community, species ))
  community$pop[[species]][,id]
###############################################################################
get.base <- function( community, species )
  community$pop[[species]]["up",1]

# --- Source: c.ewing.R ---
c.ewing <- function(...) {
  communities <- list(...)
  if(length(communities) < 2) {
    if(length(communities) == 1)
      return(communities[[1]])
    return(NULL)
  }

  community <- communities[[1]]
  
  # *** need to verify that all communities have save structure
  # Elements org, temp should be identical
  
  for(comi in seq(2, length(communities))) {
    # Element pop
    for(species in names(community$pop)) {
      community$pop[[species]] <-
        cbind(community$pop[[species]], communities[[comi]]$pop[[species]])
    }
    # Element cpu
    community$cpu <- community$cpu + communities[[comi]]$cpu
  }
  # Element plot is more complicated as it contains items for plots
  # Probably want some form of appendX functions
}

# --- Source: move.R ---
## $Id: move.R,v 1.0 2002/12/09 yandell@stat.wisc.edu Exp $
##
## Functions for Bland Ewing's modeling.
##
##     Copyright (C) 2000,2001,2002 Brian S. Yandell.
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by the
## Free Software Foundation; either version 2, or (at your option) any
## later version.
##
## These functions are distributed in the hope that they will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## The text of the GNU General Public License, version 2, is available
## as http://www.gnu.org/copyleft or by writing to the Free Software
## Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
###############################################################################
event.move <- function( community, species, individual )
{
  individual <- as.matrix( individual )
  ## move only if individual is in stage that moves
  if( all( is.move( community, species, individual ))) {
    ## move among substrates?
    individual["sub.future",] <-
      sampleOrgSubstrate( community, species, individual["sub.stage",] )
    ## move to new position in substrate
    position <- paste( "pos", letters[1:3], sep = "." )
    individual[position,] <- rtri( ncol( individual ), 10, individual[position,] )
  }
  if( ncol( individual ) == 1 )
    individual <- individual[,1]
  individual
}
###############################################################################
is.move <- function( community, species, individual )
{
  !is.na( match( getOrgFeature( community, species, "move" ), 
                getOrgFuture( community, species, "current" )[ individual["stage",] ] ))
}
###############################################################################
event.find <- function( community, species, host, event )
{
  individual <- get.individual( community, species )
  substrate <- individual["sub.stage"]
  ## pending event: need to find available hosts on substrate
  avail <- get.alive( community, host, substrate )
  navail <- length( avail )
  if( !navail )
    return( avail )
  
  ## preferences based on schedule
  interact <- get.interact( community, species, host, avail, event )
  if( length( event ) > 1 )
    interact <- apply( interact, 1, sum )
  sinteract <- sum( interact )
  if( sinteract ) {
    if( navail > 1 ) {
      found <- sample( avail, 1, prob = interact / sinteract )
    }
    else
      found <- avail
    found
  }
  else
    numeric(0)
}

# --- Source: offspring.R ---
## $Id: init.R,v 1.0 2002/12/09 yandell@stat.wisc.edu Exp $
##
## Functions for Bland Ewing's modeling.
##
##     Copyright (C) 2000,2001,2002 Brian S. Yandell.
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by the
## Free Software Foundation; either version 2, or (at your option) any
## later version.
##
## These functions are distributed in the hope that they will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## Offspring Information
##########################################################################################
getOffspring <- function( community, species,
                         offspring = getOrgFeature( community, species, "offspring" ))
{
  if( is.na( offspring ))
    return( 0 )
  if( is.numeric( offspring ))
    return( offspring )
  getOrgInteract( community, offspring, species, "offspring" )
}
##########################################################################################
initOffspring <- function( community, species )
{
  hostname <- getOrgFeature( community, species, "offspring" )

  ## find if there is offspring load based on host
  orgoffspring <- getOffspring( community, species, hostname )

  norganism <- sum( getOrgAlive( community, species ))

  if( length( orgoffspring ) == 1 ) {
    ## mean offspring does not depend on any host
    offspring <- stats::rpois( norganism, orgoffspring )
  }
  else {
    orgoffspring <- orgoffspring[ orgoffspring > 0 ]
    
    if(!length(orgoffspring))
      return(community)
  
    ## figure out initial offspring load based on host distribution

    ## mean offspring depends on host stages and events
    host <- get.species( community, hostname )
    if( is.null( host ))
      stop( paste( "Host", hostname, "not initiated yet" ))

    ## get weights of host stages in terms of future event times
    host <- host[ , getOrgAlive( community, hostname ) ]

    ## find host stages that are preferred by parasite
    ## need to take subset of current that are actually in host
    hoststages <- match( names( orgoffspring ), getOrgFuture( community, hostname )$current,
                        nomatch = 0 )
    host <- as.matrix( host[ , !is.na( match( host["stage",], hoststages )) ] )
    if( ncol( host ) == 0 )
      return( community )

    tmp <- !is.na( match( hoststages, host["stage",] ))
    hoststages <- hoststages[tmp]
    orgoffspring <- orgoffspring[tmp]

    if(!length(orgoffspring))
      return(community)
    
    dd <- tapply( host["time",], host["stage",], sum )
    dd[ as.character( hoststages[
      is.na( match( hoststages, names( dd ))) ] ) ] <- 0
    dd[ is.na( dd ) ] <- 0
    sdd <- sum( dd )
    if( length( dd ) > 1 & sdd > 0)
      offspring <- as.vector( sample( orgoffspring, norganism, replace = TRUE,
        prob = dd / sdd ))
    else
      offspring <- rep( ( sdd > 0 ) * orgoffspring[1], norganism )
    offspring[ is.na( offspring ) ] <- 0
  }
  organism <- get.species( community, species )
  organism["offspring",-1] <- offspring
  put.species( community, species, organism )
}
###############################################################################
get.offspring <- function( community, species )
{
  individual <- get.individual( community, species )
  if( individual["offspring"] > 0 )
    1
  else
    0
}
###########################################################################################
set.offspring <- function( community, species, host, dead )
{
  stage <- get.species.element( community, host, "stage", dead )
  current <- getOrgFuture( community, host, "current", stage )
  offspring <- getOrgInteract( community, host, species, "offspring")
  offspring <- as.vector( offspring[ as.character( current ) ] )
  offspring[ is.na( offspring ) ] <- 0
  offspring
}

# --- Source: sim.R ---
## $Id: sim.R,v 0.9 2002/12/09 yandell@stat.wisc.edu Exp $
##
## Functions for Bland Ewing's modeling.
##
##     Copyright (C) 2000,2001,2002 Brian S. Yandell.
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by the
## Free Software Foundation; either version 2, or (at your option) any
## later version.
##
## These functions are distributed in the hope that they will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## The text of the GNU General Public License, version 2, is available
## as http://www.gnu.org/copyleft or by writing to the Free Software
## Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
### migrate count$count* to writeCount and then retrieve with readCount
###########################################################################################
updateCount <- function(community, species, individual, is.death = FALSE, step) {
  community <- updateEvents(community, species, individual["future"])
  if (!missing(step)) {
    community <- setCount(community, elements = list(step = step))
  }

  ## move individual through age classes or drop if it dies
  countage <- getCount( community, species, "countage" )
  ageclass <- getOrgAgeClass( community, species, individual["stage"] )
  if( !is.na( ageclass )) {
    ageclass <- as.character( ageclass )
    countage[ageclass] <- max(countage[ageclass] - 1, 0)
  }
  ageclass <- getOrgAgeClass( community, species, individual["future"] )
  if( !is.na( ageclass )) {
    ageclass <- as.character( ageclass )
    countage[ageclass] <- countage[ageclass] + 1
  }
  ## move individual across substrate elements or drop if it dies
  countsub <- getCount( community, species, "countsub" )
  substrate <- getOrgFeature( community, species, "substrate" )
  elements <- getOrgInteract( community, substrate, species, "substrate" )
  element <- elements[ individual["sub.stage"] ]
  subclass <- getOrgFeature( community, species, "subclass" )
  include <- subclass == getOrgAgeClass( community, species, individual[c("stage","future")] )
  # need 2 values; if only one, replicate it.
  include <- rep_len(include, 2)
  ## leave old substrate
  if (!is.na(element) && include[1]) {
    element <- as.character( element )
    countsub[element] <- max(countsub[element] - 1, 0)
  }
  if (!is.death && include[2]) {
    ## move to new substrate
    newsub <- individual["sub.future"]
    if( !is.na( newsub )) {
      element <- elements[newsub]
      if( !is.na( element )) {
        element <- as.character( element )
        countsub[element] <- countsub[element] + 1
      }
    }
  }
  ## record counts
  community <- setCount( community, species,
                        list( countage = countage, countsub = countsub ))
  community <- writeCount( community, species, individual["time"], individual["future"],
               countage, countsub )
}
###########################################################################################
updateCounts <- function( community, species, newbirths )
{
  community <- updateEvents( community, species, 1, newbirths )
  
  countage <- getCount( community, species, "countage" )
  countsub <- getCount( community, species, "countsub" )
  subclass <- getOrgFeature( community, species, "subclass" )
  ageclass <- getOrgAgeClass( community, species, 1 )
  individual <- get.individual( community, species )
  if( !is.na( ageclass[1] )) {
    ageclass <- as.character( ageclass )
    countage[ageclass] <- newbirths + countage[ageclass]
    if( subclass == ageclass ) {
      substrate <- getOrgSubstrate( community, species, individual["sub.stage"] )
      if( !is.na( substrate[1] )) {
        substrate <- as.character( substrate )
        countsub[substrate] <- newbirths + countsub[substrate]
      }
    }
  }
  community <- setCount( community, species,
                        list( countage = countage, countsub = countsub ))
  community <- writeCount( community, species, individual["time"], 1, countage, countsub )
}
###########################################################################################
put.base <- function( community, species, id,
                     free = getCount( community, species, "free" ))
{
  base <- get.base( community, species )
  if( !missing( id ))
    free <- leftist.free( free, id )
  mintime <- max( getCount( community, species, "mintime" ),
                 getTime( community, species,
                         get.species.element( community, species, "time", base )))
  setCount( community, species, list( base = base, free = free, mintime = mintime ))
}

# --- Source: simple.R ---
simpleServer <- function(id) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    
  })
}
simpleInput <- function(id) {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::h4("Simulation Settings"),
    shiny::sliderInput(ns("steps"),
                       label = "Simulation steps:",
                       min = 1000,
                       max = 10000,
                       value = 1000,
                       step = 500),
    shiny::radioButtons(ns("nsim"),
                        "Number of Simulations",
                        c(1,10,20,50,100,200),
                        1, inline = TRUE),
    shiny::actionButton(ns("go"), "Start Simulation"),
    
    shiny::HTML("<hr style='height:1px;border:none;color:#333;background-color:#333;' />"),
    shiny::h4("Save Files"),
    shiny::uiOutput(ns("version"))
  )
}
simpleOutput <- function(id) {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::radioButtons(ns("button"), "", c("Plots", "Input Data"),
                        "Plots", inline = TRUE),
  )
}
simpleApp <- function(title = "Population Ethology") {
  ui <- shiny::fluidPage(
    shiny::titlePanel(title),
    shiny::sidebarLayout(
      shiny::sidebarPanel(
        simpleInput("simple")
      ),
      shiny::mainPanel(
        simpleOutput("simple")
    )))
  
  server <- function(input, output, server) {
    simpleServer("simple")
  }
  
  shiny::shinyApp(ui = ui, server = server)
}

# --- Source: ring.R ---
## $Id: ring.R,v 1.0 2002/12/09 yandell@stat.wisc.edu Exp $
##
## Functions for Bland Ewing's modeling.
##
##     Copyright (C) 2000,2001,2002 Brian S. Yandell.
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by the
## Free Software Foundation; either version 2, or (at your option) any
## later version.
##
## These functions are distributed in the hope that they will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## The text of the GNU General Public License, version 2, is available
## as http://www.gnu.org/copyleft or by writing to the Free Software
## Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
###########################################################################################
## Doubly linked rings
###########################################################################################
ring.add <- function( ring = data.frame( root = c( key = NA, left = 1 + nx, right = 2 )),
  x )
{
  nx <- length( x )
  n1 <- ring["left","root"]
  ring[[ as.character( n1 + 1 ) ]] <- c(
    key = x[1],
    left = suppressWarnings(as.numeric( ring["left","root"] )),
    right = 1 )
  
  if( nx > 1 ) for( i in seq( 2, nx ))
  {
    ring[[ as.character( n1 + i ) ]] <- c(
      key = x[i],
      left = n1+i-1,
      right = 1 )
    ring[ "right", as.character( n1 + i - 1 ) ] <- n1+i
  }
  ring["left","root"] <- n1 + nx
  ring
}
###########################################################################################
ring.remove <- function( ring, P )
{
  aP <- as.character( P )
  if( is.na( match( aP, names( ring ))))
  {
    cat( paste( "Warning:", aP, "not found in ring\n" ))
    return( ring )
  }
  left <- ring["left",aP]
  right <- ring["right",aP]
  ring[ "right", as.character( left ) ] <- right
  ring[ "left", as.character( right ) ] <- left
  ring[[aP]] <- NULL
  ring
}

# --- Source: sierpinski.R ---
sierpinski <- function( stage = 5, reset = TRUE )
{
  if( reset )
    tmpar <- graphics::par( pty = "s", bty = "n", xaxt = "n", yaxt = "n", omi = rep(0,4),
                            mar = rep(0,4) )
  aa <- 0:1
  bb <- - aa
  for( i in seq( stage )) {
    tmp <- gasket( aa, bb )
    aa <- tmp$aa
    bb <- tmp$bb
    tri <- tri2car.default( aa, bb )
    r <- range( unlist( tri ))
    plot( r, r, type = "n", xlab = "", ylab = "" )
    graphics::lines(tri )
    graphics::mtext( paste( "(", letters[1+i], ") Gasket of Order ", i, sep = "" ), 3, -2 )
    #    graphics::mtext( paste( "Gasket of order", i ), 3, -2 )
  }
  if( reset )
    graphics::par( tmpar )
  invisible( tri )
}

# --- Source: fileCount.R ---
putCount <- function( community, append = FALSE )
{
  species <- get.species( community )
  
  countage <- getCount( community,, "countage")
  countsub <- getCount( community,, "countsub")
  countbase <- getCount( community,, "base")
  step_now <- if (append) (getCount( community,, "step" ) %||% 0) else 0
  
  cnames <- c("step", "time", "future",
              paste( "count", 
                     seq( max( unlist( lapply( countage, length )) +
                                 unlist( lapply( countsub, length )))),
                     sep = "" ))

  cnums <- list()
  for( i in species ) {
    cnums[[i]] <- c(
      step_now,
      get.species.element( community, i, c("time","stage"), countbase[i] ),
      countage[[i]],
      countsub[[i]])
  }

  file <- getCount( community,, "file" )
  if(!is.null(file) && is.character(file) && length(file) > 0) {
    if( !( file.exists( file ) & append ))
      cat( "species", cnames, "\n", file = file )
    for( i in species ) {
      cat( i, cnums[[i]], "\n", file = file, append = TRUE )
    }
    community
  } else {
    if(append) {
      counts <- getCount( community,, "counts")
    } else {
      counts <- NULL
    }
    newcounts <- matrix(NA, length(species), length(cnames),
                       dimnames = list(species, cnames)) 
    for(i in species) {
      newcounts[i, seq_along(cnums[[i]])] <- cnums[[i]]
    }
    newcounts <- tibble::tibble(
      data.frame(
        species = species,
        newcounts))
    counts <- dplyr::bind_rows(
      counts,
      newcounts)
    
    setCount( community,, list(counts = counts))
  }
}
###########################################################################################
writeCount <- function( community, species, time, future, countage, countsub)
{
  nstep <- getCount( community,, "step" )
  
  cnums <- c(nstep, time, future, countage, countsub)
  
  file <- getCount( community,, "file" )
  if(!is.null(file) && is.character(file) && length(file) > 0) {
    cat( species, cnums, "\n", file = file, append = TRUE )
    
    community
  } else {
    counts <- getCount( community,, "counts")
    cnames <- names(counts)[-1]
    
    newcounts <- matrix(NA, 1, length(cnames),
                          dimnames = list(species, cnames)) 
    newcounts[1, seq_along(cnums)] <- cnums
    
    newcounts <- tibble::tibble(
      data.frame(
        species = species,
        newcounts))
    counts <- dplyr::bind_rows(
      counts,
      newcounts)
    
    setCount( community,, list(counts = counts))
  }
}
###########################################################################################
readCount <- function( community, species = unique(counts$species) )
{
  file <- getCount( community,, "file" )
  if(!is.null(file)) {
    counts <- utils::read.table( file, header = TRUE, fill = TRUE )
  } else {
    counts <- getCount( community,, "counts")
  }
  
  count <- list()
  for( i in species ) {
    colnames <- c( levels( getOrgFuture( community, i, "ageclass" )),
                   levels( getOrgInteract( community,, i, "substrate" )))
    count[[i]] <- as.matrix( counts[ counts$species == i, seq( 2, 4 + length( colnames )) ] )
    dimnames( count[[i]] ) <- list( count[[i]][,"step"],
                                    c( "step", "time", "future", colnames ))
  }
  count
}


# --- Source: count.join.R ---
count.join <- function( ... )
{
  x <- list( ... )
  numnum <- list()
  for( i in seq( length( x ))) {
    for( j in names( x[[i]] )) {
      if( is.null( numnum[[j]] ))
        numnum[[j]] <- x[[i]][[j]]
      else
        numnum[[j]] <- cbind( numnum[[j]], x[[i]][[j]] )
    }
  }
  numnum
}

# --- Source: summary.ewing.R ---
summary.ewing <- function(object, ...) {
  out <- list()
  out$package <- object$org$package
  out$species <- names(object$org$Future)
  out$interact <- names(object$org$Interact)
  out$meanvalue <- list()
  for(i in out$species) {
    out$meanvalue <- names(object$org$MeanValue[[i]])
  }
  if(length(object$pop)) {
    out$stage <- list()
    for(i in out$species)
      out$stage[[i]] <- 
        table(object$org$Future[[i]]$current[getOrgAlive(object, i, "stage")])
  }
  if(!is.null(object$count)) {
    out$events <- object$count$events
    
    for( i in seq( length(out$events)))
      out$events[[i]] <- apply(out$events[[i]], 2, function(x) {
        tmp <- sum(x, na.rm = TRUE)
        if(tmp > 0)
          c(round(100 * x / tmp, 1), total = tmp)
        else
          c(x, total = 0)
      })
  }
  out$cpu <- signif(object$cpu, 4)
  class(out) <- c("summary.ewing", class(out))
  out
}
print.summary.ewing <- function(x, ...) {
  cat("Data initialization package:", x$package, "\n")
  cat("Community species:", paste(x$species, collapse = ", " ), "\n")
  cat("Community hosts:", paste(x$interact, collapse = ", "), "\n")
  cat("Mean Value curves by species:")
  mv <- FALSE
  for(i in x$species) {
    meanvalue <- x$meanvalue[[i]]
    mv <- mv | !is.null(meanvalue)
    if( !is.null(meanvalue)) {
      cat("\n  ", i, ":", paste( meanvalue, collapse = ", " ), "\n")
    }
  }
  if(!mv)
    cat(" none\n")
  
  if(!is.null(x$stage)) {
    cat("\nSimulation community has following counts:\n",
        paste(x$species, lapply(x$stage, sum), sep = "=", collapse = ", "),
        "\n")
    print(x$stage)
  }
  if( !is.null( x$temp )) {
    # ** later
  }
  if(!is.null(x$events)) {
    print(x$events)
  }
  if(!is.null(x$cpu)) {
    cat( "CPU timing by event in simulation\n" )
    print(x$cpu)
  }
}

# --- Source: summary_simobj.R ---
summary_simobj <- function(object) {
  out <- sapply(object, function(x) {
    if(is.list(x)) {
      x <- names(x)
    }
    paste(x, collapse=",")
  })
  paste(paste(names(out), out, sep = ": "), collapse = "<br>")
}

# --- Source: ewing_discrete.R ---
ewing_discrete <- function(nsim, verbose = FALSE, ...) {
  sims <- seq_len(nsim)
  
  object <- as.list(sims)
  names(object) <- sims
  
  for(i in sims) {
    if(verbose) cat(".")
    object[[i]] <- ewing_discrete1(...)
  }
  make_ewing_discrete(object)
}
ewing_discrete1 <- function(siminit = init.simulation(interact = FALSE,
                                                    messages = FALSE, ...),
                            increment = 0.5, ...)
{
  # Make sure increment is 1,2,5 x power of 10
  incr <- pretty(increment)
  increment <- incr[which.min(abs(incr - increment))[1]]
  
  out <- future.events(siminit, refresh = 1000,
                       plotit = FALSE, messages = FALSE, ...)
  attrs <- attributes(out)
  
  # Get age classes used later for summaries and plots
  items <- purrr::map(out$org$Future, function(x) levels(factor(x$ageclass)))
  
  out <- readCount(out)
  out <- purrr::map(
    out,
    function(x) {
      purrr::map_df(
        dplyr::distinct(
          purrr::map_df(
            dplyr::mutate(
              as.data.frame(x),
              time = ifelse(.data$step == 0, 0,
                            increment * ceiling(.data$time / increment))),
            rev),
          .data$time, .keep_all = TRUE),
        rev)
    })
  attr(out, "count") <- attrs$count
  attr(out, "nstep") <- attrs$nstep
  attr(out, "items") <- items
  out
}
summary.ewing_discrete <- function(object, ...) {
  summary(ewing_envelopes(object), ...)
}

# --- Source: make_ewing_discrete.R ---
make_ewing_discrete <- function(object) {  
  nsim <- length(object)
  class(object) <- c("ewing_discrete", class(object))
  
  attr(object, "species") <- species <- names(object[[1]])
  attr(object, "ordinate") <- "time"
  attr(object, "count") <- attr(object[[1]], "count")
  attr(object, "nstep") <- attr(object[[1]], "nstep")
  attr(object, "items") <- attr(object[[1]], "items")
  attr(object, "nsim") <- nsim
  object
}

# --- Source: ggplot_ewing.R ---
ggplot_ewing <- function(object, step = 0, ageclass = TRUE,
                         substrate = !ageclass, ...)
{
  if(!inherits(object, "ewing_snapshot")) {
    object <- ewing_snapshot(object, step, ...)
  }
  step <- object$step
  
  p <- list()
  i <- 0
  if(ageclass) {
    i <- i + 1
    p[[i]] <- ggplot2::autoplot(object$ageclass, ...)
  }
  if(substrate) {
    species <- names(object$substrate)
    for(j in species) {
      i <- i + 1
      p[[i]] <- ggplot2::autoplot(object$substrate[[j]], ...)
    }
  }
  if(length(p) == 1) p <- p[[1]]
  p
}
autoplot.ewing <- function(object, ...) {
  ggplot_ewing(object, ...)
}
plot.ewing <- function(x, ...) {
  ggplot_ewing(x, ...)
}

# --- Source: ggplot_current.R ---
ggplot_current <- function( x,
                          species,
                          col = as.character( future$color[stage] ),
                          headstuff = c( 0, "start"),
                          units = getOrgFeature( x, species, "units" ),
                          right = species, adj = c(0,.5,1),
                          position = paste( "pos", letters[1:3], sep = "." ),
                          pch = as.character( future$pch[stage] ), cex = 0.5,
                          stage = organism["stage",],
                          xlab = "horizontal", ylab = "vertical",
                          future = getOrgFuture( x, species, c("color","pch") ),
                          facet = TRUE, ...)
{
  ## plot current stages for species (except random parasites)
  organism <- get.species( x, species )[,-1]
  if(is.null(organism))
    return(NULL)
  
  tri_coord <- tri2car( organism[position,] )
  tri_coord$col <- NA
  tri_coord$label <- pch
  values <- future$color
  names(values) <- future$pch
  values <- unique(values)

  # Facet by Substrate.
  # This needs to use generic function to get substrate names.
  tmp <- names(x$org$Interact$substrate$substrate)
  tri_coord$substrate <- tmp[organism["sub.stage",]]
  
  p <- ggplot2::ggplot(tri_coord) +
    ggplot2::aes(x, y, col = label, label = label) +
    ggplot2::geom_text(size=3) +
    ggplot2::xlab(xlab) +
    ggplot2::ylab(ylab) +
    ggplot2::ggtitle(paste(units, "future event", right),
      subtitle = paste(headstuff, collapse = " ")) +
    ggplot2::scale_color_manual(values = values)
  if(facet) {
    p <- p + ggplot2::facet_wrap(~substrate)
  }
  p
}

# --- Source: plot_null.R ---
plot_null <- function(msg = "no data") {
  ggplot2::ggplot(data.frame(x = 1, y = 1), 
                  ggplot2::aes(.data$x, .data$y, label = msg)) +
    ggplot2::geom_text(size = 10) + 
    ggplot2::theme_void()
}

# --- Source: step_controls.R ---
step_size_choices <- c(1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000)

step_size_slider <- function(inputId, label = "Steps per click:", selected = 50) {
  idx <- match(selected, step_size_choices)
  if (is.na(idx)) idx <- 6
  sl <- shiny::sliderInput(inputId, label, min = 1, max = length(step_size_choices), value = idx, step = 1, ticks = TRUE)
  sl$children[[2]]$attribs[['data-values']] <- paste(step_size_choices, collapse = ",")
  sl
}

parse_step_size <- function(val) {
  if (is.null(val)) return(50)
  num <- round(as.numeric(val))
  if (is.na(num)) return(50)
  
  # Direct large values (e.g., 20, 50, 100, 200, 500, 1000, 2000)
  if (num %in% step_size_choices && num > 10) {
    return(num)
  }
  
  # 0-based JavaScript index from ion.rangeSlider (0 to 10)
  if (num >= 0 && num < length(step_size_choices)) {
    return(step_size_choices[num + 1])
  }
  
  # Fallback for direct value
  if (num %in% step_size_choices) {
    return(num)
  }
  
  50
}

axisUnitInput <- function(id, time_label = "Time", selected = "step") {
  ns <- shiny::NS(id)
  choices <- c("Steps" = "step")
  choices[time_label] <- "time"
  shiny::radioButtons(ns("x_var"), "Display Units:",
                      choices = choices,
                      selected = selected, inline = TRUE)
}

axisUnitServer <- function(id) {
  shiny::moduleServer(id, function(input, output, session) {
    shiny::reactive({ if (!is.null(input$x_var)) input$x_var else "step" })
  })
}

ageClassControlInput <- function(id, time_label = "Time") {
  ns <- shiny::NS(id)
  shiny::tagList(
    axisUnitInput(ns("axis_unit"), time_label = time_label),
    shiny::checkboxInput(ns("norm"), "Normalize Plot", TRUE),
    shiny::checkboxInput(ns("total"), "Include Total in Plot", TRUE)
  )
}

ageClassControlServer <- function(id) {
  shiny::moduleServer(id, function(input, output, session) {
    x_var <- axisUnitServer("axis_unit")
    list(
      x_var = x_var,
      norm = shiny::reactive({ if (!is.null(input$norm)) input$norm else TRUE }),
      total = shiny::reactive({ if (!is.null(input$total)) input$total else TRUE })
    )
  })
}

# --- Source: initParApp.R ---
initParApp <- function(title = "Population Ethology") {
  ui <- bslib::page_sidebar(
    title = title,
    sidebar = bslib::sidebar(
      initParInput("init_par")),
    initParUI("init_par"),
    initParOutput("init_par")
  )
  server <- function(input, output, server) {
    init_par <- initParServer("init_par")
  }
  shiny::shinyApp(ui = ui, server = server)
}
initParServer <- function(id, simres = shiny::reactiveVal(NULL), datafile = shiny::reactiveVal("")) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    
    species <- shiny::reactive({
      get.organisms()$species
    })
    output$sppsize <- shiny::renderUI({
      shiny::req(species()) # "host", "parasite"
      lapply(species(), function(x) {
        shiny::sliderInput(ns(x),
                           label = paste0("Number of ", x, "s:"),
                           min = 0,
                           max = 500,
                           value = 100,
                           step = 20)
      })
    })

    datanames <- shiny::reactive({
      getOrgNames()
    })
    output$inputfiles <- shiny::renderUI({
      shiny::tagList(
        shiny::selectInput(ns("dataname"), "", datanames(), "organism.features"),
        DT::dataTableOutput(ns("org_table")))
    })
    
    output$org_table <- DT::renderDataTable({
      getOrgDataSimple(simres(), shiny::req(input$dataname), datafile())
    }, escape = FALSE, options = list(scrollX = TRUE, pageLength = 10))
    
    # Show parameters
    output$show_par <- shiny::renderUI({
      nlist <- names(input)
      # Remove any internal inputs, which have numbers.
      glist <- grep("[0-9]", names(input))
      if(length(glist))
        nlist <- nlist[-glist]
      # Construct output string.
      out <- paste0("inputs: ", paste(nlist, collapse = ", "))
      for(i in nlist) {
        out <- paste(out, "<br>",
                     paste(i, input[[i]], sep = " = "))
      }
      shiny::HTML(out)
    })
    
    # Return.
    input
  })
}
initParInput <- function(id) {
  ns <- shiny::NS(id)
  shiny::uiOutput(ns("sppsize"))
}
initParUI <- function(id) {
  ns <- shiny::NS(id)
  shiny::uiOutput(ns("show_par"))
}
initParOutput <- function(id) {
  ns <- shiny::NS(id)
  shiny::uiOutput(ns("inputfiles"))
}

# --- Source: initApp.R ---
initApp <- function(title = "Population Ethology") {
  ui <- bslib::page_sidebar(
    title = title,
    sidebar = bslib::sidebar(
      initParInput("init_par")),
    initOutput("init"),
    substrateOutput("substrate")
  )
  server <- function(input, output, server) {
    init_par <- initParServer("init_par")
    siminit <- initServer("init", init_par)
    substrateServer("substrate", siminit)
  }
  shiny::shinyApp(ui = ui, server = server)
}
initServer <- function(id, init_par) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    
    siminit <- shiny::reactive({
      init.simulation(count = as.numeric(c(shiny::req(init_par$host),
                                           shiny::req(init_par$parasite))))
    })
    
    output$init <- shiny::renderUI({
      out <- summary_simobj(shiny::req(siminit()))
      shiny::HTML(out)
    })

    # Return.
    siminit
  })
}
initOutput <- function(id) {
  ns <- shiny::NS(id)
  shiny::uiOutput(ns("init"))
}

# --- Source: substrateApp.R ---
substrateApp <- function(title = "Substrate Organism Movement Explorer") {
  ui <- bslib::page_sidebar(
    title = title,
    sidebar = bslib::sidebar(
      initParInput("init_par"),
      shiny::hr(),
      substrateInput("substrate")),
    substrateOutput("substrate")
  )
  server <- function(input, output, server) {
    init_par <- initParServer("init_par")
    siminit <- initServer("init", init_par)
    substrateServer("substrate", siminit)
  }
  
  shiny::shinyApp(ui = ui, server = server)
}

substrateInput <- function(id) {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::div(
      style = "font-size: 0.85rem;",
      shiny::h4("Substrate Display & Stepping", style = "font-size: 1rem; font-weight: 600; margin-bottom: 8px;"),
      shiny::checkboxGroupInput(ns("show_species"), "Species to Display:",
                                choices = c("Host" = "host", "Parasite" = "parasite"),
                                selected = c("host", "parasite"),
                                inline = TRUE),
      shiny::radioButtons(ns("species_mode"), "Species Mode:",
                          choices = c("Overlay (1 Map)" = "overlay", "Separate (Adjacent Maps)" = "separate"),
                          selected = "overlay", inline = TRUE),
      shiny::div(style = "border-top: 1px solid rgba(0,0,0,0.1); margin: 6px 0;"),
      shiny::radioButtons(ns("layout"), "Layout View:",
                          choices = c("Hex Substrate Overlay" = "hex", "Faceted Substrates" = "facet"),
                          selected = "hex", inline = TRUE),
      shiny::div(style = "border-top: 1px solid rgba(0,0,0,0.1); margin: 6px 0;"),
      shiny::span("Simulation Stepping:", style = "font-weight: 600; color: #1a73e8; display: block; margin-bottom: 4px;"),
      shiny::div(
        style = "display: flex; gap: 4px; margin-bottom: 8px;",
        shiny::actionButton(ns("step1"), "+1 Step", class = "btn-sm btn-outline-primary flex-fill"),
        shiny::actionButton(ns("step10"), "+10 Steps", class = "btn-sm btn-outline-primary flex-fill"),
        shiny::actionButton(ns("step100"), "+100 Steps", class = "btn-sm btn-outline-primary flex-fill"),
        shiny::actionButton(ns("reset_sim"), "Reset", class = "btn-sm btn-outline-secondary")
      ),
      shiny::div(style = "border-top: 1px solid rgba(0,0,0,0.1); margin: 6px 0;"),
      shiny::checkboxGroupInput(ns("layers"), "Display Layers:",
                                choices = c("Substrate Boundaries" = "poly",
                                            "Hex Grid Overlay" = "hex",
                                            "Organisms" = "organisms",
                                            "Substrate Names" = "centers",
                                            "Side Numbers" = "labels"),
                                selected = c("poly", "hex", "organisms", "centers", "labels")),
      shiny::div(style = "border-top: 1px solid rgba(0,0,0,0.1); margin: 6px 0;"),
      shiny::div(
        style = "display: flex; gap: 8px;",
        shiny::numericInput(ns("width"), "Radius:", value = 10, min = 2, max = 30, step = 1),
        shiny::numericInput(ns("step_density"), "Step Density:", value = 1, min = 0.5, max = 5, step = 0.5)
      )
    )
  )
}

substrateServer <- function(id, simres, width = 10, step_density = 1) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    
    current_sim <- shiny::reactiveVal(NULL)
    
    shiny::observeEvent(simres(), {
      current_sim(simres())
    })
    
    shiny::observeEvent(input$step1, {
      sim <- current_sim()
      if (!is.null(sim)) {
        res <- future.events(sim, nstep = 1, plotit = FALSE)
        current_sim(res)
      }
    })
    
    shiny::observeEvent(input$step10, {
      sim <- current_sim()
      if (!is.null(sim)) {
        res <- future.events(sim, nstep = 10, plotit = FALSE)
        current_sim(res)
      }
    })
    
    shiny::observeEvent(input$step100, {
      sim <- current_sim()
      if (!is.null(sim)) {
        res <- future.events(sim, nstep = 100, plotit = FALSE)
        current_sim(res)
      }
    })
    
    shiny::observeEvent(input$reset_sim, {
      current_sim(simres())
    })
    
    available_species <- shiny::reactive({
      sim <- current_sim()
      if (!is.null(sim) && !is.null(sim$pop)) names(sim$pop) else NULL
    })
    
    selected_species <- shiny::reactive({
      avail <- available_species()
      if (is.null(avail)) return(NULL)
      sel <- input$show_species
      if (is.null(sel) || length(sel) == 0) avail else intersect(sel, avail)
    })
    
    sppplot <- shiny::reactive({
      spp <- selected_species()
      shiny::req(spp)
      sim <- current_sim()
      shiny::req(sim)
      
      layout_val <- if (!is.null(input$layout)) input$layout else "hex"
      mode_val <- if (!is.null(input$species_mode)) input$species_mode else "overlay"
      w_val <- if (!is.null(input$width)) input$width else width
      sd_val <- if (!is.null(input$step_density)) input$step_density else step_density
      layers_val <- if (!is.null(input$layers)) input$layers else c("poly", "hex", "organisms", "centers", "labels")
      
      sim_single <- if (inherits(sim, "ewing_discrete") && is.list(sim) && length(sim) > 0) sim[[1]] else sim
      if (inherits(sim_single, "ewing")) {
        if (mode_val == "overlay" && layout_val == "hex") {
          sub_data <- ewing_substrate(sim_single, spp, layout = layout_val, width = w_val, step_density = sd_val)
          if (!is.null(sub_data)) {
            p_obj <- ggplot_ewing_substrate(sub_data, layout = layout_val, width = w_val, step_density = sd_val, layers = layers_val)
            list(p_obj)
          } else {
            list()
          }
        } else {
          p <- lapply(spp, function(x) {
            sub_data <- ewing_substrate(sim_single, x, layout = layout_val, width = w_val, step_density = sd_val)
            if (!is.null(sub_data)) {
              p_obj <- ggplot_ewing_substrate(sub_data, layout = layout_val, width = w_val, step_density = sd_val, layers = layers_val)
              p_obj
            } else {
              NULL
            }
          })
          p[!sapply(p, is.null)]
        }
      } else {
        list()
      }
    })
    
    output$sppPlot <- shiny::renderPlot({
      plots <- sppplot()
      if (!is.null(plots) && length(plots) > 0) {
        cowplot::plot_grid(plotlist = plots, ncol = length(plots), align = "h")
      } else {
        ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::ggtitle("No active species selected to plot")
      }
    })
    
    output$substrate_plot <- shiny::renderUI({
      plots <- sppplot()
      h_px <- 500
      shiny::plotOutput(ns("sppPlot"), height = paste0(h_px, "px"))
    })
    
    # Return current simulation state for downstream composition
    current_sim
  })
}

substrateOutput <- function(id) {
  ns <- shiny::NS(id)
  shiny::uiOutput(ns("substrate_plot"))
}

# --- Source: distPlotApp.R ---
distPlotApp <- function(title = "Population Ethology") {
  ui <- bslib::page_sidebar(
    title = title,
    sidebar = bslib::sidebar(
      initParInput("init_par"),
      distPlotInput("dist_plot"),
      futureInput("future")),
    distPlotOutput("dist_plot")
  )
  server <- function(input, output, server) {
    init_par <- initParServer("init_par")
    siminit <- initServer("init", init_par)
    simres <- futureServer("future", siminit)
    distPlotServer("dist_plot", simres)
  }
  
  shiny::shinyApp(ui = ui, server = server)
}
distPlotServer <- function(id, simres, x_var = NULL, total = NULL, norm = NULL) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    
    dist_plot <- shiny::reactive({
      sim <- if (is.reactive(simres)) simres() else simres
      shiny::req(sim)
      
      tot_val <- if (is.reactive(total)) total() else if (!is.null(total)) total else if (!is.null(input$total)) input$total else TRUE
      norm_val <- if (is.reactive(norm)) norm() else if (!is.null(norm)) norm else if (!is.null(input$norm)) input$norm else TRUE
      xv_val <- if (is.reactive(x_var)) x_var() else if (!is.null(x_var)) x_var else if (!is.null(input$x_var)) input$x_var else "step"
      
      if (is.null(tot_val)) tot_val <- TRUE
      if (is.null(norm_val)) norm_val <- TRUE
      if (is.null(xv_val) || !xv_val %in% c("step", "time")) xv_val <- "step"
      
      object <- tryCatch({
        ewing_ageclass(sim, total = tot_val, normalize = norm_val)
      }, error = function(e) NULL)
      
      if (is.null(object)) return(plot_null("no simulation"))
      ggplot2::autoplot(object, x_var = xv_val)
    })
    
    output$dist_plot <- shiny::renderPlot({
      dist_plot()
    })
    
    # Return.
    dist_plot
  })
}
distPlotInput <- function(id) {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::checkboxInput(ns("norm"), "Normalize Plot", TRUE),
    shiny::checkboxInput(ns("total"), "Include Total", TRUE))
}
distPlotOutput <- function(id) {
  ns <- shiny::NS(id)
  shiny::plotOutput(ns("dist_plot"), height = "400px")
}

# --- Source: multApp.R ---
multApp <- function(title = "Population Ethology") {
  ui <- shiny::fluidPage(
    shiny::titlePanel(title),
    shiny::sidebarLayout(
      shiny::sidebarPanel(
        multInput("ewing")
      ),
      shiny::mainPanel(
        multOutput("ewing")
      )))
  
  server <- function(input, output, server) {
    multServer("ewing")
  }
  
  shiny::shinyApp(ui = ui, server = server)
}
multServer <- function(id) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    
    simres <- shiny::reactive({
      nsim <- as.integer(shiny::req(input$nsim))
      shiny::withProgress(
        message = paste('Ewing Discrete', nsim, 'Simulations ...'),
                        value = 0,
        {
          out <- as.list(seq_len(nsim))
          inc <- 1 / nsim
          for(i in seq_len(nsim)) {
            shiny::incProgress(inc)
            out[[i]] <- ewing_discrete1(
              count = as.numeric(c(input$host, input$parasite)),
              nstep = input$steps)
          }
        }
      )
    })
    
    distplot <- shiny::reactive({
      if(inherits(simres(), "ewing")) {
        ggplot2::autoplot(ewing_ageclass(simres(), total = input$total,
                                         normalize = input$norm))
      } else {
        NULL
      }
    })
    output$distPlot <- shiny::renderPlot({
      distplot()
    })
    # *** This is not right. Need to get each species name here and in `ewing_substrate`
    # species <- ewing:::getOrgFeature(simres)
    #   gives list but includes substrates.
    # can figure out what substrate goes to species with 
    # ewing:::getOrgFeature(simres, species[i], "substrate")
    # if it is NA (or "NA"), then that is a substrate.
    # So cycle through species generating plots.
    # put as much in `ewing_substrate` as possible.
    species <- shiny::reactive({
      get.organisms(datafile())$species
    })
    substrates <- shiny::reactive({
      get.organisms(datafile())$substrates
    })
    output$sppsize <- shiny::renderUI({
      shiny::req(species())
      lapply(species(), function(x) {
        shiny::sliderInput(ns(x),
                           label = paste0("Number of ", x, "s:"),
                           min = 0,
                           max = 500,
                           value = 100,
                           step = 20)
      })
    })
    sppplot <- shiny::reactive({
      shiny::req(species(), simres())
      if(inherits(simres(), "ewing")) {
        if(!is.null(simres())) {
          p <- lapply(species(), function(x) {
            p <- ggplot2::autoplot(ewing_substrate(simres(), x))
            if(inherits(p, "ggplot"))
              p <- p + ggplot2::ggtitle(paste(x, "on", substrates()[1]))
            p
          })
          if(any(unlist(purrr::map(p, is.null))))
            p <- NULL
          p
        }
      } else {
        ggplot2::ggplot()
      }
    })
    output$sppPlot <- shiny::renderPlot({
      if(!is.null(sppplot())) {
        spp <- length(species())
        cowplot::plot_grid(plotlist = sppplot(), nrow = spp)
      } else {
        ggplot2::ggplot()
      }
    })
    envdata <- shiny::reactive({
      shiny::req(simres())
      if(inherits(simres(), "ewing_discrete")) {
        ewing_envelopes(simres())
      } else {
        NULL
      }
    })
    envelopePlot <- shiny::reactive({
      shiny::req(envdata())
      nsim <- as.integer(shiny::req(input$nsim))
      conf <- (nsim >= 10) & input$conf 
      if(inherits(simres(), "ewing_discrete")) {
        ggplot_ewing_envelopes(envdata(), conf)
      } else {
        NULL
      }
    })
    output$envPlot <- shiny::renderPlot({
      envelopePlot()
    })
    output$plots <- shiny::renderUI({
      nsim <- as.integer(shiny::req(input$nsim), simres())
      if(nsim == 1) {
        shiny::req(species())
        shiny::tagList(
          shiny::plotOutput(ns("distPlot"), height = "400px"),
          shiny::plotOutput(ns("sppPlot"), height = paste0(200 * length(species()), "px")))
      } else {
        shiny::plotOutput(ns("envPlot"))
      }
    })
    
    data <- reactive({
      nsim <- as.integer(shiny::req(input$nsim))
      species <- shiny::req(input$species)
      if(nsim == 1) {
        readCount(simres())[[species]]
      } else {
        shiny::req(envdata())
        print(envdata(), species = species)
      }
    })
    params <- shiny::reactive({
      nsim <- shiny::req(input$nsim)
      paste(shiny::req(input$host), shiny::req(input$parasite),
            shiny::req(input$steps), nsim, sep = "_")
    })
    output$downloadRun <- shiny::downloadHandler(
      filename = function() {
        paste0(paste(shiny::req(input$outfile), shiny::req(input$species), params(), sep = "_"), ".csv") },
      content = function(file) {
        utils::write.csv(data(), file, row.names = FALSE)
      }
    )
    
    output$downloadPlot <- shiny::downloadHandler(
      filename = function() {
        paste0(paste(shiny::req(input$plotfile), params(), sep = "_"), ".pdf") },
      content = function(file) {
        grDevices::pdf(file, width = 9)
        nsim <- as.integer(shiny::req(input$nsim))
        if(nsim == 1) {
          print(distplot())
          for(i in species()) {
            print(sppplot()[[i]])
          }
        } else {
          print(envelopePlot())
        }
        grDevices::dev.off()
      }
    )
    
    datanames <- shiny::reactive({
      getOrgNames(datafile())
    })
    output$inputfiles <- shiny::renderUI({
      shiny::tagList(
        shiny::selectInput(ns("dataname"), "", datanames(), "organism.features"),
        DT::renderDataTable({
          getOrgDataSimple(simres(),shiny::req(input$dataname), datafile())
        }, escape = FALSE,
        options = list(scrollX = TRUE, pageLength = 10)))
    })
    
    datafile <- shiny::reactive({
      if(shiny::isTruthy(input$datafile)) {
        input$datafile$datapath
      } else {
        ""
      }
    })
    output$outs <- shiny::renderUI({
      switch(input$button,
             Plots        =  shiny::uiOutput(ns("plots")),
             "Input Data" =  shiny::uiOutput(ns("inputfiles")))
    })
    
    output$plottype <- shiny::renderUI({
      if(input$nsim == 1) {
        shiny::tagList(
          shiny::checkboxInput(ns("norm"), "Normalize Plot", TRUE),
          shiny::checkboxInput(ns("total"), "Include Total", TRUE))
      } else {
        shiny::checkboxInput(ns("conf"), "Confidence band", FALSE)
      }
    })
    output$version <- shiny::renderText({
      paste("Ewing package version ", utils::packageVersion("ewing"))
    })
  })
}
multInput <- function(id) {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::h4("Simulation Settings"),
    shiny::uiOutput(ns("sppsize")),
    shiny::sliderInput(ns("steps"),
                       label = "Simulation steps:",
                       min = 1000,
                       max = 10000,
                       value = 1000,
                       step = 500),
    shiny::radioButtons(ns("nsim"),
                        "Number of Simulations",
                        c(1,10,20,50,100,200),
                        1, inline = TRUE),
    
    shiny::fileInput(ns("datafile"), "Optional XLSX Input Data File",
                     multiple = FALSE,
                     accept = c(".xls", ".xlsx")),
    
    shiny::actionButton(ns("go"), "Start Simulation"),
    
    shiny::HTML("<hr style='height:1px;border:none;color:#333;background-color:#333;' />"),
    shiny::uiOutput(ns("plottype")),
    shiny::h4("Save Files"),
    shiny::fluidRow(
      shiny::column(6, shiny::textInput(ns("outfile"), "Species Table", "mysim")),
      shiny::column(3, shiny::selectInput(ns("species"), "", c("host", "parasite"), "host")),
      shiny::column(3, shiny::downloadButton(ns("downloadRun"), "Table"))),
    shiny::fluidRow(
      shiny::column(9, shiny::textInput(ns("plotfile"), "Plot File", "myplot")),
      shiny::column(3, shiny::downloadButton(ns("downloadPlot"), "Plots"))),
    
    shiny::HTML("<hr  style='height:1px;border:none;color:#333;background-color:#333;' />"),
    shiny::HTML("See <a href='https://github.com/byandell/ewing'>ewing package on github</a>"),
    shiny::uiOutput(ns("version"))
  )
}
multOutput <- function(id) {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::radioButtons(ns("button"), "", c("Plots", "Input Data"),
                        "Plots", inline = TRUE),
    shiny::uiOutput(ns("outs"))
  )
}

# --- Source: inputApp.R ---
inputApp <- function(title = "Input Data Explorer") {
  ui <- bslib::page_sidebar(
    title = title,
    sidebar = bslib::sidebar(
      inputAppInput("input_app")
    ),
    bslib::card(
      inputAppOutput("input_app")
    )
  )
  server <- function(input, output, session) {
    inputAppServer("input_app")
  }
  shiny::shinyApp(ui = ui, server = server)
}

discover_dataset_tables <- function(datafile = "", sim = NULL) {
  found <- character(0)
  
  # 1. Inspect datafile directory
  d_path <- if (is.character(datafile) && datafile != "") datafile else if (!is.null(sim) && !is.null(sim$datafile)) sim$datafile else ""
  
  if (d_path != "" && file.exists(d_path)) {
    if (dir.exists(d_path)) {
      files <- list.files(d_path, pattern = "\\.(txt|csv|rds)$", full.names = FALSE)
      if (length(files) > 0) {
        found <- tools::file_path_sans_ext(files)
      }
    } else if (grepl("\\.xlsx$", d_path, ignore.case = TRUE)) {
      sheets <- tryCatch(readxl::excel_sheets(d_path), error = function(e) character(0))
      if (length(sheets) > 0) found <- sheets
    }
  }
  
  # 2. Inspect sim$datasets if present
  if (!is.null(sim) && !is.null(sim$datasets)) {
    found <- unique(c(found, names(sim$datasets)))
  }
  
  # 3. Default fallback choices if nothing found
  if (length(found) == 0) {
    found <- c(
      "organism.features", "future.host", "future.parasite",
      "substrate.host", "substrate.parasite", "substrate.substrate",
      "host.parasite", "temperature.base", "temperature.par"
    )
  }
  
  unique(found)
}

inputAppInput <- function(id, choices = NULL) {
  ns <- shiny::NS(id)
  default_choices <- if (!is.null(choices)) choices else c(
    "organism.features", "future.host", "future.parasite",
    "substrate.host", "substrate.parasite", "substrate.substrate",
    "host.parasite", "temperature.base", "temperature.par"
  )
  shiny::tagList(
    shiny::selectInput(ns("dataname"), "Select Dataset Table:",
      choices = default_choices,
      selected = default_choices[1]
    )
  )
}

inputAppOutput <- function(id) {
  ns <- shiny::NS(id)
  shiny::tableOutput(ns("org_table"))
}

inputAppServer <- function(id, simres = shiny::reactiveVal(NULL), datafile = shiny::reactiveVal("")) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns

    # Dynamically update select choices based on folder / simulation contents
    shiny::observe({
      sim <- if (is.reactive(simres)) simres() else simres
      dfile <- if (is.reactive(datafile)) datafile() else datafile
      
      discovered <- discover_dataset_tables(dfile, sim)
      if (length(discovered) > 0) {
        current_sel <- input$dataname
        sel <- if (!is.null(current_sel) && current_sel %in% discovered) current_sel else discovered[1]
        shiny::updateSelectInput(session, "dataname", choices = discovered, selected = sel)
      }
    })

    output$org_table <- shiny::renderTable(
      {
        name <- input$dataname %||% "organism.features"
        sim <- if (is.reactive(simres)) simres() else simres
        dfile <- if (is.reactive(datafile)) datafile() else datafile

        res <- NULL

        # 0. Check direct file in datafile directory if points to folder
        if (is.character(dfile) && dfile != "" && dir.exists(dfile)) {
          txt_path <- file.path(dfile, paste0(name, ".txt"))
          if (file.exists(txt_path)) res <- tryCatch(utils::read.table(txt_path, header = TRUE, sep = "\t", stringsAsFactors = FALSE), error = function(e) NULL)
          if (is.null(res)) {
            csv_path <- file.path(dfile, paste0(name, ".csv"))
            if (file.exists(csv_path)) res <- tryCatch(utils::read.csv(csv_path, stringsAsFactors = FALSE), error = function(e) NULL)
          }
        }

        # 1. Check if dataset is stored in sim$datasets (e.g. injected in webR demo)
        if (is.null(res) && !is.null(sim) && !is.null(sim$datasets) && !is.null(sim$datasets[[name]])) {
          res <- sim$datasets[[name]]
        }

        # 2. Extract dynamically via getOrgDataSimple or getOrg* package routines
        if (is.null(res) && !is.null(sim) && inherits(sim, "ewing")) {
          sim_single <- if (inherits(sim, "ewing_discrete")) sim[[1]] else sim
          res <- tryCatch({
            getOrgDataSimple(sim_single, name, datafile = dfile)
          }, error = function(e) NULL)
        }

        # 3. Dynamic fallback to extracting from sim_single$org state structures
        if ((is.null(res) || !is.data.frame(res) || nrow(res) == 0) && !is.null(sim) && inherits(sim, "ewing")) {
          sim_single <- if (inherits(sim, "ewing_discrete")) sim[[1]] else sim
          res <- tryCatch({
            left <- stringr::str_remove(name, "\\..*")
            right <- stringr::str_remove(name, ".*\\.")

            if (left == "organism" && right == "features") {
              if (!is.null(sim_single$org$Feature)) as.data.frame(sim_single$org$Feature) else NULL
            } else if (left == "future") {
              if (!is.null(sim_single$org$Future[[right]])) sim_single$org$Future[[right]] else getOrgFuture(sim_single, right)
            } else if (!is.null(sim_single$org$Interact[[left]][[right]])) {
              sim_single$org$Interact[[left]][[right]]
            } else if (!is.null(sim_single$org[[left]][[right]])) {
              sim_single$org[[left]][[right]]
            } else {
              NULL
            }
          }, error = function(e) NULL)
        }

        if (is.null(res) || !is.data.frame(res) || nrow(res) == 0) {
          res <- data.frame(Info = paste("Dataset", name, "is not available in current simulation instance."))
        }

        res
      },
      striped = TRUE,
      hover = TRUE,
      bordered = TRUE
    )
  })
}

# --- Source: origEwingApp.R ---
origEwingApp <- function(title = "Population Ethology") {
  ui <- shiny::fluidPage(
    shiny::titlePanel(title),
    shiny::sidebarLayout(
      shiny::sidebarPanel(
        origEwingInput("ewing")
      ),
      shiny::mainPanel(
        origEwingOutput("ewing")
      )))
  
  server <- function(input, output, server) {
    origEwingServer("ewing")
  }
  
  shiny::shinyApp(ui = ui, server = server)
}
origEwingServer <- function(id) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    
    simres <- shiny::bindEvent(
      shiny::bindCache(
        shiny::reactive({
          nsim <- as.integer(input$nsim)
          if(nsim == 1) {
            # Ideally, would like to continue simulation. That would require
            # - feed simres() back into future.events, which requires some logic
            # - use option "append = TRUE" to append to outfile
            siminit <- init.simulation(count = as.numeric(c(input$host, input$parasite)),
                                       datafile = datafile()) # initialize simulation
            future.events(siminit, nstep = input$steps, plotit = FALSE) # simulate future events
          } else {
            shiny::withProgress(message = paste('Ewing Discrete', nsim,
                                                'Simulations ...'),
                                value = 0,
                                {
                                  out <- as.list(seq_len(nsim))
                                  inc <- 1 / nsim
                                  for(i in seq_len(nsim)) {
                                    shiny::incProgress(inc)
                                    out[[i]] <- ewing_discrete1(
                                      count = as.numeric(c(input$host, input$parasite)),
                                      nstep = input$steps)
                                  }
                                  make_ewing_discrete(out)
                                })
          }
        }),
        input$host, input$parasite, input$steps, input$nsim, input$go,
        input$datafile),
      input$go)
    
    distplot <- shiny::reactive({
      if(inherits(simres(), "ewing")) {
        ggplot2::autoplot(ewing_ageclass(simres(), total = input$total,
                                         normalize = input$norm))
      } else {
        NULL
      }
    })
    output$distPlot <- shiny::renderPlot({
      distplot()
    })
    # *** This is not right. Need to get each species name here and in `ewing_substrate`
    # species <- ewing:::getOrgFeature(simres)
    #   gives list but includes substrates.
    # can figure out what substrate goes to species with 
    # ewing:::getOrgFeature(simres, species[i], "substrate")
    # if it is NA (or "NA"), then that is a substrate.
    # So cycle through species generating plots.
    # put as much in `ewing_substrate` as possible.
    species <- shiny::reactive({
      get.organisms(datafile())$species
    })
    substrates <- shiny::reactive({
      get.organisms(datafile())$substrates
    })
    output$sppsize <- shiny::renderUI({
      shiny::req(species())
      lapply(species(), function(x) {
        shiny::sliderInput(ns(x),
                           label = paste0("Number of ", x, "s:"),
                           min = 0,
                           max = 500,
                           value = 100,
                           step = 20)
      })
    })
    sppplot <- shiny::reactive({
      shiny::req(species(), simres())
      if(inherits(simres(), "ewing")) {
        if(!is.null(simres())) {
          p <- lapply(species(), function(x) {
            p <- ggplot2::autoplot(ewing_substrate(simres(), x))
            if(inherits(p, "ggplot"))
              p <- p + ggplot2::ggtitle(paste(x, "on", substrates()[1]))
            p
          })
          if(any(unlist(purrr::map(p, is.null))))
            p <- NULL
          p
        }
      } else {
        ggplot2::ggplot()
      }
    })
    output$sppPlot <- shiny::renderPlot({
      if(!is.null(sppplot())) {
        spp <- length(species())
        cowplot::plot_grid(plotlist = sppplot(), nrow = spp)
      } else {
        ggplot2::ggplot()
      }
    })
    envdata <- shiny::reactive({
      shiny::req(simres())
      if(inherits(simres(), "ewing_discrete")) {
        ewing_envelopes(simres())
      } else {
        NULL
      }
    })
    envelopePlot <- shiny::reactive({
      shiny::req(envdata())
      nsim <- as.integer(shiny::req(input$nsim))
      conf <- (nsim >= 10) & input$conf 
      if(inherits(simres(), "ewing_discrete")) {
        ggplot_ewing_envelopes(envdata(), conf)
      } else {
        NULL
      }
    })
    output$envPlot <- shiny::renderPlot({
      envelopePlot()
    })
    output$plots <- shiny::renderUI({
      nsim <- as.integer(shiny::req(input$nsim), simres())
      if(nsim == 1) {
        shiny::req(species())
        shiny::tagList(
          shiny::plotOutput(ns("distPlot"), height = "400px"),
          shiny::plotOutput(ns("sppPlot"), height = paste0(200 * length(species()), "px")))
      } else {
        shiny::plotOutput(ns("envPlot"))
      }
    })
    
    data <- reactive({
      nsim <- as.integer(shiny::req(input$nsim))
      species <- shiny::req(input$species)
      if(nsim == 1) {
        readCount(simres())[[species]]
      } else {
        shiny::req(envdata())
        print(envdata(), species = species)
      }
    })
    params <- shiny::reactive({
      nsim <- shiny::req(input$nsim)
      paste(shiny::req(input$host), shiny::req(input$parasite),
            shiny::req(input$steps), nsim, sep = "_")
    })
    output$downloadRun <- shiny::downloadHandler(
      filename = function() {
        paste0(paste(shiny::req(input$outfile), shiny::req(input$species), params(), sep = "_"), ".csv") },
      content = function(file) {
        utils::write.csv(data(), file, row.names = FALSE)
      }
    )
    
    output$downloadPlot <- shiny::downloadHandler(
      filename = function() {
        paste0(paste(shiny::req(input$plotfile), params(), sep = "_"), ".pdf") },
      content = function(file) {
        grDevices::pdf(file, width = 9)
        nsim <- as.integer(shiny::req(input$nsim))
        if(nsim == 1) {
          print(distplot())
          for(i in species()) {
            print(sppplot()[[i]])
          }
        } else {
          print(envelopePlot())
        }
        grDevices::dev.off()
      }
    )
    
    datanames <- shiny::reactive({
      getOrgNames(datafile())
    })
    output$inputfiles <- shiny::renderUI({
      shiny::tagList(
        shiny::selectInput(ns("dataname"), "", datanames(), "organism.features"),
        DT::renderDataTable({
          getOrgDataSimple(simres(),shiny::req(input$dataname), datafile())
        }, escape = FALSE,
        options = list(scrollX = TRUE, pageLength = 10)))
    })
    
    datafile <- shiny::reactive({
      if(shiny::isTruthy(input$datafile)) {
        input$datafile$datapath
      } else {
        ""
      }
    })
    output$outs <- shiny::renderUI({
      switch(input$button,
             Plots        =  shiny::uiOutput(ns("plots")),
             "Input Data" =  shiny::uiOutput(ns("inputfiles")))
    })
    
    output$plottype <- shiny::renderUI({
      if(input$nsim == 1) {
        shiny::tagList(
          shiny::checkboxInput(ns("norm"), "Normalize Plot", TRUE),
          shiny::checkboxInput(ns("total"), "Include Total", TRUE))
      } else {
        shiny::checkboxInput(ns("conf"), "Confidence band", FALSE)
      }
    })
    output$version <- shiny::renderText({
      paste("Ewing package version ", utils::packageVersion("ewing"))
    })
  })
}
origEwingInput <- function(id) {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::h4("Simulation Settings"),
    shiny::uiOutput(ns("sppsize")),
    shiny::sliderInput(ns("steps"),
                       label = "Simulation steps:",
                       min = 1000,
                       max = 10000,
                       value = 1000,
                       step = 500),
    shiny::radioButtons(ns("nsim"),
                        "Number of Simulations",
                        c(1,10,20,50,100,200),
                        1, inline = TRUE),
    
    shiny::fileInput(ns("datafile"), "Optional XLSX Input Data File",
                     multiple = FALSE,
                     accept = c(".xls", ".xlsx")),
    
    shiny::actionButton(ns("go"), "Start Simulation"),
    
    shiny::HTML("<hr style='height:1px;border:none;color:#333;background-color:#333;' />"),
    shiny::uiOutput(ns("plottype")),
    shiny::h4("Save Files"),
    shiny::fluidRow(
      shiny::column(6, shiny::textInput(ns("outfile"), "Species Table", "mysim")),
      shiny::column(3, shiny::selectInput(ns("species"), "", c("host", "parasite"), "host")),
      shiny::column(3, shiny::downloadButton(ns("downloadRun"), "Table"))),
    shiny::fluidRow(
      shiny::column(9, shiny::textInput(ns("plotfile"), "Plot File", "myplot")),
      shiny::column(3, shiny::downloadButton(ns("downloadPlot"), "Plots"))),
    
    shiny::HTML("<hr  style='height:1px;border:none;color:#333;background-color:#333;' />"),
    shiny::HTML("See <a href='https://github.com/byandell/ewing'>ewing package on github</a>"),
    shiny::uiOutput(ns("version"))
  )
}
origEwingOutput <- function(id) {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::radioButtons(ns("button"), "", c("Plots", "Input Data"),
                        "Plots", inline = TRUE),
    shiny::uiOutput(ns("outs"))
  )
}

# --- Source: envPlotApp.R ---
envPlotApp <- function(title = "Envelope Plots") {
  ui <- bslib::page_sidebar(
    title = title,
    sidebar = bslib::sidebar(
      envPlotInput("env_plot")
    ),
    envPlotOutput("env_plot")
  )
  server <- function(input, output, server) {
    # Dummy mock context
  }
  shiny::shinyApp(ui = ui, server = server)
}

envPlotServer <- function(id, simres, nsim) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    
    envdata <- shiny::reactive({
      res <- shiny::req(simres())
      if(inherits(res, "ewing_discrete")) {
        ewing_envelopes(res)
      } else {
        NULL
      }
    })
    
    envelopePlot <- shiny::reactive({
      shiny::req(envdata())
      sims <- shiny::req(nsim())
      conf <- (sims >= 10) & input$conf 
      if(inherits(simres(), "ewing_discrete")) {
        ggplot_ewing_envelopes(envdata(), conf)
      } else {
        NULL
      }
    })
    
    output$envPlot <- shiny::renderPlot({
      envelopePlot()
    })
    
    # Return outputs for download hooks
    list(
      envdata = envdata,
      envelopePlot = envelopePlot
    )
  })
}

envPlotInput <- function(id) {
  ns <- shiny::NS(id)
  shiny::checkboxInput(ns("conf"), "Confidence band", FALSE)
}

envPlotOutput <- function(id) {
  ns <- shiny::NS(id)
  shiny::plotOutput(ns("envPlot"))
}

# --- Source: downloadApp.R ---
downloadApp <- function(title = "Download Options") {
  # Stub for testing downloadApp independently
  ui <- bslib::page_sidebar(
    title = title,
    sidebar = bslib::sidebar(
      downloadInput("download")
    ),
    "Test Server Hook"
  )
  server <- function(input, output, server) { }
  shiny::shinyApp(ui = ui, server = server)
}

downloadServer <- function(id, sim_data, distplot = shiny::reactive(NULL), sppplot = shiny::reactive(NULL), envplot = shiny::reactive(NULL)) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    
    # Internal parameters string extraction
    params <- shiny::reactive({
      nsim <- shiny::req(sim_data$nsim)
      inps <- shiny::req(sim_data$input)
      paste(inps$steps, nsim(), sep = "_") # Modified bounds logic since we dynamically extract
    })
    
    # CSV Data Generator extraction mapping exactly to original ewingApp
    data <- shiny::reactive({
      nsim <- as.integer(shiny::req(sim_data$nsim()))
      sp <- shiny::req(input$species)
      res <- shiny::req(sim_data$simres())
      if(nsim == 1) {
        readCount(res)[[sp]]
      } else {
        # For discrete states, evaluate envdata
        env_d <- shiny::req(envplot$envdata())
        print(env_d, species = sp)
      }
    })
    
    output$downloadRun <- shiny::downloadHandler(
      filename = function() {
        paste0(paste(shiny::req(input$outfile), shiny::req(input$species), params(), sep = "_"), ".csv") 
      },
      content = function(file) {
        utils::write.csv(data(), file, row.names = FALSE)
      }
    )
    
    output$downloadPlot <- shiny::downloadHandler(
      filename = function() {
        paste0(paste(shiny::req(input$plotfile), params(), sep = "_"), ".pdf") 
      },
      content = function(file) {
        grDevices::pdf(file, width = 9)
        on.exit(grDevices::dev.off(), add = TRUE)
        nsim <- as.integer(shiny::req(sim_data$nsim()))
        
        if(nsim == 1) {
          # Handle discrete plot captures natively
          print(shiny::req(distplot()))
          sp_lists <- shiny::req(sppplot())
          if (!is.null(sp_lists)) {
            for(p in sp_lists) {
              if(!is.null(p)) print(p)
            }
          }
        } else {
          print(shiny::req(envplot$envelopePlot()))
        }
      }
    )
  })
}

downloadInput <- function(id) {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::h4("Save Files"),
    shiny::fluidRow(
      shiny::column(6, shiny::textInput(ns("outfile"), "Species Table", "mysim")),
      shiny::column(3, shiny::selectInput(ns("species"), "", c("host", "parasite"), "host")),
      shiny::column(3, shiny::downloadButton(ns("downloadRun"), "Table"))),
    shiny::fluidRow(
      shiny::column(9, shiny::textInput(ns("plotfile"), "Plot File", "myplot")),
      shiny::column(3, shiny::downloadButton(ns("downloadPlot"), "Plots")))
  )
}

# --- Source: simApp.R ---
simApp <- function(title = "Population Ethology Simulation") {
  # Stub for testing simApp individually if desired
  ui <- bslib::page_sidebar(
    title = title,
    sidebar = bslib::sidebar(
      initParInput("init_par"),
      simInput("sim")
    ),
    simUI("sim"),
    initParOutput("init_par")
  )
  server <- function(input, output, server) {
    init_par <- initParServer("init_par")
    simServer("sim", init_par)
  }
  shiny::shinyApp(ui = ui, server = server)
}

simServer <- function(id, init_par, datafile = shiny::reactiveVal("")) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    
    active_sim <- shiny::reactiveVal(NULL)

    shiny::observeEvent(input$go_init, {
      nsim <- as.integer(input$nsim)
      if(nsim == 1) {
        siminit <- init.simulation(count = as.numeric(c(shiny::req(init_par$host), shiny::req(init_par$parasite))),
                                   datafile = datafile())
        # automatically advance by step_size to avoid step 0 blank plots
        new_state <- future.events(siminit, nstep = input$step_size, plotit = FALSE)
        active_sim(new_state)
      } else {
        shiny::withProgress(message = paste('Ewing Discrete', nsim, 'Simulations ...'),
                            value = 0,
                            {
                              out <- as.list(seq_len(nsim))
                              inc <- 1 / nsim
                              for(i in seq_len(nsim)) {
                                shiny::incProgress(inc)
                                out[[i]] <- ewing_discrete1(
                                  count = as.numeric(c(shiny::req(init_par$host), shiny::req(init_par$parasite))),
                                  nstep = input$steps) # Does ewing_discrete1 accept datafile? Original didn't pass it.
                              }
                              active_sim(make_ewing_discrete(out))
                            })
      }
    })

    shiny::observeEvent(input$go_step, {
      nsim <- as.integer(input$nsim)
      if(nsim == 1) {
        shiny::req(active_sim())
        new_state <- future.events(active_sim(), nstep = input$step_size, plotit = FALSE)
        active_sim(new_state)
      }
    })

    simres <- shiny::reactive({ active_sim() })
    
    # Return both the active reactive and the input bindings natively
    list(
      simres = simres,
      nsim = shiny::reactive({ as.integer(input$nsim) }),
      input = input
    )
  })
}

simInput <- function(id) {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::sliderInput(ns("steps"),
                       label = "Simulation steps:",
                       min = 1000,
                       max = 10000,
                       value = 1000,
                       step = 500),
    shiny::radioButtons(ns("nsim"),
                        "Number of Simulations",
                        c(1,10,20,50,100,200),
                        1, inline = TRUE),
    shiny::sliderInput(ns("step_size"),
                       label = "Steps per click (for nsim=1):",
                       min = 10,
                       max = 500,
                       value = 50,
                       step = 10)
  )
}

simUI <- function(id) {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::fluidRow(
      shiny::column(6, shiny::actionButton(ns("go_init"), "Init / Run")),
      shiny::conditionalPanel(
        condition = sprintf("input['%s'] > 0 && input['%s'] == '1'", ns("go_init"), ns("nsim")),
        shiny::column(6, shiny::actionButton(ns("go_step"), "Step Forward"))
      )
    )
  )
}

# --- Source: futureApp.R ---
futureApp <- function(title = "Population Ethology") {
  ui <- bslib::page_sidebar(
    title = title,
    sidebar = bslib::sidebar(
      initParInput("init_par"),
      distPlotInput("dist_plot"),
      futureInput("future")),
    bslib::navset_tab(
      bslib::nav_panel("Dist", distPlotOutput("dist_plot")),
      bslib::nav_panel("Substrate",
        bslib::card(substrateOutput("substrate_init")),
        bslib::card(substrateOutput("substrate"))),
      bslib::nav_panel("Params", futureOutput("future")))
  )
  server <- function(input, output, server) {
    init_par <- initParServer("init_par")
    siminit <- initServer("init", init_par)
    simres <- futureServer("future", siminit)
    distPlotServer("dist_plot", simres)
    substrateServer("substrate_init", siminit)
    substrateServer("substrate", simres)
  }
  
  shiny::shinyApp(ui = ui, server = server)
}
futureServer <- function(id, siminit) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    
    simres <- shiny::reactive({
      future.events(shiny::req(siminit()), nstep = shiny::req(input$steps),
                    plotit = FALSE) # simulate future events
    })

    output$summary <- shiny::renderUI({
      out <- summary_simobj(summary(shiny::req(simres())))
      shiny::HTML(out)
    })
    
    # Result.
    simres
  })
}
futureInput <- function(id) {
  ns <- shiny::NS(id)
  shiny::sliderInput(ns("steps"),
                     label = "Simulation steps:",
                     min = 1000,
                     max = 10000,
                     value = 1000,
                     step = 500)
}
futureOutput <- function(id) {
  ns <- shiny::NS(id)
  shiny::uiOutput(ns("summary"))
}

# --- Source: sysetholApp.R ---
sysetholApp <- function(title = "Systems Ethology Platform") {
  ui <- bslib::page_sidebar(
    title = title,
    sidebar = bslib::sidebar(
      width = 320,
      sysetholInput("sysethol")
    ),
    sysetholOutput("sysethol")
  )
  
  server <- function(input, output, session) {
    sysetholServer("sysethol")
  }
  
  shiny::shinyApp(ui = ui, server = server)
}

sysetholInput <- function(id) {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::div(
      style = "font-size: 0.85rem;",
      shiny::h4("Simulation Setup", style = "font-size: 0.95rem; font-weight: 600; margin-bottom: 6px;"),
      shiny::sliderInput(ns("n_host"), "Number of hosts:", min = 0, max = 500, value = 200, step = 20),
      shiny::sliderInput(ns("n_parasite"), "Number of parasites:", min = 0, max = 500, value = 100, step = 20),
      shiny::radioButtons(ns("nsim"), "Number of Simulations:", choices = c(1, 10, 20, 50, 100, 200), selected = 1, inline = TRUE),
      
      # Conditional Control: Steps per click ONLY shown when nsim == 1 (Geometric log scale choices 1..2000)
      shiny::conditionalPanel(
        condition = sprintf("input['%s'] == '1'", ns("nsim")),
        step_size_slider(ns("step_size"), "Steps per click:", selected = 50)
      ),
      
      # Conditional Control: Total Simulation steps ONLY shown when nsim > 1
      shiny::conditionalPanel(
        condition = sprintf("input['%s'] != '1'", ns("nsim")),
        shiny::sliderInput(ns("steps"), "Total Simulation steps:", min = 500, max = 5000, value = 1000, step = 500)
      ),
      
      shiny::div(
        style = "display: flex; gap: 6px; margin: 10px 0 8px 0;",
        shiny::actionButton(ns("run_engine"), "Run Engine", class = "btn-sm btn-primary flex-fill", style = "font-weight: 600;"),
        shiny::actionButton(ns("reset_engine"), "Reset", class = "btn-sm btn-outline-secondary")
      ),
      
      # Conditional Controls for Substrate Plots (shown ONLY on Substrate Plots tab)
      shiny::conditionalPanel(
        condition = sprintf("input['%s'] == 'Substrate Plots'", ns("tabset")),
        shiny::div(style = "border-top: 1px solid rgba(0,0,0,0.1); margin: 6px 0;"),
        shiny::h4("Substrate Display Options", style = "font-size: 0.9rem; font-weight: 600; margin-bottom: 4px;"),
        axisUnitInput(ns("substrate_axis")),
        substrateInput(ns("substrate"))
      ),
      
      # Conditional Controls for Age Classes (shown ONLY on Age Classes tab)
      shiny::conditionalPanel(
        condition = sprintf("input['%s'] == 'Age Classes'", ns("tabset")),
        shiny::div(style = "border-top: 1px solid rgba(0,0,0,0.1); margin: 6px 0;"),
        shiny::h4("Age Classes Display Options", style = "font-size: 0.9rem; font-weight: 600; margin-bottom: 4px;"),
        ageClassControlInput(ns("age_ctrls"))
      ),
      
      # Conditional Controls for Envelope Plots (shown ONLY on Envelope Plots tab when nsim > 1)
      shiny::conditionalPanel(
        condition = sprintf("input['%s'] == 'Envelope Plots' && input['%s'] != '1'", ns("tabset"), ns("nsim")),
        shiny::div(style = "border-top: 1px solid rgba(0,0,0,0.1); margin: 6px 0;"),
        shiny::h4("Envelope Display Options", style = "font-size: 0.9rem; font-weight: 600; margin-bottom: 4px;"),
        shiny::checkboxInput(ns("confidence"), "Confidence Band Envelope", TRUE)
      )
    )
  )
}

sysetholOutput <- function(id) {
  ns <- shiny::NS(id)
  shiny::uiOutput(ns("sysethol_tabs"))
}

sysetholServer <- function(id) {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    
    current_sim <- shiny::reactiveVal(NULL)
    sub_x_var <- axisUnitServer("substrate_axis")
    age_ctrls <- ageClassControlServer("age_ctrls")
    
    # Initialize simulation on start
    shiny::observe({
      if (is.null(current_sim())) {
        nh <- input$n_host %||% 200
        np <- input$n_parasite %||% 100
        sim <- init.simulation(count = c(nh, np))
        sz <- parse_step_size(input$step_size %||% 5)
        sim <- future.events(sim, nstep = sz, plotit = FALSE)
        current_sim(sim)
      }
    })
    
    # Run Engine button
    shiny::observeEvent(input$run_engine, {
      nsim_val <- as.numeric(input$nsim %||% 1)
      nh <- input$n_host %||% 200
      np <- input$n_parasite %||% 100
      
      withProgress(message = "Executing Simulation Engine...", value = 0.2, {
        if (nsim_val == 1) {
          # Single run mode
          sz <- parse_step_size(input$step_size %||% 50)
          sim <- current_sim()
          if (is.null(sim)) sim <- init.simulation(count = c(nh, np))
          sim <- future.events(sim, nstep = sz, plotit = FALSE)
          current_sim(sim)
        } else {
          # Multi-run discrete envelope mode
          tot_steps <- as.numeric(input$steps %||% 1000)
          sims <- ewing_discrete(nsim = nsim_val, nstep = tot_steps, count = c(nh, np), verbose = FALSE)
          current_sim(sims)
        }
        incProgress(0.8, detail = "Done")
      })
    })
    
    # Reset Engine button
    shiny::observeEvent(input$reset_engine, {
      nh <- input$n_host %||% 200
      np <- input$n_parasite %||% 100
      sim <- init.simulation(count = c(nh, np))
      current_sim(sim)
    })
    
    # Input Data App Server
    inputAppServer("input_app", simres = current_sim)
    
    # Compose Dist Plot Module for Age Classes
    distPlotServer("dist_plot", simres = current_sim, x_var = age_ctrls$x_var, total = age_ctrls$total, norm = age_ctrls$norm)
    
    # Dynamic Tabs (Envelope Plots shown ONLY when nsim > 1)
    output$sysethol_tabs <- shiny::renderUI({
      nsim_val <- as.numeric(input$nsim %||% 1)
      if (nsim_val == 1) {
        bslib::navset_tab(
          id = ns("tabset"),
          bslib::nav_panel("Substrate Plots", bslib::card(substrateOutput(ns("substrate")))),
          bslib::nav_panel("Age Classes", bslib::card(distPlotOutput(ns("dist_plot")))),
          bslib::nav_panel("Input Data", bslib::card(
            inputAppInput(ns("input_app")),
            inputAppOutput(ns("input_app"))
          ))
        )
      } else {
        bslib::navset_tab(
          id = ns("tabset"),
          bslib::nav_panel("Substrate Plots", bslib::card(substrateOutput(ns("substrate")))),
          bslib::nav_panel("Age Classes", bslib::card(distPlotOutput(ns("dist_plot")))),
          bslib::nav_panel("Envelope Plots", bslib::card(shiny::plotOutput(ns("env_plot"), height = "500px"))),
          bslib::nav_panel("Input Data", bslib::card(
            inputAppInput(ns("input_app")),
            inputAppOutput(ns("input_app"))
          ))
        )
      }
    })
    
    # Substrate Plots Tab Module
    substrateServer("substrate", simres = current_sim)
    
    # Envelope Plots Tab
    output$env_plot <- shiny::renderPlot({
      sim <- current_sim()
      conf_val <- input$confidence %||% TRUE
      if (inherits(sim, "ewing_discrete")) {
        ggplot_ewing_envelopes(sim, confidence = conf_val)
      } else if (!is.null(sim)) {
        # Single run fallback: build envelope from current sim
        env <- ewing_envelopes(sim)
        ggplot_ewing_envelopes(env, confidence = conf_val)
      } else {
        ggplot2::ggplot() + ggplot2::theme_void() + ggplot2::ggtitle("No active simulation for envelope plot")
      }
    })
    
    # Return active simulation state
    current_sim
  })
}

# --- Launch Application ---
sysetholApp()

Programmatic Application Usage

Launch the Systems Ethology Platform natively in R using exported package functions:

library(ewing)

sysetholApp()