/*
    Copyright (C) 1998  Dennis Roddeman
    email: dennis.roddeman@uibk.ac.at

    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 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software Foundation 
    59 Temple Place, Suite 330, Boston, MA, 02111-1307, USA
*/

#include "tochnog.h"

#define ITERATION_WISHED 4
#define EPS_DTIME 1.e-4

void top( void )

{
  long int inod=0, max=0, max_node=0, max_control=0, 
    start_control=0, length=0, iinc=0, ninc=0, 
    timestep_iterations=1, swit=0, swit_timestep=0, idat=0, 
    inverse_iterations=0, inverse_iter=0, ipar=0, npar=0, 
    ipar_i=0, ipar_n=0, max_ipar=0, max_ipar_i=0, icontrol=0, iteration=0, 
    number_processors=1, number_iterations=0, control_solver=0, 
    iteration_min=0, iteration_max=0,
    use_control_timestep_iterations_automatic=0, 
    converged_once=0, time_at_start=0, ldum=0, idum[1];
  double time_increment=0., time_current=0., dtime_initial=0.,
    dtime=0., time_old=0., time_new=0., ratio_criterium=0., 
    maximum_timestep=0., post_node_rhside_ratio=0.,
    ddum[1], control_timestep_iterations_automatic[2], 
    dwork[MUKNWN], *timestep=NULL;

  set_environment();

  swit = set_swit(-1,-1,"top" );
  if ( swit ) pri( "In routine TOP" );

  if ( swit ) {
    pri( "ndim", ndim );
    pri( "npuknwn", npuknwn );
    pri( "nuknwn", nuknwn );
  }

  time_at_start = (long int) time(NULL);
  length=1; db( TIME_AT_START, 0, &time_at_start, ddum, length, VERSION_NORMAL, PUT );

  db( OPTIONS_PROCESSORS, 0, &number_processors, ddum, 
    length, VERSION_NORMAL, GET_IF_EXISTS );
  if ( number_processors<1 ) db_error( OPTIONS_PROCESSORS, 0 );
  if ( number_processors>MTHREAD ) number_processors = MTHREAD;
  if ( db_max_index( GROUP_USER_UMAT, max, VERSION_NORMAL, GET ) >=0 )
    number_processors = 1;
  length=1; db( OPTIONS_PROCESSORS, 0, &number_processors, ddum, 
    length, VERSION_NORMAL, PUT );

  parallel_sys_initialize();

    // initialize node_dof, if not specified
  if ( nuknwn>0 ) {
    array_set( dwork, 0., nuknwn );
    db_max_index( NODE, max_node, VERSION_NORMAL, GET );
    for ( inod=0; inod<=max_node; inod++ ) {
      if ( db_active_index( NODE, inod, VERSION_NORMAL ) && 
           !db_active_index( NODE_DOF, inod, VERSION_NORMAL ) )
        db( NODE_DOF, inod, idum, dwork, nuknwn, VERSION_NORMAL, PUT );
    }
  }

    // initialize times, if not specified
  if ( !db_active_index( TIME_CURRENT, 0, VERSION_NORMAL ) ) {
    length = 1;
    db( TIME_CURRENT, 0, idum, &time_current, length, VERSION_NORMAL, PUT );
  }

    // determine the highest index of timestep, print, etc..
  for ( idat=0; idat<MDAT; idat++ ) {
    if ( db_data_class(idat)==CONTROL ) {
      db_highest_index( idat, max, VERSION_NORMAL );
      if ( max>max_control ) max_control = max;
    }
  }

    // number of inverse parameters
  db( INVERSE_ITERATIONS, 0, &inverse_iterations, ddum, ldum, 
    VERSION_NORMAL, GET_IF_EXISTS );
  db_max_index( INVERSE_PARAMETER, max, VERSION_NORMAL, GET ); 
  if ( max>=0 ) {
    ipar_n = 3;
    for ( ipar=0; ipar<=max; ipar++ ) {
      if ( db_active_index( INVERSE_PARAMETER, ipar, VERSION_NORMAL ) ) 
        npar = ipar + 1;
    }
  }
  max_ipar = scalar_imax(1,npar);
  max_ipar_i = scalar_imax(1,ipar_n);

    // store START data
  db_version_copy( VERSION_NORMAL, VERSION_START );
  db_copy( NODE, NODE_START_REFINED, VERSION_NORMAL );
  db_copy( NODE_DOF, NODE_DOF_START_REFINED, VERSION_NORMAL );

    // initialize mesh
  mesh_has_changed( VERSION_NORMAL );

    // inverse iterations loop
  for ( inverse_iter=0; inverse_iter<=inverse_iterations; inverse_iter++ ) {
    if ( swit ) pri( "inverse_iter", inverse_iter );
    if ( npar>0 ) {
      length = 1;
      db( INVERSE_ITERATION_NUMBER, 0, &inverse_iter, ddum, length, 
        VERSION_NORMAL, PUT );
    }
      // inverse parameters loop
    for ( ipar=0; ipar<max_ipar; ipar++ ) {
      if ( npar==0 || db_active_index( INVERSE_PARAMETER, ipar, VERSION_NORMAL ) ) {
          // inverse central differences loop
        for ( ipar_i=0; ipar_i<max_ipar_i; ipar_i++ ) {
          inverse_calculation( ipar, npar, ipar_i, ipar_n, max_control, 
            INVERSE_PARAMETER_SET );
          start_control=0;
          repeat_point:
          if ( max_control>=0 ) {
              // time loop
            for ( icontrol=start_control; icontrol<=max_control; icontrol++ ) {
              if ( swit ) pri( "icontrol", icontrol );
              db( TIME_CURRENT, 0, idum, &time_current, ldum, VERSION_NORMAL, GET );
              swit_timestep = swit; swit = swit && set_swit(-1,-1,"");
              length=1; db( ICONTROL, 0, &icontrol, ddum, length, VERSION_NORMAL, PUT );
              if ( db_active_index( CONTROL_TIMESTEP, icontrol, VERSION_NORMAL ) ) {
                if ( db_active_index( CONTROL_SOLVER, icontrol, VERSION_NORMAL ) )
                  db( CONTROL_SOLVER, icontrol, &control_solver, 
                    ddum, ldum, VERSION_NORMAL, GET );
                else {
                  control_solver = -MATRIX_DIRECT;
                  length = 1;
                  db( CONTROL_SOLVER, icontrol, &control_solver, 
                    ddum, length, VERSION_NORMAL, PUT );
                }
                maximum_timestep = DBL_MAX;
                ratio_criterium = DBL_MAX;
                use_control_timestep_iterations_automatic = 0;
                if ( db_active_index(  CONTROL_TIMESTEP_ITERATIONS_AUTOMATIC, 
                    icontrol, VERSION_NORMAL ) ) {
                  db( CONTROL_TIMESTEP_ITERATIONS_AUTOMATIC, icontrol, 
                    idum, control_timestep_iterations_automatic, ldum, 
                    VERSION_NORMAL, GET );
                  iteration_min = 2;
                  iteration_max = 20;
                  use_control_timestep_iterations_automatic = 1;
                  ratio_criterium = control_timestep_iterations_automatic[0];
                  maximum_timestep = control_timestep_iterations_automatic[1];
                }
                else if ( db_active_index(  CONTROL_TIMESTEP_ITERATIONS, 
                    icontrol, VERSION_NORMAL ) ) {
                  db( CONTROL_TIMESTEP_ITERATIONS, icontrol, &timestep_iterations, 
                    ddum, ldum, VERSION_NORMAL, GET_IF_EXISTS );
                  iteration_min = timestep_iterations;
                  iteration_max = timestep_iterations;
                }
                else {
                  iteration_min = 2;
                  iteration_max = 2;
                }
                timestep = db_dbl( CONTROL_TIMESTEP, icontrol, VERSION_NORMAL );
                ninc = db_len( CONTROL_TIMESTEP, icontrol, VERSION_NORMAL )/2;
                if ( ninc==0 ) db_error( CONTROL_TIMESTEP, icontrol );
                  // time increments loop
                for ( iinc=0; iinc<ninc; iinc++ ) {
                  time_old = time_current;
                  dtime_initial = dtime = timestep[iinc*2];
                  time_increment = timestep[iinc*2+1];
                  time_new = time_old + time_increment;
                  length = 1;
                  db( TIME_OLD, 0, idum, &time_old, length, VERSION_NORMAL, PUT );
                  db( TIME_NEW, 0, idum, &time_new, length, VERSION_NORMAL, PUT );
                  if ( swit ) {
                    pri( "dtime", dtime );
                    pri( "time_old", time_old );
                    pri( "time_increment", time_increment );
                    pri( "time_new", time_new );
                    pri( "timestep_iterations", timestep_iterations );
                  }
                  while ( time_current<(time_new-1.e-3*dtime) && dtime>EPS_DTIME*dtime_initial ){
                    start_point:
                    step_start();
                    db_version_copy( VERSION_NORMAL, VERSION_NEW );
                    db( TIME_CURRENT, 0, idum, &time_current, length, VERSION_NEW, GET );
                    time_current += dtime;
                    if ( time_current>time_new ) {
                      dtime -= time_current-time_new;
                      time_current = time_new;
                    }
                    length=1; 
                    db( DTIME, 0, idum, &dtime, length, VERSION_NEW, PUT );
                    db( TIME_CURRENT, 0, idum, &time_current, length, VERSION_NEW, PUT );
                    delete_geom( ELEMENT_DELETE_FACTOR ); 
                    if ( nuknwn>0 ) {
                        // equilibrium loop
                      for ( iteration=1; ( iteration<=iteration_min || 
                          post_node_rhside_ratio>=ratio_criterium ) && 
                          iteration<=iteration_max; iteration++ ) {
                        if ( swit ) pri( "iteration", iteration );
                        number_iterations = iteration;
                        db( NUMBER_ITERATIONS, 0, &number_iterations, 
                          ddum, length, VERSION_NORMAL, PUT );
                        initialize_bounda_elem( control_solver );
                        bounda(); 
                        element_loop(); 
                        tyings_rhside_lhside();
                        parallel_sys_routine( &parallel_contact );
                        parallel_sys_routine( &parallel_new_dof_before );
                        slide( -NODE_RHSIDE ); 
                        solve( control_solver );
                        parallel_sys_routine( &parallel_new_dof_diagonal );
                        slide( -NODE_DOF ); 
                        locate();
                        tyings_node_dof( VERSION_NEW );
                        post_node_rhside_fixed_free();
                        db( POST_NODE_RHSIDE_RATIO, 0, idum, &post_node_rhside_ratio, 
                          ldum, VERSION_NORMAL, GET );
                      }
                      if ( use_control_timestep_iterations_automatic ) {
                        if ( number_iterations==iteration_max ) {
                          if ( converged_once ) {
                            dtime /= 10.;
                            if ( dtime>EPS_DTIME*dtime_initial ) goto start_point;
                          }
                        }
                        else {
                          converged_once = 1;
                          dtime *= 1. + (ITERATION_WISHED-number_iterations)/20.;
                          if ( dtime>maximum_timestep ) dtime = maximum_timestep;
                        }
                      }
                    }
                    if ( dtime>EPS_DTIME*dtime_initial ) {
                      db_version_copy( VERSION_NEW, VERSION_NORMAL );
                      db_version_delete( VERSION_NEW );
                      step_close( ipar, npar, ipar_i, ipar_n );
                      if ( repeat(start_control) ) goto repeat_point;
                    }
                    else {
                      pri( "" );
                      pri( "Warning: the minimal time step size is reached." );
                      pri( "The time stepping is not completed" );
                      pri( "" );
                    }
                  }
                  delete_geom( ELEMENT ); 
                }
                swit = swit_timestep;
              }
              else {
                step_start();
                tyings_node_dof( VERSION_NORMAL );
                step_close( ipar, npar, ipar_i, ipar_n );
                if ( repeat(start_control) ) goto repeat_point;
              }
              inverse_calculation( ipar, npar, ipar_i, ipar_n,
                max_control, INVERSE_DETERMINE_SENSITIVITY );
            }
          }
          if ( npar>0 && !( inverse_iter==inverse_iterations && 
              ipar==npar-1 && ipar_i==ipar_n-1 ) ) {
            db_version_copy( VERSION_START, VERSION_NORMAL );
            db_copy( NODE, NODE_START_REFINED, VERSION_NORMAL );
            db_copy( NODE_DOF, NODE_DOF_START_REFINED, VERSION_NORMAL );
            db_delete( ELEMENT_TENDON_NUMBER, VERSION_NORMAL );
            db_delete( ELEMENT_TENDON_VOLUME, VERSION_NORMAL );
            db_delete( ELEMENT_TENDON_STRESS, VERSION_NORMAL );
            db_delete( ELEMENT_TENDON_DIRECTION, VERSION_NORMAL );
          }
        }
      }
    }
    inverse_calculation( ipar, npar, ipar_i, ipar_n, 
      max_control, INVERSE_DETERMINE_NEW_ESTIMATES );
  }

  if ( swit ) pri( "Out routine TOP" );

}

void step_close( long int ipar, long int npar, long int ipar_i, long int ipar_n )

{
  long int i=0, nval=0, data_item=0, icontrol=0, ldum=0, control_split=0,
    use_control_refine_globally_geometry=0, length_control_refine_globally=0,
    length=0, time_of_calculation=0, time_at_start=0, time_at_end=0,
    control_refine_globally[4], control_refine_globally_geometry[2], 
    renumber[2], ival[DATA_ITEM_SIZE];
  double ddum[MDIM];

  db( ICONTROL, 0, &icontrol, ddum, ldum, VERSION_NORMAL, GET );

  if ( db_active_index( CONTROL_MESH_RENUMBER, icontrol, VERSION_NORMAL ) ) {
    db( CONTROL_MESH_RENUMBER, icontrol, renumber, ddum, ldum, VERSION_NORMAL, GET );
    if ( renumber[0]<0 || renumber[1]<0 ) db_error( CONTROL_MESH_RENUMBER, icontrol );
    renumbering( VERSION_NORMAL, renumber[0], renumber[1] );
  }

  if ( db_active_index( CONTROL_EIGEN, icontrol, VERSION_NORMAL ) ) solve( -CONTROL_EIGEN );

  delete_geom( ELEMENT ); 

  macro();

  adjust_geom(); 

  data(); 

  merge(); 

  failure(); 

  new_mesh( );

  if ( db_active_index( CONTROL_MESH_REFINE_GLOBALLY, icontrol, VERSION_NORMAL ) ) {
    error( PUT );
    db( CONTROL_MESH_REFINE_GLOBALLY, icontrol, control_refine_globally, 
      ddum, length_control_refine_globally, VERSION_NORMAL, GET );
    use_control_refine_globally_geometry = db( CONTROL_MESH_REFINE_GLOBALLY_GEOMETRY, 
      icontrol, control_refine_globally_geometry, ddum, ldum, 
      VERSION_NORMAL, GET_IF_EXISTS );
    refine_globally( control_refine_globally, 
      length_control_refine_globally, 
      use_control_refine_globally_geometry, 
      control_refine_globally_geometry, 
      PROJECT_EXACT, VERSION_NORMAL );
  }

  if ( db_active_index( CONTROL_MESH_SPLIT, icontrol, VERSION_NORMAL ) ) {
    db( CONTROL_MESH_SPLIT, icontrol, &control_split, ddum, ldum, VERSION_NORMAL, GET );
    if ( control_split==-YES ) split( VERSION_NORMAL ); 
  }

  refine_locally( );

  restart();

  post(); 

  calculate();

  db( TIME_AT_START, 0, &time_at_start, ddum, ldum, VERSION_NORMAL, GET );
  time_at_end = (long int) time(NULL);
  time_of_calculation = time_at_end - time_at_start;
  length=1; db( TIME_CALCULATION, 0, &time_of_calculation, 
    ddum, length, VERSION_NORMAL, PUT );

  if ( npar==0 || ( ipar==npar-1 && ipar_i==ipar_n-1 ) ) {
    if ( db_active_index( CONTROL_PRINT, icontrol, VERSION_NORMAL ) ) {
      db( CONTROL_PRINT, icontrol, ival, ddum, nval, VERSION_NORMAL, GET );
      for ( i=0; i<nval; i++ ) {
        data_item = ival[i];
        print( icontrol, VERSION_NORMAL, data_item );
      }
      cout << "\n\n";
    }
    if ( db_active_index( CONTROL_PRINT_DATABASE, icontrol, VERSION_NORMAL ) ) {
      db( CONTROL_PRINT_DATABASE, icontrol, ival, ddum, ldum, VERSION_NORMAL, GET );
      if ( ival[0]==-YES ) print( icontrol, VERSION_NORMAL, -DATABASE );
      else if ( ival[0]==-EVERYTHING ) print( icontrol, VERSION_NORMAL, -EVERYTHING );
    }
    if ( db_active_index( CONTROL_PRINT_DATA_VERSUS_DATA, icontrol, VERSION_NORMAL ) ) {
      db( CONTROL_PRINT_DATA_VERSUS_DATA, icontrol, ival, ddum, ldum, VERSION_NORMAL, GET );
      print_data_versus_data( ival );
    }
    if ( db_active_index( CONTROL_PRINT_HISTORY, icontrol, VERSION_NORMAL ) ) {
      db( CONTROL_PRINT_HISTORY, icontrol, ival, ddum, nval, VERSION_NORMAL, GET );
      print_history( ival, nval );
    }
    if ( db_active_index( CONTROL_PRINT_PLOTMTV, icontrol, VERSION_NORMAL ) ) {
      db( CONTROL_PRINT_PLOTMTV, icontrol, ival, ddum, ldum, VERSION_NORMAL, GET );
      if ( ival[0]==-YES || ival[0]>=0 ) print_plotmtv( icontrol, ival );
    }
    if ( db_active_index( CONTROL_PRINT_GMV, icontrol, VERSION_NORMAL ) ) {
      db( CONTROL_PRINT_GMV, icontrol, ival, ddum, ldum, VERSION_NORMAL, GET );
      if ( ival[0]==-YES || ival[0]>=0 ) print_gmv( icontrol, ival );
    }
    if ( db_active_index( CONTROL_PRINT_MATLAB, icontrol, VERSION_NORMAL ) ) {
      db( CONTROL_PRINT_MATLAB, icontrol, ival, ddum, ldum, VERSION_NORMAL, GET );
      if ( ival[0]==-YES || ival[0]>=0 ) print_matlab( );
    }
    if ( db_active_index( CONTROL_PRINT_TECPLOT, icontrol, VERSION_NORMAL ) ) {
      db( CONTROL_PRINT_TECPLOT, icontrol, ival, ddum, ldum, VERSION_NORMAL, GET );
      if ( ival[0]==-YES || ival[0]==-MESH || ival[0]>=0 ) print_tecplot( ival );
    }
    if ( db_active_index( CONTROL_PRINT_VTK, icontrol, VERSION_NORMAL ) ) {
      db( CONTROL_PRINT_VTK, icontrol, ival, ddum, ldum, VERSION_NORMAL, GET );
      if ( ival[0]==-YES ) print_vtk( icontrol );
    }
  }

  cout << flush;

}

void step_start( void )

{
  long int ldum=0, icontrol=0, iremesh=0, control_mesh_remesh=0, max_node=0;
  double ddum[1];

  db( ICONTROL, 0, &icontrol, ddum, ldum, VERSION_NORMAL, GET );
  if ( db_active_index( CONTROL_MESH_REMESH, icontrol, VERSION_NORMAL ) ) {
    db( CONTROL_MESH_REMESH, icontrol, &control_mesh_remesh, ddum, 
      ldum, VERSION_NORMAL, GET );
    db_max_index( NODE, max_node, VERSION_NORMAL, GET );
    db_allocate_class( NODE, max_node, VERSION_NORMAL );
    for ( iremesh=0; iremesh<control_mesh_remesh; iremesh++ ) {
      db_version_copy( VERSION_NORMAL, VERSION_TMP );
      parallel_sys_routine( &parallel_remesh );
      db_version_copy( VERSION_TMP, VERSION_NORMAL );
      db_version_delete( VERSION_TMP );
    }
  }

}

void initialize_bounda_elem( long int control_solver )
{
  long int element=0, max_element=0, length=0, nnol=0, mnol=0, highest_eigen=0;

  db_set_dbl( NODE_LHSIDE, VERSION_NORMAL );
  db_set_dbl( NODE_RHSIDE, VERSION_NORMAL );
  db_set_dbl( NODE_RHSIDE_INTERNAL, VERSION_NORMAL );
  db_set_dbl( NODE_RHSIDE_STATIC, VERSION_NORMAL );
  db_set_dbl( ELEMENT_VOLUME, VERSION_NORMAL );

  if ( control_solver==-MATRIX_DIRECT || control_solver==-MATRIX_ITERATIVE ) {
    db_max_index( ELEMENT, max_element, VERSION_NORMAL, GET );
    for ( element=0; element<=max_element; element++ ) {
      if ( db_active_index( ELEMENT, element, VERSION_NORMAL ) ) {
        length = db_len( ELEMENT, element, VERSION_NORMAL );
        nnol = length - 1;
        if ( nnol>mnol ) mnol = nnol;
      }
    }
    length = mnol*nprinc*mnol*nprinc;
    db_data_length_put( ELEMENT_MATRIX_UNKNOWNS, 2*length );
    db_data_length_put( ELEMENT_MATRIX_VALUES, length );
    db_set_int( ELEMENT_MATRIX_UNKNOWNS, VERSION_NORMAL );
    db_set_dbl( ELEMENT_MATRIX_VALUES, VERSION_NORMAL );
    db_highest_index( CONTROL_EIGEN, highest_eigen, VERSION_NORMAL );
    if ( materi_stress && highest_eigen>=0 ) {
      db_data_length_put( ELEMENT_MATRIX_STRESS_STIFFNESS_VALUES, length );
      db_set_dbl( ELEMENT_MATRIX_STRESS_STIFFNESS_VALUES, VERSION_NORMAL );
    }
  }
}
