#ifndef CPP_ENROLL_COLLECTION_H_
#define CPP_ENROLL_COLLECTION_H_

#include <vector>
#include "cppcades.h"
#include "CPPEnrollObjectId.h"
#include "CPPEnrollX509Extension.h"
#include "CPPEnrollX509Attribute.h"
#include "CPPEnrollCryptAttribute.h"
#include "CPPEnrollCspAlgorithm.h"
#include "CPPEnrollCspInformation.h"
#include "CPPEnrollCspStatus.h"
#include "CPPEnrollPolicyQualifier.h"
#include "CPPEnrollCertificatePolicy.h"
#include "CPPEnrollX509NameValuePair.h"
#include "CPPEnrollReaderMode.h"

namespace CryptoPro {
namespace PKI {
namespace Enroll {

class CPPEnrollObjectId;
class CPPEnrollX509Extension;
class CPPEnrollX509Attribute;
class CPPEnrollCryptAttribute;
class CPPEnrollCspAlgorithm;
class CPPEnrollCspInformation;
class CPPEnrollCspStatus;
class CPPEnrollPolicyQualifier;
class CPPEnrollCertificatePolicy;
class CPPEnrollX509NameValuePair;
class CPPEnrollX509NameValuePairs;
class CPPEnrollReaderMode;

template <class T> class CPPEnrollCollection
{
public:
    CPPEnrollCollection<T>(void){};
    virtual ~CPPEnrollCollection<T>(void);

    virtual HRESULT Add(const boost::shared_ptr<T>& value);
    virtual HRESULT get_Count(unsigned int* pVal);
    virtual HRESULT get_ItemByIndex(long Index, boost::shared_ptr<T>& pVal);
    std::vector<boost::shared_ptr<T> > m_value;
private:
    DISALLOW_COPY_AND_ASSIGN(CPPEnrollCollection<T>);
};

template <class T>
CPPEnrollCollection<T>::~CPPEnrollCollection(void)
{
}

template <class T>
HRESULT CPPEnrollCollection<T>::Add(const boost::shared_ptr<T>& value)
{
    try
    {
        m_value.push_back(value);
    }
    CPPCADESCATCH;
    return S_OK;
}

template <class T>
HRESULT CPPEnrollCollection<T>::get_Count(unsigned int* pVal)
{
    try
    {
        *pVal = (unsigned int)m_value.size();
    }
    CPPCADESCATCH;
    return S_OK;
}

template <class T>
HRESULT CPPEnrollCollection<T>::get_ItemByIndex(long Index, boost::shared_ptr<T>& pVal)
{
    try
    {
        // Конечно, такую проверку std::vector сделает и сам, но 
        // нам нужно вернуть строго определенный код ошибки - E_INVALIDARG,
        // а std::vector выбросит исключение out-of-range
        if ((long)m_value.size() <= Index)
        {
            return E_INVALIDARG;
        }
        // Нумерация объектов в коллекции начинается с нуля
        pVal = m_value.at(Index);
    }
    CPPCADESCATCH;
    return S_OK;
}
class CPPEnrollX509Attributes: public CPPEnrollCollection<CPPEnrollX509Attribute>{};
class CPPEnrollReaderModes: public CPPEnrollCollection<CPPEnrollReaderMode>{};
class CPPEnrollCspStatuses: public CPPEnrollCollection<CPPEnrollCspStatus>
{
public:
    HRESULT get_ItemByName(
        const CAtlString& CspName,
        const CAtlStringW& AlgName,
        boost::shared_ptr<CPPEnrollCspStatus>& pVal);
};

class CPPEnrollObjectIds: public CPPEnrollCollection<CPPEnrollObjectId>
{
public:
    HRESULT Add(const boost::shared_ptr<CPPEnrollObjectId>& value);
};

class CPPEnrollReaders: public CPPEnrollCollection<CAtlString>{};

class CPPEnrollCspAlgorithms: public CPPEnrollCollection<CPPEnrollCspAlgorithm>
{
public:
    HRESULT get_ItemByName(const CAtlString& AlgName,
        boost::shared_ptr<CPPEnrollCspAlgorithm>& pVal);
};

class CPPEnrollCspInformations: public CPPEnrollCollection<CPPEnrollCspInformation>,
    private ATL2::CCryptProvidersEnum
{
public:
    HRESULT AddAvailableCsps();
    HRESULT GetCspStatusFromProviderName(const CAtlStringW& strProviderName,
        X509KeySpec LegacyKeySpec, boost::shared_ptr<CPPEnrollCspStatus>& pValue);
    HRESULT GetCspStatusesFromOperations(AlgorithmOperationFlags Operations,
        boost::shared_ptr<CPPEnrollCspInformation>& pCspInformation,
        boost::shared_ptr<CPPEnrollCspStatuses>& pValue);
    HRESULT get_ItemByName(const CAtlString& AlgName,
        boost::shared_ptr<CPPEnrollCspInformation>& pVal);

private:
    HRESULT AddStatusesFromCsp(AlgorithmOperationFlags Operations,
        boost::shared_ptr<CPPEnrollCspInformation>& pCspInformation,
        boost::shared_ptr<CPPEnrollCspStatuses>& pCollectionToAdd); 

    bool OnProv(LPCTSTR szProvName, DWORD dwProvType);
};

template <class T> class CPPEnrollCollectionWithOid : public CPPEnrollCollection<T>
{
public:
    CPPEnrollCollectionWithOid<T>(void){};
    ~CPPEnrollCollectionWithOid<T>(void){};
    HRESULT Add(const boost::shared_ptr<T>& value);
    HRESULT IsPresentByOid(LPCSTR pOid, bool* pResult);
};

class CPPEnrollX509Extensions: public CPPEnrollCollectionWithOid<CPPEnrollX509Extension>
{
public:
    PCERT_EXTENSION getCertExtensionsArray();
    DWORD getExtensionsCount();
private:
    std::vector<CERT_EXTENSION> m_certExtensions;
};

class CPPEnrollX509NameValuePairs: public CPPEnrollCollection<CPPEnrollX509NameValuePair>
{
public:
    std::vector<boost::shared_ptr<CPPEnrollX509NameValuePair> > getX509NameValuePairs();
    DWORD getNameValuePairsCount();
private:
    std::vector<CPPEnrollX509NameValuePair> m_NameValuePairs;
};

class CPPEnrollCryptAttributes: public CPPEnrollCollectionWithOid<CPPEnrollCryptAttribute>
{
public:
    PCRYPT_ATTRIBUTE getCryptAttributesArray();
    DWORD getAttributesCount();
private:
    std::vector<CRYPT_ATTRIBUTE> m_cryptAttributes;
};

class CPPEnrollPolicyQualifiers: public CPPEnrollCollection<CPPEnrollPolicyQualifier>{};
class CPPEnrollCertificatePolicies: public CPPEnrollCollection<CPPEnrollCertificatePolicy>{};

template <class T>
HRESULT CPPEnrollCollectionWithOid<T>::Add(const boost::shared_ptr<T>& value)
{
    try
    {
        bool bIsPresentSameOid;
        ATL_HR_ERRORCHECK_RETURN(IsPresentByOid(value->GetOid()->get_pszOID(), &bIsPresentSameOid));
        if (bIsPresentSameOid)
        {
            return NTE_EXISTS;
        }
        this->m_value.push_back(value);
    }
    CPPCADESCATCH;
    return S_OK;
}

template <class T>
HRESULT CPPEnrollCollectionWithOid<T>::IsPresentByOid(LPCSTR pOid, bool* pResult)
{
    try
    {
        if (!pResult)
        {
            return E_INVALIDARG;
        }
        *pResult = false;
        for (size_t i = 0; i < this->m_value.size(); i++)
        {
            *pResult = (0 == strcmp(this->m_value[i]->GetOid()->get_pszOID(), pOid));
            if (*pResult)
            {
                return S_OK;
            }
        }
    }
    CPPCADESCATCH;
    return S_OK;
}


} /* namespace CAdES */
} /* namespace PKI */
} /* namespace CryptoPro */

#endif //CPP_ENROLL_COLLECTION_H_
