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

#ifndef READNEXUSFILE
#define READNEXUSFILE

#include <map>
#include "Header.hh"
#include "HeaderBase.hh"
#include "SplitString.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"

//! Read NeXus binary file.
/*!
  NeXus binary files can be read with this class.
  and NeXus C-API is utilized in this class.
  "ElementContainer" and the other data-container in Manyo-Lib
  will be constructed from NeXus file with this class.
  The sample codes for the Python environment is attached
  at the end of this file, "ReadNeXusFile.hh".
 */

class ReadNeXusFile
{
private:
  SplitString *ss;
  UInt4 w_handleFlag;
protected:
  NXhandle handle;
  void OpenNeXusFile( const std::string &FileName );
  void CloseNeXusFile();
  Int4 CurrentNumber;

public:
  ReadNeXusFile( const std::string &FileName );
  ReadNeXusFile( NXhandle w_handle );
 ~ReadNeXusFile();

  NXhandle PutHandle(){ return handle; }

  std::vector<std::string> GetGroupInfo();
  /*!<
    Returns std::vector<std::string>, and set CurrentNumber.
    The size of the vector is two.
    "NXgetgroupinfo(....)" is executed in this method.
    The first element of the vector is GroupName, and that of
    second element is GroupClass. The second argument of
    "NXgetgroupinfo(....)" can extract with "PutCurrentNumber()".
   */
  std::vector<std::string> GetNextEntry();
  /*!<
    Returns std::vector<std::string>, and set CurrentNumber.
    The size of the vector is two.
    "NXgetnextentry(......)" is executed in this method.
    The first and second elements of the vector are
    "GroupName" and "GroupClass". The second argument
    of "NXgetnextentry(.....)" is set into CurrentNumber,
    and its value is obtained with "PutCurrentNumber()".
   */
  std::vector<Int4>   GetInfo();
  Int4 PutCurrentNumber(){ return CurrentNumber; };
  /*!<
    Returns std::vector<Int4> and set CurrentNumber.
    The size of the vector is two.
    "NXgetinfo(.....)" is executed in this method.
    The first and second elements of the vector
    are Dimension and DataType.
    The Current number is "Rank", which is the third argument value
    of "NXgetinfo(.....)".
    "CurrentNumber" is obtained with "PutCurrentNumber()".
   */


  void OpenGroup( const std::string &GroupName, const std::string &GroupClass );
  /*!<
    Open groups. The first and second argument of this method
    will be obtained with "GetNextEntry()".
    The first and second element of the vector
    obtained with "GetNextEntry()" are set into the first and second
    argument of this method.
   */
  void CloseGroup(){ NXclosegroup( handle ); }
  /*!< Close group.*/
  void OpenData( const std::string &DataSetName );
  /*!<
    Open data directories.
    The argument, "DataSetName" is obtained
    with "GetNextEntry()" is the name of each value.
   */
  void CloseData(){ NXclosedata( handle ); }
  /*!<
    Close data directory which is opened by "OpenData(std::string)".
   */

  void ReadData(const std::string &Name, UInt4 &i);
  void ReadData(const std::string &Name, Int4 &i);
  void ReadData(const std::string &Name, Double &d);
  void ReadData(const std::string &Name, std::string &s);
  void ReadData(const std::string &Name, std::vector<UInt4> &vi);
  void ReadData(const std::string &Name, std::vector<Int4> &vi);
  void ReadData(const std::string &Name, std::vector<Double> &vd);
  void ReadData(const std::string &Name, std::vector<std::string> &vs);
  void ReadData(const std::string &Name, std::vector< std::vector<Double> > &vvd);
  void ReadData(const std::string &Name, bool &f);
  std::vector<Int4> GetInfo2d();

  UInt4 ReadDataUInt4( const std::string &Name ) {
    UInt4 i;
    ReadData(Name, i);
    return i;
  }
  /*!< Read data. The data-directory is opened and closed
    in this method.
   */
  Int4 ReadDataInt4( const std::string &Name ) {
    Int4 i;
    ReadData(Name, i);
    return i;
  }
  /*!< Read data. The data-directory is opened and closed
    in this method.*/
  Double ReadDataDouble( const std::string &Name ) {
    Double d;
    ReadData(Name, d);
    return d;
  }
  /*!< Read data. The data-directory is opened and closed
  in this method.*/
  std::string ReadDataString( const std::string &Name ) {
    std::string s;
    ReadData(Name, s);
    return s;
  }
  /*!< Read data. The data-directory is opened and closed
    in this method.*/

  std::vector<Double> ReadDoubleVector( const std::string &Name ) {
    std::vector<Double> vd;
    ReadData(Name, vd);
    return vd;
  }
  /*!< Read data. The data-directory is opened and closed
    in this method.*/
  std::vector<Int4>   ReadInt4Vector( const std::string &Name ) {
    std::vector<Int4> vi;
    ReadData(Name, vi);
    return vi;
  }
  /*!< Read data. The data-directory is opened and closed
    in this method.*/
  std::vector<UInt4>  ReadUInt4Vector( const std::string &Name ) {
    std::vector<UInt4> vi;
    ReadData(Name, vi);
    return vi;
  }
  /*!< Read data. The data-directory is opened and closed
    in this method.*/
  std::vector<std::string> ReadStringVector( const std::string &Name ) {
    std::vector<std::string> vs;
    ReadData(Name, vs);
    return vs;
  }
  /*!< Read data. The data-directory is opened and closed
    in this method.*/



  HeaderBase ReadHeaderBase( const std::string &Name ) {
    HeaderBase header;
    ReadData(Name, header);
    return header;
  }
  ElementContainer ReadElementContainer( const std::string &Name ) {
    ElementContainer EC;
    ReadData(Name, EC);
    return EC;
  }

  ElementContainer *ReadElementContainerP( const std::string &Name ) {
    ElementContainer *EC = new ElementContainer();
    ReadData(Name, *EC);
    return EC;
  }

  ElementContainerArray ReadElementContainerArray( const std::string &Name ) {
    ElementContainerArray ECA;
    ReadData(Name, ECA);
    return ECA;
  }

  ElementContainerArray *ReadElementContainerArrayP( const std::string &Name ) {
    ElementContainerArray *ECA = new ElementContainerArray();
    ReadData(Name, *ECA);
    return ECA;
  }

  ElementContainerMatrix ReadElementContainerMatrix( const std::string &Name ) {
    ElementContainerMatrix ECM;
    ReadData(Name, ECM);
    return ECM;
  }

  ElementContainerMatrix *ReadElementContainerMatrixP( const std::string &Name ) {
    ElementContainerMatrix *ECM = new ElementContainerMatrix();
    ReadData(Name, *ECM);
    return ECM;
  }

  UInt4Container ReadUInt4Container( const std::string &Name ) {
    UInt4Container UC;
    ReadData(Name, UC);
    return UC;
  }
  UInt4ContainerArray ReadUInt4ContainerArray( const std::string &Name ) {
    UInt4ContainerArray UCA;
    ReadData(Name, UCA);
    return UCA;
  }
  UInt4ContainerMatrix  ReadUInt4ContainerMatrix( const std::string &Name ) {
    UInt4ContainerMatrix UCM;
    ReadData(Name, UCM);
    return UCM;
  }
  ////////


  /** proxy class to read NeXus file by using ReadData2() or ReadData1()
   * for "version" = 2 or unversioned
   *
   *  @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 NXread(),
   *  be to resized and overwritten
   */
  template <class T>
  void ReadData( const std::string &Name, T &t ) {
    OpenGroup( Name, "NXdata" );

    try {
      UInt4 v = GetAttribute2<UInt4>("version");

      if (v==2) {
        ReadData2(t);
      } else {
        std::cerr << "ReadNeXusFile::ReadData(): unsupported version: "
                  << v << std::endl;
      }
    }
    catch (NXstatus s) {
      // no "version" means version1 (old code)
      ReadData1(t);
    }

    CloseGroup();
  }


  /** template method to read a stl std::map of any classes
   *
   *  It is used to read 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 (overwritten)
   */
  template <class T>
  void ReadData( const std::string &Name, std::map<std::string, T> &t );


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


private:
  /** template method to read any class with NeXus format
   *  by calling their method T::NXread()
   *
   *
   *  @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 NXread(),
   *  be to resized and overwritten
   */
  template <class T>
  void ReadData2(T &t ) {
    t.NXread(*this);
  }

  /** Dummy to avoid compilation error
   *   (there are no need to read Map with ReadData1())
   *
   */
  template <class T>
  void ReadData1( Map<T> &H ) {
    std::cout << "CAUTION: ReadNeXusFile::ReadData1(const std::string &Name, Map<T> &H) shouldn't be called" << std::endl;
  }

  /** Dummy to avoid compilation error
   *   (there are no need to read StlMapDouble with ReadData1())
   *
   */
  void ReadData1( StlMapDouble &H ) {
    std::cout << "CAUTION: ReadNeXusFile::ReadData1(const std::string &Name, StlMapDouble &H) shouldn't be called" << std::endl;
  }

  void ReadData1( HeaderBase &H );
  void ReadData1( ElementContainer &EC );
  void ReadData1( ElementContainerArray &ECA );
  void ReadData1( ElementContainerMatrix &ECM );
  void ReadData1( UInt4Container &UC );
  void ReadData1( UInt4ContainerArray &UCA );
  void ReadData1( UInt4ContainerMatrix &UCM );
  ////////


public:
  TwoDimElementContainer ReadTwoDimElementContainer( const std::string &Name );

private:
  ElementContainer ReadElementContainer1( const std::string &Name ) {
    ElementContainer EC;
    OpenGroup( Name, "NXdata" );
    ReadData1(EC);
    CloseGroup();
    return EC;
  }
  /*!< Read data. The data-directory is opened and closed
    in this method.*/

  HeaderBase ReadHeaderBase1( const std::string &Name ) {
    HeaderBase HB;
    OpenGroup( Name, "NXdata" );
    ReadData1(HB);
    CloseGroup();
    return HB;
  }
  /*!< Read data. The data-directory is opened and closed
   in this method.*/

  ElementContainerArray ReadElementContainerArray1( const std::string &Name ) {
    ElementContainerArray ECA;
    OpenGroup( Name, "NXdata" );
    ReadData1(ECA);
    CloseGroup();
    return ECA;
  }
  /*!< Read data. The data-directory is opened and closed
   in this method.*/

  ElementContainerMatrix ReadElementContainerMatrix1( const std::string &Name ) {
    ElementContainerMatrix ECM;
    OpenGroup( Name, "NXdata" );
    ReadData1(ECM);
    CloseGroup();
    return ECM;
  }
  /*!< Read data. The data-directory is opened and closed
   in this method.*/


  UInt4ContainerMatrix  ReadUInt4ContainerMatrix1( const std::string &Name ) {
    UInt4ContainerMatrix UCM;
    OpenGroup( Name, "NXdata" );
    ReadData1(UCM);
    CloseGroup();
    return UCM;
  }
  /*!< Read data. The data-directory is opened and closed
   in this method.*/

  UInt4ContainerArray ReadUInt4ContainerArray1( const std::string &Name ) {
    UInt4ContainerArray UCA;
    OpenGroup( Name, "NXdata" );
    ReadData1(UCA);
    CloseGroup();
    return UCA;
  }
  /*!< Read data. The data-directory is opened and closed
   in this method.*/

  UInt4Container ReadUInt4Container1( const std::string &Name ) {
    UInt4Container UC;
    OpenGroup( Name, "NXdata" );
    ReadData1(UC);
    CloseGroup();
    return UC;
  }
  /*!< Read data. The data-directory is opened and closed
   in this method.*/


  /** get Attribute if exist otherwise throw an exception
   *
   * use this within try {} catch () {} structure
   *
   * This function shold not be called twice in a group.
   * because in this function NXgetnextattr() is used.
   *
   * This is a template function, so you should call it like
   * Int4 ver = GetAttribute2<Int4>(name);
   *
   * @param handle
   * @param Name    attribute name
   * @return value of attribute
   */
  template <class T>
  T GetAttribute2(const std::string &Name);


  /** This method is used only to obtain version attribute from GetAttribute2()
   *
   */
  template <class T>
  UInt4 GetAttribute3( NXhandle handle, std::string Name, T *value,
                       Int4 DataLength, Int4 DataType ) {
    return NXgetattr( handle, const_cast<char *>(Name.c_str()), static_cast<void *>(value),
                    &DataLength, &DataType );
  }

public:
  /** call NXgetnextentry directly
   *
   * @param Name    NXname(char[128]) for entry name
   * @param nxclass    NXname(char[128]) for class name, maybe
   * @param type    maybe a data type
   * @return    NX_OK(1), NX_ERROR(0), NX_EOD(-1)
   */
  NXstatus GetNextEntry2( char *Name, char *nxclass, int *type) {
    return NXgetnextentry(handle, Name, nxclass, type);
  }
};

////////////////////////////////////////////////////
template <class T>
void ReadNeXusFile::
ReadData( const std::string &Name, std::map<std::string, T> &t )
{
  OpenGroup( Name, "NXdata" );

  NXname _name, _class;
  int _id, _status;

  std::vector<std::string> keys;
  while ((_status=GetNextEntry2(_name, _class, &_id))==1) {
//    std::cerr << "NEXTENTRY: " << _name << "," << _class << ", "
//              << _id << "," << _status << std::endl;
    std::string _s(_name);
    ReadData(_s, t[_s]);
    keys.push_back(_s);
  }

  for (typename std::map<std::string, T>::iterator p=t.begin(),
       end=t.end();p!=end;++p) {
    // if p->first (key) does not exist in keys std::vector
    // then p (keys, val) should be removed
    if ((std::find(keys.begin(), keys.end(), p->first))==keys.end())
      t.erase(p);
    // is this code really safe?
  }

  CloseGroup();
}

////////////////////////////////////////////////////
template <class T>
void ReadNeXusFile::
ReadData( const std::string &Name, std::vector<T *> &t )
{
  OpenGroup( Name, "NXdata" );
  Int4 newsize = ReadDataInt4("size");
  Int4 cursize = (Int4)(t.size());

  for (signed int i=newsize;i<cursize;++i)
    delete t[i];
  t.resize(newsize);
  for (signed int i=cursize;i<newsize;++i)
    t[i] = new T;

  UInt4 i=0;
  static char index[128];
  for (typename std::vector<T *>::iterator p=t.begin(),
       end=t.end();p!=end;++p, ++i) {
    std::snprintf(index, sizeof(index), "%s%d", Name.c_str(), i);
    ReadData(index, **p);
  }

  CloseGroup();
}

///////////////////////////////////////
template <class T>
T ReadNeXusFile::
GetAttribute2(const std::string &Name){
  NXstatus status = 0;

  while( status != -1 ){
    NXname AttrName;
    Int4 Length;
    Int4 Type;
    //status = NXgetnextattr( handle, AttrName, &Length, &Type );
    int rank;
    int d[100];
    status = NXgetnextattra( handle, AttrName, &rank, d, &Type );
    Length = d[rank-1];
    //std::cout << "AttrName = " << AttrName << std::endl;
    std::string StAttrName = std::string( AttrName );
    //std::cout << "StAttrName = " << StAttrName << " " << Name << std::endl;
    if( StAttrName == Name ){
      T val;

      GetAttribute3<T>(handle, Name, &val, Length, Type);
      // following doesn't work
      //NXgetattr( handle, const_cast<char *>(Name.c_str()), (void*)(val), &Length, &Type );

      return val;
    }
  }

  // if attribute is not exist
  throw (-1); // NXstatus
}
#endif


/* Test code on Python environment.
r = Neutron.ReadNeXusFile( "nx.nx" )

vs = r.GetNextEntry()
r.OpenGroup( vs[0], vs[1] )

vs = r.GetNextEntry()
r.OpenGroup( vs[0], vs[1] )

vs = r.GetNextEntry()
E = r.ReadElementContainer( vs[0] )

r.CloseGroup()
r.CloseGroup()
del r


 */
