/*
 * Copyright 2019 Alistair Leslie-Hughes
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */
#include <stdarg.h>
#include "windef.h"
#include "winbase.h"
#define COBJMACROS
#include "objbase.h"
#include "msdasc.h"
#include "msado15_backcompat.h"

#include "wine/debug.h"
#include "wine/heap.h"

#include "msado15_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(msado15);

struct command
{
    _Command         Command_iface;
    LONG             ref;
    CommandTypeEnum  type;
    BSTR             text;
    _Connection     *connection;
};

static inline struct command *impl_from_Command( _Command *iface )
{
    return CONTAINING_RECORD( iface, struct command, Command_iface );
}

static HRESULT WINAPI command_QueryInterface( _Command *iface, REFIID riid, void **obj )
{
    TRACE( "%p, %s, %p\n", iface, debugstr_guid(riid), obj );

    *obj = NULL;

    if (IsEqualIID(riid, &IID_IUnknown)   ||
        IsEqualIID(riid, &IID_IDispatch)  ||
        IsEqualIID(riid, &IID__ADO)       ||
        IsEqualIID(riid, &IID_Command15)  ||
        IsEqualIID(riid, &IID_Command25)  ||
        IsEqualIID(riid, &IID__Command))
    {
        *obj = iface;
    }
    else
    {
        FIXME( "interface %s not implemented\n", debugstr_guid(riid) );
        return E_NOINTERFACE;
    }

    _Command_AddRef( iface );
    return S_OK;
}

static ULONG WINAPI command_AddRef( _Command *iface )
{
    struct command *command = impl_from_Command( iface );
    return InterlockedIncrement( &command->ref );
}

static ULONG WINAPI command_Release( _Command *iface )
{
    struct command *command = impl_from_Command( iface );
    LONG ref = InterlockedDecrement( &command->ref );
    if (!ref)
    {
        TRACE( "destroying %p\n", command );
        if (command->connection) _Connection_Release(command->connection);
        heap_free( command->text );
        heap_free( command );
    }
    return ref;
}

static HRESULT WINAPI command_GetTypeInfoCount( _Command *iface, UINT *count )
{
    struct command *command = impl_from_Command( iface );
    TRACE( "%p, %p\n", command, count );
    *count = 1;
    return S_OK;
}

static HRESULT WINAPI command_GetTypeInfo( _Command *iface, UINT index, LCID lcid, ITypeInfo **info )
{
    struct command *command = impl_from_Command( iface );
    TRACE( "%p, %u, %u, %p\n", command, index, lcid, info );
    return get_typeinfo(Command_tid, info);
}

static HRESULT WINAPI command_GetIDsOfNames( _Command *iface, REFIID riid, LPOLESTR *names, UINT count,
                                             LCID lcid, DISPID *dispid )
{
    struct command *command = impl_from_Command( iface );
    HRESULT hr;
    ITypeInfo *typeinfo;

    TRACE( "%p, %s, %p, %u, %u, %p\n", command, debugstr_guid(riid), names, count, lcid, dispid );

    hr = get_typeinfo(Command_tid, &typeinfo);
    if(SUCCEEDED(hr))
    {
        hr = ITypeInfo_GetIDsOfNames(typeinfo, names, count, dispid);
        ITypeInfo_Release(typeinfo);
    }

    return hr;
}

static HRESULT WINAPI command_Invoke( _Command *iface, DISPID member, REFIID riid, LCID lcid, WORD flags,
                                      DISPPARAMS *params, VARIANT *result, EXCEPINFO *excep_info, UINT *arg_err )
{
    struct command *command = impl_from_Command( iface );
    HRESULT hr;
    ITypeInfo *typeinfo;

    TRACE( "%p, %d, %s, %d, %d, %p, %p, %p, %p\n", command, member, debugstr_guid(riid), lcid, flags, params,
           result, excep_info, arg_err );

    hr = get_typeinfo(Connection_tid, &typeinfo);
    if(SUCCEEDED(hr))
    {
        hr = ITypeInfo_Invoke(typeinfo, &command->Command_iface, member, flags, params,
                               result, excep_info, arg_err);
        ITypeInfo_Release(typeinfo);
    }

    return hr;
}

static HRESULT WINAPI command_get_Properties( _Command *iface, Properties **props )
{
    FIXME( "%p, %p\n", iface, props );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_get_ActiveConnection( _Command *iface, _Connection **connection )
{
    struct command *command = impl_from_Command( iface );
    TRACE( "%p, %p\n", iface, connection );

    *connection = command->connection;
    if (command->connection) _Connection_AddRef(command->connection);
    return S_OK;
}

static HRESULT WINAPI command_putref_ActiveConnection( _Command *iface, _Connection *connection )
{
    struct command *command = impl_from_Command( iface );
    TRACE( "%p, %p\n", iface, connection );

    if (command->connection) _Connection_Release(command->connection);
    command->connection = connection;
    if (command->connection) _Connection_AddRef(command->connection);
    return S_OK;
}

static HRESULT WINAPI command_put_ActiveConnection( _Command *iface, VARIANT connection )
{
    FIXME( "%p, %s\n", iface, debugstr_variant(&connection) );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_get_CommandText( _Command *iface, BSTR *text )
{
    struct command *command = impl_from_Command( iface );
    BSTR cmd_text = NULL;

    TRACE( "%p, %p\n", command, text );

    if (command->text && !(cmd_text = SysAllocString( command->text ))) return E_OUTOFMEMORY;
    *text = cmd_text;
    return S_OK;
}

static HRESULT WINAPI command_put_CommandText( _Command *iface, BSTR text )
{
    struct command *command = impl_from_Command( iface );
    WCHAR *source = NULL;

    TRACE( "%p, %s\n", command, debugstr_w( text ) );

    if (text && !(source = strdupW( text ))) return E_OUTOFMEMORY;
    heap_free( command->text );
    command->text = source;
    return S_OK;
}

static HRESULT WINAPI command_get_CommandTimeout( _Command *iface, LONG *timeout )
{
    FIXME( "%p, %p\n", iface, timeout );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_put_CommandTimeout( _Command *iface, LONG timeout )
{
    FIXME( "%p, %d\n", iface, timeout );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_get_Prepared( _Command *iface, VARIANT_BOOL *prepared )
{
    FIXME( "%p, %p\n", iface, prepared );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_put_Prepared( _Command *iface, VARIANT_BOOL prepared )
{
    FIXME( "%p, %d\n", iface, prepared );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_Execute( _Command *iface, VARIANT *affected, VARIANT *parameters,
                                       LONG options, _Recordset **recordset )
{
    FIXME( "%p, %p, %p, %d, %p\n", iface, affected, parameters, options, recordset );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_CreateParameter( _Command *iface, BSTR name, DataTypeEnum type,
                                               ParameterDirectionEnum direction, LONG size, VARIANT value,
                                               _Parameter **parameter )
{
    FIXME( "%p, %s, %d, %d, %d, %p\n", iface, debugstr_w(name), type, direction, size, parameter );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_get_Parameters( _Command *iface, Parameters **parameters )
{
    FIXME( "%p, %p\n", iface, parameters );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_put_CommandType( _Command *iface, CommandTypeEnum type )
{
    struct command *command = impl_from_Command( iface );

    TRACE( "%p, %d\n", iface, type );

    switch (type)
    {
    case adCmdUnspecified:
    case adCmdUnknown:
    case adCmdText:
    case adCmdTable:
    case adCmdStoredProc:
    case adCmdFile:
    case adCmdTableDirect:
        command->type = type;
        return S_OK;
    }

    return MAKE_ADO_HRESULT( adErrInvalidArgument );
}

static HRESULT WINAPI command_get_CommandType( _Command *iface, CommandTypeEnum *type )
{
    struct command *command = impl_from_Command( iface );

    TRACE( "%p, %p\n", iface, type );

    *type = command->type;
    return S_OK;
}

static HRESULT WINAPI command_get_Name(_Command *iface, BSTR *name)
{
    FIXME( "%p, %p\n", iface, name );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_put_Name( _Command *iface, BSTR name )
{
    FIXME( "%p, %s\n", iface, debugstr_w(name) );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_get_State( _Command *iface, LONG *state )
{
    FIXME( "%p, %p\n", iface, state );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_Cancel( _Command *iface )
{
    FIXME( "%p\n", iface );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_putref_CommandStream( _Command *iface, IUnknown *stream )
{
    FIXME( "%p, %p\n", iface, stream );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_get_CommandStream( _Command *iface, VARIANT *stream )
{
    FIXME( "%p, %p\n", iface, stream );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_put_Dialect( _Command *iface, BSTR dialect )
{
    FIXME( "%p, %s\n", iface, debugstr_w(dialect) );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_get_Dialect( _Command *iface, BSTR *dialect )
{
    FIXME( "%p, %p\n", iface, dialect );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_put_NamedParameters( _Command *iface, VARIANT_BOOL parameters )
{
    FIXME( "%p, %d\n", iface, parameters );
    return E_NOTIMPL;
}

static HRESULT WINAPI command_get_NamedParameters( _Command *iface, VARIANT_BOOL *parameters )
{
    FIXME( "%p, %p\n", iface, parameters );
    return E_NOTIMPL;
}

static const struct _CommandVtbl command_vtbl =
{
    command_QueryInterface,
    command_AddRef,
    command_Release,
    command_GetTypeInfoCount,
    command_GetTypeInfo,
    command_GetIDsOfNames,
    command_Invoke,
    command_get_Properties,
    command_get_ActiveConnection,
    command_putref_ActiveConnection,
    command_put_ActiveConnection,
    command_get_CommandText,
    command_put_CommandText,
    command_get_CommandTimeout,
    command_put_CommandTimeout,
    command_get_Prepared,
    command_put_Prepared,
    command_Execute,
    command_CreateParameter,
    command_get_Parameters,
    command_put_CommandType,
    command_get_CommandType,
    command_get_Name,
    command_put_Name,
    command_get_State,
    command_Cancel,
    command_putref_CommandStream,
    command_get_CommandStream,
    command_put_Dialect,
    command_get_Dialect,
    command_put_NamedParameters,
    command_get_NamedParameters
};

HRESULT Command_create( void **obj )
{
    struct command *command;

    if (!(command = heap_alloc( sizeof(*command) ))) return E_OUTOFMEMORY;
    command->Command_iface.lpVtbl = &command_vtbl;
    command->type = adCmdUnknown;
    command->text = NULL;
    command->connection = NULL;
    command->ref = 1;

    *obj = &command->Command_iface;
    TRACE( "returning iface %p\n", *obj );
    return S_OK;
}
