//--------------------------------------------------------------------
// Microsoft OLE DB Sample Provider
// (C) Copyright 1991 - 1999 Microsoft Corporation. All Rights Reserved.
//
// @doc
//
// @module ROWSET.CPP | CRowset object implementation
//
//

// Includes ------------------------------------------------------------------

#include "headers.h"

static const int MAX_BITS = 1008;

static const int TYPE_CHAR = 1;
static const int TYPE_SLONG = 3;

// Code ----------------------------------------------------------------------

// CRowset::CRowset ----------------------------------------------------------
//
// @mfunc Constructor for this class
//
// @rdesc NONE
//
CRowset::CRowset
    (
    LPUNKNOWN pUnkOuter         //@parm IN | Outer Unkown Pointer
    )	// invoke ctor for base class
	: CBaseObj( BOT_ROWSET )
{

    //  Initialize simple member vars
    m_cRef          = 0L;
	m_pUnkOuter		= pUnkOuter ? pUnkOuter : this;

    // Intialize buffered row count + pointers to allocated memory
    m_pFileio           = NULL;
    m_cRows             = 0;
    m_irowFilePos       = 0;
    m_irowLastFilePos   = 0;
    m_cbTotalRowSize    = 0;
    m_cbRowSize         = 0;
    m_irowMin           = 0;
    m_ulRowRefCount     = 0;
    m_pextbufferAccessor= NULL;
    m_pIBuffer          = NULL;
    m_prowbitsIBuffer   = NULL;
    m_pLastBindBase     = NULL;
    m_rgbRowData        = NULL;
    m_dwStatus          = 0;
    m_pUtilProp         = NULL;
	m_pCreator			= NULL;

	m_wszFilePath[0]		= L'\0';
	m_wszDataSourcePath[0]	= L'\0';

    //  Initially, NULL all contained interfaces
    m_pIAccessor                    = NULL;
    m_pIColumnsInfo                 = NULL;
	m_pIConvertType					= NULL;
    m_pIRowset                      = NULL;
    m_pIRowsetChange                = NULL;
    m_pIRowsetIdentity              = NULL;
    m_pIRowsetInfo                  = NULL;
	m_pIGetRow						= NULL;

    // Increment global object count.
    OBJECT_CONSTRUCTED();

    return;
}


// CRowset::~CRowset ---------------------------------------------------------
//
// @mfunc Destructor for this class
//
// @rdesc NONE
//
CRowset:: ~CRowset
    (
    void
    )
{
    // Free pointers.
    // (Note delete is safe for NULL ptr.)
    SAFE_DELETE( m_prowbitsIBuffer );
    SAFE_DELETE( m_pUtilProp );

    if (m_pIBuffer)
        ReleaseSlotList( m_pIBuffer );

    // Free accessors.
    // Each accessor is allocated via new/delete.
    // We store an array of ptrs to each accessor (m_pextbufferAccessor).
    if (m_pextbufferAccessor)
	{
        HACCESSOR   hAccessor, hAccessorLast;
        PACCESSOR   pAccessor;

        m_pextbufferAccessor->GetFirstLastItemH(hAccessor, hAccessorLast);
        
		for (; hAccessor <= hAccessorLast; hAccessor++)
		{
            m_pextbufferAccessor->GetItemOfExtBuffer(hAccessor, &pAccessor);
            SAFE_DELETE( pAccessor );
		}
        
		SAFE_DELETE( m_pextbufferAccessor );
	}

    //  Free contained interfaces
    SAFE_DELETE( m_pIAccessor );
    SAFE_DELETE( m_pIColumnsInfo );
    SAFE_DELETE( m_pIConvertType );
    SAFE_DELETE( m_pIRowset );
    SAFE_DELETE( m_pIRowsetChange );
    SAFE_DELETE( m_pIRowsetIdentity );
    SAFE_DELETE( m_pIRowsetInfo );
	SAFE_DELETE( m_pIGetRow );

    // free CFileio object
    if (m_pFileio)
        SAFE_DELETE( m_pFileio );

	if( m_pCreator )
		m_pCreator->GetOuterUnknown()->Release();

	if( m_pParentObj )	
		m_pParentObj->GetOuterUnknown()->Release();

    // Decrement global object count.
    OBJECT_DESTRUCTED();

    return;
}

// CRowset::FInit ------------------------------------------------------------
//
// @mfunc Initialize the rowset Object
//
// @rdesc Did the Initialization Succeed
//      @flag  TRUE | Initialization succeeded
//      @flag  FALSE | Initialization failed
//
BOOL CRowset::FInit
    (
    CFileIO *   pCFileio,		//@parm IN | pointer to Fileio object
	CBaseObj *	pParentBaseObj,	//@parm IN | pointer to Base Object creating the rowset
	WCHAR *		pwszFilePath,	//@parm IN | The File Path of the csv file
	WCHAR *		pwszDataSource	//@parm IN | The datasource path
    )
{
	// Asserts
	assert(pCFileio);
	assert(pParentBaseObj);
    assert(m_pUnkOuter);
    assert(pParentBaseObj);

    LPUNKNOWN pIUnknown = m_pUnkOuter;
    m_pFileio = pCFileio;

	m_pParentObj = pParentBaseObj;
	m_pParentObj->GetOuterUnknown()->AddRef();

    //--------------------
    // Get FileInfo
    //--------------------
    // Find # of columns in the result set.
    m_cCols = m_pFileio->GetColumnCnt();
    if( m_cCols <= 0 )
        return FALSE;

	m_cbRowSize = m_pFileio->GetRowSize();
    
    if (FAILED( CreateHelperFunctions()))
        return FALSE;
    
    m_cbTotalRowSize = m_pIBuffer->cbSlot;

    //--------------------
    // Perform binding
    //--------------------
    // Bind result set columns to the first row of the internal buffer.
    // For each column bind it's data as well as length. Leave space for
    // derived status info.
    // Note that we could defer binding, but this way we can check for
    // bad errors before we begin.
    // We may need to bind again if going back and forth
    // with GetNextRows.
    assert(m_rgbRowData);
    if (FAILED( Rebind((BYTE *) GetRowBuff( m_irowMin, TRUE ))))
        return FALSE;

    // allocate utility object that manages our properties
    m_pUtilProp = new CUtilProp();
	if (!m_pUtilProp)
		return FALSE;

    // Allocate contained interface objects
    // Note that our approach is simple - we always create *all* of the Rowset interfaces
    // If our properties were read\write (i.e., could be set), we would need to
    // consult properties to known which interfaces to create.
    // Also, none of our interfaces conflict. If any did conflict, then we could
    // not just blindly create them all.
    m_pIColumnsInfo             = new CImpIColumnsInfo( this, pIUnknown );
	m_pIConvertType				= new CImpIConvertType(this, pIUnknown);
    m_pIRowset                  = new CImpIRowset( this, pIUnknown );
    m_pIRowsetIdentity          = new CImpIRowsetIdentity( this, pIUnknown );
    m_pIRowsetInfo              = new CImpIRowsetInfo( this, pIUnknown );
	m_pIAccessor                = new CImpIAccessor( this, pIUnknown );
	m_pIGetRow					= new CImpIGetRow( this, pIUnknown );

	if (!m_pFileio->IsReadOnly())
		m_pIRowsetChange		= new CImpIRowsetChange( this, pIUnknown );

	if (m_pIAccessor && FAILED(m_pIAccessor->FInit(TRUE)))
		return FALSE;

	StringCchCopyW(m_wszFilePath, sizeof(m_wszFilePath)/sizeof(WCHAR), pwszFilePath);
	StringCchCopyW(m_wszDataSourcePath, sizeof(m_wszDataSourcePath)/sizeof(WCHAR), pwszDataSource);

    // if all interfaces were created, return success
    return (BOOL) (m_pIAccessor && m_pIColumnsInfo && m_pIConvertType &&
                   m_pIRowset && m_pIRowsetIdentity && m_pIRowsetInfo &&
				   ((m_pFileio->IsReadOnly() && !m_pIRowsetChange) ||
				    (!m_pFileio->IsReadOnly() && m_pIRowsetChange)) &&
					m_pIGetRow);
}


// CRowset::QueryInterface ---------------------------------------------------
//
// @mfunc Returns a pointer to a specified interface. Callers use
// QueryInterface to determine which interfaces the called object
// supports.
//
// @rdesc HRESULT indicating the status of the method
//      @flag S_OK | Interface is supported and ppvObject is set.
//      @flag E_NOINTERFACE | Interface is not supported by the object
//      @flag E_INVALIDARG | One or more arguments are invalid.
//
STDMETHODIMP CRowset::QueryInterface
    (
    REFIID riid,
    LPVOID * ppv
    )
{
    if (NULL == ppv)
        return ResultFromScode( E_INVALIDARG );

    //  Place NULL in *ppv in case of failure
    *ppv = NULL;

    //  This is the non-delegating IUnknown implementation
    if (riid == IID_IUnknown)
        *ppv = (LPVOID) this;
    else if (riid == IID_IAccessor)
        *ppv = (LPVOID) m_pIAccessor;
    else if (riid == IID_IColumnsInfo)
        *ppv = (LPVOID) m_pIColumnsInfo;
    else if (riid == IID_IConvertType)
        *ppv = (LPVOID) m_pIConvertType;
    else if (riid == IID_IRowset)
        *ppv = (LPVOID) m_pIRowset;
    else if (riid == IID_IRowsetChange && SupportIRowsetChange() )
        *ppv = (LPVOID) m_pIRowsetChange;
    else if (riid == IID_IRowsetIdentity)
        *ppv = (LPVOID) m_pIRowsetIdentity;
    else if (riid == IID_IRowsetInfo)
        *ppv = (LPVOID) m_pIRowsetInfo;
	else if (riid == IID_IGetRow)
		*ppv = (LPVOID) m_pIGetRow;

    //  If we're going to return an interface, AddRef it first
    if (*ppv)
        {
        ((LPUNKNOWN) *ppv)->AddRef();
        return ResultFromScode( S_OK );
        }
    else
        return ResultFromScode( E_NOINTERFACE );
}


// CRowset::AddRef -----------------------------------------------------------
//
// @mfunc Increments a persistence count for the object
//
// @rdesc Current reference count
//
STDMETHODIMP_( DBREFCOUNT ) CRowset::AddRef
     (
     void
     )
{
    return ++m_cRef;
}


// CRowset::Release ----------------------------------------------------------
//
// @mfunc Decrements a persistence count for the object and if
// persistence count is 0, the object destroys itself.
//
// @rdesc Current reference count
//
STDMETHODIMP_( DBREFCOUNT ) CRowset::Release
     (
     void
     )
{
    if (!--m_cRef)
        {
        this->m_pCreator->DecrementOpenRowsets();
        delete this;
        return 0;
        }

    return m_cRef;
}

// CRowset::CreateHelperFunctions --------------------------------------------
//
// @mfunc Creates Helper classes that are needed to manage the Rowset Object
//
// @rdesc HRESULT
//      @flag S_OK | Helper classes created
//      @flag E_FAIL | Helper classes were not created
//
HRESULT CRowset::CreateHelperFunctions
    (
    void
    )
{
    //----------------------
    // Create helper objects
    //----------------------

    // Bit array to track presence/absense of rows.
    m_prowbitsIBuffer = new CBitArray;
    if( !m_prowbitsIBuffer || FAILED(m_prowbitsIBuffer->FInit(MAX_BITS, g_dwPageSize)))
        return ResultFromScode( E_FAIL );

    // List of free slots.
    // This manages the allocation of sets of contiguous rows.
    if (FAILED( InitializeSlotList( MAX_TOTAL_ROWBUFF_SIZE / (ULONG)m_cbRowSize,
                         (ULONG) m_cbRowSize, g_dwPageSize, m_prowbitsIBuffer,
                         &m_pIBuffer, &m_rgbRowData )))
        return ResultFromScode( E_FAIL );

    // Locate some free slots.
    // Should be at very beginning.
    // This tells us which row we will bind to: m_irowMin.
    if (FAILED( GetNextSlots( m_pIBuffer, 1, &m_irowMin )))
        return ResultFromScode( E_FAIL );

    ReleaseSlots( m_pIBuffer, m_irowMin, 1 );

    return ResultFromScode( S_OK );
}


// CRowset::Rebind --------------------------------------------
//
// @mfunc Establishes data offsets and type for the file
// routines to place the data
//
// @rdesc HRESULT
//      @flag S_OK | Bindings set fine
//      @flag E_FAIL | Bindings could not be set
//
HRESULT CRowset::Rebind
    (
    BYTE *pBase                 //@parm IN | Base pointer for Data Area
    )
{
    COLUMNDATA  *pColumn;

    // Bind result set columns.
    // Use established types and sizes and offsets.
    // Bind to internal row buffer, area beginning with 'pRowBuff'.
    //
    // For each column, bind it's data as well as length.
    // Offsets point to start of COLUMNDATA structure.

    assert( pBase );

    // Optimize by not doing it over again.
    if( pBase != m_pLastBindBase )
	{
        m_pLastBindBase = 0;

        for (DBORDINAL cCols=1; cCols <= m_cCols; cCols++)
		{
			pColumn = m_pFileio->GetColumnData(cCols, (ROWBUFF *)pBase);
            
			if( FAILED( m_pFileio->SetColumnBind(cCols, pColumn)) )
                return( E_FAIL );
		}

        // Remember in case we bind to same place again.
        m_pLastBindBase = pBase;
	}

    return( S_OK );
}


// CRowset::GetRowBuff--------------------------------------------
//
// @mfunc Shorthand way to get the address of a row buffer.
// Later we may extend this so that it can span several
// non-contiguous blocks of memory.
//
// @rdesc Pointer to the buffer.
//
ROWBUFF* CRowset::GetRowBuff
    (
    DBCOUNTITEM iRow,           //@parm IN | Row to get address of.
    BOOL  fDataLocation         //@parm IN | Get the Data offset.
    )
{
    // This assumes iRow is valid...
    // How can we calculate maximum row?
    // Should we return NULL if it's out of range?
    assert( m_rgbRowData );
    assert( m_cbRowSize );
    assert( iRow > 0 );

	// Get the LSTSlot address or the Data offset (We need to make sure this structure is properly aligned)
	if ( fDataLocation )
		return (ROWBUFF *) ROUND_UP ((m_rgbRowData + m_cbTotalRowSize*iRow), COLUMN_ALIGNVAL);
	else
		return (ROWBUFF *) ROUND_UP ((m_rgbRowData + m_cbRowSize + (m_cbTotalRowSize*(iRow-1))), COLUMN_ALIGNVAL);
}




//////////////////////////////////////////////////////////////////////////////
//      Helper functions    Helper functions    Helper functions
//////////////////////////////////////////////////////////////////////////////

// GetInternalTypeFromCSVType ------------------------------------------------
//
// @func This function returns the default OLE DB representation
// for a data type
//
HRESULT GetInternalTypeFromCSVType
    (
    SWORD  swDataType,  //@parm IN | Data Type
    BOOL   fIsSigned,   //@parm IN | Signed or Unsigned
    DWORD  *pdwdbType   //@parm OUT | OLE DB type for DBColumnInfo
    )
{
    static struct {
        SWORD   swCSVType;
        BOOL    fIsSigned;          // 1=signed, 0=unsigned
        BOOL    fSignedDistinction; // 1=signed matters
        DWORD   dwdbType;
    } TypeTable[] =
        {
            {TYPE_CHAR,         0, 0, DBTYPE_STR },
            {TYPE_SLONG,        1, 1, DBTYPE_I4  },
        };

    for (int j=0; j < NUMELEM( TypeTable ); j++)
        {
        if (swDataType == TypeTable[j].swCSVType        // type match
        && (!TypeTable[j].fSignedDistinction            // care about sign?
            || fIsSigned == TypeTable[j].fIsSigned))    // sign match
            {
            assert( pdwdbType );
            *pdwdbType     = TypeTable[j].dwdbType;
            return ResultFromScode( S_OK );
            }
        }

    // Should never get here, since we supposedly have
    // a table of all possible CSV types.
    assert( !"Unmatched CSV Type." );
    return ResultFromScode( E_FAIL );
}

// SupportIRowsetChange ------------------------------------------------
//
// @func This function returns if IrowsetChange is supported
//
BOOL CRowset::SupportIRowsetChange()
{
	BOOL		fIRowsetChange = FALSE;
	ULONG		cPropSets      = 0;
	DBPROPSET *	rgPropSets     = NULL;
	DBPROPID	rgPropId[1];
	DBPROPIDSET	rgPropertySets[1];

    // Get the value of the DBPROP_CANHOLDROWS property
	rgPropertySets[0].guidPropertySet = DBPROPSET_ROWSET;
	rgPropertySets[0].rgPropertyIDs	  = rgPropId;
	rgPropertySets[0].cPropertyIDs	  = 1;
	rgPropId[0]                       = DBPROP_IRowsetChange;

	// Get the IRowsetChange Property from m_pUtilProp
	GetCUtilProp()->GetProperties(PROPSET_ROWSET, 1, rgPropertySets, 
											&cPropSets, &rgPropSets);
	
	// Get the Prompt value
	if( V_BOOL(&rgPropSets->rgProperties->vValue) == VARIANT_TRUE )
		fIRowsetChange = TRUE;

	// release properties 
	FreeProperties(&cPropSets, &rgPropSets);

	return fIRowsetChange;
}
