// soil.C
// 
// Copyright 1996-2004 Per Abrahamsen and Søren Hansen
// Copyright 2000-2004 KVL.
//
// This file is part of Daisy.
// 
// Daisy is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
// 
// Daisy 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 Public License for more details.
// 
// You should have received a copy of the GNU Lesser Public License
// along with Daisy; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#define BUILD_DLL

#include "soil.h"
#include "horizon.h"
#include "geometry.h"
#include "hydraulic.h"
#include "tortuosity.h"
#include "groundwater.h"
#include "metalib.h"
#include "library.h"
#include "frame_submodel.h"
#include "mathlib.h"
#include "librarian.h"
#include "submodeler.h"
#include "log.h"
#include "check.h"
#include "vcheck.h"
#include "plf.h"
#include "treelog.h"
#include "memutils.h"
#include "mathlib.h"
#include "secondary.h"
#include "volume.h"
#include "water.h"
#include <sstream>

struct Soil::Implementation
{
  // Layers.
  struct Layer
  {
    // Content.
    const double end;
    std::auto_ptr<Horizon> horizon;

    // Simulation.
    void output (Log& log) const
    { output_derived (horizon, "horizon", log); }

    // Create and Destroy.
    static void load_syntax (Frame& frame)
    { 
      frame.declare ("end", "cm", Check::negative (), Attribute::Const,
		  "End point of this layer (a negative number).");
      frame.declare_object ("horizon", Horizon::component, 
                         "Soil properties of this layer.");
      frame.order ("end", "horizon");
    }
    Layer (const Block& al)
      : end (al.number ("end")),
	horizon (Librarian::build_item<Horizon> (al, "horizon"))
    { }
    Layer (const double end_, const double K_sat)
      : end (end_),
	horizon (Horizon::create_aquitard (K_sat))
    { }
    ~Layer ()
    { }
  };
  auto_vector<const Layer*> layers;
  const size_t original_layer_size; // Size before adding aquitard, for logging.

  // Zones.
  struct Zone
  {
    // Content.
    std::auto_ptr<Volume> volume;
    std::auto_ptr<Horizon> horizon;

    // Simulation.
    void output (Log& log) const
    { output_derived (horizon, "horizon", log); }

    // Create and Destroy.
    static void load_syntax (Frame& frame)
    { 
      frame.declare_object ("volume", Volume::component, 
                         "Volume covered by this zone.");
      frame.declare_object ("horizon", Horizon::component, 
                         "Soil properties of this zone.");
      frame.order ("volume", "horizon");
    }
    Zone (const Block& al)
      : volume (Librarian::build_item<Volume> (al, "volume")),
	horizon (Librarian::build_item<Horizon> (al, "horizon"))
    { }
    ~Zone ()
    { }
  };
  const auto_vector<const Zone*> zones;

  // Parameters
  /* const */ double MaxRootingDepth;
  const double dispersivity;
  const double dispersivity_transversal;
  const std::vector<double> border;

  // Cache.
  std::vector<double> anisotropy_edge;
  
  bool has_attribute (const symbol name, Treelog& msg) const
  { 
    bool missing = false;
    for (size_t i = 0; i < layers.size (); i++)
      if (!layers[i]->horizon->has_attribute (name))
        {
          msg.error ("Required attribute '" 
                     + name + "' is missing from the soil horizon '"
                     + layers[i]->horizon->objid + "'");
          missing = true;
        }
    for (size_t i = 0; i < zones.size (); i++)
      if (!zones[i]->horizon->has_attribute (name))
        {
          msg.error ("Required attribute '" 
                     + name + "' is missing from the soil zone '"
                     + zones[i]->horizon->objid + "'");
          missing = true;
        }
    return !missing;
  }
  
  bool has_attribute (const symbol name) const
  { 
    bool missing = false;
    for (size_t i = 0; i < layers.size (); i++)
      if (!layers[i]->horizon->has_attribute (name))
	missing = true;
    for (size_t i = 0; i < zones.size (); i++)
      if (!zones[i]->horizon->has_attribute (name))
	missing = true;
    return !missing;
  }
  
  // Create and Destroy.
  Implementation (const Block& al)
    : layers (map_submodel_const<Layer> (al, "horizons")),
      original_layer_size (layers.size ()),
      zones (map_submodel_const<Zone> (al, "zones")),
      MaxRootingDepth (al.number ("MaxRootingDepth")),
      dispersivity (al.number ("dispersivity")),
      dispersivity_transversal (al.number ("dispersivity_transversal",
					   dispersivity * 0.1)),
      border (al.number_sequence ("border"))
  { }
  ~Implementation ()
  { }
};

static DeclareSubmodel 
soil_layer_submodel (Soil::Implementation::Layer::load_syntax, "SoilLayer", "\
A location and content of a soil layer.\n\
The layers apply to the soil section not covered by the 'zones' parameter.");

static DeclareSubmodel 
soil_zone_submodel (Soil::Implementation::Zone::load_syntax, "SoilZone", "\
A location and content of a soil zone.\n\
If several zones cover the same soil, the first one listed is used.\n\
If no zones cover the soil, the 'horizons' parameter is used.\n\
\n\
With regard to the numeric discretization, the whole cell is assumed to\n\
be of the soil found in the cell center.");

size_t 
Soil::size () const
{ return horizon_.size (); }

double 
Soil::K (size_t i, double h, double h_ice, double T) const
{ 
  static struct ViscosityFactor : public PLF
  {
    ViscosityFactor ()
    {
      const double v20 = Water::viscosity (20.0);
      add ( 0.0, v20 / Water::viscosity (0.0));
      add ( 5.0, v20 / Water::viscosity (5.0));
      add (10.0, v20 / Water::viscosity (10.0));
      add (15.0, v20 / Water::viscosity (15.0));
      add (20.0, v20 / Water::viscosity (20.0));
      add (25.0, v20 / Water::viscosity (25.0));
      add (30.0, v20 / Water::viscosity (30.0));
      add (35.0, v20 / Water::viscosity (35.0));
      add (40.0, v20 / Water::viscosity (40.0));
    }
  } viscosity_factor;

  const double T_factor = viscosity_factor (T);
  const double h_water = std::min (h, h_ice);
  return horizon_[i]->K (h_water) * T_factor;
}

double 
Soil::Cw1 (size_t i, double h, double h_ice) const
{ return Theta (i, h, h_ice) - Cw2 (i, h) * h; }

double
Soil::Cw2 (size_t i, double h) const
{ 
  const double answer = horizon_[i]->hydraulic->Cw2 (h); 
  if (answer > 0.0)
    return answer;
  // We divide with this.
  return 1.0e-8;
}

double Soil::Theta (size_t i, double h, double h_ice) const
{ 
  if (h < h_ice)
    return horizon_[i]->hydraulic->Theta (h);
  else
    return horizon_[i]->hydraulic->Theta (h_ice);
}

double 
Soil::Theta_res (size_t i) const
{ return horizon_[i]->hydraulic->Theta_res; }

double 
Soil::h (size_t i, double Theta) const
{ return horizon_[i]->hydraulic->h (Theta); }

double 
Soil::M (size_t i, double h) const
{ return horizon_[i]->hydraulic->M (h); }

double 
Soil::dispersivity (size_t) const
{ return impl->dispersivity; }

double 
Soil::dispersivity_transversal (size_t c) const 
{ return impl->dispersivity_transversal; } 

void
Soil::set_porosity (size_t i, double Theta)
{ horizon_[i]->hydraulic->set_porosity (Theta); }

double              // Activation pressure for secondary domain. [cm] 
Soil::h_secondary (size_t i) const
{ return horizon_[i]->secondary_domain ().h_lim (); }

double  // Exchange rate between primary and secondary water.  [h^-1] 
Soil::alpha (size_t i) const
{ return horizon_[i]->secondary_domain ().alpha (); }

double 
Soil::tortuosity_factor (size_t i, double Theta) const
{ return horizon_[i]->tortuosity->factor (*horizon_[i]->hydraulic, Theta); }

double 
Soil::anisotropy_cell (size_t c) const
{ return horizon_[c]->anisotropy (); }

double 
Soil::anisotropy_edge (size_t e) const
{ 
  daisy_assert (impl->anisotropy_edge.size () > e);
  return impl->anisotropy_edge[e];
}

double 
Soil::dry_bulk_density (size_t i) const
{ return horizon_[i]->dry_bulk_density (); }

double 
Soil::clay (size_t i) const
{ return horizon_[i]->clay (); }

double 
Soil::texture_below (size_t i, double size) const
{ return horizon_[i]->texture_below (size); }

double 
Soil::humus (size_t i) const
{ return horizon_[i]->humus (); }

double 
Soil::humus_C (size_t i) const
{ return horizon_[i]->humus_C (); }

const std::vector<double>& 
Soil::SOM_fractions (size_t i) const
{ return horizon_[i]->SOM_fractions (); }

const std::vector<double>& 
Soil::SOM_C_per_N (size_t i) const
{ return horizon_[i]->SOM_C_per_N (); }

double
Soil::C_per_N (size_t i) const
{ return horizon_[i]->C_per_N (); }

double 
Soil::turnover_factor (size_t i) const
{ return horizon_[i]->turnover_factor (); }

double 
Soil::heat_conductivity (size_t i, double Theta, double Ice) const
{ return horizon_[i]->heat_conductivity (Theta, Ice); }

double 
Soil::heat_capacity (size_t i, double Theta, double Ice) const
{ return horizon_[i]->heat_capacity (Theta, Ice); }

bool
Soil::has_attribute (const symbol name, Treelog& msg) const
{ return impl->has_attribute (name, msg); }

bool
Soil::has_attribute (const symbol name) const
{ return impl->has_attribute (name); }

bool 
Soil::has_attribute (size_t i, const symbol name) const
{ return horizon_[i]->has_attribute (name); }

double 
Soil::get_attribute (size_t i, const symbol name) const
{ return horizon_[i]->get_attribute (name); }

symbol
Soil::get_dimension (size_t i, const symbol name) const
{ return horizon_[i]->get_dimension (name); }

void 
Soil::append_attributes (size_t i, std::set<symbol>& all) const
{ horizon_[i]->append_attributes (all); }

void
Soil::output (Log& log) const
{
  static const symbol horizons_symbol ("horizons");
  if (log.check_interior (horizons_symbol))
    {
      Log::Open open (log, horizons_symbol);
      for (size_t i = 0; i < impl->original_layer_size; i++)
	{
	  Log::Unnamed unnamed (log);
	  impl->layers[i]->output (log);
	}
    }
  static const symbol zones_symbol ("zones");
  if (log.check_interior (zones_symbol))
    {
      Log::Open open (log, zones_symbol);
      for (size_t i = 0; i < impl->zones.size (); i++)
	{
	  Log::Unnamed unnamed (log);
	  impl->zones[i]->output (log);
	}
    }
}

void 
Soil::nitrification (const size_t i,
                     const double M, const double C, 
                     const double h, const double T,
                     double& NH4, double& N2O, double& NO3) const
{ horizon_[i]->nitrification (M, C, h,  T, NH4, N2O, NO3); }

double
Soil::MaxRootingHeight () const
{
  return -impl->MaxRootingDepth;
}

double
Soil::end_of_first_horizon () const
{ 
  daisy_assert (impl->layers.size () > 0);
  return impl->layers[0]->end;
}

bool 
Soil::check (const int som_size, Geometry& geo, Treelog& err) const
{
  bool ok = true;
  if (som_size >= 0)
    {
      {
        Treelog::Open nest (err, "horizons");
        for (size_t i = 0; i < impl->layers.size (); i++)
          {
            const Horizon& horizon = *impl->layers[i]->horizon;
            Treelog::Open nest (err, horizon.objid);
            const size_t f_size = horizon.SOM_fractions ().size ();
            if (f_size > 0 && f_size != som_size)
              {
                Treelog::Open nest (err, "SOM_fractions");
                std::ostringstream tmp;
                tmp << "Need " << som_size << " fractions, got " << f_size;
                err.error (tmp.str ());
                ok = false;
              }
            const size_t n_size = horizon.SOM_C_per_N ().size ();
            if (n_size != som_size)
              {
                Treelog::Open nest (err, "SOM_C_per_N");
                std::ostringstream tmp;
                tmp << "Need " << som_size << " C/N numbers, got " << n_size;
                err.error (tmp.str ());
                ok = false;
              }
          }
      }
      {
        Treelog::Open nest (err, "zones");
        for (size_t i = 0; i < impl->zones.size (); i++)
          {
            const Horizon& horizon = *impl->zones[i]->horizon;
            Treelog::Open nest (err, horizon.objid);
            const size_t f_size = horizon.SOM_fractions ().size ();
            if (f_size > 0 && f_size != som_size)
              {
                Treelog::Open nest (err, "SOM_fractions");
                std::ostringstream tmp;
                tmp << "Need " << som_size << " fractions, got " << f_size;
                err.error (tmp.str ());
                ok = false;
              }
            const size_t n_size = horizon.SOM_C_per_N ().size ();
            if (n_size != som_size)
              {
                Treelog::Open nest (err, "SOM_C_per_N");
                std::ostringstream tmp;
                tmp << "Need " << som_size << " C/N numbers, got " << n_size;
                err.error (tmp.str ());
                ok = false;
              }
          }
      }
    }

  bool geo_ok = true;
  for (size_t i = 0; i < geo.cell_size (); i++)
    if (horizon_[i] == NULL)
      geo_ok = false;

  if (!geo_ok)
    {
      Treelog::Open nest (err, "horizons");
      err.error ("Some cells have no associated horizon");
      ok = false;
    }

  return ok;
}

bool
Soil::check_z_border (const double value, Treelog& err) const
{
  bool ok = false;

  for (size_t i = 0; i < impl->border.size (); i++)
    if (approximate (value, impl->border[i]))
      ok = true;

  if (!ok)
    {
      std::ostringstream tmp;
      tmp << "No soil border near " << value
          << " [cm], log results may be inexact";
      err.warning (tmp.str ());
    }
  return ok;
}

bool
Soil::check_x_border (const double, Treelog&) const
{ return true; }

bool
Soil::check_y_border (const double, Treelog&) const
{ return true; }

static bool
check_alist (const Metalib&, const Frame& al, Treelog& err)
{
  bool ok = true;

  const std::vector<boost::shared_ptr<const FrameSubmodel>/**/>& layers 
    = al.submodel_sequence ("horizons");

  if (layers.size () < 1U)
    {
      err.entry ("You need at least one horizon");
      ok = false;
    }
  double last = 0.0;

  for (size_t i = 0; i < layers.size (); i++)
    {
      double end = layers[i]->number ("end");
      if (end >= last)
	{
	  err.entry ("Horizon endpoints must be monotonically decreasing");
	  ok = false;
	  break;
	}
      last = end;
    }
  return ok;
}  

void
Soil::load_syntax (Frame& frame)
{ 
  frame.add_check (check_alist);
  frame.declare_submodule_sequence ("horizons", Attribute::State, "\
Layered description of the soil properties.\n\
The horizons can be overlapped by the 'zones' parameter.\n\
Some groundwater models, specifically 'pipe', may cause an extra horizon to\n\
be added below the one specified here if you do not also specify an explicit\n\
geometry.",
				 Implementation::Layer::load_syntax);
  frame.declare_submodule_sequence ("zones", Attribute::State, "\
Zones with special soil properties.\n\
This overrules the 'horizons' paramter.",
				 Implementation::Zone::load_syntax);
  frame.set_empty ("zones");
  frame.declare ("MaxRootingDepth", "cm", Check::positive (), Attribute::Const,
	      "Depth at the end of the root zone (a positive number).");
  frame.declare ("dispersivity", "cm", Check::positive (), 
	      Attribute::Const, "Dispersion length.");
  frame.set ("dispersivity", 5.0);
  frame.declare ("dispersivity_transversal", "cm", Check::non_negative (), 
	      Attribute::OptionalConst, "Transversal dispersion length.\n\
By default, this is 0.1 times the dispersivity.");
  frame.declare ("border", "cm", Check::negative (), 
              Attribute::Const, Attribute::Variable, "\
List of flux depths where a mass balance should be possible when logging.\n\
This attribute is ignored if the geometry is specified explicitly.");
  frame.set_check ("border", VCheck::decreasing ());
  std::vector<double> default_borders;
  default_borders.push_back (-100.0);
  frame.set ("border", default_borders);
}
  
Soil::Soil (const Block& al)
  : impl (new Implementation (al))
{ }

double
Soil::initialize_aquitard (const Block& top,
                           const double Z_aquitard, const double K_aquitard)
{
  const double old_end = impl->layers[impl->layers.size () - 1]->end;
  const double Z_horizon
    = (Z_aquitard > 5.0) ? floor (Z_aquitard / 3.0)	: (Z_aquitard / 3.0);
  const double new_end = old_end - Z_horizon;

  impl->layers.push_back (new Implementation::Layer (new_end, K_aquitard));

  // Return the old end of soil.
  return old_end;
}

void
Soil::initialize (const Block& block, Geometry& geo,
                  Groundwater& groundwater,
                  const int som_size)
{
  Treelog::Open nest (block.msg (), "Soil");

  // Extra aquitard layer.
  if (groundwater.is_pipe ())
    {
      // Find parameters.
      const double Z_aquitard = groundwater.Z_aquitard ();
      const double K_aquitard = groundwater.K_aquitard ();
      const double old_bottom 
        = initialize_aquitard (block, Z_aquitard, K_aquitard);
      groundwater.set_original_bottom (old_bottom);
    }
  const bool volatile_bottom =
    groundwater.bottom_type () == Groundwater::lysimeter 
    || groundwater.is_pipe (); 

  const std::vector<const Implementation::Layer*>::const_iterator begin
    = impl->layers.begin ();
  const std::vector<const Implementation::Layer*>::const_iterator end 
    = impl->layers.end ();
  daisy_assert (begin != end);
  std::vector<const Implementation::Layer*>::const_iterator layer;

  // Initialize zone horizons.
  for (int i = 0; i < impl->zones.size (); i++)
    // BUGLET: top_soil is always false.
    impl->zones[i]->horizon->initialize (false, som_size, block.msg ());

  // Initialize geometry and layer horizons.
  std::vector<double> fixed;
  {
    Treelog::Open nest (block.msg (), "Horizons");
    double last = 0.0;
    size_t next_border = 0;
    for (layer = begin; layer != end; layer++)
      {
        const double current = (*layer)->end;
	daisy_assert (current < last);

	const bool top_soil = (layer == begin);
	(*layer)->horizon->initialize (top_soil, som_size, block.msg ());

        while (next_border < impl->border.size ()
               && current < impl->border[next_border])
          {
            if (last > impl->border[next_border])
              fixed.push_back (impl->border[next_border]);
            next_border++;
          }
      
	last = current;
	fixed.push_back (last);
      }
    if (-last < impl->MaxRootingDepth)
      impl->MaxRootingDepth = -last;
  }
  geo.initialize_zplus (volatile_bottom, fixed, -impl->MaxRootingDepth, 
                        2 * impl->dispersivity, block.msg ());
  const size_t cell_size = geo.cell_size ();

  // Initialize horizons.
  horizon_.insert (horizon_.end (), cell_size, NULL);
  daisy_assert (horizon_.size () == cell_size);

  // Check zones first.
  for (size_t c = 0; c < cell_size; c++)
    {
      for (size_t i = 0; i < impl->zones.size (); i++)
        if (impl->zones[i]->volume->contain_point (geo.cell_z (c), 
                                                  geo.cell_x (c), geo.cell_y (c)))
          {
            daisy_assert (horizon_[c] == NULL);
            horizon_[c] = impl->zones[i]->horizon.get ();
            break;
          }
    }

  // Fill in missing stuff by layers.
  double last = 0.0;
  for (layer = begin; layer != end; layer++)
    {
      Horizon *const h  = (*layer)->horizon.get ();
      const double next = (*layer)->end;

      for (size_t i = 0; i < cell_size; i++)
        {
          if (horizon_[i] != NULL)
            // Already defined by a zone.
            continue;

          const double z = geo.cell_z (i);
          if (last > z && z >= next)
            { 
              daisy_assert (horizon_[i] == NULL);
              horizon_[i] = h;
            }
        }
      last = next;
    }
  for (size_t i = 0; i < cell_size; i++)
    {
      std::ostringstream tmp;
      tmp << "cell[" << i << "] of " << cell_size
          << " z = " << geo.cell_z (i) << ", last = " << last;
      Treelog::Open nest (block.msg (), tmp.str ());
      daisy_assert (horizon_[i] != NULL);
    }

  // anisotropy_edge.
  const size_t edge_size = geo.edge_size ();
  for (size_t e = 0; e < edge_size; e++)
    {
      const int from = geo.edge_from (e);
      const int to = geo.edge_to (e);
  
      // External edges.
      if (!geo.cell_is_internal (from))
        {
          daisy_assert (geo.cell_is_internal (to));
          impl->anisotropy_edge.push_back (anisotropy_cell (to));
          continue;
        }
      if (!geo.cell_is_internal (to))
        {
          daisy_assert (geo.cell_is_internal (from));
          impl->anisotropy_edge.push_back (anisotropy_cell (from));
          continue;
        }

      // Internal edges.
      const double sin_angle = geo.edge_sin_angle (e);
      const double cos_angle = geo.edge_cos_angle (e);
      const double a_from = anisotropy_cell (from);
      const double a_to = anisotropy_cell (to);

      // Arithmetic average.  Because we don't know.
      const double factor = (a_from + a_to) / 2.0;

      // Geometric average.  Because it is a geometric problem.
      const double aniso = sqrt (sqr (sin_angle) + sqr (factor * cos_angle));
      impl->anisotropy_edge.push_back (aniso);
    }
  daisy_assert (impl->anisotropy_edge.size () == edge_size);
}

Soil::~Soil ()
{ }

static DeclareSubmodel 
soil_submodel (Soil::load_syntax, "Soil", "\
The soil submodel provides the numeric and physical properties of the soil.");

// soil.C ends here.
