/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2008 by Sun Microsystems, Inc.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sc.hxx"

// INCLUDE ---------------------------------------------------------------

#include "doubleref.hxx"
#include "cell.hxx"
#include "global.hxx"
#include "document.hxx"
#include "queryparam.hxx"
#include "globstr.hrc"

#include <memory>
#include <vector>

using ::rtl::OUString;
using ::std::auto_ptr;
using ::std::vector;

namespace {

void lcl_toUpper(OUString& rStr)
{
    rStr = ScGlobal::pCharClass->toUpper(rStr.trim(), 0, static_cast<xub_StrLen>(rStr.getLength()));
}

bool lcl_createStarQuery(ScQueryParamBase* pParam, const ScDBRangeBase* pDBRef, const ScDBRangeBase* pQueryRef)
{
    // A valid StarQuery must be at least 4 columns wide. To be precise it
    // should be exactly 4 columns ...
    // Additionally, if this wasn't checked, a formula pointing to a valid 1-3
    // column Excel style query range immediately left to itself would result
    // in a circular reference when the field name or operator or value (first
    // to third query range column) is obtained (#i58354#). Furthermore, if the
    // range wasn't sufficiently specified data changes wouldn't flag formula
    // cells for recalculation.

    if (pQueryRef->getColSize() < 4)
        return false;

    sal_Bool bValid;
    OUString aCellStr;
    SCSIZE nIndex = 0;
    SCROW nRow = 0;
    SCROW nRows = pDBRef->getRowSize();
    SCSIZE nNewEntries = static_cast<SCSIZE>(nRows);
    pParam->Resize(nNewEntries);

    do
    {
        ScQueryEntry& rEntry = pParam->GetEntry(nIndex);

        bValid = false;

        if (nIndex > 0)
        {
            // For all entries after the first one, check the and/or connector in the first column.
            aCellStr = pQueryRef->getString(0, nRow);
            lcl_toUpper(aCellStr);
            if ( aCellStr.equals(ScGlobal::GetRscString(STR_TABLE_UND)) )
            {
                rEntry.eConnect = SC_AND;
                bValid = sal_True;
            }
            else if ( aCellStr.equals(ScGlobal::GetRscString(STR_TABLE_ODER)) )
            {
                rEntry.eConnect = SC_OR;
                bValid = sal_True;
            }
        }

        if ((nIndex < 1) || bValid)
        {
            // field name in the 2nd column.
            aCellStr = pQueryRef->getString(1, nRow);
            SCCOL nField = pDBRef->findFieldColumn(aCellStr); // TODO: must be case insensitive comparison.
            if (ValidCol(nField))
            {
                rEntry.nField = nField;
                bValid = true;
            }
            else
                bValid = false;
        }
        
        if (bValid)
        {
            // equality, non-equality operator in the 3rd column.
            aCellStr = pQueryRef->getString(2, nRow);
            lcl_toUpper(aCellStr);
            const sal_Unicode* p = aCellStr.getStr();
            if (p[0] == sal_Unicode('<'))
            {
                if (p[1] == sal_Unicode('>'))
                    rEntry.eOp = SC_NOT_EQUAL;
                else if (p[1] == sal_Unicode('='))
                    rEntry.eOp = SC_LESS_EQUAL;
                else
                    rEntry.eOp = SC_LESS;
            }
            else if (p[0] == sal_Unicode('>'))
            {
                if (p[1] == sal_Unicode('='))
                    rEntry.eOp = SC_GREATER_EQUAL;
                else
                    rEntry.eOp = SC_GREATER;
            }
            else if (p[0] == sal_Unicode('='))
                rEntry.eOp = SC_EQUAL;

        }
        
        if (bValid)
        {
            // Finally, the right-hand-side value in the 4th column.
            *rEntry.pStr = pQueryRef->getString(3, nRow);
            rEntry.bDoQuery = sal_True;
        }
        nIndex++;
        nRow++;
    }
    while (bValid && (nRow < nRows) /* && (nIndex < MAXQUERY) */ );
    return bValid;
}

bool lcl_createExcelQuery(
    ScQueryParamBase* pParam, const ScDBRangeBase* pDBRef, const ScDBRangeBase* pQueryRef)
{
    bool bValid = true;
    SCCOL nCols = pQueryRef->getColSize();
    SCROW nRows = pQueryRef->getRowSize();
    vector<SCCOL> aFields(nCols);
    SCCOL nCol = 0;
    while (bValid && (nCol < nCols))
    {
        OUString aQueryStr = pQueryRef->getString(nCol, 0);
        SCCOL nField = pDBRef->findFieldColumn(aQueryStr);
        if (ValidCol(nField))
            aFields[nCol] = nField;
        else
            bValid = false;
        ++nCol;
    }

    if (bValid)
    {
        // Count the number of visible cells (excluding the header row).  Each
        // visible cell corresponds with a single query.
        SCSIZE nVisible = pQueryRef->getVisibleDataCellCount();
        if ( nVisible > SCSIZE_MAX / sizeof(void*) )
        {
            OSL_FAIL("zu viele Filterkritierien");
            nVisible = 0;
        }

        SCSIZE nNewEntries = nVisible;
        pParam->Resize( nNewEntries );

        SCSIZE nIndex = 0;
        SCROW nRow = 1;
        String aCellStr;
        while (nRow < nRows)
        {
            nCol = 0;
            while (nCol < nCols)
            {
                aCellStr = pQueryRef->getString(nCol, nRow);
                ScGlobal::pCharClass->toUpper( aCellStr );
                if (aCellStr.Len() > 0)
                {
                    if (nIndex < nNewEntries)
                    {
                        pParam->GetEntry(nIndex).nField = aFields[nCol];
                        pParam->FillInExcelSyntax(aCellStr, nIndex);
                        nIndex++;
                        if (nIndex < nNewEntries)
                            pParam->GetEntry(nIndex).eConnect = SC_AND;
                    }
                    else
                        bValid = false;
                }
                nCol++;
            }
            nRow++;
            if (nIndex < nNewEntries)
                pParam->GetEntry(nIndex).eConnect = SC_OR;
        }
    }
    return bValid;
}

bool lcl_fillQueryEntries(
    ScQueryParamBase* pParam, const ScDBRangeBase* pDBRef, const ScDBRangeBase* pQueryRef)
{
    SCSIZE nCount = pParam->GetEntryCount();
    for (SCSIZE i = 0; i < nCount; ++i)
        pParam->GetEntry(i).Clear();

    // Standard QueryTabelle
    bool bValid = lcl_createStarQuery(pParam, pDBRef, pQueryRef);
    // Excel QueryTabelle
    if (!bValid)
        bValid = lcl_createExcelQuery(pParam, pDBRef, pQueryRef);

    nCount = pParam->GetEntryCount();
    if (bValid)
    {
        //  bQueryByString muss gesetzt sein
        for (SCSIZE i = 0; i < nCount; ++i)
            pParam->GetEntry(i).bQueryByString = true;
    }
    else
    {
        //  nix
        for (SCSIZE i = 0; i < nCount; ++i)
            pParam->GetEntry(i).Clear();
    }
    return bValid;
}

}

// ============================================================================

ScDBRangeBase::ScDBRangeBase(ScDocument* pDoc, RefType eType) : 
    mpDoc(pDoc), meType(eType)
{
}

ScDBRangeBase::~ScDBRangeBase()
{
}

bool ScDBRangeBase::fillQueryEntries(ScQueryParamBase* pParam, const ScDBRangeBase* pDBRef) const
{
    if (!pDBRef)
        return false;

    return lcl_fillQueryEntries(pParam, pDBRef, this);
}

void ScDBRangeBase::fillQueryOptions(ScQueryParamBase* pParam)
{
    pParam->bHasHeader = true;
    pParam->bByRow = true;
    pParam->bInplace = true;
    pParam->bCaseSens = false;
    pParam->bRegExp = false;
    pParam->bDuplicate = true;
    pParam->bMixedComparison = false;
}

ScDocument* ScDBRangeBase::getDoc() const
{
    return mpDoc;
}

// ============================================================================

ScDBInternalRange::ScDBInternalRange(ScDocument* pDoc, const ScRange& rRange) :
    ScDBRangeBase(pDoc, INTERNAL), maRange(rRange)
{
}

ScDBInternalRange::~ScDBInternalRange()
{
}

const ScRange& ScDBInternalRange::getRange() const
{
    return maRange;
}

SCCOL ScDBInternalRange::getColSize() const
{
    return maRange.aEnd.Col() - maRange.aStart.Col() + 1;
}

SCROW ScDBInternalRange::getRowSize() const
{
    return maRange.aEnd.Row() - maRange.aStart.Row() + 1;
}

SCSIZE ScDBInternalRange::getVisibleDataCellCount() const
{
    SCCOL nCols = getColSize();
    SCROW nRows = getRowSize();
    if (nRows <= 1)
        return 0;

    return (nRows-1)*nCols;
}

OUString ScDBInternalRange::getString(SCCOL nCol, SCROW nRow) const
{
    OUString aStr;
    const ScAddress& s = maRange.aStart;
    ScBaseCell* pCell = getDoc()->GetCell(ScAddress(s.Col() + nCol, s.Row() + nRow, maRange.aStart.Tab()));
    if (!pCell)
        return aStr;

    getCellString(aStr, pCell);
    return aStr;
}

SCCOL ScDBInternalRange::getFirstFieldColumn() const
{
    return getRange().aStart.Col();
}

SCCOL ScDBInternalRange::findFieldColumn(SCCOL nIndex) const
{
    const ScRange& rRange = getRange();
    const ScAddress& s = rRange.aStart;

    SCCOL nDBCol1 = s.Col();

    // Don't handle out-of-bound condition here.  We'll do that later.
    return nIndex + nDBCol1 - 1;
}

sal_uInt16 ScDBInternalRange::getCellString(OUString& rStr, ScBaseCell* pCell) const
{
    sal_uInt16 nErr = 0;
    String aStr;
    if (pCell)
    {
        SvNumberFormatter* pFormatter = getDoc()->GetFormatTable();
        switch (pCell->GetCellType())
        {
            case CELLTYPE_STRING:
                ((ScStringCell*) pCell)->GetString(aStr);
            break;
            case CELLTYPE_EDIT:
                ((ScEditCell*) pCell)->GetString(aStr);
            break;
            case CELLTYPE_FORMULA:
            {
                ScFormulaCell* pFCell = (ScFormulaCell*) pCell;
                nErr = pFCell->GetErrCode();
                if (pFCell->IsValue())
                {
                    double fVal = pFCell->GetValue();
                    sal_uLong nIndex = pFormatter->GetStandardFormat(
                                        NUMBERFORMAT_NUMBER,
                                        ScGlobal::eLnge);
                    pFormatter->GetInputLineString(fVal, nIndex, aStr);
                }
                else
                    pFCell->GetString(aStr);
            }
            break;
            case CELLTYPE_VALUE:
            {
                double fVal = ((ScValueCell*) pCell)->GetValue();
                sal_uLong nIndex = pFormatter->GetStandardFormat(
                                        NUMBERFORMAT_NUMBER,
                                        ScGlobal::eLnge);
                pFormatter->GetInputLineString(fVal, nIndex, aStr);
            }
            break;
            default:
                ;
        }
    }
    rStr = aStr;
    return nErr;
}

SCCOL ScDBInternalRange::findFieldColumn(const OUString& rStr, sal_uInt16* pErr) const
{
    const ScAddress& s = maRange.aStart;
    const ScAddress& e = maRange.aEnd;
    OUString aUpper = rStr;
    lcl_toUpper(aUpper);

    SCCOL nDBCol1 = s.Col();
    SCROW nDBRow1 = s.Row();
    SCTAB nDBTab1 = s.Tab();
    SCCOL nDBCol2 = e.Col();

    SCCOL   nField = nDBCol1;
    sal_Bool    bFound = sal_True;

    bFound = false;
    OUString aCellStr;
    ScAddress aLook( nDBCol1, nDBRow1, nDBTab1 );
    while (!bFound && (aLook.Col() <= nDBCol2))
    {
        ScBaseCell* pCell = getDoc()->GetCell( aLook );
        sal_uInt16 nErr = getCellString( aCellStr, pCell );
        if (pErr)
            *pErr = nErr;
        lcl_toUpper(aCellStr);
        bFound = ScGlobal::GetpTransliteration()->isEqual(aCellStr, aUpper);
        if (!bFound)
            aLook.IncCol();
    }
    nField = aLook.Col();

    return bFound ? nField : -1;
}

ScDBQueryParamBase* ScDBInternalRange::createQueryParam(const ScDBRangeBase* pQueryRef) const
{
    auto_ptr<ScDBQueryParamInternal> pParam(new ScDBQueryParamInternal);

    // Set the database range first.
    const ScAddress& s = maRange.aStart;
    const ScAddress& e = maRange.aEnd;
    pParam->nCol1 = s.Col();
    pParam->nRow1 = s.Row();
    pParam->nCol2 = e.Col();
    pParam->nRow2 = e.Row();
    pParam->nTab  = s.Tab();

    fillQueryOptions(pParam.get());

    // Now construct the query entries from the query range.
    if (!pQueryRef->fillQueryEntries(pParam.get(), this))
        return NULL;

    return pParam.release();
}

bool ScDBInternalRange::isRangeEqual(const ScRange& rRange) const
{
    return maRange == rRange;
}

// ============================================================================

ScDBExternalRange::ScDBExternalRange(ScDocument* pDoc, const ScMatrixRef& pMat) :
    ScDBRangeBase(pDoc, EXTERNAL), mpMatrix(pMat)
{
    SCSIZE nC, nR;
    mpMatrix->GetDimensions(nC, nR);
    mnCols = static_cast<SCCOL>(nC);
    mnRows = static_cast<SCROW>(nR);
}

ScDBExternalRange::~ScDBExternalRange()
{
}

SCCOL ScDBExternalRange::getColSize() const
{
    return mnCols;
}

SCROW ScDBExternalRange::getRowSize() const
{
    return mnRows;
}

SCSIZE ScDBExternalRange::getVisibleDataCellCount() const
{
    SCCOL nCols = getColSize();
    SCROW nRows = getRowSize();
    if (nRows <= 1)
        return 0;

    return (nRows-1)*nCols;
}

OUString ScDBExternalRange::getString(SCCOL nCol, SCROW nRow) const
{
    if (nCol >= mnCols || nRow >= mnRows)
        return OUString();

    return mpMatrix->GetString(nCol, nRow);
}

SCCOL ScDBExternalRange::getFirstFieldColumn() const
{
    return 0;
}

SCCOL ScDBExternalRange::findFieldColumn(SCCOL nIndex) const
{
    return nIndex - 1;
}

SCCOL ScDBExternalRange::findFieldColumn(const OUString& rStr, sal_uInt16* pErr) const
{
    if (pErr)
        pErr = 0;

    OUString aUpper = rStr;
    lcl_toUpper(aUpper);
    for (SCCOL i = 0; i < mnCols; ++i)
    {
        OUString aUpperVal = mpMatrix->GetString(i, 0);
        lcl_toUpper(aUpperVal);
        if (aUpper.equals(aUpperVal))
            return i;
    }
    return -1;
}

ScDBQueryParamBase* ScDBExternalRange::createQueryParam(const ScDBRangeBase* pQueryRef) const
{
    auto_ptr<ScDBQueryParamMatrix> pParam(new ScDBQueryParamMatrix);
    pParam->mpMatrix = mpMatrix;
    fillQueryOptions(pParam.get());

    // Now construct the query entries from the query range.
    if (!pQueryRef->fillQueryEntries(pParam.get(), this))
        return NULL;

    return pParam.release();
}

bool ScDBExternalRange::isRangeEqual(const ScRange& /*rRange*/) const
{
    return false;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
