/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 *  Effective License of whole file:
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library 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 for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 *  Parts "Copyright by Sun Microsystems, Inc" prior to August 2011:
 *
 *    The Contents of this file are made available subject to the terms of
 *    the GNU Lesser General Public License Version 2.1
 *
 *    Copyright: 2000 by Sun Microsystems, Inc.
 *
 *    Contributor(s): Joerg Budischewski
 *
 *  All parts contributed on or after August 2011:
 *
 *    This Source Code Form is subject to the terms of the Mozilla Public
 *    License, v. 2.0. If a copy of the MPL was not distributed with this
 *    file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 ************************************************************************/

#include <sal/log.hxx>
#include "pq_preparedstatement.hxx"
#include "pq_tools.hxx"
#include "pq_statics.hxx"
#include "pq_statement.hxx"

#include <o3tl/safeint.hxx>
#include <rtl/strbuf.hxx>
#include <rtl/ustrbuf.hxx>


#include <comphelper/sequence.hxx>

#include <com/sun/star/sdbc/DataType.hpp>
#include <com/sun/star/sdbc/ResultSetConcurrency.hpp>
#include <com/sun/star/sdbc/ResultSetType.hpp>
#include <com/sun/star/sdbc/SQLException.hpp>

#include <memory>
#include <string.h>
#include <string_view>

#include <connectivity/dbconversion.hxx>
#include <utility>

using osl::MutexGuard;


using com::sun::star::uno::Any;
using com::sun::star::uno::Type;
using com::sun::star::uno::Sequence;
using com::sun::star::uno::Reference;
using com::sun::star::uno::UNO_QUERY;

using com::sun::star::lang::IllegalArgumentException;

using com::sun::star::sdbc::XCloseable;
using com::sun::star::sdbc::XResultSet;
using com::sun::star::sdbc::XRef;
using com::sun::star::sdbc::XBlob;
using com::sun::star::sdbc::XClob;
using com::sun::star::sdbc::XArray;
using com::sun::star::sdbc::XConnection;
using com::sun::star::sdbc::SQLException;

using com::sun::star::beans::Property;
using com::sun::star::beans::XPropertySetInfo;

using namespace dbtools;

namespace pq_sdbc_driver
{
static ::cppu::IPropertyArrayHelper & getPreparedStatementPropertyArrayHelper()
{
    static ::cppu::OPropertyArrayHelper arrayHelper(
        Sequence<Property>{
            Property(
                u"CursorName"_ustr, 0,
                ::cppu::UnoType<OUString>::get() , 0 ),
            Property(
                u"EscapeProcessing"_ustr, 1,
                cppu::UnoType<bool>::get() , 0 ),
            Property(
                u"FetchDirection"_ustr, 2,
                ::cppu::UnoType<sal_Int32>::get() , 0 ),
            Property(
                u"FetchSize"_ustr, 3,
                ::cppu::UnoType<sal_Int32>::get() , 0 ),
            Property(
                u"MaxFieldSize"_ustr, 4,
                ::cppu::UnoType<sal_Int32>::get() , 0 ),
            Property(
                u"MaxRows"_ustr, 5,
                ::cppu::UnoType<sal_Int32>::get() , 0 ),
            Property(
                u"QueryTimeOut"_ustr, 6,
                ::cppu::UnoType<sal_Int32>::get() , 0 ),
            Property(
                u"ResultSetConcurrency"_ustr, 7,
                ::cppu::UnoType<sal_Int32>::get() , 0 ),
            Property(
                u"ResultSetType"_ustr, 8,
                ::cppu::UnoType<sal_Int32>::get() , 0 )},
        true );
    static ::cppu::IPropertyArrayHelper *pArrayHelper = &arrayHelper;

    return *pArrayHelper;
}

static bool isOperator( char c )
{
    static const char * const operators = "<>=()!/&%.,;";

    const char * w = operators;
    while (*w && *w != c)
    {
        ++w;
    }
    return *w != 0;
}

static bool isNamedParameterStart( std::string_view o , int index )
{
    return o[index] == ':' && (
        isWhitespace( o[index-1] ) || isOperator(o[index-1]) );
}

static bool isQuoted( std::string_view str )
{
    return str[0] == '"' || str[0] == '\'';
}

PreparedStatement::PreparedStatement(
    const ::rtl::Reference< comphelper::RefCountedMutex > & refMutex,
    const Reference< XConnection > & conn,
    struct ConnectionSettings *pSettings,
    OString stmt )
    : PreparedStatement_BASE(refMutex->GetMutex())
    , OPropertySetHelper(PreparedStatement_BASE::rBHelper)
    , m_connection(conn)
    , m_pSettings(pSettings)
    , m_stmt(std::move(stmt))
    , m_xMutex(refMutex)
    , m_multipleResultAvailable(false)
    , m_multipleResultUpdateCount(0)
    , m_lastOidInserted( InvalidOid )
{
    m_props[PREPARED_STATEMENT_QUERY_TIME_OUT] <<= sal_Int32(0);
    m_props[PREPARED_STATEMENT_MAX_ROWS] <<= sal_Int32(0);
    m_props[PREPARED_STATEMENT_RESULT_SET_CONCURRENCY] <<=
        css::sdbc::ResultSetConcurrency::READ_ONLY;
    m_props[PREPARED_STATEMENT_RESULT_SET_TYPE] <<=
        css::sdbc::ResultSetType::SCROLL_INSENSITIVE;

    splitSQL( m_stmt, m_splittedStatement );
    int elements = 0;
    for(const OString & str : m_splittedStatement)
    {
        // ignore quoted strings...
        if( ! isQuoted( str ) )
        {
            // the ':' cannot be the first or the last part of the
            // token,
            // the ? cannot be the first part of the token , so we start
            // at one
            for( int index = 1 ; index < str.getLength() ; index ++ )
            {
                if( str[index] == '?' ||
                    isNamedParameterStart( str , index )
                    )
                {
                    elements ++;
                }
            }
        }
    }
    m_vars = std::vector< OString >( elements );
}

PreparedStatement::~PreparedStatement()
{
}

void PreparedStatement::checkColumnIndex( sal_Int32 parameterIndex )
{
    if( parameterIndex < 1 || o3tl::make_unsigned(parameterIndex) > m_vars.size() )
    {
        throw SQLException(
            "pq_preparedstatement: parameter index out of range (expected 1 to "
            + OUString::number( m_vars.size() )
            + ", got " + OUString::number( parameterIndex )
            + ", statement '" + OStringToOUString( m_stmt, ConnectionSettings::encoding )
            + "')",
            *this, OUString(), 1, Any () );
    }
}
void PreparedStatement::checkClosed()
{
    if( ! m_pSettings || ! m_pSettings->pConnection )
        throw SQLException(
            u"pq_driver: PreparedStatement or connection has already been closed !"_ustr,
            *this, OUString(),1,Any());
}

Any PreparedStatement::queryInterface( const Type & rType )
{
    Any aRet = PreparedStatement_BASE::queryInterface(rType);
    return aRet.hasValue() ? aRet : OPropertySetHelper::queryInterface(rType);
}


Sequence< Type > PreparedStatement::getTypes()
{
    static Sequence< Type > collection(
        ::comphelper::concatSequences(
            OPropertySetHelper::getTypes(),
            PreparedStatement_BASE::getTypes()));

    return collection;
}

Sequence< sal_Int8> PreparedStatement::getImplementationId()
{
    return css::uno::Sequence<sal_Int8>();
}

void PreparedStatement::close(  )
{
    // let the connection die without acquired mutex !
    Reference< XConnection > r;
    Reference< XCloseable > resultSet;
    {
        MutexGuard guard( m_xMutex->GetMutex() );
        m_pSettings = nullptr;
        r = m_connection;
        m_connection.clear();

        resultSet = m_lastResultset;
        m_lastResultset.clear();
    }
    if( resultSet.is() )
    {
        resultSet->close();
    }
}

void PreparedStatement::raiseSQLException( const char * errorMsg )
{
    OUStringBuffer buf(128);
    buf.append( "pq_driver: "
        + OUString( errorMsg, strlen(errorMsg) , ConnectionSettings::encoding )
        + " (caused by statement '" );
    buf.appendAscii( m_executedStatement.getStr() );
    buf.append( "')" );
    OUString error = buf.makeStringAndClear();
    SAL_WARN("connectivity.postgresql", error);
    throw SQLException( error, *this, OUString(), 1, Any() );
}

Reference< XResultSet > PreparedStatement::executeQuery( )
{
    if( ! execute( ) )
    {
        raiseSQLException(  "not a query" );
    }
    return Reference< XResultSet > ( m_lastResultset, css::uno::UNO_QUERY );
}

sal_Int32 PreparedStatement::executeUpdate( )
{
    if( execute( ) )
    {
        raiseSQLException( "not a command" );
    }
    return m_multipleResultUpdateCount;
}

sal_Bool PreparedStatement::execute( )
{
    osl::MutexGuard guard( m_xMutex->GetMutex() );

    OStringBuffer buf( m_stmt.getLength() *2 );

    std::vector< OString >::size_type vars = 0;
    for(const OString & str : m_splittedStatement)
    {
        // LEM TODO: instead of this manual mucking with SQL
        // could we use PQexecParams / PQExecPrepared / ...?
        // Only snafu is giving the types of the parameters and
        // that it needs $1, $2, etc instead of "?"

//         printf( "Split %d %s\n" , i , str.getStr() );
        if( isQuoted( str ) )
        {
            buf.append( str );
        }
        else
        {
            int start = 0,index;
            for( index = 1 ; index < str.getLength() ; index ++ )
            {
                if( str[index] == '?' )
                {
                    buf.append( str.getStr()+start, index - start );
                    buf.append( m_vars[vars] );
                    vars ++;
                    start =index+1;
                }
                else
                {
                    if ( isNamedParameterStart( str, index ) )
                    {
                        buf.append( str.getStr()+start, index -start );
                        buf.append( m_vars[vars] );

                        // skip to the end of the named parameter
                        while (   index < str.getLength()
                               && !(   isWhitespace(str[index])
                                    || isOperator  (str[index])))
                        {
                            ++index;
                        }
                        start = index;
                        vars ++;
                    }
                }
            }
//             if( index +1 >= str.getLength() )
//             {
            buf.append( str.getStr() + start, index -start );
//             }
        }
    }

    m_executedStatement = buf.makeStringAndClear();

    Reference< XCloseable > lastResultSet = m_lastResultset;
    if( lastResultSet.is() )
        lastResultSet->close();

    m_lastResultset.clear();
    m_lastTableInserted.clear();

    struct CommandData data;
    data.refMutex = m_xMutex;
    data.ppSettings = &m_pSettings;
    data.pLastOidInserted = &m_lastOidInserted;
    data.pLastQuery = &m_lastQuery;
    data.pMultipleResultUpdateCount = &m_multipleResultUpdateCount;
    data.pMultipleResultAvailable = &m_multipleResultAvailable;
    data.pLastTableInserted = &m_lastTableInserted;
    data.pLastResultset = &m_lastResultset;
    data.owner = *this;
    data.tableSupplier.set( m_connection, UNO_QUERY );
    data.concurrency = extractIntProperty( this, getStatics().RESULT_SET_CONCURRENCY );

    return executePostgresCommand( m_executedStatement , &data );   // see pq_statement.cxx
}

Reference< XConnection > PreparedStatement::getConnection(  )
{
    Reference< XConnection > ret;
    {
        MutexGuard guard( m_xMutex->GetMutex() );
        checkClosed();
        ret = m_connection;
    }
    return ret;
}


void PreparedStatement::setNull( sal_Int32 parameterIndex, sal_Int32 )
{
    MutexGuard guard( m_xMutex->GetMutex() );
    checkClosed();
    checkColumnIndex( parameterIndex );
    m_vars[parameterIndex-1] = "NULL"_ostr;
}

void PreparedStatement::setObjectNull(
    sal_Int32 parameterIndex, sal_Int32, const OUString& )
{
    MutexGuard guard( m_xMutex->GetMutex() );
    checkClosed();
    checkColumnIndex( parameterIndex );
    m_vars[parameterIndex-1] = "NULL"_ostr;
}


void PreparedStatement::setBoolean( sal_Int32 parameterIndex, sal_Bool x )
{
    MutexGuard guard(m_xMutex->GetMutex() );
    checkClosed();
    checkColumnIndex( parameterIndex );
    if( x )
        m_vars[parameterIndex-1] = "'t'"_ostr;
    else
        m_vars[parameterIndex-1] = "'f'"_ostr;
}

void PreparedStatement::setByte( sal_Int32 parameterIndex, sal_Int8 x )
{
    setInt(parameterIndex,x);
}

void PreparedStatement::setShort( sal_Int32 parameterIndex, sal_Int16 x )
{
    setInt(parameterIndex, x );
}

void PreparedStatement::setInt( sal_Int32 parameterIndex, sal_Int32 x )
{
//     printf( "setString %d %d\n ",  parameterIndex, x);
    MutexGuard guard(m_xMutex->GetMutex() );
    checkClosed();
    checkColumnIndex( parameterIndex );
    m_vars[parameterIndex-1] = "'" + OString::number(x) + "'";
}

void PreparedStatement::setLong( sal_Int32 parameterIndex, sal_Int64 x )
{
    MutexGuard guard(m_xMutex->GetMutex() );
    checkClosed();
    checkColumnIndex( parameterIndex );
    m_vars[parameterIndex-1] = "'" + OString::number(x) + "'";
}

void PreparedStatement::setFloat( sal_Int32 parameterIndex, float x )
{
    MutexGuard guard(m_xMutex->GetMutex() );
    checkClosed();
    checkColumnIndex( parameterIndex );
    m_vars[parameterIndex-1] = "'" + OString::number(x) + "'";
}

void PreparedStatement::setDouble( sal_Int32 parameterIndex, double x )
{
    MutexGuard guard(m_xMutex->GetMutex() );
    checkClosed();
    checkColumnIndex( parameterIndex );
    m_vars[parameterIndex-1] = "'" + OString::number(x) + "'";
}

void PreparedStatement::setString( sal_Int32 parameterIndex, const OUString& x )
{
//     printf( "setString %d %s\n ", parameterIndex,
//             OUStringToOString( x , RTL_TEXTENCODING_ASCII_US ).getStr());
    MutexGuard guard(m_xMutex->GetMutex() );
    checkClosed();
    checkColumnIndex( parameterIndex );
    OStringBuffer buf( 20 );
    buf.append( "'" );
    OString y = OUStringToOString( x, ConnectionSettings::encoding );
    buf.ensureCapacity( y.getLength() * 2 + 2 );
    int len = PQescapeString( const_cast<char*>(buf.getStr())+1, y.getStr() , y.getLength() );
    buf.setLength( 1 + len );
    buf.append( "'" );
    m_vars[parameterIndex-1] = buf.makeStringAndClear();
}

void PreparedStatement::setBytes(
    sal_Int32 parameterIndex, const Sequence< sal_Int8 >& x )
{
    MutexGuard guard(m_xMutex->GetMutex() );
    checkClosed();
    checkColumnIndex( parameterIndex );
    size_t len;
    const std::unique_ptr<unsigned char, deleter_from_fn<PQfreemem>> escapedString(
        PQescapeBytea( reinterpret_cast<unsigned char const *>(x.getConstArray()), x.getLength(), &len));
    if( ! escapedString )
    {
        throw SQLException(
            u"pq_preparedstatement.setBytes: Error during converting bytesequence to an SQL conform string"_ustr,
            *this, OUString(), 1, Any() );
    }
    m_vars[parameterIndex-1]
        = OString::Concat("'") + std::string_view(reinterpret_cast<char *>(escapedString.get()), len -1) + "'";
}


void PreparedStatement::setDate( sal_Int32 parameterIndex, const css::util::Date& x )
{
    setString( parameterIndex, DBTypeConversion::toDateString( x ) );
}

void PreparedStatement::setTime( sal_Int32 parameterIndex, const css::util::Time& x )
{
    setString( parameterIndex, DBTypeConversion::toTimeString( x ) );
}

void PreparedStatement::setTimestamp(
    sal_Int32 parameterIndex, const css::util::DateTime& x )
{
    setString( parameterIndex, DBTypeConversion::toDateTimeString( x ) );
}

void PreparedStatement::setBinaryStream(
    sal_Int32,
    const Reference< css::io::XInputStream >&,
    sal_Int32 )
{
    throw SQLException(
        u"pq_preparedstatement: setBinaryStream not implemented"_ustr,
        *this, OUString(), 1, Any () );
}

void PreparedStatement::setCharacterStream(
    sal_Int32,
    const Reference< css::io::XInputStream >&,
    sal_Int32 )
{
    throw SQLException(
        u"pq_preparedstatement: setCharacterStream not implemented"_ustr,
        *this, OUString(), 1, Any () );
}

void PreparedStatement::setObject( sal_Int32 parameterIndex, const Any& x )
{
    if( ! implSetObject( this, parameterIndex, x ))
    {
        throw SQLException(
            "pq_preparedstatement::setObject: can't convert value of type " + x.getValueTypeName(),
            *this, OUString(), 1, Any () );
    }
}

void PreparedStatement::setObjectWithInfo(
    sal_Int32 parameterIndex,
    const Any& x,
    sal_Int32 targetSqlType,
    sal_Int32 )
{
    if( css::sdbc::DataType::DECIMAL == targetSqlType ||
        css::sdbc::DataType::NUMERIC == targetSqlType )
    {
        double myDouble = 0.0;
        OUString myString;
        if( x >>= myDouble )
        {
            myString = OUString::number( myDouble );
        }
        else
        {
            x >>= myString;
        }
        if( myString.isEmpty() )
        {
            throw SQLException(
                "pq_preparedstatement::setObjectWithInfo: can't convert value of type "
                +  x.getValueTypeName() + " to type DECIMAL or NUMERIC",
                *this, OUString(), 1, Any () );
        }

        setString( parameterIndex, myString );
    }
    else
    {
        setObject( parameterIndex, x );
    }

}

void PreparedStatement::setRef(
    sal_Int32,
    const Reference< XRef >& )
{
    throw SQLException(
        u"pq_preparedstatement: setRef not implemented"_ustr,
        *this, OUString(), 1, Any () );
}

void PreparedStatement::setBlob(
    sal_Int32,
    const Reference< XBlob >& )
{
    throw SQLException(
        u"pq_preparedstatement: setBlob not implemented"_ustr,
        *this, OUString(), 1, Any () );
}

void PreparedStatement::setClob(
    sal_Int32,
    const Reference< XClob >& )
{
    throw SQLException(
        u"pq_preparedstatement: setClob not implemented"_ustr,
        *this, OUString(), 1, Any () );
}

void PreparedStatement::setArray(
    sal_Int32 parameterIndex,
    const Reference< XArray >& x )
{
    setString( parameterIndex, array2String( x->getArray( nullptr ) ) );
}

void PreparedStatement::clearParameters(  )
{
    MutexGuard guard(m_xMutex->GetMutex() );
    m_vars = std::vector< OString >( m_vars.size() );
}

Any PreparedStatement::getWarnings(  )
{
    return Any();
}

void PreparedStatement::clearWarnings(  )
{
}

Reference< css::sdbc::XResultSetMetaData > PreparedStatement::getMetaData()
{
    Reference< css::sdbc::XResultSetMetaData > ret;
    Reference< css::sdbc::XResultSetMetaDataSupplier > supplier( m_lastResultset, UNO_QUERY );
    if( supplier.is() )
        ret = supplier->getMetaData();
    return ret;
}

::cppu::IPropertyArrayHelper & PreparedStatement::getInfoHelper()
{
    return getPreparedStatementPropertyArrayHelper();
}


sal_Bool PreparedStatement::convertFastPropertyValue(
        Any & rConvertedValue, Any & rOldValue, sal_Int32 nHandle, const Any& rValue )
{
    bool bRet;
    rOldValue = m_props[nHandle];
    switch( nHandle )
    {
    case PREPARED_STATEMENT_CURSOR_NAME:
    {
        OUString val;
        bRet = ( rValue >>= val );
        rConvertedValue <<= val;
        break;
    }
    case PREPARED_STATEMENT_ESCAPE_PROCESSING:
    {
        bool val(false);
        bRet = ( rValue >>= val );
        rConvertedValue <<= val;
        break;
    }
    case PREPARED_STATEMENT_FETCH_DIRECTION:
    case PREPARED_STATEMENT_FETCH_SIZE:
    case PREPARED_STATEMENT_MAX_FIELD_SIZE:
    case PREPARED_STATEMENT_MAX_ROWS:
    case PREPARED_STATEMENT_QUERY_TIME_OUT:
    case PREPARED_STATEMENT_RESULT_SET_CONCURRENCY:
    case PREPARED_STATEMENT_RESULT_SET_TYPE:
    {
        sal_Int32 val;
        bRet = ( rValue >>= val );
        rConvertedValue <<= val;
        break;
    }
    default:
    {
        throw IllegalArgumentException(
            "pq_statement: Invalid property handle ("
            + OUString::number( nHandle ) +  ")",
            *this, 2 );
    }
    }
    return bRet;
}


void PreparedStatement::setFastPropertyValue_NoBroadcast(
    sal_Int32 nHandle,const Any& rValue )
{
    m_props[nHandle] = rValue;
}

void PreparedStatement::getFastPropertyValue( Any& rValue, sal_Int32 nHandle ) const
{
    rValue = m_props[nHandle];
}

Reference < XPropertySetInfo >  PreparedStatement::getPropertySetInfo()
{
    return OPropertySetHelper::createPropertySetInfo( getPreparedStatementPropertyArrayHelper() );
}

void PreparedStatement::disposing()
{
    close();
}


Reference< XResultSet > PreparedStatement::getResultSet(  )
{
    return Reference< XResultSet > ( m_lastResultset, css::uno::UNO_QUERY );
}
sal_Int32 PreparedStatement::getUpdateCount(  )
{
    return m_multipleResultUpdateCount;
}
sal_Bool PreparedStatement::getMoreResults(  )
{
    Reference< XCloseable > lastResultSet = m_lastResultset;
    if( lastResultSet.is() )
        lastResultSet->close();
    m_multipleResultUpdateCount = -1;
    return false;
}

Reference< XResultSet > PreparedStatement::getGeneratedValues(  )
{
    osl::MutexGuard guard( m_xMutex->GetMutex() );
    return getGeneratedValuesFromLastInsert(
        m_pSettings, m_connection, m_lastOidInserted, m_lastTableInserted, m_lastQuery );
}


}

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