/*===========================================================================
*
*                            PUBLIC DOMAIN NOTICE
*               National Center for Biotechnology Information
*
*  This software/database is a "United States Government Work" under the
*  terms of the United States Copyright Act.  It was written as part of
*  the author's official duties as a United States Government employee and
*  thus cannot be copyrighted.  This software/database is freely available
*  to the public for use. The National Library of Medicine and the U.S.
*  Government have not placed any restriction on its use or reproduction.
*
*  Although all reasonable efforts have been taken to ensure the accuracy
*  and reliability of the software and data, the NLM and the U.S.
*  Government do not and cannot warrant the performance or results that
*  may be obtained by using this software or data. The NLM and the U.S.
*  Government disclaim all warranties, express or implied, including
*  warranties of performance, merchantability or fitness for any particular
*  purpose.
*
*  Please cite the author in any work or product based on this material.
*
* ===========================================================================
*
*/

/**
* Unit tests for database declarations in schema
*/

#include "AST_Fixture.hpp"

#include <ktst/unit_test.hpp>

#include <klib/symbol.h>

//#include "../../libs/vdb/schema-expr.h"

using namespace std;
using namespace ncbi::NK;

TEST_SUITE ( SchemaDbTestSuite );

class DbAccess // encapsulates access to an STable in a VSchema
{
public:
    DbAccess ( const SDatabase* p_db )
    : m_self ( p_db )
    {
    }

    const SDatabase * Parent () { return m_self -> dad; }
    uint32_t Version () const { return m_self -> version; }
    uint32_t Id () const { return m_self -> id; }

    const BSTree & Scope () const { return m_self -> scope; }

    uint32_t DbMemberCount () const { return VectorLength ( & m_self -> db ); }
    const SDBMember * GetDbMember ( uint32_t p_idx ) const
    {
        return static_cast < const SDBMember * > ( VectorGet ( & m_self -> db, p_idx ) );
    }

    uint32_t TableMemberCount () const { return VectorLength ( & m_self -> tbl ); }
    const STblMember * GetTableMember ( uint32_t p_idx ) const
    {
        return static_cast < const STblMember * > ( VectorGet ( & m_self -> tbl, p_idx ) );
    }

    uint32_t AliasMemberCount () const { return VectorLength ( & m_self -> aliases ); }
    const SViewAliasMember * GetAliasMember ( uint32_t p_idx ) const
    {
        return static_cast < const SViewAliasMember * > ( VectorGet ( & m_self -> aliases, p_idx ) );
    }

    uint32_t OverloadCount () const
    {
        const SNameOverload * ovl = static_cast < const SNameOverload * > ( m_self -> name -> u . obj );
        return VectorLength ( & ovl -> items );
    }
    const SDatabase * GetOverload ( uint32_t p_idx ) const
    {
        const SNameOverload * ovl = static_cast < const SNameOverload * > ( m_self -> name -> u . obj );
        return static_cast < const SDatabase * > ( VectorGet ( & ovl -> items, p_idx ) );
    }

    const SDatabase * m_self;
};


class AST_Db_Fixture : public AST_Fixture
{
public:
    AST_Db_Fixture ()
    {
    }
    ~AST_Db_Fixture ()
    {
    }

    DbAccess ParseDatabase ( const char * p_source, const char * p_name, uint32_t p_idx = 0 )
    {
        MakeAst ( p_source );

        const SDatabase * ret = static_cast < const SDatabase* > ( VectorGet ( & GetSchema () -> db, p_idx ) );
        if ( ret == 0 )
        {
            throw std :: logic_error ( "AST_Db_Fixture::ParseDatabase : DB not found" );
        }
        if ( ret -> name == 0 || string ( p_name ) != ToCppString ( ret -> name -> name ) )
        {
            throw std :: logic_error ( "AST_Db_Fixture::ParseDatabase : wrong name" );
        }
        return DbAccess ( ret );
    }
};

FIXTURE_TEST_CASE(DB_Empty_NoParent, AST_Db_Fixture)
{
    DbAccess db = ParseDatabase ( "database d#1.2 { };", "d" );
    REQUIRE_NULL ( db . Parent () );
    REQUIRE_EQ ( Version ( 1, 2 ), db .Version () );
    REQUIRE_EQ ( 0u, db .Id () );
    REQUIRE_EQ ( 1u, VectorLength ( & GetSchema() -> dname ) );
    REQUIRE_EQ ( 1u, db . OverloadCount () );
    REQUIRE_EQ ( db . m_self, db . GetOverload ( 0 ) );
}

FIXTURE_TEST_CASE(DB_Parent, AST_Db_Fixture)
{
    DbAccess db = ParseDatabase ( "database p#1 {}; database d#1 = p#1 {};", "d", 1 );
    REQUIRE_EQ ( 1u, db .Id () );
    REQUIRE_NOT_NULL ( db . Parent () );
    REQUIRE_EQ ( 0u, db . Parent () -> id );
}

FIXTURE_TEST_CASE(DB_ParentNotADatabase, AST_Db_Fixture)
{
    VerifyErrorMessage ( "table p#1 {}; database d#1 = p#1 {};", "Not a database: 'p'" );
}

FIXTURE_TEST_CASE(DB_NewVersion, AST_Db_Fixture)
{
    DbAccess db = ParseDatabase ( "database d#1.0 { }; database d#1.1 { };", "d", 1 );
    REQUIRE_EQ ( Version ( 1, 1 ), db .Version () ); // second decl accepted
}
FIXTURE_TEST_CASE(DB_OldVersion, AST_Db_Fixture)
{
    REQUIRE_THROW ( ParseDatabase ( "database d#1.1 { }; database d#1.0 { };", "d", 1 ) ); // second decl ignored
}

FIXTURE_TEST_CASE(DB_DbMember, AST_Db_Fixture)
{
    DbAccess db = ParseDatabase ( "database p#1 {}; database d#1 { database p m_p; };", "d", 1 );
    REQUIRE_EQ ( 1u, db . DbMemberCount () );
    const SDBMember * m = db . GetDbMember ( 0 );
    REQUIRE_NOT_NULL ( m );
    REQUIRE_NOT_NULL ( m -> name );
    REQUIRE_EQ ( string ( "m_p" ), ToCppString ( m -> name -> name ) );
    REQUIRE ( ! m -> tmpl );
    REQUIRE_NOT_NULL ( m -> db );
    REQUIRE_EQ ( 0u, m -> db -> id );
    const KSymbol * sym = reinterpret_cast < const KSymbol * > ( BSTreeFirst ( & db . Scope () ) );
    REQUIRE_NOT_NULL ( sym );
    REQUIRE_EQ ( string ( "m_p" ), ToCppString ( sym -> name ) );
    REQUIRE_EQ ( (uint32_t)eDBMember, sym -> type );
}

FIXTURE_TEST_CASE(DB_DbMemberDeclaredTwice, AST_Db_Fixture)
{
    VerifyErrorMessage ( "database p#1 {}; database d#1 { database p m_p; database p m_p; };",
                         "Member already exists: 'm_p'",
                         1, 60 );
}

//TODO: table member declared twice

FIXTURE_TEST_CASE(DB_ItselfAsMember, AST_Db_Fixture)
{
    VerifyErrorMessage ( "database d#1 { database d m_p; };", "Database declared but not defined: 'd'" );
}

FIXTURE_TEST_CASE(DB_DbMemberNotADatabase, AST_Db_Fixture)
{
    VerifyErrorMessage ( "table p#1 {}; database d#1 { database p m_p; };", "Not a database: 'p'" );
}

FIXTURE_TEST_CASE(DB_DbMemberVersionDoesNotExist, AST_Db_Fixture)
{
    VerifyErrorMessage ( "database p#1 {}; database d#1 { database p#2 m_p; };",
                         "Requested version does not exist: 'p#2'" );
}

FIXTURE_TEST_CASE(DB_DbMemberTemplate, AST_Db_Fixture)
{
    DbAccess db = ParseDatabase ( "database p#1 {}; database d#1 { template database p m_p; };", "d", 1 );
    REQUIRE_EQ ( 1u, db . DbMemberCount () );
    const SDBMember * m = db . GetDbMember ( 0 );
    REQUIRE ( m -> tmpl );
}

FIXTURE_TEST_CASE(DB_TableMember, AST_Db_Fixture)
{
    DbAccess db = ParseDatabase ( "table t#1 {}; database d#1 { table t m_p; };", "d", 0 );
    REQUIRE_EQ ( 1u, db . TableMemberCount () );
    const STblMember * m = db . GetTableMember ( 0 );
    REQUIRE_NOT_NULL ( m );
    REQUIRE_NOT_NULL ( m -> name );
    REQUIRE_EQ ( string ( "m_p" ), ToCppString ( m -> name -> name ) );
    REQUIRE ( ! m -> tmpl );
    REQUIRE_NOT_NULL ( m -> tbl );
    REQUIRE_EQ ( 0u, m -> tbl -> id );
    const KSymbol * sym = reinterpret_cast < const KSymbol * > ( BSTreeFirst ( & db . Scope () ) );
    REQUIRE_NOT_NULL ( sym );
    REQUIRE_EQ ( string ( "m_p" ), ToCppString ( sym -> name ) );
    REQUIRE_EQ ( (uint32_t)eTblMember, sym -> type );
}

FIXTURE_TEST_CASE(DB_TableMemberTemplate, AST_Db_Fixture)
{
    DbAccess db = ParseDatabase ( "table t#1 {}; database d#1 { template table t m_p; };", "d", 0 );
    REQUIRE_EQ ( 1u, db . TableMemberCount () );
    const STblMember * m = db . GetTableMember ( 0 );
    REQUIRE ( m -> tmpl );
}

FIXTURE_TEST_CASE(DB_ViewAliasMember_NotAView, AST_Db_Fixture)
{
    VerifyErrorMessage ( "version 2; table t#1 {}; view v#1 < t t > {}; database d#1 { table t T; alias t<T> vv; };",
                         "Not a view: 't'" );
}
FIXTURE_TEST_CASE(DB_ViewAliasMember_AlreadyExists, AST_Db_Fixture)
{
    VerifyErrorMessage ( "version 2; table t#1 {}; view v#1 <t t > {}; database d#1 { table t T; alias v<T> T; };",
                         "Member already exists: 'T'" );
}
FIXTURE_TEST_CASE(DB_ViewAliasMember_UnknownParameter, AST_Db_Fixture)
{
    VerifyErrorMessage ( "version 2; table t#1 {}; view v#1 <t t > {}; database d#1 { table t T; alias v<zz> vv; };",
                         "Undeclared identifier: 'zz'" );
}
FIXTURE_TEST_CASE(DB_ViewAliasMember_WrongParameter, AST_Db_Fixture)
{
    VerifyErrorMessage ( "version 2; table t#1 {}; view v#1 <t t > {}; database d#1 { table t T; alias v<t> vv; };",
                         "Not a table/view member: 't'" );
}
FIXTURE_TEST_CASE(DB_ViewAliasMember_WrongParameterCount, AST_Db_Fixture)
{
    VerifyErrorMessage ( "version 2; table t#1 {}; view v#1 <t t > {}; database d#1 { table t T; alias v<T,T> vv; };",
                         "Incorrect number of view parameters: 'v'" );
}
FIXTURE_TEST_CASE(DB_ViewAliasMember_TableParamTypeMismatch, AST_Db_Fixture)
{
    VerifyErrorMessage (
        "version 2;"
        "table t1#1 {}; table t2#1 {}; view v#1 < t1 t > {}; "
        "database d#1 { table t2 T; alias v<T> vv; };",
        "View parameter type mismatch: 'T'" );
}
FIXTURE_TEST_CASE(DB_ViewAliasMember_ViewParamTypeMismatch, AST_Db_Fixture)
{
    VerifyErrorMessage (
        "version 2;"
        "table t#1 {}; view v1#1 < t t > {}; view v2#1 < t t > {}; view u#1<v1 v>{};"
        "database d#1 { table t T; alias v2<T> vv; alias u< vv > uu; };",
        "View parameter type mismatch: 'vv'" );
}

FIXTURE_TEST_CASE(DB_ViewAliasMember, AST_Db_Fixture)
{
    DbAccess db = ParseDatabase ( "version 2; table t#1 {}; view v#1 < t t > {}; database d#1 { table t T; alias v<T> vv; };", "d", 0 );
    REQUIRE_EQ ( 1u, db . AliasMemberCount () );
    const SViewAliasMember * m = db . GetAliasMember ( 0 );
    REQUIRE ( m );
    // verify the internals
    REQUIRE_EQ( string("vv"), ToCppString ( m -> name -> name ) );
    const struct SViewInstance & inst = m -> view;
    REQUIRE_EQ( string("v"), ToCppString ( inst . dad -> name -> name ) );

    VdbVector<KSymbol> params ( inst . params );
    REQUIRE_EQ( 1u, params.Count() );
    const KSymbol* p ( params.Get( 0 ) );
    REQUIRE_NOT_NULL( p );
    REQUIRE_EQ( string("T"), ToCppString ( p -> name ) );

    REQUIRE_EQ( 0u, m -> cid . ctx ); // currently not used for view aliases
    REQUIRE_EQ( 0u, m -> cid . id ); // index into db.aliases
}

FIXTURE_TEST_CASE(DB_ViewOnAliasMemberOnAnotherViewAliasMember, AST_Db_Fixture)
{
    DbAccess db = ParseDatabase (
        "version 2;"
        "table t#1 {}; view v1#1 < t t > {}; view v2#1 < v1 v > {};"
        "database d#1 { table t T; alias v1<T> vv1; alias v2< vv1 > vv2; };",
        "d", 0 );
    REQUIRE_EQ ( 2u, db . AliasMemberCount () );
    const SViewAliasMember * m = db . GetAliasMember ( 1 );
    REQUIRE ( m );
    // verify the internals
    REQUIRE_EQ( string("vv2"), ToCppString ( m -> name -> name ) );
    const struct SViewInstance & inst = m -> view;
    REQUIRE_EQ( string("v2"), ToCppString ( inst . dad -> name -> name ) );

    VdbVector<KSymbol> params ( inst . params );
    REQUIRE_EQ( 1u, params.Count() );
    const KSymbol* p ( params.Get( 0 ) );
    REQUIRE_NOT_NULL( p );
    REQUIRE_EQ( string("vv1"), ToCppString ( p -> name ) );

    REQUIRE_EQ( 0u, m -> cid . ctx );
    REQUIRE_EQ( 1u, m -> cid . id );
}

//TODO: DB_ViewOnAliasMemberOnAnotherViewAliasMember
//TODO: DB_ViewAliasMemberOnTableFromParentDatabase

//TODO: SViewAliasMemberMark
//TODO: SViewAliasMemberDefDump
//TODO: SViewAliasMemberDump

//////////////////////////////////////////// Main
#include <kapp/args.h>
#include <kfg/config.h>
#include <klib/out.h>

extern "C"
{

ver_t CC KAppVersion ( void )
{
    return 0x1000000;
}

const char UsageDefaultName[] = "wb-test-schema-db";

rc_t CC UsageSummary (const char * progname)
{
    return KOutMsg ( "Usage:\n" "\t%s [options] -o path\n\n", progname );
}

rc_t CC Usage( const Args* args )
{
    return 0;
}

rc_t CC KMain ( int argc, char *argv [] )
{
    return SchemaDbTestSuite(argc, argv);
}

}

