#!/usr/bin/env perl

use Env;
use File::Copy;
Env::import();

$PluginName = "__nada__";
$ReactionName = "__nada__";
$ReactionFactoryTag = "__nada__";

if($#ARGV < 0)
{
	&Usage();
	exit;
}
elsif($#ARGV == 0)
{
	$PluginName = $ARGV[0];
	$ReactionName = $PluginName;
	$ReactionFactoryTag = $ReactionName;
}
elsif($#ARGV == 1)
{
	$PluginName = $ARGV[0];
	$ReactionName = $ARGV[1];
	$ReactionFactoryTag = $ReactionName;
}
else
{
	$PluginName = $ARGV[0];
	$ReactionName = $ARGV[1];
	$ReactionFactoryTag = $ARGV[2];
}

print "\n";
print "Generating files for skim plugin \"$PluginName\", with DReaction \"$ReactionName\" defined in DReaction_factory_${ReactionFactoryTag} \n";

# Create directory to hold source
mkdir($PluginName);

# Create C++ Header File for Factory Class
$dhfile = $fname = "DReaction_factory_${ReactionFactoryTag}.h";
open(FILE, ">$PluginName/$dhfile");
&PrintFileHeader();
&PrintFactoryClass();
close(FILE);
print " - $dhfile\n";

# Create C++ Implementation file for Factory Class
$ccfile = $fname = "DReaction_factory_${ReactionFactoryTag}.cc";
open(FILE, ">$PluginName/$ccfile");
&PrintFileHeader();
&PrintFactoryMethods();
close(FILE);
print " - $ccfile\n";

# Create Makefile
open(FILE, ">$PluginName/Makefile");
&PrintMakefile();
close(FILE);
print " - Makefile\n";

# Create SConscript
# Not really needed unless included in GlueX Build
#open(FILE, ">$PluginName/SConscript");
#&PrintSConscript();
#close(FILE);
#print " - SConscript\n";
#
# Create SConstruct
open(FILE, ">$PluginName/SConstruct");
&PrintSConstruct();
close(FILE);
print " - SConstruct\n";

# Copy SConstruct from build
#copy("${HALLD_HOME}/src/SBMS/SConstruct.plugin","${PluginName}/SConstruct") or die "Unable to copy SConstruct check HALLD_HOME is set correctly\r";
#print " - SConstruct\n";


# Create C++ Header File for Factory Generator Class
$dhfile = $fname = "DFactoryGenerator_${PluginName}.h";
open(FILE, ">$PluginName/$dhfile");
&PrintFileHeader();
&PrintFactoryGeneratorClass();
close(FILE);
print " - $dhfile\n";

# Create C++ Header File for Event Processor Class
$dhfile = $fname = "DEventProcessor_${PluginName}.h";
open(FILE, ">$PluginName/$dhfile");
&PrintFileHeader();
&PrintEventProcessorClass();
close(FILE);
print " - $dhfile\n";

# Create C++ Implementation file for Event Processor Class
$ccfile = $fname = "DEventProcessor_${PluginName}.cc";
open(FILE, ">$PluginName/$ccfile");
&PrintFileHeader();
&PrintEventProcessorMethods();
close(FILE);
print " - $ccfile\n";

###############
# PrintFileHeader
###############
sub PrintFileHeader()
{
	# print a few lines at the very top of the file
	$uname = `uname -nprs`;
	chomp($uname);
	print FILE "// \$Id\$\n";
	print FILE "//\n";
	print FILE "//    File: $fname\n";
	print FILE "// Created: ".`date`;
	print FILE "// Creator: ".$ENV{"USER"}." (on $uname)\n";
	print FILE "//\n";
}

###############
# PrintFactoryClass
###############
sub PrintFactoryClass()
{
	$content = "
\#ifndef _DReaction_factory_${ReactionFactoryTag}_
\#define _DReaction_factory_${ReactionFactoryTag}_

\#include <iostream>
\#include <iomanip>

\#include <JANA/JFactory.h>
\#include <ANALYSIS/DReaction.h>
\#include <ANALYSIS/DHistogramActions.h>
\#include <ANALYSIS/DCutActions.h>

using namespace std;
using namespace jana;

class DReaction_factory_${ReactionFactoryTag} : public jana::JFactory<DReaction>
{
	public:
		DReaction_factory_${ReactionFactoryTag}()
		{
			// This is so that the created DReaction objects persist throughout the life of the program instead of being cleared each event. 
			SetFactoryFlag(PERSISTANT);
		}
		const char* Tag(void){return \"$ReactionFactoryTag\";}

	private:
		jerror_t init(void);						///< Called once at program start.
		jerror_t fini(void);						///< Called after last event of last event source has been processed.

		deque<DReactionStep*> dReactionStepPool; //to prevent memory leaks
};

\#endif // _DReaction_factory_${ReactionFactoryTag}_

";
	print FILE $content;
}

###############
# PrintFactoryMethods
###############
sub PrintFactoryMethods()
{
	$content = "

\#include \"DReaction_factory_${ReactionFactoryTag}.h\"

//------------------
// init
//------------------
jerror_t DReaction_factory_${ReactionFactoryTag}::init(void)
{
	// Make as many DReaction objects as desired
	DReactionStep* locReactionStep = NULL;
	DReaction* locReaction = new DReaction(\"${ReactionName}\"); //needs to be a unique name for each DReaction object, CANNOT (!) be \"Thrown\"

	// DOCUMENTATION:
	// ANALYSIS library: https://halldweb1.jlab.org/wiki/index.php/GlueX_Analysis_Software
	// DReaction factory: https://halldweb1.jlab.org/wiki/index.php/Analysis_DReaction

	/**************************************************** ${ReactionName} Reaction Steps ****************************************************/

	/* 
	//Required: DReactionSteps to specify the channel and decay chain you want to study
		//Particles are of type Particle_t, an enum defined in sim-recon/src/libraries/include/particleType.h

	//Example: g, p -> pi+, pi-, pi0, (p)
	locReactionStep = new DReactionStep();
	locReactionStep->Set_InitialParticleID(Gamma);
	locReactionStep->Set_TargetParticleID(Proton);
	locReactionStep->Add_FinalParticleID(PiPlus);
	locReactionStep->Add_FinalParticleID(PiMinus);
	locReactionStep->Add_FinalParticleID(Pi0);
	locReactionStep->Add_FinalParticleID(Proton, true); //true: proton missing
	locReaction->Add_ReactionStep(locReactionStep);
	dReactionStepPool.push_back(locReactionStep); //register so will be deleted later: prevent memory leak

	//Example: pi0 -> g, g
	locReactionStep = new DReactionStep();
	locReactionStep->Set_InitialParticleID(Pi0);
	locReactionStep->Add_FinalParticleID(Gamma);
	locReactionStep->Add_FinalParticleID(Gamma);
	locReaction->Add_ReactionStep(locReactionStep);
	dReactionStepPool.push_back(locReactionStep); //register so will be deleted later: prevent memory leak
	*/

	/**************************************************** ${ReactionName} Control Variables ****************************************************/

	// Highly Recommended: When generating particle combinations, reject all tracks with a tracking confidence level < 0.27% (+/- 3-sigma)
	locReaction->Set_MinIndividualTrackingFOM(0.0027);

	// Highly Recommended: When generating particle combinations, reject all tracks without a matching hit in a detector system (ST/TOF/BCAL/FCAL)
	locReaction->Set_HasDetectorMatchFlag(true);

	// Highly Recommended: When generating particle combinations, reject all neutral particles with a PID confidence level < 5.73303E-7 (+/- 5-sigma)
	locReaction->Set_MinIndividualNeutralPIDFOM(5.73303E-7);

	/**************************************************** ${ReactionName} Analysis Actions ****************************************************/

	// Recommended: Analysis actions automatically performed by the DAnalysisResults factories to histogram useful quantities.
		//These actions are executed sequentially, and are executed on each surviving (non-cut) particle combination 
		//Pre-defined actions can be found in ANALYSIS/DHistogramActions.h and ANALYSIS/DCutActions.h

	// Transverse Momentum //Recommended for no-missing-particle reactions only!
	// locReaction->Add_AnalysisAction(new DCutAction_TransverseMomentum(locReaction, 0.4)); //Max Missing Pt of 0.4 GeV

	// Potentially add EXTREMELY loose invariant/missing-mass cuts

	_data.push_back(locReaction); //Register the DReaction with the factory

	return NOERROR;
}

//------------------
// fini
//------------------
jerror_t DReaction_factory_${ReactionFactoryTag}::fini(void)
{
	for(size_t loc_i = 0; loc_i < dReactionStepPool.size(); ++loc_i)
		delete dReactionStepPool[loc_i]; //cleanup memory
	return NOERROR;
}

";
	print FILE $content;
}

###############
# PrintFactoryGeneratorClass
###############
sub PrintFactoryGeneratorClass()
{
	$content = "
\#ifndef _DFactoryGenerator_${PluginName}_
\#define _DFactoryGenerator_${PluginName}_

\#include <JANA/jerror.h>
\#include <JANA/JFactoryGenerator.h>

\#include \"DReaction_factory_${ReactionFactoryTag}.h\"

class DFactoryGenerator_${PluginName} : public jana::JFactoryGenerator
{
	public:
		virtual const char* className(void){return static_className();}
		static const char* static_className(void){return \"DFactoryGenerator_${PluginName}\";}
		
		jerror_t GenerateFactories(jana::JEventLoop* locEventLoop)
		{
			locEventLoop->AddFactory(new DReaction_factory_${ReactionFactoryTag}());
			return NOERROR;
		}
};

\#endif // _DFactoryGenerator_${PluginName}_

";
	print FILE $content;
}

###############
# PrintEventProcessorClass
###############
sub PrintEventProcessorClass()
{
	$content = "
\#ifndef _DEventProcessor_${PluginName}_
\#define _DEventProcessor_${PluginName}_

\#include <JANA/JEventProcessor.h>
\#include <JANA/JApplication.h>

\#include <ANALYSIS/DEventWriterROOT.h>
\#include <HDDM/DEventWriterREST.h>
\#include <ANALYSIS/DHistogramActions.h>

\#include \"DFactoryGenerator_${PluginName}.h\"

using namespace jana;
using namespace std;

class DEventProcessor_${PluginName} : public jana::JEventProcessor
{
	public:
		const char* className(void){return \"DEventProcessor_${PluginName}\";}

	private:
		jerror_t init(void);						///< Called once at program start.
		jerror_t brun(jana::JEventLoop* locEventLoop, int locRunNumber);	///< Called every time a new run number is detected.
		jerror_t evnt(jana::JEventLoop* locEventLoop, int locEventNumber);	///< Called every event.
		jerror_t erun(void);						///< Called every time run number changes, provided brun has been called.
		jerror_t fini(void);						///< Called after last event of last event source has been processed.

		ofstream dIDXAStream;
};

\#endif // _DEventProcessor_${PluginName}_

";
	print FILE $content;
}

###############
# PrintEventProcessorMethods
###############
sub PrintEventProcessorMethods
{
	$content = "
\#include \"DEventProcessor_${PluginName}.h\"

// Routine used to create our DEventProcessor

extern \"C\"
{
	void InitPlugin(JApplication *locApplication)
	{
		InitJANAPlugin(locApplication);
		locApplication->AddProcessor(new DEventProcessor_${PluginName}()); //register this plugin
		locApplication->AddFactoryGenerator(new DFactoryGenerator_${PluginName}()); //register the factory generator
	}
} // \"C\"

//------------------
// init
//------------------
jerror_t DEventProcessor_${PluginName}::init(void)
{
	// This is called once at program startup. If you are creating
	// and filling historgrams in this plugin, you should lock the
	// ROOT mutex like this:
	//
	// japp->RootWriteLock();
	//  ... create historgrams or trees ...
	// japp->RootUnLock();
	//

	string locFileName = \"eventlist_${ReactionName}.idxa\";
	dIDXAStream.open(locFileName.c_str());
	dIDXAStream << \"IDXA\" << endl;

	return NOERROR;
}

//------------------
// brun
//------------------
jerror_t DEventProcessor_${PluginName}::brun(jana::JEventLoop* locEventLoop, int locRunNumber)
{
	// This is called whenever the run number changes

	return NOERROR;
}

//------------------
// evnt
//------------------
jerror_t DEventProcessor_${PluginName}::evnt(jana::JEventLoop* locEventLoop, int locEventNumber)
{
	// This is called for every event. Use of common resources like writing
	// to a file or filling a histogram should be mutex protected. Using
	// locEventLoop->Get(...) to get reconstructed objects (and thereby activating the
	// reconstruction algorithm) should be done outside of any mutex lock
	// since multiple threads may call this method at the same time.
	//
	// Here\'s an example:
	//
	// vector<const MyDataClass*> mydataclasses;
	// locEventLoop->Get(mydataclasses);
	//
	// japp->RootWriteLock();
	//  ... fill historgrams or trees ...
	// japp->RootUnLock();

	// DOCUMENTATION:
	// ANALYSIS library: https://halldweb1.jlab.org/wiki/index.php/GlueX_Analysis_Software

	//Get the analysis results for all DReactions. 
		//Getting these objects triggers the analysis, if it wasn't performed already. 
		//These objects contain the DParticleCombo objects that survived the DAnalysisAction cuts that were added to the DReactions
	vector<const DAnalysisResults*> locAnalysisResultsVector;
	locEventLoop->Get(locAnalysisResultsVector);

	// If a particle combo passed the cuts, save the event info in the output file
	for(size_t loc_i = 0; loc_i < locAnalysisResultsVector.size(); ++loc_i)
	{
		const DAnalysisResults* locAnalysisResults = locAnalysisResultsVector[loc_i];
		if(locAnalysisResults->Get_Reaction()->Get_ReactionName() != \"${ReactionName}\")
			continue; // analysis results were for a different reaction
		if(locAnalysisResults->Get_NumPassedParticleCombos() == 0)
			continue; // no combos passed

		JEvent& locEvent = locEventLoop->GetJEvent();
		JEventSource* locEventSource = locEvent.GetJEventSource();
		string locSourceName = locEventSource->GetSourceName();

		string locFileNumberString = locSourceName.substr(locSourceName.size() - 12, 7);
		istringstream locIStream(locFileNumberString);
		unsigned int locFileNumber = 0;
		locIStream >> locFileNumber;

		dIDXAStream << locEvent.GetRunNumber() << \" \" << locEventNumber << \" \" << locFileNumber << endl;
	}

	return NOERROR;
}

//------------------
// erun
//------------------
jerror_t DEventProcessor_${PluginName}::erun(void)
{
	// This is called whenever the run number changes, before it is
	// changed to give you a chance to clean up before processing
	// events from the next run number.
	return NOERROR;
}

//------------------
// fini
//------------------
jerror_t DEventProcessor_${PluginName}::fini(void)
{
	// Called before program exit after event processing is finished.
	dIDXAStream.close();
	return NOERROR;
}

";
	print FILE $content;
}

$PluginName = "__nada__";
$ReactionName = "__nada__";
$ReactionFactoryTag = "__nada__";

###############
# PrintMakefile
###############
sub PrintMakefile()
{
	# Contents of default Makefile for plugins
	print FILE "\n";
	print FILE "PACKAGES = ROOT:JANA\n";
	print FILE "\n";
	print FILE "include \$(HALLD_HOME)/src/BMS/Makefile.shlib\n";
	print FILE "\n";
}

###############
# PrintSConscript
###############
sub PrintSConscript()
{
	# Contents of default SConscript file for plugins
	print FILE "\n";
	print FILE "import sbms\n";
	print FILE "Import('*')\n";
	print FILE "env = env.Clone()\n";
	print FILE "sbms.AddDANA(env)\n";
	print FILE "sbms.AddROOT(env)\n";
	print FILE "sbms.plugin(env)\n";
	print FILE "\n";
}

###############
# Print SConstruct
###############

sub PrintSConstruct()
{
	# Contents of default SConstruct file for plugins
        $content = "

import os
import sys
import subprocess
import glob

# Get HALLD_HOME environment variable, verifying it is set
halld_home = os.getenv('HALLD_HOME')
if(halld_home == None):
        print 'HALLD_HOME environment variable not set!'
        exit(-1)

# Get HALLD_MY if it exists. Otherwise use HALLD_HOME
halld_my = os.getenv('HALLD_MY', halld_home)

# Get plugin name
pluginname = os.path.split(os.getcwd())[1]

# Add SBMS directory to PYTHONPATH
sbmsdir = \"%s/src/SBMS\" % halld_home
sys.path.append(sbmsdir)

import sbms

# Get command-line options
SHOWBUILD = ARGUMENTS.get('SHOWBUILD', 0)

# Get platform-specific name
osname = os.getenv('BMS_OSNAME', 'build')

# Get architecture name
arch = subprocess.Popen([\"uname\"], stdout=subprocess.PIPE).communicate()[0].strip()

# Setup initial environment
plugininstalldir = halld_my
gluexinstalldir = \"%s/%s\" %(halld_home, osname)
include = \"%s/include\" % (gluexinstalldir)
bin = \"%s/bin\" % (plugininstalldir)
lib = \"%s/lib\" % (plugininstalldir)
plugins = \"%s/%s/plugins\" % (plugininstalldir,osname)
env = Environment(    CPPPATH = [include],
                      LIBPATH = [\"%s/%s/lib\" %(halld_home, osname)],  # n.b. add HALLD_HOME here and prepend HALLD_MY below
                  variant_dir = \".%s\" % (osname))

# Only add HALLD_MY library search path if it already exists
# since we'll get a warning otherwise
if (os.path.exists(lib)): env.PrependUnique(lib)

# These are SBMS-specific variables (i.e. not default scons ones)
env.Replace(INSTALLDIR    = plugininstalldir,
                                OSNAME        = osname,
                                INCDIR        = include,
                                BINDIR        = bin,
                                LIBDIR        = lib,
                                PLUGINSDIR    = plugins,
                                ALL_SOURCES   = [],        # used so we can add generated sources
                                SHOWBUILD     = SHOWBUILD,
                 COMMAND_LINE_TARGETS = COMMAND_LINE_TARGETS)
# Use terse output unless otherwise specified
if SHOWBUILD==0:
        env.Replace(   CCCOMSTR       = \"Compiling  [\$SOURCE]\",
                                  CXXCOMSTR       = \"Compiling  [\$SOURCE]\",
                                  FORTRANPPCOMSTR = \"Compiling  [\$SOURCE]\",
                                  FORTRANCOMSTR   = \"Compiling  [\$SOURCE]\",
                                  SHCCCOMSTR      = \"Compiling  [\$SOURCE]\",
                                  SHCXXCOMSTR     = \"Compiling  [\$SOURCE]\",
                                  LINKCOMSTR      = \"Linking    [\$TARGET]\",
                                  SHLINKCOMSTR    = \"Linking    [\$TARGET]\",
                                  INSTALLSTR      = \"Installing [\$TARGET]\",
                                  ARCOMSTR        = \"Archiving  [\$TARGET]\",
                                  RANLIBCOMSTR    = \"Ranlib     [\$TARGET]\")
# Get compiler from environment variables (if set)
env.Replace( CXX = os.getenv('CXX', 'g++'),
             CC  = os.getenv('CC' , 'gcc'),
             FC  = os.getenv('FC' , 'gfortran') )

# Add local directory, directories from HALLD_MY and HALLD_HOME to include search path
#env.PrependUnique(CPPPATH = ['#'])
env.PrependUnique(CPPPATH = ['%s/src' % halld_my, '%s/src/libraries' % halld_my, '%s/src/libraries/include' % halld_my])
env.PrependUnique(CPPPATH = ['%s/src' % halld_home, '%s/src/libraries' % halld_home, '%s/src/libraries/include' % halld_home])

# Turn on debug symbols and warnings
env.PrependUnique(      CFLAGS = ['-g', '-fPIC', '-Wall'])
env.PrependUnique(    CXXFLAGS = ['-g', '-fPIC', '-Wall'])
env.PrependUnique(FORTRANFLAGS = ['-g', '-fPIC', '-Wall'])

# Apply any platform/architecture specific settings
sbms.ApplyPlatformSpecificSettings(env, arch)
sbms.ApplyPlatformSpecificSettings(env, osname)


# Make plugin from source in this directory
sbms.AddDANA(env)
sbms.AddROOT(env)
sbms.plugin(env,\".%s/%s\" % (osname,pluginname))

# Make install target
env.Alias('install', env.Install(plugins, \".%s/%s.so\" % (osname,pluginname)))

";
        print FILE $content;
}

###############
# Usage
###############
sub Usage()
{
	print "\n";
	print "Usage:\n\n    MakeSkimPlugin plugin_name [reaction_name [reaction_factory_tag]] \n";
	print "\n    NOTE: The last two arguments are optional; they will be set to \"plugin_name\" and \n";
	print "    \"reaction_name\" (respectively) if not specified.\n";
	print "\n";
	print "Generate the C++ source and header files to implement a new analysis skim\n";
	print "plugin for use with the JANA framework. The plugin will implement\n";
	print "a DReaction factory (complete with factory generator).\n";
	print "\n";
	print "This will create a directory and generate a few files in it, including\n";
	print "a default Makefile and SConstruct so that one can use to immediately compile the new plugin.\n";
	print "\n";

	print "\n";
	print "The C++ files generated will define classes based on JFactory,\n";
	print "JFactoryGenerator, and JEventProcessor. The \"plugin_name\" parameter is also\n";
	print "the name of the directory created (which can be changed).\n";
	print "\n";	
	print "\n";
}

