/* -*- C++ -*- */

/****************************************************************************
** Copyright (c) quickfixengine.org  All rights reserved.
**
** This file is part of the QuickFIX FIX Engine
**
** This file may be distributed under the terms of the quickfixengine.org
** license as defined by quickfixengine.org and appearing in the file
** LICENSE included in the packaging of this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.quickfixengine.org/LICENSE for licensing information.
**
** Contact ask@quickfixengine.org if any conditions of this licensing are
** not clear to you.
**
****************************************************************************/

#ifndef FIX_DATADICTIONARY_H
#define FIX_DATADICTIONARY_H

#ifdef _MSC_VER
#pragma warning( disable : 4503 4355 4786 4290 )
#endif

#include "Fields.h"
#include "FieldMap.h"
#include "DOMDocument.h"
#include "Exceptions.h"
#include <set>
#include <map>
#include <string.h>

namespace FIX
{
class FieldMap;
class Message;

/**
 * Represents a data dictionary for a version of %FIX.
 *
 * Generally loaded from an XML document.  The DataDictionary is also
 * responsible for validation beyond the basic structure of a message.
 */

class DataDictionary
{
  typedef std::set < int > MsgFields;
  typedef std::map < std::string, MsgFields > MsgTypeToField;
  typedef std::set < std::string > MsgTypes;
  typedef std::set < int > Fields;
  typedef std::map < int, bool > NonBodyFields;
  typedef std::vector< int > OrderedFields;
  typedef int* OrderedFieldsArray;
  typedef std::map < int, TYPE::Type > FieldTypes;
  typedef std::set < std::string > Values;
  typedef std::map < int, Values > FieldToValue;
  typedef std::map < int, std::string > FieldToName;
  typedef std::map < std::string, int > NameToField;
  typedef std::map < std::pair < int, std::string > ,
  std::string  > ValueToName;
  typedef std::map < std::pair < std::string, int > ,
  std::pair < int, DataDictionary* > > FieldToGroup;

public:
  DataDictionary();
  DataDictionary( const DataDictionary& copy );
  DataDictionary( std::istream& stream ) throw( ConfigError );
  DataDictionary( const std::string& url ) throw( ConfigError );
  virtual ~DataDictionary();

  void readFromURL( const std::string& url ) throw( ConfigError );
  void readFromDocument( DOMDocumentPtr pDoc ) throw( ConfigError );
  void readFromStream( std::istream& stream ) throw( ConfigError );

  int* getOrderedFields() const;

  // storage functions
  void setVersion( const std::string& beginString )
  {
    m_beginString = beginString;
    m_hasVersion = true;
  }
  std::string getVersion() const
  {
    return m_beginString.getString();
  }

  void addField( int field )
  {
    m_fields.insert( field );
    m_orderedFields.push_back( field );
  }

  void addFieldName( int field, const std::string& name )
  {
    if( m_names.insert( std::make_pair(name, field) ).second == false )
      throw ConfigError( "Field named " + name + " defined multiple times" );
    m_fieldNames[field] = name;
  }

  bool getFieldName( int field, std::string& name ) const
  {
    FieldToName::const_iterator i = m_fieldNames.find( field );
    if(i == m_fieldNames.end()) return false;
    name = i->second;
    return true;
  }

  bool getFieldTag( std::string name, int& field ) const
  {
    NameToField::const_iterator i = m_names.find( name );
    if(i == m_names.end()) return false;
    field = i->second;
    return true;
  }

  void addValueName( int field, const std::string& value, const std::string& name )
  {
    m_valueNames[std::make_pair(field, value)] = name;
  }

  bool getValueName( int field, const std::string& value, std::string& name ) const
  {
    ValueToName::const_iterator i = m_valueNames.find( std::make_pair(field, value) );
    if(i == m_valueNames.end()) return false;
    name = i->second;
    return true;
  }

  bool isField( int field ) const
  {
    return m_fields.find( field ) != m_fields.end();
  }

  void addMsgType( const std::string& msgType )
  {
    m_messages.insert( msgType );
  }

  bool isMsgType( const std::string& msgType ) const
  {
    return m_messages.find( msgType ) != m_messages.end();
  }

  void addMsgField( const std::string& msgType, int field )
  {
    m_messageFields[ msgType ].insert( field );
  }

  bool isMsgField( const std::string& msgType, int field ) const
  {
    MsgTypeToField::const_iterator i = m_messageFields.find( msgType );
    if ( i == m_messageFields.end() ) return false;
    return i->second.find( field ) != i->second.end();
  }

  void addHeaderField( int field, bool required )
  {
    m_headerFields[ field ] = required;
  }

  bool isHeaderField( int field ) const
  {
    return m_headerFields.find( field ) != m_headerFields.end();
  }

  void addTrailerField( int field, bool required )
  {
    m_trailerFields[ field ] = required;
  }

  bool isTrailerField( int field ) const
  {
    return m_trailerFields.find( field ) != m_trailerFields.end();
  }

  void addFieldType( int field, FIX::TYPE::Type type )
  {
    m_fieldTypes[ field ] = type;
  }

  bool getFieldType( int field, FIX::TYPE::Type& type ) const
  {
    FieldTypes::const_iterator i = m_fieldTypes.find( field );
    if ( i == m_fieldTypes.end() ) return false;
    type = i->second;
    return true;
  }

  void addRequiredField( const std::string& msgType, int field )
  {
    m_requiredFields[ msgType ].insert( field );
  }

  bool isRequiredField( const std::string& msgType, int field ) const
  {
    MsgTypeToField::const_iterator i = m_requiredFields.find( msgType );
    if ( i == m_requiredFields.end() ) return false;
    return i->second.find( field ) != i->second.end();
  }

  void addFieldValue( int field, const std::string& value )
  {
    m_fieldValues[ field ].insert( value );
  }

  bool hasFieldValue( int field ) const
  {
    FieldToValue::const_iterator i = m_fieldValues.find( field );
    return i != m_fieldValues.end();
  }

  bool isFieldValue( int field, const std::string& value ) const
  {
    FieldToValue::const_iterator i = m_fieldValues.find( field );
    if ( i == m_fieldValues.end() )
      return false;
    if( !isMultipleValueField( field ) )
      return i->second.find( value ) != i->second.end();

    // MultipleValue
    std::string::size_type startPos = 0;
    std::string::size_type endPos = 0;
    do
    {
      endPos = value.find_first_of(' ', startPos);
      std::string singleValue =
        value.substr( startPos, endPos - startPos );
      if( i->second.find( singleValue ) == i->second.end() )
        return false;
      startPos = endPos + 1;
    } while( endPos != std::string::npos );
    return true;
  }

  void addGroup( const std::string& msg, int field, int delim,
                 const DataDictionary& dataDictionary )
  {
    DataDictionary * pDD = new DataDictionary;
    *pDD = dataDictionary;
    pDD->setVersion( getVersion() );
    m_groups[ std::make_pair( msg, field ) ] = std::make_pair( delim, pDD );
  }

  bool isGroup( const std::string& msg, int field ) const
  {
    return m_groups.find( std::make_pair( msg, field ) ) != m_groups.end();
  }

  bool getGroup( const std::string& msg, int field, int& delim,
                 const DataDictionary*& pDataDictionary ) const
  {
    FieldToGroup::const_iterator i =
      m_groups.find( std::make_pair( msg, field ) );
    if ( i == m_groups.end() ) return false;
    std::pair < int, DataDictionary* > pair = i->second;
    delim = pair.first;
    pDataDictionary = pair.second;
    return true;
  }

  bool isDataField( int field ) const
  {
    FieldTypes::const_iterator i = m_fieldTypes.find( field );
    return i != m_fieldTypes.end() && i->second == TYPE::Data;
  }

  bool isMultipleValueField( int field ) const
  {
    FieldTypes::const_iterator i = m_fieldTypes.find( field );
    return i != m_fieldTypes.end() 
      && (i->second == TYPE::MultipleValueString 
          || i->second == TYPE::MultipleCharValue 
          || i->second == TYPE::MultipleStringValue );
  }

  void checkFieldsOutOfOrder( bool value )
  { m_checkFieldsOutOfOrder = value; }
  void checkFieldsHaveValues( bool value )
  { m_checkFieldsHaveValues = value; }
  void checkUserDefinedFields( bool value )
  { m_checkUserDefinedFields = value; }

  /// Validate a message.
  static void validate( const Message& message,
                        const DataDictionary* const pSessionDD,
                        const DataDictionary* const pAppID ) throw( FIX::Exception );

  void validate( const Message& message ) const throw ( FIX::Exception )
  { validate( message, false ); }
  void validate( const Message& message, bool bodyOnly ) const throw( FIX::Exception )
  { validate( message, bodyOnly ? (DataDictionary*)0 : this, this ); }

  DataDictionary& operator=( const DataDictionary& rhs );

private:
  /// Iterate through fields while applying checks.
  void iterate( const FieldMap& map, const MsgType& msgType ) const;

  /// Check if message type is defined in spec.
  void checkMsgType( const MsgType& msgType ) const
  {
    if ( !isMsgType( msgType.getValue() ) )
      throw InvalidMessageType();
  }

  /// If we need to check for the tag in the dictionary
  bool shouldCheckTag( const FieldBase& field ) const
  {
    if( !m_checkUserDefinedFields && field.getField() >= FIELD::UserMin )
      return false;
    else
      return true;
  }

  /// Check if field tag number is defined in spec.
  void checkValidTagNumber( const FieldBase& field ) const
  throw( InvalidTagNumber )
  {
    if( m_fields.find( field.getField() ) == m_fields.end() )
      throw InvalidTagNumber( field.getField() );
  }

  void checkValidFormat( const FieldBase& field ) const
  throw( IncorrectDataFormat )
  {
    try
    {
      TYPE::Type type = TYPE::Unknown;
      getFieldType( field.getField(), type );
      switch ( type )
      {
      case TYPE::String:
        STRING_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Char:
        CHAR_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Price:
        PRICE_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Int:
        INT_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Amt:
        AMT_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Qty:
        QTY_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Currency:
        CURRENCY_CONVERTOR::convert( field.getString() ); break;
      case TYPE::MultipleValueString:
        MULTIPLEVALUESTRING_CONVERTOR::convert( field.getString() ); break;
      case TYPE::MultipleStringValue:
        MULTIPLESTRINGVALUE_CONVERTOR::convert( field.getString() ); break;
      case TYPE::MultipleCharValue:
        MULTIPLECHARVALUE_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Exchange:
        EXCHANGE_CONVERTOR::convert( field.getString() ); break;
      case TYPE::UtcTimeStamp:
        UTCTIMESTAMP_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Boolean:
        BOOLEAN_CONVERTOR::convert( field.getString() ); break;
      case TYPE::LocalMktDate:
        LOCALMKTDATE_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Data:
        DATA_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Float:
        FLOAT_CONVERTOR::convert( field.getString() ); break;
      case TYPE::PriceOffset:
        PRICEOFFSET_CONVERTOR::convert( field.getString() ); break;
      case TYPE::MonthYear:
        MONTHYEAR_CONVERTOR::convert( field.getString() ); break;
      case TYPE::DayOfMonth:
        DAYOFMONTH_CONVERTOR::convert( field.getString() ); break;
      case TYPE::UtcDate:
        UTCDATE_CONVERTOR::convert( field.getString() ); break;
      case TYPE::UtcTimeOnly:
        UTCTIMEONLY_CONVERTOR::convert( field.getString() ); break;
      case TYPE::NumInGroup:
        NUMINGROUP_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Percentage:
        PERCENTAGE_CONVERTOR::convert( field.getString() ); break;
      case TYPE::SeqNum:
        SEQNUM_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Length:
        LENGTH_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Country:
        COUNTRY_CONVERTOR::convert( field.getString() ); break;
      case TYPE::TzTimeOnly:
        TZTIMEONLY_CONVERTOR::convert( field.getString() ); break;
      case TYPE::TzTimeStamp:
        TZTIMESTAMP_CONVERTOR::convert( field.getString() ); break;
      case TYPE::XmlData:
        XMLDATA_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Language:
        LANGUAGE_CONVERTOR::convert( field.getString() ); break;
      case TYPE::Unknown: break;
      }
    }
    catch ( FieldConvertError& )
    { throw IncorrectDataFormat( field.getField(), field.getString() ); }
  }

  void checkValue( const FieldBase& field ) const
  throw( IncorrectTagValue )
  {
    if ( !hasFieldValue( field.getField() ) ) return ;

    const std::string& value = field.getString();
    if ( !isFieldValue( field.getField(), value ) )
      throw IncorrectTagValue( field.getField() );
  }

  /// Check if a field has a value.
  void checkHasValue( const FieldBase& field ) const
  throw( NoTagValue )
  {
    if ( m_checkFieldsHaveValues && !field.getString().length() )
      throw NoTagValue( field.getField() );
  }

  /// Check if a field is in this message type.
  void checkIsInMessage
  ( const FieldBase& field, const MsgType& msgType ) const
  throw( TagNotDefinedForMessage )
  {
    if ( !isMsgField( msgType, field.getField() ) )
      throw TagNotDefinedForMessage( field.getField() );
  }

  /// Check if group count matches number of groups in
  void checkGroupCount
  ( const FieldBase& field, const FieldMap& fieldMap, const MsgType& msgType ) const
  throw( RepeatingGroupCountMismatch )
  {
    int fieldNum = field.getField();
    if( isGroup(msgType, fieldNum) )
    {
      if( fieldMap.groupCount(fieldNum)
        != IntConvertor::convert(field.getString()) )
      throw RepeatingGroupCountMismatch(fieldNum);
    }
  }

  /// Check if a message has all required fields.
  void checkHasRequired
  ( const FieldMap& header, const FieldMap& body, const FieldMap& trailer,
    const MsgType& msgType ) const
  throw( RequiredTagMissing )
  {
    NonBodyFields::const_iterator iNBF;
    for( iNBF = m_headerFields.begin(); iNBF != m_headerFields.end(); ++iNBF )
    {
      if( iNBF->second == true && !header.isSetField(iNBF->first) )
        throw RequiredTagMissing( iNBF->first );
    }

    for( iNBF = m_trailerFields.begin(); iNBF != m_trailerFields.end(); ++iNBF )
    {
      if( iNBF->second == true && !trailer.isSetField(iNBF->first) )
        throw RequiredTagMissing( iNBF->first );
    }

    MsgTypeToField::const_iterator iM
      = m_requiredFields.find( msgType.getString() );
    if ( iM == m_requiredFields.end() ) return ;

    const MsgFields& fields = iM->second;
    MsgFields::const_iterator iF;
    for( iF = fields.begin(); iF != fields.end(); ++iF )
    {
      if( !body.isSetField(*iF) )
        throw RequiredTagMissing( *iF );
    }

    FieldMap::g_iterator groups;
    for( groups = body.g_begin(); groups != body.g_end(); ++groups )
    {
      int delim;
      const DataDictionary* DD = 0;
      int field = groups->first;
      if( getGroup( msgType.getValue(), field, delim, DD ) )
      {
        std::vector<FieldMap*>::const_iterator group;
        for( group = groups->second.begin(); group != groups->second.end(); ++group )
          DD->checkHasRequired( **group, **group, **group, msgType );
      }
    }
  }

  /// Read XML file using MSXML.
  void readMSXMLDOM( const std::string& );
  void readMSXML( const std::string& );
  /// Read XML file using libXML.
  void readLibXml( const std::string& );

  int lookupXMLFieldNumber( DOMDocument*, DOMNode* ) const;
  int lookupXMLFieldNumber( DOMDocument*, const std::string& name ) const;
  int addXMLComponentFields( DOMDocument*, DOMNode*, const std::string& msgtype, DataDictionary&, bool );
  void addXMLGroup( DOMDocument*, DOMNode*, const std::string& msgtype, DataDictionary&, bool );
  TYPE::Type XMLTypeToType( const std::string& xmlType ) const;

  bool m_hasVersion;
  bool m_checkFieldsOutOfOrder;
  bool m_checkFieldsHaveValues;
  bool m_checkUserDefinedFields;
  BeginString m_beginString;
  MsgTypeToField m_messageFields;
  MsgTypeToField m_requiredFields;
  MsgTypes m_messages;
  Fields m_fields;
  OrderedFields m_orderedFields;
  mutable OrderedFieldsArray m_orderedFieldsArray;
  NonBodyFields m_headerFields;
  NonBodyFields m_trailerFields;
  FieldTypes m_fieldTypes;
  FieldToValue m_fieldValues;
  FieldToName m_fieldNames;
  NameToField m_names;
  ValueToName m_valueNames;
  FieldToGroup m_groups;
};
}

#endif //FIX_DATADICTIONARY_H
