/*++

Copyright (C) 1996-2001 Microsoft Corporation

Module Name:

    SQL1FILT.CPP

Abstract:

History:

--*/

#include "sql1filt.h"

void* CSql1Filter::GetInterface(REFIID riid)
{
    if(riid == IID_IHmmFilter || riid == IID_IHmmSql1Filter)
        return (IHmmSql1Filter*)&m_XFilter;
    else if(riid == IID_IConfigureHmmSql1Filter)
        return (IConfigureHmmSql1Filter*)&m_XConfigure;
    else if(riid == IID_IHmmParse)
        return (IHmmParse*)&m_XParse;
    else 
        return NULL;
}

STDMETHODIMP CSql1Filter::XFilter::
IsSpecial()
{
    if(m_pObject->m_apTokens.GetSize() == 0)
    {
        return HMM_S_ACCEPTS_EVERYTHING;
    }
    else
    {
        return HMM_S_FALSE;
    }
}

STDMETHODIMP CSql1Filter::XFilter::
CheckObject(IN IHmmPropertySource* pSource, 
            OUT IHmmPropertyList** ppList, OUT IUnknown** ppHint)
{
    if(ppHint) *ppHint = NULL;

    return m_pObject->Evaluate(FALSE, pSource, ppList);
}

STDMETHODIMP CSql1Filter::XFilter::
GetType(IID* piid)
{
    if(piid == NULL)
        return HMM_E_INVALID_PARAMETER;
    *piid = IID_IHmmSql1Filter;
    return HMM_S_NO_ERROR;
}

STDMETHODIMP CSql1Filter::XFilter::
GetSelectedPropertyList(IN long lFlags, OUT IHmmPropertyList** ppList)
{
    return m_pObject->m_TargetClass.GetSelected().QueryInterface(
        IID_IHmmPropertyList,
        (void**)ppList);
}

STDMETHODIMP CSql1Filter::XFilter::
GetTargetClass(OUT HMM_CLASS_INFO* pTargetClass)
{
    if(pTargetClass == NULL)
        return HMM_E_INVALID_PARAMETER;

    m_pObject->m_XParse.EnsureTarget();
    m_pObject->m_TargetClass.Save(*pTargetClass);
    return HMM_S_NO_ERROR;
}

STDMETHODIMP CSql1Filter::XFilter::
GetTokens(IN long lFirstIndex, IN long lNumTokens, OUT long* plTokensReturned,
          OUT HMM_SQL1_TOKEN* aTokens)
{
    if(plTokensReturned == NULL || lNumTokens <= 0 || lFirstIndex < 0 ||
        aTokens == NULL)
    {
        return HMM_E_INVALID_PARAMETER;
    }

    m_pObject->m_XParse.EnsureWhere();

    long lHaveTokens = m_pObject->m_apTokens.GetSize();
    if(lFirstIndex >= lHaveTokens)
    {
        *plTokensReturned = 0;
        return HMM_S_NO_MORE_DATA;
    }

    long lGaveTokens = 0;
    long lCurrentIndex = lFirstIndex;
    while(lGaveTokens < lNumTokens && lCurrentIndex < lHaveTokens)
    {
        CSql1Token* pToken = m_pObject->m_apTokens[lCurrentIndex];
        if(pToken->m_lTokenType != TOKENTYPE_SPECIAL)
        {
            pToken->Save(aTokens[lGaveTokens++]);
        }
        lCurrentIndex++;
    }

    *plTokensReturned = lGaveTokens;
    return HMM_S_NO_ERROR;
}

//********************************* Configuration ******************************

STDMETHODIMP CSql1Filter::XConfigure::
SetTargetClass(IN HMM_CLASS_INFO* pTargetClass)
{
    if(pTargetClass == NULL)
        return HMM_E_INVALID_PARAMETER;

    m_pObject->m_TargetClass.Load(*pTargetClass);
    m_pObject->InvalidateTree();
    return HMM_S_NO_ERROR;
}

STDMETHODIMP CSql1Filter::XConfigure::
AddTokens(IN long lNumTokens, IN HMM_SQL1_TOKEN* aTokens)
{
    if(lNumTokens <= 0 || aTokens == NULL) 
        return HMM_E_INVALID_PARAMETER;

    for(long l = 0; l < lNumTokens; l++)
    {
        m_pObject->m_apTokens.Add(new CSql1Token(aTokens[l]));
    }
    m_pObject->InvalidateTree();
    return HMM_S_NO_ERROR;
}

STDMETHODIMP CSql1Filter::XConfigure::
RemoveAllTokens()
{
    m_pObject->m_apTokens.RemoveAll();
    m_pObject->InvalidateTree();
    return HMM_S_NO_ERROR;
}

STDMETHODIMP CSql1Filter::XConfigure::
RemoveAllProperties()
{
    return m_pObject->m_TargetClass.GetSelected().RemoveAllProperties();
}

STDMETHODIMP CSql1Filter::XConfigure::
AddProperties(IN long lNumProps, HMM_WSTR* awszProps)
{
    return m_pObject->m_TargetClass.GetSelected().
        AddProperties(lNumProps, awszProps);
}

//*********************************** Parsing *********************************


STDMETHODIMP CSql1Filter::XParse::
Parse(IN HMM_WSTR wszText, IN long lFlags)
{
    delete m_pParser;
    m_pParser = NULL;

    if((lFlags & HMM_FLAG_LAZY) == 0)
    {
        m_wsText.Empty();
        m_nStatus = not_parsing;

        m_pObject->InvalidateTree();
        CAbstractSql1Parser Parser(new CTextLexSource(wszText));
        int nRes = Parser.Parse(this, CAbstractSql1Parser::FULL_PARSE);
        m_InnerStack.Empty();
        return ParserError(nRes);
    }
    else
    {
        m_wsText = wszText;
        m_pParser = new CAbstractSql1Parser(new CTextLexSource((LPWSTR)m_wsText));
        m_nStatus = at_0;
        return HMM_S_NO_ERROR;
    }
}

void CSql1Filter::XParse::SetClassName(LPCWSTR wszClass)
{
    m_pObject->m_TargetClass.AccessClassName() = (LPWSTR)wszClass;
    m_pObject->m_TargetClass.AccessIncludeChildren() = TRUE;
}

void CSql1Filter::XParse::AddToken(COPY const HMM_SQL1_TOKEN& Token)
{
    m_pObject->m_apTokens.Add(new CSql1Token(Token));
    if(Token.m_lTokenType == SQL1_OR || Token.m_lTokenType == SQL1_AND)
    {
        // Find the inner guy
        // ==================

        long lInnerIndex;
        if(!m_InnerStack.Pop(lInnerIndex))
            return;

        V_I4(&m_pObject->m_apTokens[lInnerIndex]->m_vConstValue) = 
            m_pObject->m_apTokens.GetSize();
    }
}

void CSql1Filter::XParse::AddProperty(COPY LPCWSTR wszProperty)
{
    m_pObject->m_TargetClass.GetSelected().
        AddProperties(1, (LPWSTR*)&wszProperty);
}

void CSql1Filter::XParse::AddAllProperties()
{
    m_pObject->m_TargetClass.GetSelected().AddAllProperties();
}

void CSql1Filter::XParse::InOrder(long lOp)
{
    /*
    CSql1Token* pToken = new CSql1Token;
    pToken->m_lTokenType = TOKENTYPE_SPECIAL;
    pToken->m_lOperator = lOp;
    V_I4(&pToken->m_vConstValue) = -1;
    m_InnerStack.Push(m_pObject->m_apTokens.GetSize());
    m_pObject->m_apTokens.Add(pToken);
    */
}

HRESULT CSql1Filter::XParse::EnsureTarget()
{
    if(m_nStatus == at_0)
    {
        m_pObject->InvalidateTree();
        int nRes = m_pParser->Parse(this, CAbstractSql1Parser::NO_WHERE);
        m_nStatus = at_where;
        return ParserError(nRes);
    }
    else return HMM_S_NO_ERROR;
}

HRESULT CSql1Filter::XParse::EnsureWhere()
{
    if(m_nStatus == not_parsing)
    {
        return HMM_S_NO_ERROR;
    }
    else
    {
        m_pObject->InvalidateTree();
        int nRes = m_pParser->Parse(this, 
            (m_nStatus == at_0)?CAbstractSql1Parser::FULL_PARSE
                               :CAbstractSql1Parser::JUST_WHERE);
        m_InnerStack.Empty();
        delete m_pParser;
        m_pParser = NULL;
        m_nStatus = not_parsing;
        return ParserError(nRes);
    }
}

HRESULT CSql1Filter::XParse::ParserError(int nRes)
{
    switch(nRes)
    {
    case CAbstractSql1Parser::SUCCESS:
        return HMM_S_NO_ERROR;
    case CAbstractSql1Parser::SYNTAX_ERROR:
        return HMM_E_INVALID_QUERY;
    case CAbstractSql1Parser::LEXICAL_ERROR:
        return HMM_E_INVALID_QUERY;
    case CAbstractSql1Parser::FAILED:
        return HMM_E_FAILED;
    default:
        return HMM_E_CRITICAL_ERROR;
    }
}
        

CSql1Filter::XParse::~XParse()
{
    delete m_pParser;
}

//*****************************************************************************
//*************************** Query Evaluator *********************************
//*****************************************************************************
//*****************************************************************************
//
//  See sql1eveal.h for documentation
//
//*****************************************************************************

HRESULT CSql1Filter::Evaluate(BOOL bSkipTarget,
                                  IN READ_ONLY IHmmPropertySource* pPropSource, 
                                  OUT IHmmPropertyList** ppList)
{
    HRESULT hres;

    GetTree();
    if(m_pTree) m_pTree->Release();

    if(ppList) *ppList = NULL;

    hres = CHmmNode::EvaluateNode(m_pTree, pPropSource);
    if(hres != HMM_S_NO_ERROR) return hres;

    if(ppList)
    {
        m_TargetClass.GetSelected().QueryInterface(IID_IHmmPropertyList, 
            (void**)ppList);
    }

    return HMM_S_NO_ERROR;


    if(!bSkipTarget)
    {
        // Check the class of the object
        // =============================

        hres = m_XParse.EnsureTarget();
        if(FAILED(hres)) return hres;

        CHmmClassInfo* pInfo = &m_TargetClass;
        hres = CHmmClassInfo::CheckObjectAgainstMany(1, &pInfo, 
            pPropSource, ppList, NULL);
        if(hres != HMM_S_NO_ERROR)
        {
            // Either an error or a simple class mismatch
            // ==========================================
    
            return hres;
        }
    }

    // Now go for the expression
    // =========================

    hres = m_XParse.EnsureWhere();
    if(FAILED(hres)) return hres;

    int nNumTokens = m_apTokens.GetSize();

    if(nNumTokens == 0)
    {
        // Empty query
        // ===========

        return HMM_S_NO_ERROR;
    }

    // Allocate boolean stack of appropriate length
    // ============================================

    CBooleanStack Stack(nNumTokens);

    for(int nTokenIndex = 0; nTokenIndex < nNumTokens; nTokenIndex++)
    {
        CSql1Token* pToken = m_apTokens[nTokenIndex];
        BOOL bVal1, bVal2;

        switch(pToken->m_lTokenType)
        {
        case SQL1_AND:
            // Pop the last two operands and AND them together
            // ===============================================

            if(!Stack.Pop(bVal1) || !Stack.Pop(bVal2))
            {
                return HMM_E_INVALID_QUERY;
            }

            Stack.Push(bVal1 && bVal2);
            break;

        case SQL1_OR:
            // Pop the last two operands and OR them together
            // ===============================================

            if(!Stack.Pop(bVal1) || !Stack.Pop(bVal2))
            {
                return HMM_E_INVALID_QUERY;
            }

            Stack.Push(bVal1 || bVal2);
            break;

        case SQL1_NOT:
            // Pop the last value and invert it
            // ================================

            if(!Stack.Pop(bVal1))
            {
                return HMM_E_INVALID_QUERY;
            }

            Stack.Push(!bVal1);
            break;

        case SQL1_OP_EXPRESSION:
            // Evaluate the expression and push its value
            // ==========================================

            hres = pToken->Evaluate(pPropSource);
            if(FAILED(hres)) return hres;
            bVal1 = (hres == HMM_S_NO_ERROR);

            Stack.Push(bVal1);
            break;

        case TOKENTYPE_SPECIAL:
            if(!Stack.Pop(bVal1))
            {
                return HMM_E_INVALID_QUERY;
            }
            Stack.Push(bVal1);
            if((pToken->m_lOperator == SQL1_OR && bVal1) ||
                (pToken->m_lOperator == SQL1_AND && !bVal1))
            {
                nTokenIndex = V_I4(&pToken->m_vConstValue) - 1;
            }
            break;
        }
    }

    // All tokens have been processed. There better be one element on the stack
    // ========================================================================

    BOOL bResult;
    if(!Stack.Pop(bResult))
    {
        return HMM_E_INVALID_QUERY;
    }

    if(!Stack.IsEmpty())
    {
        return HMM_E_INVALID_QUERY;
    }

    if(bResult)
        return HMM_S_NO_ERROR;
    else
        return HMM_S_FALSE;
}



CHmmNode* CSql1Filter::GetTree()
{
    if(m_pTree != NULL)
    {
        m_pTree->AddRef();
        return m_pTree;
    }

    // Construct the tree from the RPN
    // ===============================

    CUniquePointerStack<CHmmNode> aNodes(m_apTokens.GetSize());
    CHmmNode* pNode1;
    CHmmNode* pNode2;
    CLogicalNode* pNewNode;
    CSql1Token* pNewToken;

    for(int i = 0; i < m_apTokens.GetSize(); i++)
    {
        CSql1Token* pToken = m_apTokens[i];
        switch(pToken->m_lTokenType)
        {
        case SQL1_OR:
        case SQL1_AND:
            if(!aNodes.Pop(pNode2)) return NULL;
            if(!aNodes.Pop(pNode1)) return NULL;
            pNewNode = new CLogicalNode;
            pNewNode->m_lTokenType = pToken->m_lTokenType;
            pNewNode->Add(pNode1);
            pNewNode->Add(pNode2);
            aNodes.Push(pNewNode);
            break;
        case SQL1_NOT:
            if(!aNodes.Pop(pNode1)) return NULL;
            pNode1->Negate();
            aNodes.Push(pNode1);
            break;

        case SQL1_OP_EXPRESSION:
            pNewToken = new CSql1Token(*pToken);
            aNodes.Push(pNewToken);
            break;
        default:
            return NULL;
        }
    }

    CHmmNode* pWhere;
    if(!aNodes.Pop(pWhere))
    {
        if(i == 0)
            pWhere = NULL;
        else 
            return NULL;
    }
    if(!aNodes.IsEmpty()) return NULL;

    // Add the class check
    // ===================

    CHmmNode* pTarget = m_TargetClass.GetTree();

    CLogicalNode* pMain = new CLogicalNode;
    pMain->m_lTokenType = SQL1_AND;
    pMain->Add(pTarget);
    pTarget->Release(); // Matching GetTree()
    if(pWhere)
    {
        pMain->Add(pWhere);
    }

    m_pTree = pMain;
    m_pTree->AddRef(); // for storage
    m_pTree->AddRef(); // for return

    m_pTree->Print(stdout, 0);
    return m_pTree;
}