/*
$Id: WriteNeXusFile.hh 2247 2011-04-27 05:20:41Z jisuzuki $
 */

#ifndef WRITENEXUSFILE
#define WRITENEXUSFILE

#include <map>
#include "Header.hh"
#include "HeaderBase.hh"
#include "ElementContainer.hh"
#include "NeutronVector.hh"
#include "UInt4Container.hh"
#include <nexus/napi.h>
#include "ElementContainerArray.hh"
#include "ElementContainerMatrix.hh"
#include "UInt4ContainerArray.hh"
#include "UInt4ContainerMatrix.hh"
#include "TwoDimElementContainer.hh"

//!Make Binary data in NeXus format.
/*!
  This class make binary data in NeXus format.
  The API of NeXus is only provided for JAVA, Fortrun and C-language.
  NeXus data files will be written
  with this class on C++ and python language.
  The test codes utilized with this class is
  attached this file, "WriteNeXusFile.hh".

  If you want to make NeXus files compressed by the gzip library,
  see the comment of the constructor.
 */

class WriteNeXusFile
{
private:
  UInt4 Comp;    //!< compression mode; one of NX_COMP_{NONE,LZW,RLE,HUF}
  UInt4 r_handleFlag;
protected:
  void CreateNeXusFile( const std::string &FileName, const std::string &UserName );
  //void CloseNeXusFile();
  NXhandle handle;
  NXlink link;

public:
  WriteNeXusFile( const std::string &FileName, const std::string &UserName="UserName", UInt4 CompMode=1 );
  /*!<
    Constructor. Set output file-name and your user-name.
    If CompMode is 1 or omitted, the output data file will be
    compressed with the gzip library.
    If it is not 1, the output file will not be compressed.

    @param CompMode 0, 1(default) for NX_COMP_NONE or NX_COMPLZW
   */
  WriteNeXusFile( NXhandle r_handle, UInt4 CompMode=1 );


 ~WriteNeXusFile();
  /*!< Destructor, NeXus binary file is closed in this method.*/

  NXhandle PutHandle(){ return handle; }
  NXlink   PutLink(){ return link; }

  void CloseNeXusFile();

  void AppendAttribute( const std::string &Name, std::string value );
  void AppendAttribute( const std::string &Name, UInt4  value );
  void AppendAttribute( const std::string &Name, Int4   value );
  void AppendAttribute( const std::string &Name, Double value );
  void AppendAttribute( const std::string &Name, std::vector<Int4>   value );
  void AppendAttribute( const std::string &Name, std::vector<Double> value );
  void AppendAttribute( const std::string &Name, std::vector<std::string> value );
  void AppendAttribute( HeaderBase *header );

  void MakeOpenGroup( const std::string &Name, std::string Class );
  /*!< make and open directory of XML.
   "NXmakegroup(.......)" and "NXopengroup(......)" are called
   in this method.
  */
  void CloseGroup();
  /*!< "NXclosegroup(.....) is called in this method."*/
  void CloseData();
  /*!< "NXclosedata(.....) is called in this method."*/

  ////////
  /** template method to write any class with NeXus format
   *  by calling their method T::NXwrite()
   *
   *  WriteNeXusFile should be a friend class of each class.
   *  Each T::NXwrite() methods write their data by calling
   *  WriteNeXusFile::WriteData() method recursively.
   *
   *  @param Name name of data written in NeXus file. If this Name is ""
   *  the element name will be set automatically  as "ManyoLibData".
   *  @param t reference to the object whose class has NXwrite().
   */
  template <class T>
  void WriteData( const std::string &Name, const T &t );

  /** template method to write a stl std::vector of pointers directly.
   *
   *  It is used to write data in UInt4Container (std::vector<std::vector<double> *> vd).
   *
   *  @param t reference to the stl std::vector, all data pointed by t[]
   *  should be written
   */
  template <class T>
  void WriteData( const std::string &Name, const std::vector<T *> &t );

  /** template method to write a stl std::map of any classes
   *
   *  It is used to write data in Manyo's StlMapDouble
   *  (std::map< std::string, std::vector<Double> > M) and
   *   HeaderBase (std::map<std::string, int> _keymap)
   *
   *  @param t reference to the stl std::map
   */
  template <class T>
  void WriteData( const std::string &Name, const std::map<std::string, T> &t );
  ////////

  //void WriteData( const std::string &Name );
  /*!< Write data only with attribute, "Name".*/
  void WriteData( const std::string &Name, UInt4  value );
  /*!< Write data with its name. */
  void WriteData( const std::string &Name, Int4   value );
  /*!< Write data with its name. */
  void WriteData( const std::string &Name, Double value );
  /*!< Write data with its name. */
  void WriteData( const std::string &Name, std::string value );
  /*!< Write data with its name. */
  void WriteData( const std::string &Name, bool value );
  void WriteData( const std::string &Name, const UInt4  *value, UInt4 size );
  /*!< Write data with its name. */
  void WriteData( const std::string &Name, const Int4   *value, UInt4 size );
  /*!< Write data with its name. */
  void WriteData( const std::string &Name, const Double *value, UInt4 size );
  void WriteData( const std::string &Name, const Double *value, UInt4 size1, UInt4 size2 );
  /*!< Write data with its name. */
  void WriteData( const std::string &Name, const std::vector< std::string > &value );
  /*!< Write data with its name. */
  void WriteData( const std::string &Name, const std::vector< UInt4  > &value );
  /*!< Write data with its name. */
  void WriteData( const std::string &Name, const std::vector< Int4   > &value );
  /*!< Write data with its name. */
  void WriteData( const std::string &Name, const std::vector< Double > &value );
  void WriteData( const std::string &Name, const std::vector< std::vector<Double> > &value );
  void WriteDataP( const std::string &Name, std::vector< Double > *value );
  /*!< Write data with its name. */
  void WriteData1( const std::string &Name, ElementContainer EC );
  /*!< Write data with its name.
    If the first argument is "",
    the element name will be set automatically
    as "ElementContainer".
   */

  void WriteData1( const std::string &Name, HeaderBase Header );
  /*!< Write data with its name.
    If the first argument is "",
    the element name will be set automatically
    as "HeaderBase". */

  void WriteData1( const std::string &Name, ElementContainerArray ECA );
  /*!< Write data with its name.
    If the first argument is "",
    the element name will be set automatically
    as "ElementContainerArray". */
  void WriteData1( const std::string &Name, ElementContainerMatrix ECM );
  /*!< Write data with its name.
    If the first argument is "",
    the element name will be set automatically
    as "ElementContainerArray". */

  void WriteData1( const std::string &Name, UInt4ContainerMatrix UCM );
  void WriteData1( const std::string &Name, UInt4ContainerArray  UCA );
  void WriteData1( const std::string &Name, UInt4Container UC );

  void WriteData1( const std::string &Name, TwoDimElementContainer TDEC );

  void MakeLink( NXlink Link );
  /*!< This method is not effective, because the function of link
   is not prepared in NeXus API. The function of "NXgetgroupID()"
   is not work, but "NXgetdataID() can be work well. "
  */



  void WriteDataP( const std::string &Name, ElementContainer *EC );
  void WriteDataP( const std::string &Name, ElementContainerArray *ECA );
  void WriteDataP( const std::string &Name, ElementContainerMatrix *ECM );
  void WriteDataP( const std::string &Name, HeaderBase *Header );
  void WriteDataWithAttribute(const std::string &Name, std::string value, const std::string &attrname, std::string attrvalue);
  /*!< This method is used for creating Nxspe file for Horace software
   *   produced by R. Murasaki @Tohoku Univ (r.murasaki@dc.tohoku.ac.jp)
   */
};

////////////////////////////////////////////////////
template <class T>
void WriteNeXusFile::
WriteData( const std::string &Name, const T &t )
{
  if( Name == "" ){
    MakeOpenGroup( "ManyoLibData", "NXdata" );
  }
  else{
    MakeOpenGroup( Name, "NXdata" );
  }

  // this new WriteData() scheme writes data with an attribute "version"=2
  AppendAttribute("version", 2);    // writes 'version="NX_INT32:2"'
  //NXputattr( handle, "version", (void*)( &v ), 1, NX_UINT32 );

  t.NXwrite(*this);


  NXgetgroupID( handle, &link );
  CloseGroup();
}

////////////////////////////////////////////////////
template <class T>
void WriteNeXusFile::
WriteData( const std::string &Name, const std::vector<T *> &t )
{
  if( Name == "" ){
    MakeOpenGroup( "vector_pointer_data", "NXdata" );
  }
  else{
    MakeOpenGroup( Name, "NXdata" );
  }

  UInt4 size = (UInt4)(t.size());
  WriteData( "size", size );
  static char index[128];
  for( UInt4 i=0; i<size; i++ ){
    std::snprintf(index, sizeof(index), "%s%d", Name.c_str(), i);
    WriteData(std::string(index), *t[i]);
  }

  NXgetgroupID( handle, &link );
  CloseGroup();
}
////////////////////////////////////////////////////
template <class T>
void WriteNeXusFile::
WriteData( const std::string &Name, const std::map<std::string, T> &t )
{
  if( Name == "" ){
    MakeOpenGroup( "map_data", "NXdata" );
  }
  else{
    MakeOpenGroup( Name, "NXdata" );
  }

  for (typename std::map<std::string, T>::const_iterator p=t.begin(),
         end=t.end();p!=end;++p) {
    WriteData(p->first, p->second);
  }

  NXgetgroupID( handle, &link );
  CloseGroup();
}
#endif



/* Test code on Python environment.
e = Neutron.ElementContainer()

w = Neutron.WriteNeXusFile( "nx.nx", "suzuki" )
w.MakeOpenGroup("Entry1","NXentry")
w.MakeOpenGroup("Data1","NXdata")

w.WriteData( "ElementContainerData", e )

w.CloseGroup() # Close Data1
w.CloseGroup() # Close Entry1
del w
 */



/* test code on Python interface
import Neutron
w = Neutron.WriteNeXusFile("nx.nx", "suzuki")
w.AppendAttribute("x", "xxx")
w.AppendAttribute("x", 12)
w.MakeOpenGroup("Entry1","NXentry")
w.MakeOpenGroup("Data1","NXdata")

w.WriteData("user_name", "suzuki")

vui = Neutron.MakeUInt4Vector( 20 )
vi  = Neutron.MakeInt4Vector( 30 )
vd  = Neutron.MakeDoubleVector( 10 )

for x in range( 10 ):
        vd[x] = float( x )

w.WriteData( "vui", vui )
w.AppendAttribute("x", 12)

w.WriteData( "vi",  vi )
w.AppendAttribute("x", 13)

w.WriteData( "vd",  vd )
w.AppendAttribute("x", 14)

s = Neutron.MakeStringVector()
s.append( "ooho" )
s.append( "kasuga" )
s.append( "tsukuba" )
s.append( "Ibarakiken Tsukubashi kasuga" )
w.WriteData( "s", s  )

w.CloseGroup() # Close Data1
w.CloseGroup() # Close Entry1
del w
 */
