/*
*	Class representing a server listening for MEE requests on the SUT.
*
*	©Copyright 2002-2006, Microsoft Corporation
*	Written by Sergey Vasilevskiy.
*/

#include "stdafx.h"

using namespace TPCE;

#define BAD_INPUT_DATA 3

void CMEEServer::PrintMEETable(PMEEIndexTable input)
{
    for (int i=0; i < input->iMEECount; ++i)
    {
        OutputStdinTimed("%d: %s on %s:%hu\n", 
            i, 
            input->MEE[i].szMEEName,
            input->MEE[i].szMachineName, 
            input->MEE[i].shPort);
    }
}

int CMEEServer::ReceiveMEETable(char* mes, TMEEServerLocals* pLocals, int iThreadNum)
{
    // Receiving MEE translation table.				
    pLocals->m_MEETable = *(PMEEIndexTable)mes;// initialize for TLI

    OutputStdinTimed("Thread %i: received MEE table (%d entry(s))\n", iThreadNum, pLocals->m_MEETable.iMEECount);

    if (pLocals->m_MEETable.iMEECount == 1)
    {	// One MEE => use it by default (no need to require command-line parameter)
        //
        strcpy(m_szMEEToUse, pLocals->m_MEETable.MEE[0].szMEEName);
    }
    else
    {
        if (m_szMEEToUse[0] == '\0')	// nothing was provided on the command line?
        {
            PrintMEETable(&pLocals->m_MEETable);

            throw new CSendToMarketErr(CSendToMarketErr::eMEENameNotSpecified, "ReceiveMEETable");
        }
    }

    if (m_bVerbose)
    {
        PrintMEETable(&pLocals->m_MEETable);
    }

    return CBaseTxnErr::SUCCESS;
}

//actually processes received message
int CMEEServer::ProcessMessage(char*                mes, 
                               DWORD                iTotalBytesRecvd, 
                               TMEEServerLocals*    pLocal, 
                               int                  iThreadNum, 
                               PSUTReplyMessage     pSUTReply)
{
    PDriverMessage   	pInput = (PDriverMessage)mes;
    int					ret = 0;	// make sure it does not coincide with PARTIAL_MESSAGE
    int					iRetryCount;

    if (pInput->eTxnType == MEE_TABLE)
    {	// Means just establised connection and no txn input has been sent yet.
        // Need to receive MEE translation table next, before any real txn input.
        if (iTotalBytesRecvd < sizeof(TMEEIndexTable))	// the driver always sends the complete table
            return PARTIAL_MESSAGE;		//return to read more
    }
    else
    {	// MEE table has already been received.
        if (iTotalBytesRecvd < TxnReqSize[pInput->eTxnType])
            return PARTIAL_MESSAGE;		//return to read more
    }

    // Prepare return structure
    //
    pSUTReply->eTxnType = pInput->eTxnType;
    iRetryCount = 0;

    while (iRetryCount < iMaxDeadlockRetries)
    {
		// Initialize status fields before attempting a txn.
		//
		pSUTReply->iStatus = CBaseTxnErr::SUCCESS;
		pSUTReply->iErrorCode = CBaseTxnErr::SUCCESS;

        try
        {
            if ( pInput->eTxnType == MEE_TABLE )	// uninitialized MEE table
            {	
                ret = ReceiveMEETable(mes, pLocal, iThreadNum);
            }
            else
            {   // Normal request

                switch (pInput->eTxnType)
                {
                case MARKET_FEED: ret = DoMarketFeed(&pInput->input.MFInput, pSUTReply->output.MFOutput, pLocal->m_MF, iThreadNum); break;
                case TRADE_RESULT: ret = DoTradeResult(&pInput->input.TRInput, pSUTReply->output.TROutput, pLocal->m_TR, iThreadNum); break;

                default: OutputStdinTimed("Unknown request from the driver (TxnType=%d)!\n", pInput->eTxnType);
                        ret = BAD_INPUT_DATA;  // initialize return code
                }
            }

            pSUTReply->iEGenStatus = ret;
			if (ret < 0)	// EGen error condition
			{
				pSUTReply->iStatus = ERR_FROM_EGEN;
			}

            break;	// exit from deadlock retry loop 
        }			
        catch (CODBCERR* pODBCErr)
        {				
            CDateTime	TimeNow;

            if (pODBCErr->m_bDeadLock)
            {	
                ++iRetryCount;

                // Print error info only for deadlocks. For other errors
                // it'll be printed by CBaseServer::ProcessingThread.
                //
                if (iRetryCount == 1)
                {
                    OutputStdin("%s Thread %d: txn %d is a deadlock victim\n", 
                        TimeNow.ToStr(iDateTimeOutputStyle),
                        iThreadNum,
                        pInput->eTxnType);
                }
                else
                {	// tell this is not the first try
                    OutputStdin("%s Thread %d: txn %d is a deadlock victim (try #%d)\n", 
                        TimeNow.ToStr(iDateTimeOutputStyle),
                        iThreadNum,
                        pInput->eTxnType,
                        iRetryCount);
                }

                // Send the error code back to the driver.
                //
                pSUTReply->iStatus = ERR_TYPE_ODBC;
                pSUTReply->iErrorCode = 1205;

                InterlockedIncrement(&m_iDeadlockCount);
            }				
            else
            {
                //throw;
                //  Do not throw and simply return error code to the driver.
                //  This will allow the driver to continue, but log an error txn.
                //
                pSUTReply->iStatus = ERR_TYPE_ODBC;
				pSUTReply->iErrorCode = pODBCErr->m_NativeError;

                // Show error to the user.
                //
                PrintError(pODBCErr, TimeNow);

                break; // exit the loop
            }

        }			
    }

    if (pSUTReply->iStatus == CBaseTxnErr::SUCCESS &&
        iRetryCount > 0)
    {
        // Set error code for the driver to indicate the number of retries.
        //
        pSUTReply->iErrorCode = ERR_DEADLOCK_VICTIM * iRetryCount;
    }
    return ret;
};

//
//  Helper routine to display an error.
//
void CMEEServer::PrintError(TPCE::CODBCERR* pODBCErr, TPCE::CDateTime& TimeNow)
{
    int                 cbRet;
    char				szMsg[2*1024];

    cbRet = _snprintf(szMsg, sizeof(szMsg), "%s ",TimeNow.ToStr(iDateTimeOutputStyle));
    
    //  Add the main error text.
    //
    cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, "\r\n%s", pODBCErr->ErrorText());

    if (pODBCErr->ErrorNum() != 0)
    {
        cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, " error %d", pODBCErr->ErrorNum());
    }

    // Add Action output for ODBC errors.
    //
    if (((CODBCERR*)pODBCErr)->m_eAction != CODBCERR::eNone)
    {
        cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, "\r\nAction: %i", 
            ((CODBCERR*)pODBCErr)->m_eAction);
    }

    // Add location if it exists.
    //
    if (pODBCErr->ErrorLoc() != NULL)
    {
        cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, " in %s", pODBCErr->ErrorLoc());				
    }
    _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, "\n");
    szMsg[sizeof(szMsg)-1] = '\0';	//null-terminate if sprintf'ed on the last byte in szMsg

    OutputStdin(szMsg);
}

//
// Perform Market Feed
//
int CMEEServer::DoMarketFeed(PMarketFeedTxnInput pInput, TMarketFeedTxnOutput &Output, CMarketFeed &Txn, int iThreadNum)
{
	Txn.DoTxn(pInput, &Output);

	if (m_bVerbose || (Output.status != 0))
    {
		OutputStdinTimed("Thread %i: performed Market Feed: send_len=%d, status=%d\n", 
			             iThreadNum, Output.send_len, Output.status);
    }

	return Output.status;
}

//
// Perform Trade Result
//
int CMEEServer::DoTradeResult(PTradeResultTxnInput pInput, TTradeResultTxnOutput &Output, CTradeResult &Txn, int iThreadNum)
{
	Txn.DoTxn(pInput, &Output);

	if (m_bVerbose || (Output.status != 0))
    {
		OutputStdinTimed("Thread %i: performed Trade Result: load_unit=%d, status=%d\n", 
			             iThreadNum, Output.load_unit, Output.status);
    }

	return Output.status;
}