#pragma once
#ifdef _MSC_VER
#pragma warning(disable:4511)
#pragma warning(disable:4512)
#pragma warning(disable:4239)
#endif

#include <vector>
#include <string>
#include <list>
#include <set>
#include <map>
#include <queue>
#include <deque>
#include <iostream>
#include <algorithm>
#include <functional>
#include <cassert>

//
// Logging facility
//

// #define _DOUT // output verbose info
// #define _FILE // output to generator.log, otherwise to console

#if defined(_DOUT)
#if defined(_FILE)
#include <fstream>
wofstream logfile;
#define DOUT(arg) logfile << arg
#else
#define DOUT(arg) wcerr << arg
#endif
#else
#define DOUT(arg)
#endif

//
// due to discrepancies between different implementations of STL, the following
// functions are defined explicitly and used throughout
//

//
// Insert a range into a collection
//
template<class _Col>
void __insert
    (
    _Col&                   collection,
    typename _Col::iterator first,
    typename _Col::iterator last
    )
{
    for ( ; first != last; ++first )
    {
        collection.insert( *first );
    }
}

//
// Push_back a range into a collection
//
template<class _Col>
void __push_back
    (
    _Col&                   collection,
    typename _Col::iterator first,
    typename _Col::iterator last
    )
{
    for ( ; first != last; ++first )
    {
        collection.push_back( *first );
    }
}

//
// Remove a node from a tree. Return the current node
//
template<class _Col>
typename _Col::iterator __map_erase
    (
    _Col&    collection,
    typename _Col::iterator where
    )
{
    if ( where == collection.end() ) return( collection.end() );
    typename _Col::iterator next = where;
    ++next;
    collection.erase( where );
    return( next );
}


#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus

namespace pictcore
{

//
// forward references
//
class Task;
class Model;
class Parameter;
class Combination;
class Exclusion;
class WorkList;

//
// callback function cancelling the generation
//
typedef bool( *AbortCallbackFunc ) ( );

//
// internal constants and enums
//
const long MaxRowsToGenerate = 1000 * 1000; // could make this bigger

typedef unsigned char TrackType;

const TrackType OPEN = 0x00;
const TrackType COVERED = 0x01;
const TrackType EXCLUDED = 0xff;

enum ComboStatus
{
    Open,
    CoveredMatch,
    Excluded
};

enum class GenerationType
{
    MixedOrder,
    FixedOrder,
    Full,
    Flat,
    Random
};

enum class GenerationMode
{
    Regular,      // regular, no shortcuts, all pairs covered, can be slow
    Preview,      // only a few test cases, most pairs *not* covered, very fast
    Approximate   // most pairs covered, can return no results
};

enum class ErrorType
{
    GenerationCancelled,
    TooManyRows,
    GenerationFailure,
    OutOfMemory,
    Unknown
};

class GenerationError
{
public:

    GenerationError( std::string file, int line, ErrorType err = ErrorType::Unknown ) :
        _file( file ), _line( line ), _err( err ){}

    ErrorType GetErrorType() { return (ErrorType) _err; }

private:
    ErrorType    _err;
    std::string  _file;
    int          _line;
};

//
//
//
typedef std::vector<Combination *> ComboCollection;
typedef std::vector<Parameter *>   ParamCollection;
typedef std::list<size_t>          ParamResult;
typedef std::pair<Parameter *, std::wstring> Value;

//
// seeding structures
//
typedef std::pair<Parameter *,int> RowSeedTerm;
typedef std::set<RowSeedTerm>      RowSeed;
typedef std::list<RowSeed>         RowSeedCollection;

void printRowSeed( RowSeed &seed );

//
// results
//
typedef std::vector<size_t>    ResultRow;
typedef std::vector<ResultRow> ResultCollection;

//
// submodels
//
typedef std::list<Model *> SubmodelCollection;

//
// exclusions
//
typedef std::pair<Parameter *, int> ExclusionTerm;

int compareExclusionTerms( const ExclusionTerm& op1, const ExclusionTerm& op2 );
int compareExclusions( const Exclusion& op1, const Exclusion& op2 );
extern bool contained( Exclusion &a, Exclusion &b );

// exclusions have to be sorted by size when they enter ProcessExclusions()
// otherwise correct combinations will not be created and generation will fail
class ExclusionSizeLess
{
public:
    bool operator() ( const Exclusion &excl1, const Exclusion &excl2 ) const;
};
typedef std::set<Exclusion, ExclusionSizeLess> ExclusionCollection;

class ExclIterCollectionPred
{
public:
    bool operator() ( const ExclusionCollection::iterator excl1, const ExclusionCollection::iterator excl2 ) const;
};
typedef std::set<ExclusionCollection::iterator, ExclIterCollectionPred> ExclIterCollection;

class ExclusionTermCompare
{
public:
    bool operator()( const ExclusionTerm& op1, const ExclusionTerm& op2 ) const;
};

//
//
//
class Exclusion
{
public:
    typedef std::set<ExclusionTerm, ExclusionTermCompare> _Exclusion;
    typedef std::vector<ExclusionTerm>                    _ExclusionVec;
    typedef _Exclusion::iterator                          iterator;
    typedef _Exclusion::const_iterator                    const_iterator;
    typedef _Exclusion::reference                         reference;
    typedef _Exclusion::const_reference                   const_reference;
    typedef _Exclusion::value_type                        value_type;

    Exclusion() : m_deleted( false ) {}

    iterator       begin()       { return( col.begin() ); }
    iterator       end()         { return( col.end() ); }
    const_iterator begin() const { return( col.begin() ); }
    const_iterator end()   const { return( col.end() ); }
    size_t size()  const         { return( col.size() ); }
    bool   empty() const         { return( col.empty() ); }

    // exclusion represented as a list, for next_permutation to work
    _ExclusionVec::iterator lbegin() { return( vec.begin() ); }
    _ExclusionVec::iterator lend()   { return( vec.end() ); }
    _ExclusionVec &GetList()         { return( vec ); }

    std::pair<iterator, bool> insert( const ExclusionTerm& Term )
    {
        std::pair<iterator, bool> ret = col.insert( Term );
        if( ret.second ) vec.push_back( Term );
        assert( col.size() == vec.size() );
        return ret;
    }

    // for inserter() to work we need two-param insert
    iterator insert( iterator pos, const ExclusionTerm& Term )
    {
        return insert( Term ).first;
    }

    bool operator <( const Exclusion& Excl ) const {
        return( size() != Excl.size() ? size() < Excl.size() : -1 == compareExclusions( *this, Excl ) );
    }

    void markDeleted() { m_deleted = true; }
    bool isDeleted() const { return( m_deleted ); }

    size_t ResultParamCount() const;

    void Print() const;

private:
    _Exclusion    col;
    _ExclusionVec vec;
    bool m_deleted;
};

//
// combination
//
const unsigned int UNDEFINED_ID = 0;

class Combination
{
public:
    Combination( Model* model );
    ~Combination();

    static void ResetId() { m_lastUsedId = UNDEFINED_ID; }
    unsigned int GetId()  { return m_id; }

    void WireModel( Model* model ) { m_model = model; }

    void PushParameter( Parameter* param ) { m_params.push_back( param ); }
    void PopParameter()                    { m_params.pop_back(); }
    Parameter& operator[]( int N ) const   { return *( m_params[ N ] ); }

    void InitBinding() { m_boundCount = 0; }
    int  Bind( int val, WorkList& worklist );
    int  AddBinding();
    int  GetBoundCount() const { return m_boundCount; }
    bool IsFullyBound()  const { return m_boundCount == m_params.size(); }

    void        SetOpen   ( int n );
    bool        IsOpen    ( int n ) const { return OPEN     == m_bitvec[ n ]; }
    bool        IsExcluded( int n ) const { return EXCLUDED == m_bitvec[ n ]; }
    ComboStatus Feasible  ( int n );
    int         Feasible();

    void ApplyExclusion( Exclusion& excl );
    bool ViolatesExclusion();

    ParamCollection& GetParameters() { return m_params; }
    int  GetParameterCount() const { return static_cast<int>( m_params.size() ); }
    int  GetOpenCount()      const { return m_openCount; }
    int  GetRange()          const { return m_range; }
    void SetMapSize( int n, TrackType val = 0 );
    int  Weight( int vak );
    void Print();

    void Assign( Combination &Combo )
    {
        m_params     = Combo.GetParameters();
        m_range      = Combo.GetRange();
        m_openCount  = Combo.GetOpenCount();
        m_boundCount = Combo.GetBoundCount();
    }

private:
    static unsigned int m_lastUsedId; // static source of identifiers
    unsigned int        m_id;         // unique identifier of this instance

    ParamCollection m_params;
    unsigned char*  m_bitvec;
    int             m_range;
    int             m_openCount;
    int             m_boundCount;

    Model* m_model;

    void applyExclusion( Exclusion& excl, int index, ParamCollection::iterator pos );
};

// Combinations pointers in ComboCollection should be sorted by id and not by memory location
// We want to avoid any indeterminism stemming from sorting random pointers
class CombinationPtrSortPred {
public:
    bool operator() (Combination *c1, Combination *c2)
    {
        return(c1->GetId() < c2->GetId());
    }
};

//
// parameter
//
class Parameter
{
public:
    Parameter( int order, int sequence, int valueCount, std::wstring name, bool expectedResultParam ) :
        m_order( order ), m_sequence( sequence ), m_valueCount( valueCount ),
        m_name( name ), m_expResultParam( expectedResultParam ), m_valueWeights( 0 )
    {
         // result params must have order = 1
        if ( m_expResultParam ) m_order = 1;
    }
    virtual ~Parameter() {}

    static const unsigned int UndefinedValue = 0xFFFFFFFF;

    void SetOrder( int Order ) { m_order = Order; }
    void SetWeights( std::vector<int> Weights );

    const std::wstring& GetName() const { return m_name; }
    ExclIterCollection& GetExclusions() { return m_exclusions; }

    int GetOrder() const { return m_order; }

    int GetWeight( int n ) const
    {
        if( 0 <= n && n < static_cast<int>( m_valueWeights.size() ) )
            return m_valueWeights[ n ];
        else
            return 1;
    }
    int GetSequence()        const { return m_sequence; }
    int GetValueCount()      const { return static_cast<int>( m_valueCount ); }
    int GetTempResultCount() const { return static_cast<int>( m_result.size() ); }
    int GetExclusionCount()  const { return static_cast<int>( m_exclusions.size() ); }
    void  ClearExclusions() { m_avgExclusionSize = 0; m_exclusions.clear(); }
    float GetAverageExclusionSize() const { return m_avgExclusionSize; }

    bool  IsExpectedResultParam() { return( m_expResultParam ); }

    void WireTask( Task* task ) { m_task = task; }

    void InitBinding()
    {
        m_bound   = false;
        m_pending = false;
    }
    bool Bind( int value, WorkList& worklist );
    bool GetBoundCount() const { return m_bound; }

    void MarkPending()     { m_pending = true; }
    bool IsPending() const { return m_pending; }

    int PickValue();

    void LinkCombination( Combination* combo ) { m_combinations.push_back( combo ); }
    
    // remember to sort by Id, not with default sorting pred (by memory address)
    void SortCombinations() { sort( m_combinations.begin(), m_combinations.end(), CombinationPtrSortPred() ); }
    ComboCollection::const_iterator GetCombinationBegin() { return m_combinations.begin(); }
    ComboCollection::const_iterator GetCombinationEnd()   { return m_combinations.end(); }

    void LinkExclusion( ExclusionCollection::iterator iter )
    {
        m_avgExclusionSize = ( m_avgExclusionSize * (float) m_exclusions.size() + (float) iter->size() ) / (float) ( m_exclusions.size() + 1 );
        std::pair<ExclIterCollection::iterator, bool> ret = m_exclusions.insert( iter );
        assert( ret.second );
    }

    void UnlinkExclusion( ExclusionCollection::iterator Iter )
    {
        size_t erased = m_exclusions.erase( Iter );
        assert( 1 == erased );
    }

    void GetFirst()   { m_resultIter = m_result.begin(); }
    size_t GetNext()  { return *m_resultIter++; }
    size_t GetLast()  { return  m_currentValue; }
    ParamResult& GetTempResults() { return m_result; }

    virtual Model*           GetModel()      { return nullptr; }
    virtual ParamCollection* GetComponents() { return nullptr; }

    void CleanUp();

protected:
    std::wstring m_name;

private:
    int    m_order;          // how many ways must this parameter combine?
    int    m_sequence;       // input sequence number, so we can reorder but output in original order
    size_t m_currentValue;   // current iteration's value cache
    int    m_valueCount;     // how many values parameter has
    bool   m_expResultParam; // is this a special, result inducing param

    bool m_bound;         // variable to use in iteration - is this bound yet?
    bool m_pending;       // have we put it on work list yet this iteration?

    ComboCollection       m_combinations;  // combinations this parameter participates in
    ExclIterCollection    m_exclusions;    // exclusions referring to this parameter

    ParamResult::iterator m_resultIter;
    ParamResult           m_result;

    std::vector<int>      m_valueWeights;

    Task*  m_task;

    float  m_avgExclusionSize;
};

//
// pseudoParameter is a parameter constructed out of results generated by a submodel
//
class PseudoParameter : public Parameter
{
public:
    PseudoParameter(int order, unsigned int sequence, Model *submodel);

    virtual Model*           GetModel() {return m_model;}
    virtual ParamCollection* GetComponents();

private:
    Model* m_model;
};

//
//
//
struct MatchParameterPointer : public std::binary_function<ExclusionTerm, Parameter*, bool>
{
    bool operator()( const ExclusionTerm excl, Parameter* pParam ) const
    {
        return excl.first == pParam;
    }
};

//
//
//
class Model
{
public:
    Model( const std::wstring& id, GenerationType type, int order, long seed = 0 ) :
        m_id( id ), m_generationType( type ), m_order( order ), m_maxRows( 0 ), m_lastParamId( UNDEFINED_ID + 1000 * 1000 ) {
        SetRandomSeed( seed );
    }
    ~Model();

    void GenerateVirtualRoot();  // generates a root of a hierarchical cluster
    void Generate();             // entry point of the generation
    GenerationType GetGenerationType() { return( m_generationType ); }

    bool AddExclusion( Exclusion& e )
    {
        std::pair<ExclusionCollection::iterator, bool> ret = m_exclusions.insert( e );
        return( ret.second );
    }
    void AddSubmodel( Model* model ) { m_submodels.push_back( model ); }
    
    void AddParameter( Parameter* param )
    {
        // parameter names should be unique but we can't assert it here
        // as there are cases where we add same name params to the collection
        // internally and delete the duplicate almost immediately
        param->WireTask( GetTask() );
        m_parameters.push_back( param );
    }

    void AddRowSeed( RowSeed& seed )
    {
        m_rowSeeds.push_back( seed );
        for( auto & submodel : m_submodels ) submodel->AddRowSeed( seed );
    }

    void AddRowSeeds( RowSeedCollection::iterator begin, RowSeedCollection::iterator end )
    {
        while( begin != end ) AddRowSeed( *begin++ );
    }

    void SetOrder     ( int order )    { m_order = order; }
    void SetMaxRows   ( long maxRows ) { m_maxRows = maxRows; }
    void SetRandomSeed( long seed )
    {
        m_randomSeed = seed;
        srand( m_randomSeed );
        for( auto & submodel : m_submodels ) submodel->SetRandomSeed( m_randomSeed );
    }

    void WireTask( Task* task )
    {
        m_task = task;
        for( auto & param : m_parameters )   param->WireTask( task );
        for( auto & submodel : m_submodels ) submodel->WireTask( task );
    }

    Task* GetTask() { return( m_task ); }

    std::wstring&         GetId()         { return m_id; }
    SubmodelCollection&   GetSubmodels()  { return m_submodels; }
    ExclusionCollection&  GetExclusions() { return m_exclusions; }
    RowSeedCollection&    GetRowSeeds()   { return( m_rowSeeds ); }
    ResultCollection&     GetResults()    { return m_results; }
    ParamCollection&      GetParameters() { return m_parameters; }

    void GetAllParameters( ParamCollection& params )
    {
        __push_back( params, m_parameters.begin(), m_parameters.end() );
        for( auto & submodel : m_submodels ) submodel->GetAllParameters( params );
    }

    size_t GetAllParametersCount()
    {
        size_t count = m_parameters.size();
        return( count );
    }

    size_t GetResultParameterCount()
    {
        size_t count = 0;
        for( auto & param : m_parameters )
            if( param->IsExpectedResultParam() )
                ++count;

        return count;
    }

    int  GetOrder()      { return m_order; }
    long GetRandomSeed() { return( m_randomSeed ); }

    long GetTotalCombinationsCount()     { return static_cast<long>( m_totalCombinations ); }
    long GetRemainingCombinationsCount() { return static_cast<long>( m_remainingCombinations ); }
    int  GetSubmodelCount()              { return static_cast<int> ( m_submodels.size() ); }
    int  GetResultCount()                { return static_cast<int> ( m_results.size() ); }

    int GlobalZerosCount;

private:

    ParamCollection        m_parameters;
    ExclusionCollection    m_exclusions;
    SubmodelCollection     m_submodels;
    RowSeedCollection      m_rowSeeds;
    std::deque<Parameter*> m_worklist;
    ResultCollection       m_results;

    std::wstring m_id;

    int  m_order;
    long m_randomSeed;
    long m_maxRows;

    GenerationType m_generationType;

    Parameter*   m_currentParam;
    unsigned int m_lastParamId;
    long         m_totalCombinations;
    long         m_remainingCombinations;

    Task* m_task;

    void generateMixedOrder();           // generates mixed-order (the most generic)
    void generateFixedOrder();           // generates fixed, n-order
    void generateFull();                 // generates exhaustive
    void generateFlat();                 // generates order of 1 with extra assumptions
    void generateRandom();               // generates randomized suites

    void resolvePseudoParams();          // helper that handles submodels
    void fixRowSeeds();                  // helper that cleans up seeding rows
    void gcd( ComboCollection& ComboCol ); // generation entry point

    void choose( ParamCollection::iterator first,
                 ParamCollection::iterator last,
                 int order, int realOrder,
                 Combination& baseCombo, ComboCollection& vecCombo );

    void processExclusions( ComboCollection& comboCol );
    bool excludeConflictingParamValues();
    bool mapExclusionsToPseudoParameters();
    void mapRowSeedsToPseudoParameters();
    void deriveSubmodelExclusions();
    bool rowViolatesExclusion( ResultRow& row );
    bool rowViolatesExclusion( Exclusion& row );

    void markUndefinedValuesInResultParams();

    Exclusion generateRandomRow();
};

//
// task governs the entire generation
//
class Task
{
public:
    Task();
    ~Task();

    // this has to be called right before generation starts
    void PrepareForGeneration();

    void AllocWorkbuf( int Size );

    int* GetWorkbuf() { return( m_workbuf ); }

    void DeallocWorkbuf();

    void SetRootModel( Model* model )
    {
        m_rootModel = model;
        model->WireTask( this );
    }

    size_t GetTotalParameterCount() { return( m_rootModel->GetAllParametersCount() ); }

    void SetAbortCallback( AbortCallbackFunc func ) { m_abortCallback = func; }
    
    bool AddExclusion( Exclusion& excl )
    {
        std::pair<ExclusionCollection::iterator, bool> ret = m_exclusions.insert( excl );
        return( ret.second );
    }

    void AddRowSeed( RowSeed& seed ) { m_rowSeeds.push_back( seed ); }

    Model* GetRootModel()                { return( m_rootModel ); }
    ExclusionCollection& GetExclusions() { return( m_exclusions ); }
    RowSeedCollection&   GetRowSeeds()   { return( m_rowSeeds ); }

    bool AbortGeneration() { return( ( nullptr == m_abortCallback ) ? false : m_abortCallback() ); }

    void SetGenerationMode( GenerationMode mode ) { m_generationMode = mode; }
    GenerationMode GetGenerationMode() { return( m_generationMode ); }

    // approximate mode
    void SetMaxRandomTries( size_t max ) { m_maxRandomTries = max; }
    size_t GetMaxRandomTries() { return( m_maxRandomTries ); }

    ResultCollection& GetResults() { return m_rootModel->GetResults(); }

    // these two functions are used by C-style API which only returns one row at a time
    void ResetResultFetching();
    ResultCollection::iterator GetNextResultRow();

private:
    Model*              m_rootModel;
    ExclusionCollection m_exclusions;
    RowSeedCollection   m_rowSeeds;

    AbortCallbackFunc   m_abortCallback;
    GenerationMode      m_generationMode;

    // in Approximate and Randomized mode, how many times we will try to
    // generate a random row before giving up
    size_t              m_maxRandomTries; 

    Model* findMatchingNode( Exclusion& exclusion, Model* root );
    bool findParamInSubtree( Parameter* param, Model* root );
    void deriveExclusions();

    // a global workspace shared by multiple objects
    int* m_workbuf = nullptr;

    // result row pointer allows C-style API to implement GetNextResultRow function
    // i.e. get one result row at a time
    ResultCollection::iterator m_currentResultRow;
};

//
//
//
class WorkList 
{
public:
    WorkList() {}
    ~WorkList() {}

    void AddItem( Parameter* pParam );
    Parameter *GetItem();

    bool IsEmpty() const { return m_rep.empty(); }
    void Print();

private:
    std::deque<Parameter*> m_rep;
};

}

#ifdef __cplusplus
}
#endif // __cplusplus