//---------------------------------------------------------------------------//

#ifndef IMCKEY
#define IMCKEY

#include <iomanip>
#include <map>
#include <string>
#include <sstream>
#include <vector>

//---------------------------------------------------------------------------//

namespace imc
{
  // define "magic bytes" announcing critical (=C) non-critical keys (=N)
  const unsigned char key_crit_ = 0x43, key_non_crit_ = 0x4e;

  // define properties of marker/key
  struct key
  {
    // (non)critical key
    bool critical_;
    // name of key (two uppercase letters)
    std::string name_;
    // short description
    std::string description_;
    // version of marker
    int version_;

    // constructor
    key(bool critical, std::string name,
        std::string description = std::string(""), int version = -1)
    {
      critical_ = critical;
      if ( name.size() != 2 ) throw std::logic_error("invalid key name");
      name_ = name;
      description_ = description;
      version_ = version;
    }

    // comparison operator
    bool operator==(const key& akey)
    {
      return ( this->critical_ == akey.critical_
            && this->name_ == akey.name_
            && this->description_ == akey.description_
            && this->version_ == akey.version_ );
    }

    // get info string
    std::string get_info(int width = 20)
    {
      std::stringstream ss;
      ss<<std::setw(width)<<std::left<<"critical:"<<(critical_?"yes":"no")<<"\n"
        <<std::setw(width)<<std::left<<"name:"<<name_<<"\n"
        <<std::setw(width)<<std::left<<"description:"<<description_<<"\n"
        <<std::setw(width)<<std::left<<"version:"<<version_<<"\n";
      return ss.str();
    }

  };

  const std::vector<key> keys = {

    // critical keys
    key(true,"CF","format version and processor",2),
    key(true,"CK","start of group of keys",1),
    key(true,"CB","group of channels",1),
    key(true,"CT","text definition",1),
    key(true,"CG","group of components",1),
    key(true,"CD","abscissa description",1),
    key(true,"CD","abscissa description",2),
    key(true,"CZ","scaling of z-axis",1),
    key(true,"CC","start of component",1),
    key(true,"CP","buffer, datatype and samples of component",1),
    key(true,"Cb","buffer description",1),
    key(true,"CR","permissible range of values in component",1),
    key(true,"CN","name and comment of channel",1),
    key(true,"CS","raw binary data",1),
    key(true,"CI","single numerical value",1),
    key(true,"Ca","add reference key",1),

    // noncritical keys
    key(false,"NO","origin of data",1),
    key(false,"NT","timestamp of trigger",1),
    key(false,"NT","timestamp of trigger",2),
    key(false,"ND","(color) display properties",1),
    key(false,"NU","user defined key",1),
    key(false,"Np","property of channel",1),
    key(false,"NE","extraction rule for BUS channels",1),
    key(false,"NL","language info and code page",1)

  };

  // check for existence of specific key
  bool check_key(key& mykey)
  {
    for ( key ky: keys ) if ( mykey == ky ) return true;
    return false;
  }

  // get key (with respect to name and evtl. version)
  key get_key(bool critical, std::string name, int version = -1)
  {
    // check validity of required key name
    if ( name.size() > 2 || name.size() < 1 )
    {
      throw std::runtime_error(std::string("invalid key name: ") + name);
    }

    // declare new key with available data
    key mykey(critical,name,std::string(""),version);

    // try to find matching key in list of predefined keys
    for ( key ky: keys )
    {
      if ( critical == ky.critical_ && name == ky.name_
           && ( version == ky.version_ || version == -1 ) )
      {
        mykey = ky;

        // provide first match
        return mykey;
      }
    }

    return mykey;
  }


}

#endif

//---------------------------------------------------------------------------//
