#include <G4LogicalVolume.hh>
#include <G4PVPlacement.hh>
#include <G4LogicalSkinSurface.hh>
#include <G4Material.hh>
#include <G4OpticalSurface.hh>

#include <RAT/GeoSolidFactory.hh>
#include <RAT/Detector.hh>
#include <RAT/Surfaces.hh>
#include <RAT/DB.hh>
#include <RAT/Log.hh>
#include <RAT/GeoSolid.hh>
using namespace RAT;

using namespace std;

void
GeoSolidFactory::Construct( DBLinkPtr table,
                            const bool checkOverlaps )
{
  DBLinkPtr solidTable = table;
  try
    {
      string solidTableIndex = table->GetS( "solid_definition" );
      solidTable = DB::Get()->GetLink( "SOLID", solidTableIndex );
    }
  catch( DBNotFoundError& e ) { /* Optional */ }

  G4VSolid* solid = GeoSolid::ConstructSolid( table->GetIndex(), solidTable );
  // See if the solid should be split
  try
    {
      double splitZ = table->GetD( "split_z" );
      pair< G4VSolid*, G4VSolid* > solids = SplitSolid( solid, table->GetIndex(), splitZ );
      G4LogicalVolume* logicalTop = BuildVolume( table->GetIndex() + "_top", table, solids.first, G4Material::GetMaterial( table->GetS( "material_top" ) ) );
      PlaceVolume( table->GetIndex() + "_top", table, logicalTop, checkOverlaps );
      G4LogicalVolume* logicalBottom = BuildVolume( table->GetIndex() + "_bottom", table, solids.second, G4Material::GetMaterial( table->GetS( "material_bottom" ) ) );
      PlaceVolume( table->GetIndex() + "_bottom", table, logicalBottom, checkOverlaps );
    }
  catch( DBNotFoundError& e ) // Just a single solid to place
    {
      const string name = table->GetIndex();
      G4LogicalVolume* logicalVolume = BuildVolume( name, table, solid, G4Material::GetMaterial( table->GetS( "material" ) ) );
      PlaceVolume( name, table, logicalVolume, checkOverlaps );
    }
}

G4LogicalVolume*
GeoSolidFactory::BuildVolume( const std::string& name,
                              DBLinkPtr table,
                              G4VSolid* const solid,
                              G4Material* const material ) const
{
  Log::Assert( material != NULL, "GeoSolidFactory::BuildVolume: Material for " + name + " not found." );
  G4LogicalVolume* logicalVolume = new G4LogicalVolume( solid, material, name + "_logical" );

  try // Optional skin surface addition
    {
      new G4LogicalSkinSurface( name + "_surface", logicalVolume, Surfaces::GetSurface( table->GetS( "material" ) ) );
    }
  catch (DBNotFoundError &e) { };

  try // Optionally disable voxel optimization, GEANT4 default is to always optimize
    {
      if( table->GetI("optimize") == 0 )
        logicalVolume->SetOptimisation( false );
    }
  catch (DBNotFoundError &e) { };

  logicalVolume->SetVisAttributes( LoadVisualisation( table ) );
  return logicalVolume;
}

void
GeoSolidFactory::PlaceVolume( const std::string& name,
                              DBLinkPtr table,
                              G4LogicalVolume* const logicalVolume,
                              const bool checkOverlaps ) const
{
  pair< G4ThreeVector, G4RotationMatrix* > translation = LoadTranslation( table );
  string motherName = table->GetS( "mother" );
  G4LogicalVolume* mother = NULL;
  if( motherName == string("") )
    mother = NULL; // World volume has no mother
  else
    mother = Detector::FindLogicalVolume( motherName );
  // Now place the logical volume in the mother
  new G4PVPlacement( translation.second, translation.first, logicalVolume, name, mother, false, 0, checkOverlaps );
}