//***************************************************************************************************************************************************
//
// Copyright (C) 2020 Selim Temizer.
//
// This file is part of COVID-19.
//
// COVID-19 is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
//
// COVID-19 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License along with COVID-19. If not, see <http://www.gnu.org/licenses/>.
//
//***************************************************************************************************************************************************




//***************************************************************************************************************************************************

import java.util.List                      ;
import javax.swing.JFrame                  ;
import javax.swing.JTable                  ;
import javax.swing.JLabel                  ;
import javax.swing.ImageIcon               ;
import javax.swing.SwingUtilities          ;
import javax.swing.border.Border           ;
import javax.swing.border.EmptyBorder      ;
import javax.swing.border.CompoundBorder   ;
import javax.swing.table.TableModel        ;
import javax.swing.table.DefaultTableModel ;
import javax.swing.table.TableCellRenderer ;
import org.math.plot.Plot2DPanel           ;
import org.math.plot.plots.LinePlot        ;

//***************************************************************************************************************************************************




//***************************************************************************************************************************************************

public class Utilities
{
  //=================================================================================================================================================

  public static ImageIcon getRandomVirusIcon ()
  {
    int       iconNo = 1 + (int) ( Math.random() * 5.0 )                                                      ;
    ImageIcon icon   = new ImageIcon( Utilities.class.getResource( "/media/CoronaVirus" + iconNo + ".png" ) ) ;

    return icon ;
  }

  //=================================================================================================================================================

  public static boolean plot
  (
    List<CountryTimeSeries> countries                    ,
    TimeSeries              timeSeries                   ,
    Demography              demography                   ,
    boolean                 includeConfirmed             ,
    boolean                 includeDeaths                ,
    boolean                 includeRecovered             ,
    int                     numberOfTimesToDifferentiate ,  // Zero or positive
    boolean                 setMinimumStartCount         ,
    double                  minimumStartCount            ,
    boolean                 addDots                      ,
    boolean                 positiveValues               ,
    boolean                 normalize                    ,
    double                  normalizeValue               ,
    boolean                 normalizeByPeople            ,
    boolean                 normalizeByArea              ,
    ImageIcon               windowIcon                   ,  // Can be null (for random virus icon            )
    String                  windowTitle                  ,  // Can be null (will be assigned by this function)
    int                     windowWidth                  ,  // Can be 0    (together with windowHeight       )
    int                     windowHeight                 ,  // Can be 0    (together with windowWidth        )
    String                  xLabel                       ,  // Can be null (will be assigned by this function)
    String                  yLabel                          // Can be null (will be assigned by this function)
  )
  {
    //-----------------------------------------------------------------------------------------------------------------------------------------------

    if ( countries == null   )  { return false ; }
    if ( countries.isEmpty() )  { return false ; }

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    int numCountries       = countries.size()                    ;
    int numDays            = countries.get( 0 ).confirmed.length ;
    int numPlotsPerCountry = 0                                   ;

    if ( includeConfirmed )  { numPlotsPerCountry++ ; }
    if ( includeDeaths    )  { numPlotsPerCountry++ ; }
    if ( includeRecovered )  { numPlotsPerCountry++ ; }

    if ( numPlotsPerCountry == 0 )  { return false ; }

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    double []   x           = new Series( 0 , numDays - 1 ).get()               ;
    double [][] ys          = new double[ numCountries * numPlotsPerCountry ][] ;
    String []   yNames      = new String[ numCountries * numPlotsPerCountry ]   ;
    int         ysIndex     = 0                                                 ;
    int         yNamesIndex = 0                                                 ;

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    for ( CountryTimeSeries cts : countries )
    {
      //---------------------------------------------------------------------------------------------------------------------------------------------

      CountryDemography cd = demography.getCountry( cts.name ) ;

      //---------------------------------------------------------------------------------------------------------------------------------------------
      // Get raw time-series data

      Series sConfirmed = includeConfirmed ? new Series( cts.confirmed ) : new Series( 0 , 1 ) ;
      Series sDeaths    = includeDeaths    ? new Series( cts.deaths    ) : new Series( 0 , 1 ) ;
      Series sRecovered = includeRecovered ? new Series( cts.recovered ) : new Series( 0 , 1 ) ;

      //---------------------------------------------------------------------------------------------------------------------------------------------
      // Normalize time-series data

      if ( normalize )
      {
        if ( normalizeByPeople )
        {
          if ( includeConfirmed )  { sConfirmed.scale( normalizeValue / cd.population ) ; }
          if ( includeDeaths    )  { sDeaths   .scale( normalizeValue / cd.population ) ; }
          if ( includeRecovered )  { sRecovered.scale( normalizeValue / cd.population ) ; }
        }
        else if ( normalizeByArea )
        {
          if ( includeConfirmed )  { sConfirmed.scale( normalizeValue / cd.landArea   ) ; }
          if ( includeDeaths    )  { sDeaths   .scale( normalizeValue / cd.landArea   ) ; }
          if ( includeRecovered )  { sRecovered.scale( normalizeValue / cd.landArea   ) ; }
        }
      }

      //---------------------------------------------------------------------------------------------------------------------------------------------
      // Start with minimum desired count

      if ( setMinimumStartCount )
      {
        if ( includeConfirmed )  { sConfirmed.leftRemoveValuesLessThan( minimumStartCount ) ; }
        if ( includeDeaths    )  { sDeaths   .leftRemoveValuesLessThan( minimumStartCount ) ; }
        if ( includeRecovered )  { sRecovered.leftRemoveValuesLessThan( minimumStartCount ) ; }
      }

      //---------------------------------------------------------------------------------------------------------------------------------------------
      // Differentiate

      if ( includeConfirmed )  { sConfirmed.differentiate( numberOfTimesToDifferentiate ) ; }
      if ( includeDeaths    )  { sDeaths   .differentiate( numberOfTimesToDifferentiate ) ; }
      if ( includeRecovered )  { sRecovered.differentiate( numberOfTimesToDifferentiate ) ; }

      //---------------------------------------------------------------------------------------------------------------------------------------------
      // Set minimum to 1.0

      if ( positiveValues )
      {
        if ( includeConfirmed )  { sConfirmed.setMinimum( 1.0 ) ; }
        if ( includeDeaths    )  { sDeaths   .setMinimum( 1.0 ) ; }
        if ( includeRecovered )  { sRecovered.setMinimum( 1.0 ) ; }
      }

      //---------------------------------------------------------------------------------------------------------------------------------------------
      // Add time-series to be plotted

      if ( includeConfirmed )  { ys[ ysIndex++ ] = sConfirmed.get() ; }
      if ( includeDeaths    )  { ys[ ysIndex++ ] = sDeaths   .get() ; }
      if ( includeRecovered )  { ys[ ysIndex++ ] = sRecovered.get() ; }

      //---------------------------------------------------------------------------------------------------------------------------------------------
      // Add time-series labels to be plotted

      if ( numCountries == 1 )
      {
        if ( includeConfirmed )  { yNames[ yNamesIndex++ ] = "Confirmed" ; }
        if ( includeDeaths    )  { yNames[ yNamesIndex++ ] = "Deaths"    ; }
        if ( includeRecovered )  { yNames[ yNamesIndex++ ] = "Recovered" ; }
      }
      else if ( numCountries > 1 )
      {
        if ( includeConfirmed )  { yNames[ yNamesIndex++ ] = cts.name + " Confirmed" ; }
        if ( includeDeaths    )  { yNames[ yNamesIndex++ ] = cts.name + " Deaths"    ; }
        if ( includeRecovered )  { yNames[ yNamesIndex++ ] = cts.name + " Recovered" ; }
      }

      //---------------------------------------------------------------------------------------------------------------------------------------------
    }

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    if ( windowIcon == null )  { windowIcon = getRandomVirusIcon() ; }

    if ( windowTitle == null )
    {
      if      ( numCountries == 1 )  { windowTitle = countries.get( 0 ).name + " : COVID-19" ; }
      else if ( numCountries >  1 )  { windowTitle =                              "COVID-19" ; }

      if ( ! setMinimumStartCount )  { windowTitle += " (Since 01/22/2020)" ; }
    }

    if ( xLabel == null )  { xLabel = "Days" ; }

    if ( yLabel == null )
    {
      yLabel = "Count" ;

      if ( numberOfTimesToDifferentiate > 0 )  { yLabel += " " ;  for ( int i = 0 ; i < numberOfTimesToDifferentiate ; i++ )  { yLabel += "'" ; } }

      if ( normalize )
      {
        if      ( normalizeByPeople )  { yLabel += String.format( " [Per %,d people]" , (int) normalizeValue ) ; }
        else if ( normalizeByArea   )  { yLabel += String.format( " [Per %,d sq km]"  , (int) normalizeValue ) ; }
      }

      if ( setMinimumStartCount )  { yLabel += String.format( " [>=%.2f]" , minimumStartCount ) ; }
      if ( positiveValues       )  { yLabel += " [>=1]"                                         ; }
    }

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    linePlot(
      /* ImageIcon   windowIcon   */ windowIcon   ,
      /* String      windowTitle  */ windowTitle  ,
      /* int         windowWidth  */ windowWidth  ,
      /* int         windowHeight */ windowHeight ,
      /* String      xLabel       */ xLabel       ,
      /* String      yLabel       */ yLabel       ,
      /* boolean     drawDots     */ addDots      ,
      /* double []   x            */ x            ,
      /* double [][] ys           */ ys           ,
      /* String []   yNames       */ yNames       ) ;

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    return true ;

    //-----------------------------------------------------------------------------------------------------------------------------------------------
  }

  //=================================================================================================================================================

  public static boolean plotRe
  (
    List<CountryTimeSeries> countries           ,
    int                     movingAverageLength ,
    boolean                 addDots             ,
    boolean                 positiveValues      ,
    ImageIcon               windowIcon          ,  // Can be null (for random virus icon            )
    String                  windowTitle         ,  // Can be null (will be assigned by this function)
    int                     windowWidth         ,  // Can be 0    (together with windowHeight       )
    int                     windowHeight        ,  // Can be 0    (together with windowWidth        )
    String                  xLabel              ,  // Can be null (will be assigned by this function)
    String                  yLabel                 // Can be null (will be assigned by this function)
  )
  {
    //-----------------------------------------------------------------------------------------------------------------------------------------------

    if ( countries == null   )  { return false ; }
    if ( countries.isEmpty() )  { return false ; }

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    int numCountries = countries.size()                    ;
    int numDays      = countries.get( 0 ).confirmed.length ;

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    double []   x           = new Series( 0 , numDays - 1 ).get() ;
    double [][] ys          = new double[ numCountries ][]        ;
    String []   yNames      = new String[ numCountries ]          ;
    int         ysIndex     = 0                                   ;
    int         yNamesIndex = 0                                   ;

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    for ( CountryTimeSeries cts : countries )
    {
      //---------------------------------------------------------------------------------------------------------------------------------------------

      Series sRe = getReSeries( cts ).setMaximum( 100 ).movingAverage( movingAverageLength ) ;

      //---------------------------------------------------------------------------------------------------------------------------------------------
      // Set minimum to 1.0

      if ( positiveValues )  { sRe.setMinimum( 1.0 ) ; }

      //---------------------------------------------------------------------------------------------------------------------------------------------
      // Add time-series to be plotted

      ys[ ysIndex++ ] = sRe.get() ;

      //---------------------------------------------------------------------------------------------------------------------------------------------
      // Add time-series labels to be plotted

      if      ( numCountries == 1 )  { yNames[ yNamesIndex++ ] = cts.name /* "Re"    */ ; }
      else if ( numCountries >  1 )  { yNames[ yNamesIndex++ ] = cts.name /* + " Re" */ ; }

      //---------------------------------------------------------------------------------------------------------------------------------------------
    }

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    if ( windowIcon == null )  { windowIcon = getRandomVirusIcon() ; }

    if ( windowTitle == null )
    {
      if      ( numCountries == 1 )  { windowTitle = countries.get( 0 ).name + " : COVID-19" ; }
      else if ( numCountries >  1 )  { windowTitle =                              "COVID-19" ; }

      windowTitle += " (Since 01/22/2020)" ;
    }

    if ( xLabel == null )  { xLabel = "Days" ; }

    if ( yLabel == null )
    {
      yLabel = "Re" ;

      if ( movingAverageLength >= 2 )  { yLabel += String.format( " [MA %d]" , movingAverageLength ) ; }
      if ( positiveValues           )  { yLabel += " [>=1]"                                          ; }
    }

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    linePlot(
      /* ImageIcon   windowIcon   */ windowIcon   ,
      /* String      windowTitle  */ windowTitle  ,
      /* int         windowWidth  */ windowWidth  ,
      /* int         windowHeight */ windowHeight ,
      /* String      xLabel       */ xLabel       ,
      /* String      yLabel       */ yLabel       ,
      /* boolean     drawDots     */ addDots      ,
      /* double []   x            */ x            ,
      /* double [][] ys           */ ys           ,
      /* String []   yNames       */ yNames       ) ;

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    return true ;

    //-----------------------------------------------------------------------------------------------------------------------------------------------
  }

  //=================================================================================================================================================

  public static void linePlot
  (
    ImageIcon   windowIcon   ,  // Can be null (for default Java icon)
    String      windowTitle  ,
    int         windowWidth  ,  // Can be 0 (together with windowHeight)
    int         windowHeight ,  // Can be 0 (together with windowWidth )
    String      xLabel       ,
    String      yLabel       ,
    boolean     drawDots     ,
    double []   x            ,
    double [][] ys           ,
    String []   yNames
  )
  {
    //-----------------------------------------------------------------------------------------------------------------------------------------------

    Plot2DPanel plot = new Plot2DPanel() ;

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    for ( int i = 0 ; i < ys.length ; i ++ )
    {
      int plotIndex = plot.addLinePlot( yNames[ i ] , x , ys[ i ] ) ;

      if ( drawDots )  { ( (LinePlot) ( plot.getPlot( plotIndex ) ) ).draw_dot = true ; }
    }

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    plot.addLegend   ( "SOUTH"    ) ;
    plot.setAxisLabel( 0 , xLabel ) ;
    plot.setAxisLabel( 1 , yLabel ) ;

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    // From: https://stackoverflow.com/questions/17925609/how-to-add-padding-to-a-jpanel-with-a-border/17925693

    Border border = plot.getBorder()                  ;
    Border margin = new EmptyBorder( 0 , 5 , 10 , 5 ) ;

    plot.setBorder( new CompoundBorder( border , margin ) ) ;

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    JFrame window = new JFrame( windowTitle ) ;

    window.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ) ;
    window.setLocationByPlatform   ( true                    ) ;
    window.add                     ( plot                    ) ;

    if   ( ( windowWidth > 0 ) && ( windowHeight > 0 ) )  { window.setSize( windowWidth , windowHeight ) ; }
    else                                                  { window.pack()                                ; }

    if ( windowIcon != null )  { window.setIconImage( windowIcon.getImage() ) ; }

    SwingUtilities.invokeLater( new Runnable()  { @Override public void run ()  { window.setVisible( true ) ; } } ) ;

    //-----------------------------------------------------------------------------------------------------------------------------------------------
  }

  //=================================================================================================================================================

  public static void setTableHeaderHorizontalAlignment ( JTable table , int alignment /* Example: JLabel.CENTER */ )
  {
    // From: https://itjbg.wordpress.com/2012/05/28/set-center-alignment-of-jtable-header-in-java/

    TableCellRenderer headerRenderer = table.getTableHeader().getDefaultRenderer() ;
    JLabel            headerLabel    = (JLabel) headerRenderer                     ;

    headerLabel.setHorizontalAlignment( alignment ) ;
  }

  //=================================================================================================================================================

  public static double getRe ( CountryTimeSeries cts , int n )
  {
    try  // Using a very simple SIR (Susceptible, Infectious, Recovered) model for the most recent "n" day
    {
      int    lastIndex        =   cts.confirmed.length - 1                                        ;
      double susceptiblePrime = - cts.confirmed[ lastIndex     ] + cts.confirmed[ lastIndex - n ] ;
      double recoveredT       =   cts.recovered[ lastIndex     ] + cts.deaths   [ lastIndex     ] ;
      double recoveredTMinusN =   cts.recovered[ lastIndex - n ] + cts.deaths   [ lastIndex - n ] ;
      double recoveredPrime   =   recoveredT - recoveredTMinusN                                   ;
      double Re               = - susceptiblePrime / recoveredPrime                               ;

      if ( Double.isNaN( Re ) )  { return -0.0 ; }
      if ( Re < -1.0          )  { return -1.0 ; }

      return Re ;
    }
    catch ( Exception e )  { return -0.0 ; }
  }

  //=================================================================================================================================================

  public static double getRe
  (
    int confirmed         ,
    int recovered         ,
    int deaths            ,
    int confirmedPrevious ,
    int recoveredPrevious ,
    int deathsPrevious
  )
  {
    try  // Using a very simple SIR (Susceptible, Infectious, Recovered) model
    {
      double susceptiblePrime = - confirmed + confirmedPrevious                               ;
      double recoveredPrime   =   (recovered + deaths) - (recoveredPrevious + deathsPrevious) ;
      double Re               = - susceptiblePrime / recoveredPrime                           ;

      if ( Double.isNaN( Re ) )  { return -0.0 ; }
      if ( Re < -1.0          )  { return -1.0 ; }

      return Re ;
    }
    catch ( Exception e )  { return -0.0 ; }
  }

  //=================================================================================================================================================

  public static Series getReSeries ( CountryTimeSeries cts )
  {
    int    length = cts.confirmed.length ;
    double Re []  = new double [ length ] ;

    Re[0] = 0.0 ;

    for ( int i = 1 ; i < length ; i++ )
    {
      Re[i] = getRe ( /* int confirmed         */ cts.confirmed[i  ] ,
                      /* int recovered         */ cts.recovered[i  ] ,
                      /* int deaths            */ cts.deaths   [i  ] ,
                      /* int confirmedPrevious */ cts.confirmed[i-1] ,
                      /* int recoveredPrevious */ cts.recovered[i-1] ,
                      /* int deathsPrevious    */ cts.deaths   [i-1] ) ;
    }

    return new Series( Re ) ;
  }

  //=================================================================================================================================================

  public static TableModel getTableModel
  (
    DailyReport report     , /* Main set                    */
    Demography  demography , /* Different (super) set       */
    TimeSeries  timeSeries   /* May be a subset of main set */
  )
  {
    String headers [] = {
      /*  0 */ "<html><b>"                               + "Country"                                  + "</b>" ,

      /*  1 */ "<html><b>"                               + "Confirmed"                                + "</b>" ,
      /*  2 */ "<html><b>"                               + "Active"                                   + "</b>" ,
      /*  3 */ "<html><b>"                               + "Recovered"                                + "</b>" ,
      /*  4 */ "<html><b>"                               + "Deaths"                                   + "</b>" ,

      /*  5 */ "<html><b style='color:rgb(255,0,0);'>"   + "A<small><sub>C</sub></small>"             + "</b>" ,
      /*  6 */ "<html><b style='color:rgb(255,0,0);'>"   + "R<small><sub>C</sub></small>"             + "</b>" ,
      /*  7 */ "<html><b style='color:rgb(255,0,0);'>"   + "D<small><sub>C</sub></small>"             + "</b>" ,

      /*  8 */ "<html><b style='color:rgb(0,200,0);'>"   + "C<small><sub>P</sub></small>"             + "</b>" ,
      /*  9 */ "<html><b style='color:rgb(0,200,0);'>"   + "A<small><sub>P</sub></small>"             + "</b>" ,
      /* 10 */ "<html><b style='color:rgb(0,200,0);'>"   + "R<small><sub>P</sub></small>"             + "</b>" ,
      /* 11 */ "<html><b style='color:rgb(0,200,0);'>"   + "D<small><sub>P</sub></small>"             + "</b>" ,

      /* 12 */ "<html><b style='color:rgb(0,100,255);'>" + "C<small><sub>1M</sub></small>"            + "</b>" ,
      /* 13 */ "<html><b style='color:rgb(0,100,255);'>" + "A<small><sub>1M</sub></small>"            + "</b>" ,
      /* 14 */ "<html><b style='color:rgb(0,100,255);'>" + "R<small><sub>1M</sub></small>"            + "</b>" ,
      /* 15 */ "<html><b style='color:rgb(0,100,255);'>" + "D<small><sub>1M</sub></small>"            + "</b>" ,

      /* 16 */ "<html><b>"                               + "R<small><sub>e</sub><sup>1</sup</small>"  + "</b>" ,
      /* 17 */ "<html><b>"                               + "R<small><sub>e</sub><sup>7</sup</small>"  + "</b>" ,
      /* 18 */ "<html><b>"                               + "R<small><sub>e</sub><sup>14</sup</small>" + "</b>" ,

      /* 19 */ "<html><b>"                               + "r<small><sub>C</sub></small>"             + "</b>" ,
      /* 20 */ "<html><b>"                               + "r<small><sub>R</sub></small>"             + "</b>" ,
      /* 21 */ "<html><b>"                               + "r<small><sub>D</sub></small>"             + "</b>" ,

      /* 22 */ "<html><b>"                               + "&Atilde;ge"                               + "</b>" ,
      /* 23 */ "<html><b>"                               + "Population"                               + "</b>" ,
      /* 24 */ "<html><b>"                               + "Share"                                    + "</b>" ,
      /* 25 */ "<html><b>"                               + "Area (Km<small><sup>2 </sup></small>)"    + "</b>" ,
      /* 26 */ "<html><b>"                               + "Share"                                    + "</b>" ,
      /* 27 */ "<html><b>"                               + "P/Km<small><sup>2 </sup></small>"         + "</b>" } ;

    Object rows [][] = new Object[ report.countries.size() ][ headers.length ] ;

    for ( int row = 0 ; row < report.countries.size() ; row++ )
    {
      CountryDailyReport country           = report.countries.get ( row          ) ;
      CountryDemography  countryDemography = demography.getCountry( country.name ) ;
      CountryTimeSeries  countryTimeSeries = timeSeries.getCountry( country.name ) ;

      rows[row][ 0] = country.name         ;
      rows[row][ 1] = country.confirmed    ;
      rows[row][ 2] = country.active       ;
      rows[row][ 3] = country.recovered    ;
      rows[row][ 4] = country.deaths       ;

      rows[row][ 5] = country.activeRate   ;
      rows[row][ 6] = country.recoveryRate ;
      rows[row][ 7] = country.deathRate    ;

      rows[row][ 8] = 0 ;
      rows[row][ 9] = 0 ;
      rows[row][10] = 0 ;
      rows[row][11] = 0 ;
      rows[row][12] = 0 ;
      rows[row][13] = 0 ;
      rows[row][14] = 0 ;
      rows[row][15] = 0 ;

      if ( countryDemography != null )
      {
        rows[row][ 8] = (double) 100.0     * country.confirmed / countryDemography.population ;
        rows[row][ 9] = (double) 100.0     * country.active    / countryDemography.population ;
        rows[row][10] = (double) 100.0     * country.recovered / countryDemography.population ;
        rows[row][11] = (double) 100.0     * country.deaths    / countryDemography.population ;

        rows[row][12] = (double) 1000000.0 * country.confirmed / countryDemography.population ;
        rows[row][13] = (double) 1000000.0 * country.active    / countryDemography.population ;
        rows[row][14] = (double) 1000000.0 * country.recovered / countryDemography.population ;
        rows[row][15] = (double) 1000000.0 * country.deaths    / countryDemography.population ;
      }

      rows[row][16] = 0 ;
      rows[row][17] = 0 ;
      rows[row][18] = 0 ;
      rows[row][19] = 0 ;
      rows[row][20] = 0 ;
      rows[row][21] = 0 ;

      if ( countryTimeSeries != null )
      {
        rows[row][16] = getRe     ( countryTimeSeries ,  1      )                                ;
        rows[row][17] = getRe     ( countryTimeSeries ,  7      )                                ;
        rows[row][18] = getRe     ( countryTimeSeries , 14      )                                ;
        rows[row][19] = new Series( countryTimeSeries.confirmed ).getExponentialGrowthModel()[3] ;
        rows[row][20] = new Series( countryTimeSeries.recovered ).getExponentialGrowthModel()[3] ;
        rows[row][21] = new Series( countryTimeSeries.deaths    ).getExponentialGrowthModel()[3] ;
      }

      rows[row][22] = 0 ;
      rows[row][23] = 0 ;
      rows[row][24] = 0 ;
      rows[row][25] = 0 ;
      rows[row][26] = 0 ;
      rows[row][27] = 0 ;

      if ( countryDemography != null )
      {
        rows[row][22] =                  countryDemography.medianAge                           ;
        rows[row][23] =                  countryDemography.population                          ;
        rows[row][24] =                  countryDemography.worldShare                          ;
        rows[row][25] =                  countryDemography.landArea                            ;
        rows[row][26] = (double) 100.0 * countryDemography.landArea / demography.totalLandArea ;
        rows[row][27] =                  countryDemography.density                             ;
      }
    }

    DefaultTableModel model = new DefaultTableModel( rows , headers )
    {
      private static final long serialVersionUID = 1L ;  // Added to prevent warning

      // Returning the Class of each column will allow different renderers to be used based on Class
      @Override public Class getColumnClass ( int column )  { return getValueAt(0, column).getClass() ; }

      @Override public boolean isCellEditable ( int rowIndex , int colIndex )  { return false ; }
    } ;

    return model ;
  }

  //=================================================================================================================================================
}

//***************************************************************************************************************************************************