/* -*- 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/config.h>

#include <string_view>

#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <rtl/ref.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
#include <com/sun/star/sdbc/SQLException.hpp>
#include <com/sun/star/sdbc/XRow.hpp>
#include <com/sun/star/sdbc/ColumnValue.hpp>

#include <cppuhelper/exc_hlp.hxx>
#include <utility>

#include "pq_xcolumns.hxx"
#include "pq_xcolumn.hxx"
#include "pq_statics.hxx"
#include "pq_tools.hxx"

using osl::MutexGuard;


using com::sun::star::beans::XPropertySet;

using com::sun::star::uno::Any;
using com::sun::star::uno::UNO_QUERY;
using com::sun::star::uno::Reference;
using com::sun::star::uno::RuntimeException;

using com::sun::star::sdbc::XRow;
using com::sun::star::sdbc::XStatement;
using com::sun::star::sdbc::XResultSet;
using com::sun::star::sdbc::XDatabaseMetaData;
using com::sun::star::sdbc::SQLException;

namespace pq_sdbc_driver
{

static Any isCurrency( std::u16string_view typeName )
{
    return Any( o3tl::equalsIgnoreAsciiCase(typeName, u"money") );
}

// static sal_Bool isAutoIncrement8( const OUString & typeName )
// {
//     return typeName.equalsIgnoreAsciiCase("serial8") ||
//         typeName.equalsIgnoreAsciiCase("bigserial");
// }

static Any isAutoIncrement( std::u16string_view defaultValue )
{
    bool ret = o3tl::starts_with( defaultValue, u"nextval(" );
//     printf( "%s %d\n",
//             OUStringToOString(defaultValue, RTL_TEXTENCODING_ASCII_US).getStr(),
//             ret );
//     {
//     static const char  * const serials[] =
//         {
//             "serial", "serial4", "serial8", "bigserial", 0
//         };
// s    sal_Bool b = sal_False;
//     for( int i = 0; !b && serials[i] ; i ++ )
//     {
//         b = b || typeName.equalsIgnoreAsciiCaseAscii( serials[i] );
//     }
    return Any ( ret );
}

Columns::Columns(
        const ::rtl::Reference< comphelper::RefCountedMutex > & refMutex,
        const css::uno::Reference< css::sdbc::XConnection >  & origin,
        ConnectionSettings *pSettings,
        OUString schemaName,
        OUString tableName)
    : Container( refMutex, origin, pSettings,  u"COLUMN"_ustr ),
      m_schemaName(std::move( schemaName )),
      m_tableName(std::move( tableName ))
{}

Columns::~Columns()
{}

OUString columnMetaData2SDBCX(
    ReflectionBase *pBase, const css::uno::Reference< css::sdbc::XRow > &xRow )
{
    Statics & st = getStatics();

    //  1. TABLE_CAT string => table catalog (may be NULL)
    //               => not supported
    //  2. TABLE_SCHEM string => table schema (may be NULL)
    //               => pg_namespace.nspname
    //  3. TABLE_NAME string => table name
    //               => pg_class.relname
    //  4. COLUMN_NAME string => column name
    //               => pg_attribute.attname
    //  5. DATA_TYPE short => SQL type from java.sql.Types
    //               => pg_type.typname => sdbc.DataType
    //  6. TYPE_NAME string => Data source dependent type name, for a UDT the
    //                         type name is fully qualified
    //               => pg_type.typname
    //  7. COLUMN_SIZE long => column size. For char or date types this is
    //                         the maximum number of characters, for numeric
    //                         or decimal types this is precision.
    //               => pg_type.typlen ( TODO: What is about variable size ? )
    //  8. BUFFER_LENGTH is not used.
    //               => not used
    //  9. DECIMAL_DIGITS long => the number of fractional digits
    //               => don't know ! TODO !
    //  10. NUM_PREC_RADIX long => Radix (typically either 10 or 2)
    //               => TODO ??
    //  11. NULLABLE long => is NULL allowed?
    //                      NO_NULLS - might not allow NULL values
    //                      NULABLE - definitely allows NULL values
    //                      NULLABLE_UNKNOWN - nullability unknown
    //               => pg_attribute.attnotnull
    //  12. REMARKS string => comment describing column (may be NULL )
    //               => Don't know, there does not seem to exist something like
    //                  that in postgres
    //  13. COLUMN_DEF string => default value (may be NULL)
    //               => pg_type.typdefault
    //  14. SQL_DATA_TYPE long => unused
    //               => empty
    //  15. SQL_DATETIME_SUB long => unused
    //               => empty
    //  16. CHAR_OCTET_LENGTH long => for char types the maximum number of
    //                                bytes in the column
    //               => pg_type.typlen
    //  17. ORDINAL_POSITION int => index of column in table (starting at 1)
    //                              pg_attribute.attnum
    //  18. IS_NULLABLE string => "NO" means column definitely does not allow
    //                            NULL values; "YES" means the column might
    //                            allow NULL values. An empty string means
    //                            nobody knows.
    //               => pg_attribute.attnotnull

    static const int COLUMN_NAME = 4;
    static const int DATA_TYPE = 5;
    static const int TYPE_NAME = 6;
    static const int COLUMN_SIZE = 7;
    static const int DECIMAL_DIGITS = 9;
    static const int IS_NULLABLE = 11;
    static const int DESCRIPTION = 12;
    static const int DEFAULT_VALUE = 13;

    OUString name = xRow->getString( COLUMN_NAME );
    OUString typeName  = xRow->getString( TYPE_NAME );

    pBase->setPropertyValue_NoBroadcast_public(
        st.NAME, Any( name ) );

    pBase->setPropertyValue_NoBroadcast_public(
        st.TYPE, Any( xRow->getInt( DATA_TYPE ) ) );

    pBase->setPropertyValue_NoBroadcast_public(
        st.TYPE_NAME, Any( typeName ) );

    pBase->setPropertyValue_NoBroadcast_public(
        st.PRECISION, Any( xRow->getInt( COLUMN_SIZE ) ) );

    pBase->setPropertyValue_NoBroadcast_public(
        st.SCALE, Any( xRow->getInt( DECIMAL_DIGITS ) ) );

    pBase->setPropertyValue_NoBroadcast_public(
        st.IS_NULLABLE, Any( xRow->getInt( IS_NULLABLE ) ) );

    pBase->setPropertyValue_NoBroadcast_public(
        st.DEFAULT_VALUE, Any( xRow->getString( DEFAULT_VALUE ) ) );

//     pBase->setPropertyValue_NoBroadcast_public(
//         st.DESCRIPTION, makeAny( xRow->getString( DESCRIPTION ) ) );

//     if( pBase->getPropertySetInfo()->hasPropertyByName( st.HELP_TEXT ) )
//         pBase->setPropertyValue_NoBroadcast_public(
//             st.HELP_TEXT, makeAny( xRow->getString( DESCRIPTION ) ) );
//     else // for key columns, etc. ...
    pBase->setPropertyValue_NoBroadcast_public(
            st.DESCRIPTION, Any( xRow->getString( DESCRIPTION ) ) );


    // maybe a better criterion than the type name can be found in future
    pBase->setPropertyValue_NoBroadcast_public(
        st.IS_AUTO_INCREMENT, isAutoIncrement(xRow->getString( DEFAULT_VALUE )) );

    pBase->setPropertyValue_NoBroadcast_public(
        st.IS_CURRENCY, isCurrency( typeName));
    return name;
}


// class CommentChanger : public cppu::WeakImplHelper< XPropertyChangeListener >
// {
//     ::rtl::Reference< comphelper::RefCountedMutex > m_xMutex;
//     css::uno::Reference< css::sdbc::XConnection > m_connection;
//     ConnectionSettings *m_pSettings;
//     OUString m_schema;
//     OUString m_table;
//     OUString m_column;

// public:
//     CommentChanger(
//         const ::rtl::Reference< comphelper::RefCountedMutex > & refMutex,
//         const css::uno::Reference< css::sdbc::XConnection > & connection,
//         ConnectionSettings *pSettings,
//         const OUString & schema,
//         const OUString & table,
//         const OUString & column ) :
//         m_xMutex( refMutex ),
//         m_connection( connection ),
//         m_pSettings( pSettings ),
//         m_schema ( schema ),
//         m_table ( table ),
//         m_column ( column )
//     {}


//     // Methods
//     virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) throw (css::uno::RuntimeException)
//     {
//         osl::MutexGuard guard( m_xMutex->GetMutex() );
//         m_connection.clear();
//     }
//         // Methods
//     virtual void SAL_CALL propertyChange( const css::beans::PropertyChangeEvent& evt ) throw (css::uno::RuntimeException)
//     {
//         osl::MutexGuard guard( m_xMutex->GetMutex() );
//         OUStringBuffer buf( 128 );
//         OUString comment;
//         evt.NewValue >>= comment;
//         buf.append( "COMMENT ON COLUMN" );
//         bufferQuoteQualifiedIdentifier( buf, m_schema, m_table , m_column );
//         buf.append( "IS " );
//         bufferQuoteConstant( buf, comment,m_pSettings->encoding);

//         printf( "changing comment of column %s to %s\n",
//                 OUStringToOString( m_column, RTL_TEXTENCODING_ASCII_US ).getStr(),
//                 OUStringToOString( comment, RTL_TEXTENCODING_ASCII_US ).getStr() );

//         m_connection->createStatement()->executeUpdate( buf.makeStringAndClear() );
//     }
// };

void Columns::refresh()
{
    try
    {
        SAL_INFO("connectivity.postgresql", "sdbcx.Columns get refreshed for table " << m_schemaName << "." << m_tableName);
        osl::MutexGuard guard( m_xMutex->GetMutex() );

        Statics &st = getStatics();
        Reference< XDatabaseMetaData > meta = m_origin->getMetaData();

        Reference< XResultSet > rs =
            meta->getColumns( Any(), m_schemaName, m_tableName, st.cPERCENT );

        DisposeGuard disposeIt( rs );
        Reference< XRow > xRow( rs , UNO_QUERY );

        String2IntMap map;

        m_values.clear();
        int columnIndex = 0;
        while( rs->next() )
        {
            rtl::Reference<Column> pColumn =
                new Column( m_xMutex, m_origin, m_pSettings );
            Reference< css::beans::XPropertySet > prop = pColumn;

            OUString name = columnMetaData2SDBCX( pColumn.get(), xRow );
//             pColumn->addPropertyChangeListener(
//                 st.HELP_TEXT,
//                 new CommentChanger(
//                     m_xMutex,
//                     m_origin,
//                     m_pSettings,
//                     m_schemaName,
//                     m_tableName,
//                     name ) );

            {
                m_values.emplace_back(prop);
                map[ name ] = columnIndex;
                ++columnIndex;
            }
        }
        m_name2index.swap( map );
    }
    catch ( css::sdbc::SQLException & e )
    {
        css::uno::Any anyEx = cppu::getCaughtException();
        throw css::lang::WrappedTargetRuntimeException( e.Message,
                        nullptr, anyEx );
    }
    fire( RefreshedBroadcaster( *this ) );
}


void alterColumnByDescriptor(
    std::u16string_view schemaName,
    std::u16string_view tableName,
    ConnectionSettings *settings,
    const Reference< XStatement > &stmt,
    const css::uno::Reference< css::beans::XPropertySet > & past,
    const css::uno::Reference< css::beans::XPropertySet > & future)
{
    Statics & st  = getStatics();

//     if( past->getPropertyValue( st.TABLE_NAME ) != future->getPropertyValue( st.TABLE_NAME ) ||
//         past->getPropertyValue( st.SCHEMA_NAME ) != future->getPropertyValue( st.SCHEMA_NAME ))
//     {
//         OUStringBuffer buf(128);
//         buf.append( "Can't move column " );
//         buf.append( extractStringProperty( past, st.COLUMN_NAME ) );
//         buf.append( " from table " );
//         buf.append( extractStringProperty( past, st.TABLE_NAME ) );
//         buf.append( " to table " );
//         buf.append( extractStringProperty( past, st.TABLE_NAME ) );
//         throw SQLException( buf.makeStringAndClear() );
//     }

//     OUString tableName = extractStringProperty( past, st.TABLE_NAME );
//     OUString schemaName = extractStringProperty( past, st.SCHEMA_NAME );
    OUString pastColumnName = extractStringProperty( past, st.NAME );
    OUString futureColumnName = extractStringProperty( future, st.NAME );
    OUString pastTypeName = sqltype2string( past );
    OUString futureTypeName = sqltype2string( future );

    TransactionGuard transaction( stmt );

    OUStringBuffer buf( 128 );
    if( ! pastColumnName.getLength())
    {
        // create a new column
        buf.append( "ALTER TABLE" );
        bufferQuoteQualifiedIdentifier( buf, schemaName, tableName, settings );
        buf.append( "ADD COLUMN" );
        bufferQuoteIdentifier( buf, futureColumnName, settings );
        buf.append( futureTypeName );
        transaction.executeUpdate( buf.makeStringAndClear() );
    }
    else
    {
        if( pastTypeName != futureTypeName )
        {
            throw RuntimeException(
                u"Can't modify column types, drop the column and create a new one"_ustr );
        }

        if( pastColumnName != futureColumnName )
        {
            buf.append( "ALTER TABLE" );
            bufferQuoteQualifiedIdentifier( buf, schemaName, tableName, settings );
            buf.append( "RENAME COLUMN" );
            bufferQuoteIdentifier( buf, pastColumnName, settings );
            buf.append( "TO" );
            bufferQuoteIdentifier( buf, futureColumnName, settings );
            transaction.executeUpdate( buf.makeStringAndClear() );
        }
    }

    OUString futureDefaultValue = extractStringProperty( future, st.DEFAULT_VALUE );
    OUString pastDefaultValue = extractStringProperty( past, st.DEFAULT_VALUE );
    if( futureDefaultValue != pastDefaultValue )
    {
        buf.truncate();
        buf.append( "ALTER TABLE" );
        bufferQuoteQualifiedIdentifier( buf, schemaName, tableName, settings );
        buf.append( "ALTER COLUMN" );
        bufferQuoteIdentifier( buf, futureColumnName, settings );
        // LEM TODO: check out
        // default value is not quoted, caller needs to quote himself (otherwise
        // how to pass e.g. nextval('something' ) ????
        buf.append( "SET DEFAULT " + futureDefaultValue );
//        bufferQuoteConstant( buf, defaultValue, encoding );
        transaction.executeUpdate( buf.makeStringAndClear() );
    }

    sal_Int32 futureNullable = extractIntProperty( future, st.IS_NULLABLE );
    sal_Int32 pastNullable = extractIntProperty( past, st.IS_NULLABLE );
    if( futureNullable != pastNullable )
    {
        buf.truncate();
        buf.append( "ALTER TABLE" );
        bufferQuoteQualifiedIdentifier( buf, schemaName, tableName, settings );
        buf.append( "ALTER COLUMN" );
        bufferQuoteIdentifier( buf, futureColumnName, settings );
        if( futureNullable == css::sdbc::ColumnValue::NO_NULLS )
        {
            buf.append( "SET" );
        }
        else
        {
            buf.append( "DROP" );
        }
        buf.append( " NOT NULL" );
        transaction.executeUpdate( buf.makeStringAndClear() );
    }

//     OUString futureComment = extractStringProperty( future, st.HELP_TEXT );
//     OUString pastComment = extractStringProperty( past, st.HELP_TEXT );
//     printf( "past Comment %s, futureComment %s\n",
//             OUStringToOString( pastComment, RTL_TEXTENCODING_ASCII_US ).getStr(),
//             OUStringToOString( futureComment, RTL_TEXTENCODING_ASCII_US ).getStr() );
    OUString futureComment = extractStringProperty( future, st.DESCRIPTION );
    OUString pastComment = extractStringProperty( past, st.DESCRIPTION );

    if( futureComment != pastComment )
    {
        buf.truncate();
        buf.append( "COMMENT ON COLUMN" );
        bufferQuoteQualifiedIdentifier( buf, schemaName, tableName , futureColumnName, settings );
        buf.append( "IS " );
        bufferQuoteConstant( buf, futureComment, settings );
        transaction.executeUpdate( buf.makeStringAndClear() );
    }
    transaction.commit();
}

void Columns::appendByDescriptor(
    const css::uno::Reference< css::beans::XPropertySet >& future )
{
    osl::MutexGuard guard( m_xMutex->GetMutex() );
    Statics & st = getStatics();
    Reference< XPropertySet > past = createDataDescriptor();
    past->setPropertyValue( st.IS_NULLABLE, Any( css::sdbc::ColumnValue::NULLABLE ) );
    alterColumnByDescriptor(
        m_schemaName, m_tableName, m_pSettings, m_origin->createStatement() , past, future  );

    refresh();
}

// void Columns::dropByName( const OUString& elementName )
//     throw (css::sdbc::SQLException,
//            css::container::NoSuchElementException,
//            css::uno::RuntimeException)
// {
//     String2IntMap::const_iterator ii = m_name2index.find( elementName );
//     if( ii == m_name2index.end() )
//     {
//         OUStringBuffer buf( 128 );
//         buf.appendAscii( "Column " );
//         buf.append( elementName );
//         buf.appendAscii( " is unknown in table " );
//         buf.append( m_schemaName );
//         buf.appendAscii( "." );
//         buf.append( m_tableName );
//         buf.appendAscii( ", so it can't be dropped" );
//         throw css::container::NoSuchElementException(
//             buf.makeStringAndClear(), *this );
//     }
//     dropByIndex( ii->second );
// }

void Columns::dropByIndex( sal_Int32 index )
{
    osl::MutexGuard guard( m_xMutex->GetMutex() );
    if( index < 0 ||  o3tl::make_unsigned(index) >= m_values.size() )
    {
        throw css::lang::IndexOutOfBoundsException(
            "COLUMNS: Index out of range (allowed 0 to "
            + OUString::number(m_values.size() -1)
            + ", got " + OUString::number( index ) + ")",
            *this );
    }

    Reference< XPropertySet > set;
    m_values[index] >>= set;
    Statics &st = getStatics();
    OUString name;
    set->getPropertyValue( st.NAME ) >>= name;

    OUStringBuffer update( 128 );
    update.append( "ALTER TABLE ONLY");
    bufferQuoteQualifiedIdentifier( update, m_schemaName, m_tableName, m_pSettings );
    update.append( "DROP COLUMN" );
    bufferQuoteIdentifier( update, name, m_pSettings );
    Reference< XStatement > stmt = m_origin->createStatement( );
    DisposeGuard disposeIt( stmt );
    stmt->executeUpdate( update.makeStringAndClear() );

    Container::dropByIndex( index );
}


css::uno::Reference< css::beans::XPropertySet > Columns::createDataDescriptor()
{
    return new ColumnDescriptor( m_xMutex, m_origin, m_pSettings );
}

Reference< css::container::XNameAccess > Columns::create(
    const ::rtl::Reference< comphelper::RefCountedMutex > & refMutex,
    const css::uno::Reference< css::sdbc::XConnection >  & origin,
    ConnectionSettings *pSettings,
    const OUString &schemaName,
    const OUString &tableName,
    rtl::Reference<Columns> *ppColumns)
{
    *ppColumns = new Columns(
        refMutex, origin, pSettings, schemaName, tableName );
    (*ppColumns)->refresh();

    return *ppColumns;
}


ColumnDescriptors::ColumnDescriptors(
        const ::rtl::Reference< comphelper::RefCountedMutex > & refMutex,
        const css::uno::Reference< css::sdbc::XConnection >  & origin,
        ConnectionSettings *pSettings )
    : Container( refMutex, origin, pSettings,  u"COLUMN-DESCRIPTOR"_ustr )
{}


Reference< css::beans::XPropertySet > ColumnDescriptors::createDataDescriptor()
{
    return new ColumnDescriptor( m_xMutex, m_origin, m_pSettings );
}

}

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