#ifndef __JAANET_JHEADTOOLKIT__
#define __JAANET_JHEADTOOLKIT__

#include <map>
#include <initializer_list>

#include "km3net-dataformat/offline/Vec.hh"
#include "km3net-dataformat/offline/Head.hh"
#include "km3net-dataformat/definitions/applications.hh"

#include "JGeometry3D/JPosition3D.hh"
#include "JGeometry3D/JCylinder3D.hh"
#include "JAAnet/JHead.hh"


/**
 * \author mdejong
 */
namespace JAANET {

  using JGEOMETRY3D::JPosition3D;
  using JGEOMETRY3D::JCylinder3D;

  /**
   * Type definition of test function of header.
   */
  typedef bool (*is_head)(const JHead&);

  
  /**
   * Check for generator.
   *
   * \param  header        header
   * \return               true if this header is produced by genhen; else false
   */
  inline bool is_genhen(const JHead& header)
  {
    for (const auto& i : header.simul) {
      if (i.program == APPLICATION_GENHEN) {
	return true;
      }
    }

    for (const auto& i : header.physics) { // legacy
      if (i.buffer.find(APPLICATION_GENHEN) != std::string::npos) {
	return true;
      }
    }

    return false;
  }

  
  /**
   * Check for generator.
   *
   * \param  header        header
   * \return               true if this header is produced by gSeaGen; else false
   */
  inline bool is_gseagen(const JHead& header)
  {
    for (const auto& i : header.simul) {
      if (i.program == APPLICATION_GSEAGEN) {
	return true;
      }
    }

    for (const auto& i : header.physics) { // legacy
      if (i.buffer.find(APPLICATION_GSEAGEN) != std::string::npos) {
	return true;
      }
    }

    return false;
  }


  /**
   * Check for generator.
   *
   * \param  header        header
   * \return               true if this header is produced by MUPAGE; else false
   */
  inline bool is_mupage(const JHead& header)
  {
    for (const auto& i : header.simul) {
      if (i.program == APPLICATION_MUPAGE) {
	return true;
      }
    }

    return false;
  }


  /**
   * Check for generator.
   *
   * \param  header        header
   * \return               true if this header is produced by Corsika; else false
   */
  inline bool is_corsika(const JHead& header)
  {
    for (const auto& i : header.simul) {
      if (i.program == APPLICATION_CORSIKA) {
	return true;
      }
    }

    return false;
  }


  /**
   * Check for generator.
   *
   * \param  header        header
   * \return               true if this header is produced by km3buu; else false
   */
  inline bool is_km3buu(const JHead& header)
  {
    for (const auto& i : header.simul) {
      if (i.program == APPLICATION_KM3BUU) {
	return true;
      }
    }

    return false;
  }


  /**
   * Check for detector simulation.
   *
   * \param  header        header
   * \return               true if this header is processed with km3; else false
   */
  inline bool is_km3(const JHead& header)
  {
    for (const auto& i : header.simul) {
      if (i.program == APPLICATION_KM3) {
	return true;
      }
    }

    return false;
  }


  /**
   * Check for detector simulation.
   *
   * \param  header        header
   * \return               true if this header is processed with KM3Sim; else false
   */
  inline bool is_km3sim(const JHead& header)
  {
    for (const auto& i : header.simul) {
      if (i.program == APPLICATION_KM3SIM) {
	return true;
      }
    }

    return false;
  }


  /**
   * Check for detector simulation.
   *
   * \param  header        header
   * \return               true if this header is processed with JSirene; else false
   */
  inline bool is_sirene(const JHead& header)
  {
    for (const auto& i : header.simul) {
      if (i.program == APPLICATION_JSIRENE) {
	return true;
      }
    }

    return false;
  }


  /**
   * Check for real data.
   *
   * \param  header        header
   * \return               true if this header corresponds to real data; else false
   */
  inline bool is_daq(const JHead& header)
  {
    return header.DAQ.livetime_s > 0.0;
  }


  /**
   * Auxiliary map of application to check method.
   */
  struct JHeadHelper :
    public std::map<std::string, is_head>
  {
    /**
     * Constructor.
     *
     * \param  input         input
     */
    JHeadHelper(const std::initializer_list<value_type> input) :
      std::map<std::string, is_head>(input)
    {}


    /**
     * Get check method for given application.
     *
     * \param  application   application
     * \return               check method
     */
    is_head operator()(const std::string& application) const
    {
      return this->at(application);
    }
  };


  /**
   * Function object to get check method for given application.
   */
  static JHeadHelper get_is_head = {
    std::make_pair(APPLICATION_GENHEN,   is_genhen),
    std::make_pair(APPLICATION_GSEAGEN,  is_gseagen),
    std::make_pair(APPLICATION_MUPAGE,   is_mupage),
    std::make_pair(APPLICATION_CORSIKA,  is_corsika),
    std::make_pair(APPLICATION_KM3BUU,   is_km3buu),
    std::make_pair(APPLICATION_KM3,      is_km3),
    std::make_pair(APPLICATION_KM3SIM,   is_km3sim),
    std::make_pair(APPLICATION_JSIRENE,  is_sirene),
    std::make_pair("DAQ",                is_daq)
  };


  /**
   * Get object from header.
   *
   * \param  header   header
   * \return          object
   */
  template<class T>
  inline T get(const JHead& header);


  /**
   * Get position offset of detector due to generator.
   *
   * \param  header   header
   * \return          position
   */
  template<>
  inline Vec get(const JHead& header)
  {
    if (is_sirene(header) || is_km3(header)) {

      return header.coord_origin;

    } else {

      if      (header.is_valid(&JHead::can))
	return Vec(0.0, 0.0, -header.can.zmin);
      else if (header.is_valid(&JHead::coord_origin))
	return header.coord_origin;
      else
	return Vec(0.0, 0.0, 0.0);
    }
  }


  /**
   * Get position offset of detector due to generator.
   *
   * \param  header   header
   * \return          position
   */
  template<>
  inline JPosition3D get(const JHead& header)
  {
    const Vec pos = get<Vec>(header);

    return JPosition3D(pos.x, pos.y, pos.z);
  }


  /**
   * Get cylinder corresponding to can.
   *
   * \param  header   header
   * \return          cylinder
   */
  template<>
  inline JCylinder3D get(const JHead& header)
  {
    using namespace JGEOMETRY2D;

    return JCylinder3D(JCircle2D(JVector2D(), header.can.r),
		       header.can.zmin,
		       header.can.zmax);
  }
}

#endif