/*
 * 
* Copyright (C) 2004-2006  Autodesk, Inc.
* 
* This library is free software; you can redistribute it and/or
* modify it under the terms of version 2.1 of the GNU Lesser
* General Public License 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
* 
 */

#include "stdafx.h"
#include "ArcSDEUtils.h"

ArcSDEInsertCommand::ArcSDEInsertCommand (FdoIConnection *connection) :
    ArcSDEFeatureCommand<FdoIInsert> (connection)
{
}

/** Do not implement the copy constructor. **/
//ArcSDEInsertCommand::ArcSDEInsertCommand (const ArcSDEInsertCommand &right) { }

ArcSDEInsertCommand::~ArcSDEInsertCommand (void)
{
}


/// <summary>Gets the FdoBatchParameterValueCollection that can be used for optimized
/// batch inserts of multiple features with a single insert command. Batch
/// inserts can be performed by using Parameters for each of the property
/// values, then adding collections of parameter values to the
/// FdoBatchParameterValueCollection. Each FdoParameterValueCollection in the
/// FdoBatchParameterValueCollection should contain one FdoParameterValue for each
/// of the parameters specified for property values.</summary>
/// <returns>Returns FdoBatchParameterValueCollection</returns>
FdoBatchParameterValueCollection* ArcSDEInsertCommand::GetBatchParameterValues ()
{
    if (mBatchParameters == NULL)
        mBatchParameters = FdoBatchParameterValueCollection::Create();

    return (FDO_SAFE_ADDREF(mBatchParameters.p));
}

// insert one row of data into the table corresponding to the given class:
void ArcSDEInsertCommand::insertOneRow (SE_STREAM &stream, CHAR* table_name, FdoClassDefinition* classDef, FdoPropertyValueCollection* values, FdoPropertyValueCollection *idCollection,
											int uuidColumns, CHAR **uuid_list)
{
    FdoPtr<FdoPropertyDefinitionCollection> properties;
    int count = 0;
    LONG result = 0L;
    FdoPtr<FdoPropertyDefinition> property;
    FdoPtr<FdoPropertyValue> value;
    FdoPtr<FdoIdentifier> propertyId;

    properties = classDef->GetProperties ();

    // Set all given property values on the stream;
    // NOTE: some properties may be missing (e.g. for read-only or nullable properties):
    assignValues (mConnection, stream, table_name, properties, values, true, uuidColumns, uuid_list, classDef->GetName(), idCollection);

    // Perform the actual insert:
    result = SE_stream_execute (stream);
    handle_sde_err<FdoCommandException>(stream, result, __FILE__, __LINE__, ARCSDE_STREAM_EXECUTE, "Stream execute failed.");


    // Add the identity property values of inserted row to the idCollection, if any:
    FdoPtr<FdoDataPropertyDefinitionCollection> identities = classDef->GetIdentityProperties ();
    if (identities->GetCount() > 0)
    {
        FdoPtr<FdoDataPropertyDefinition> idProperty = identities->GetItem(0);
        FdoPtr <FdoPropertyValue> outputValue = FdoPropertyValue::Create ();
        outputValue->SetName (idProperty->GetName());

        if (idProperty->GetIsAutoGenerated())  // sde-generated id property
        {
            LONG newIdentity = 0L;
            result = SE_stream_last_inserted_row_id (stream, &newIdentity);
            handle_sde_err<FdoCommandException>(stream, result, __FILE__, __LINE__, ARCSDE_STREAM_GET_LAST_INSERTED_ID_FAILED, "Failed to retrieve the last inserted id from the stream.");

            FdoPtr <FdoDataValue> data = FdoDataValue::Create ((FdoInt32)newIdentity);
            outputValue->SetValue (data);
        }
        else  // NON-auto-generated id property
        {
            // Simply give the caller back what they gave us:
            FdoPtr<FdoPropertyValue> inputValue = values->GetItem(idProperty->GetName());
            FdoPtr<FdoValueExpression> inputValueExpr = inputValue->GetValue();
            outputValue->SetValue(inputValueExpr->ToString());
        }

        idCollection->Insert(0, outputValue);
    }
}

/// <summary>Executes the insert command and returns a reference to an
/// FdoIFeatureReader. Some feature providers can generate automatic identity
/// values for features. This will happen automatically as the features are
/// inserted. The returned FdoIFeatureReader allows the client to obtain the
/// automatic identity property value(s) of newly inserted object(s). The
/// returned feature reader at a minimum will read the unique identity
/// properties of the objects just inserted. Multiple objects will be
/// returned through the reader in the case of a batch insert.</summary>
/// <returns>Returns an FdoIFeatureReader</returns> 
FdoIFeatureReader* ArcSDEInsertCommand::Execute ()
{
    FdoPtr<ArcSDEConnection> connection;
    FdoPtr<FdoIFeatureReader> idReader;
    CHAR table[SE_QUALIFIED_TABLE_NAME];
    LONG result = 0L;
    int count = 0;
    SE_STREAM stream = NULL;
    FdoException *exception = NULL;
	CHAR **uuid_list = NULL;
	int uuidColumns = 0;

    try
    {
        // verify the connection
        connection = static_cast<ArcSDEConnection*>(GetConnection ());
        if (connection == NULL)
            throw FdoException::Create (NlsMsgGet (ARCSDE_CONNECTION_NOT_ESTABLISHED, "Connection not established."));

        // verify the feature class name is specified
        if (mClassName == NULL)
            throw FdoException::Create (NlsMsgGet (ARCSDE_FEATURE_CLASS_UNSPECIFIED, "Feature class name not specified."));

        // get the class definition which reflects the requested feature class name
        FdoPtr<FdoClassDefinition> classDef = connection->GetRequestedClassDefinition (mClassName);

        // Handle read-only properties and default values, for NON-batch-mode only:
        // TODO: we should handle read-only properties, nullable properties and default values
        //       for the batch mode as well; however, we wont for R1 for the following reasons:
        //       1) Since ApplySchema is out-of-scope for R1 and default values only apply to
        //          ApplySchema
        //       2) The nullable scenario occurs in batch mode only when 0 parameters are specified,
        //          which is highly unlikely
        //       3) ArcSDE will give us errors anyway if we set read-only properties (without ApplySchema,
        //          this only occurs for auto-generated ID properties).
        if ((mBatchParameters == NULL) || (mBatchParameters->GetCount()==0))
            HandleReadOnlyAndDefaultValues(mValues, true);


        // Create the ArcSDE stream (with buffer mode off by default):
        //TODO: consider calling SE_connection_set_stream_spec() to adjust arcsde buffer size
        result = SE_stream_create (connection->GetConnection (), &stream);
        handle_sde_err<FdoCommandException> (connection->GetConnection (), result, __FILE__, __LINE__, ARCSDE_STREAM_ALLOC, "Cannot initialize SE_STREAM structure.");

        // Create the collection of IDs this method will return:
        FdoPtr<FdoDataPropertyDefinitionCollection> ids = classDef->GetIdentityProperties ();
        FdoPtr<FdoDataPropertyDefinition> idProperty;
        FdoPtr <FdoPropertyValueCollection> idCollection = FdoPropertyValueCollection::Create ();
        if (ids->GetCount() > 0)
        {
            idProperty = ids->GetItem(0);
        }

        // get the table_name and column names
        connection->ClassToTable (table, classDef);

		// SE_UUID_TYPE types are mapped as FdoDataType_String

		// Get the number of uuid columns
		// There should usually be at most 1 uuid column but handle the case if there is more than 1 uuid column

		SHORT lcolumn_count = 0;
		SE_COLUMN_DEF *lcolumns = NULL;
		int lresult = SE_table_describe (connection->GetConnection (), table, &lcolumn_count, &lcolumns);
		if (lresult == SE_SUCCESS)
		{
			for (int i=0; i<lcolumn_count; i++)
			{
				if (lcolumns[i].sde_type == SE_UUID_TYPE)
					uuidColumns++;
			}
		}

        count = mValues->GetCount ();
        CHAR** columns = new CHAR*[count+uuidColumns];

		int index=0;

		if (lresult == SE_SUCCESS)
		{
			// Always add the UUID columns first
			// Then in the method assignValues, we can assume that the UUID columns are always first

			for (int i=0; i<lcolumn_count; i++)
			{
				if (lcolumns[i].sde_type == SE_UUID_TYPE)
				{
					columns[index] = (CHAR*)alloca ((sde_strlen (sde_pcus2wc(lcolumns[i].column_name)) + 1) * sizeof(CHAR));
					sde_strcpy (sde_pus2wc(columns[index]), sde_pcus2wc(lcolumns[i].column_name));
					index++;
					break;
				}
			}

			if (lcolumns != NULL)
				SE_table_free_descriptions (lcolumns);
		}

		if (uuidColumns > 0)
		{
			SE_UUIDGENERATOR uuid_generator;
			connection->GetUuidGenerator(uuid_generator);
			result = SE_uuidgenerator_get_uuids(uuid_generator, uuidColumns, &uuid_list);
			handle_sde_err<FdoCommandException>(stream, result, __FILE__, __LINE__, ARCSDE_INSERT_CANNOT_GET_UUID, "Failed to get uuid.");
		}

        for (int i = 0; i < count; i++)
        {
            CHAR column[SE_QUALIFIED_COLUMN_LEN];

            FdoPtr<FdoPropertyValue> propertyValue = mValues->GetItem(i);
            FdoPtr<FdoIdentifier> propertyId = propertyValue->GetName();
            connection->PropertyToColumn (column, classDef, propertyId);
            columns[i+uuidColumns] = (CHAR*)alloca ((sde_strlen (sde_pcus2wc(column)) + 1) * sizeof(CHAR));
            sde_strcpy (sde_pus2wc(columns[i+uuidColumns]), sde_pcus2wc(column));
        }

        // if necessary, version the table and version enable the stream
        ArcSDELongTransactionUtility::VersionStream (connection, stream, table, true);

        // initialize the stream with table_name and column names
        result = SE_stream_insert_table (stream, table, count+uuidColumns, (const CHAR**)columns);
        handle_sde_err<FdoCommandException>(stream, result, __FILE__, __LINE__, ARCSDE_INSERT_UNEXPECTED_ERROR, "Unexpected error while performing insert.");

        delete[] columns;  columns=NULL;

        // Perform the actual insert:
        if ((mBatchParameters == NULL) || (mBatchParameters->GetCount()==0))
        {
            // Turn off stream buffering:
            result = SE_stream_set_write_mode(stream, FALSE);
            handle_sde_err<FdoCommandException>(stream, result, __FILE__, __LINE__, ARCSDE_INSERT_UNEXPECTED_ERROR, "Unexpected error while performing insert.");

			if (uuidColumns > 0)
				insertOneRow (stream, table, classDef, mValues, idCollection, uuidColumns, uuid_list);
			else
				insertOneRow (stream, table, classDef, mValues, idCollection);
        }
        else
        {
            FdoPtr<FdoPropertyValueCollection>  propertyValues = FdoPropertyValueCollection::Create();
            FdoPtr<FdoPropertyValue>            propertyValue;
            FdoPtr<FdoPropertyValue>            propertyToParamValue;
            FdoPtr<FdoParameterValueCollection> parameterValues;
            FdoPtr<FdoParameterValue>           parameterValue;

            // Turn on stream buffering; this is the main benefit of doing a batch insert:
            result = SE_stream_set_write_mode(stream, TRUE);
            handle_sde_err<FdoCommandException>(stream, result, __FILE__, __LINE__, ARCSDE_INSERT_UNEXPECTED_ERROR, "Unexpected error while performing insert.");

            // Insert all the rows of data:
            for (int row=0; row < mBatchParameters->GetCount(); row++)
            {
                parameterValues = mBatchParameters->GetItem(row);

                // skip empty parameter collections:
                if (parameterValues->GetCount() == 0)
                    continue;

                // map properties to parameter values, storing the mapping in a FdoParameterValueCollection:
                propertyValues->Clear();
                for (int propValIndex=0; propValIndex < mValues->GetCount(); propValIndex++)
                {
                    propertyToParamValue = mValues->GetItem(propValIndex);

                    FdoPtr<FdoIdentifier> identifier = propertyToParamValue->GetName();
                    FdoPtr<FdoPropertyValue> propValue = mValues->GetItem(identifier->GetName());
                    FdoPtr<FdoParameter> paramExpression = static_cast<FdoParameter*>(propValue->GetValue());
                    parameterValue = parameterValues->GetItem(paramExpression->GetName());
                    FdoPtr<FdoLiteralValue> value = parameterValue->GetValue ();
                    propertyValue = FdoPropertyValue::Create (identifier, value);
                    propertyValues->Add(propertyValue);
                }

                // Insert the row:
				if (uuidColumns > 0)
					insertOneRow (stream, table, classDef, propertyValues, idCollection, uuidColumns, uuid_list);
				else
				    insertOneRow (stream, table, classDef, propertyValues, idCollection);
            }

            // Flush stream:
            result = SE_stream_flush_buffered_writes(stream);
            handle_sde_err<FdoCommandException>(stream, result, __FILE__, __LINE__, ARCSDE_INSERT_UNEXPECTED_ERROR, "Unexpected error while performing insert.");
        }

        // Create a FeatureReader to return the new identity property values, if any:
        if (ids->GetCount() > 0)
            idReader = new ArcSDEFeatureInfoReader (idCollection, classDef);
    }
    catch (FdoException *e)
    {
        exception = e;
    }

	if (uuidColumns > 0)
		SE_uuidgenerator_free_uuids(uuid_list, uuidColumns);

    // Free the stream:
    if (NULL != stream)
    {
        result = SE_stream_free (stream);
        handle_sde_err<FdoCommandException>(connection->GetConnection (), result, __FILE__, __LINE__, ARCSDE_STREAM_FREE, "Stream free failed.");
    }

    // Re-throw exception, if any:
    if (exception)
        throw exception;

    return (FDO_SAFE_ADDREF(idReader.p));
}


