/*****************************************************************************
* Copyright (c) 1990-2000 Digi International Inc., All Rights Reserved
* 
* This software contains proprietary and confidential information of Digi
* International Inc.  By accepting transfer of this copy, Recipient agrees
* to retain this software in confidence, to prevent disclosure to others,
* and to make no use of this software other than that for which it was
* delivered.  This is an unpublished copyrighted work of Digi International
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
* prohibited.
* 
* Restricted Rights Legend
*
* Use, duplication, or disclosure by the Government is subject to
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
* Technical Data and Computer Software clause at DFARS 252.227-7031 or
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -
* Restricted Rights at 48 CFR 52.227-19, as applicable.
*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
*
*****************************************************************************/


// ComOnlyAdapter.cpp: implementation of the CComOnlyAdapter class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "ObjectFactory.h"
#include "Resource.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

IMPLEMENT_DYNAMIC(CComOnlyAdapter, CAdapter)

CComOnlyAdapter::CComOnlyAdapter(HKEY const hKeyRoot, CString const & strRegPath, CConfigObject const * const pParent) : CAdapter(hKeyRoot, strRegPath, pParent)
{
#if defined NT5
	m_MaxPorts = 0;
	m_PortUsageBuffer = NULL;
	m_PortsToClaim = NULL;
	m_PortsToRelease = NULL;
	m_PortsAlreadyClaimed = NULL;
   InitPortUsageBuffers();

   try
   {
      // Get the currently configured com number from all of the port children
      // enumerate all of the ports for this adapter, they're always type StdCom
      DWORD nComNumber;
      CPort * pPort = NULL;
      SEnumDescendentsCriteria EnumCriteria(DXB_REGVAL_PORT_TYPE_STD_COM);
      while ( (pPort = (CPort *)EnumDescendents(EnumCriteria)) != NULL)
      {
         nComNumber = pPort->GetComNumber();
         if (0 != nComNumber)
            AddAlreadyClaimedPortNumber(nComNumber);
      }
   }
   catch (CAffirmException ae)
   {
   }
#endif // #if defined NT5
}

CComOnlyAdapter::~CComOnlyAdapter()
{
#if defined NT5
	DeletePortUsageBuffers();
	if ( m_hComDB != NULL )
	{
		ComDBClose( m_hComDB );
		m_hComDB = NULL;
	}
#endif // #if defined NT5
}

BOOL CComOnlyAdapter::IsNumberConfiguredForPortAbove(DWORD PortNumber,DWORD ComNumber)
{
   BOOL Success = FALSE;
   // Get the currently configured com number from all of the port children
   // enumerate all of the ports for this adapter, they're always type StdCom
   // NOTE: A one to one mapping for lines/ports is assumed...
   CPort * pPort = NULL;
   SEnumDescendentsCriteria EnumCriteria(DXB_REGVAL_PORT_TYPE_STD_COM);
   while ( (pPort = (CPort *)EnumDescendents(EnumCriteria)) != NULL)
   {
      // Since we're one to one, get the line number for the port index
      TRACE("CComOnlyAdapter::IsNumberConfiguredForPortAbove:Checking port %d for COM%d\n", pPort->GetLineNumber(), ComNumber);
      if ( ((DWORD)(pPort->GetLineNumber()) > PortNumber ) && (ComNumber == pPort->GetComNumber()) )
      {
         TRACE("CComOnlyAdapter::IsNumberConfiguredForPortAbove:Matched!\n");
         Success = TRUE;
         break;
      }
   }
   return Success;
}

#if defined NT5

BOOL CComOnlyAdapter::SetParameters(void)
{
   BOOL fSuccess = FALSE;

   TRACE("CComOnlyAdapter::SetParameters\n");

   try
   {
      AffirmThrow(CAdapter::SetParameters());

      // Updates the W2K ComDb
      UpdateDeferredPortNameUsage();

      fSuccess = TRUE;
   }
   catch (CAffirmException ae)
   {
   }

   return fSuccess;
}

void CComOnlyAdapter::DeletePortUsageBuffers()
{
	if ( m_PortUsageBuffer != NULL )
	{
		delete [] m_PortUsageBuffer;
		m_PortUsageBuffer = NULL;
	}
	if ( m_PortsToClaim != NULL )
	{
		delete [] m_PortsToClaim;
		m_PortsToClaim = NULL;
	}
	if ( m_PortsToRelease != NULL )
	{
		delete [] m_PortsToRelease;
		m_PortsToRelease = NULL;
	}
	if ( m_PortsAlreadyClaimed != NULL )
	{
		delete [] m_PortsAlreadyClaimed;
		m_PortsAlreadyClaimed = NULL;
	}
	m_MaxPorts = 0;
}

void CComOnlyAdapter::TraceAssignedPorts()
{
	DWORD idx = 0;
	TRACE("CComOnlyAdapter::TraceAssignedPorts\n");
   if ( m_PortUsageBuffer != NULL )
   {
	   while ( idx < m_MaxPorts )
	   {
		   if ( m_PortUsageBuffer[idx] != 0 )
			   TRACE(" COM%d",idx+1);
		   idx++;
	   }
   }
	TRACE("\n");

   TRACE("CComOnlyAdapter::TraceAssignedPorts:AlreadyClaimed\n");
   idx = 0;
   if ( m_PortsAlreadyClaimed != NULL )
   {
	   while ( idx < m_MaxPorts )
	   {
		   if ( m_PortsAlreadyClaimed[idx] != 0 )
			   TRACE(" COM%d",idx+1);
		   idx++;
	   }
   }
	TRACE("\n");

}

DWORD CComOnlyAdapter::InitPortUsageBuffers()
{
   TRACE("CComOnlyAdapter::InitPortUsageBuffers\n");

	DWORD retStatus = ERROR_SUCCESS;
	DWORD MaxPortsReported = COMDB_MIN_PORTS_ARBITRATED;
   DWORD dwResult = ComDBOpen( &m_hComDB );
   if ( INVALID_HANDLE_VALUE == m_hComDB )
   {
      TRACE(TEXT("DGCONFIG: Failed on ComDBOpen: handle: %x dwResult: %d\n"),m_hComDB,dwResult);
		m_hComDB = NULL;
   }
   else
   {
	   // Determine size of buffer that is needed
	   if ( m_hComDB != NULL  )
      {
         retStatus = ComDBGetCurrentPortUsage( m_hComDB, NULL, NULL, CDB_REPORT_BYTES, &MaxPortsReported );
         if ( retStatus != ERROR_SUCCESS )
         {
            TRACE( TEXT("Failed ComDBGetCurrentPortUsage to determine size of buffer with %x\n"),retStatus);
            MaxPortsReported = COMDB_MIN_PORTS_ARBITRATED;       // Set a default
         }         

         DeletePortUsageBuffers(); // We're about to new them...

	      m_MaxPorts = MaxPortsReported;
	      // Allocate buffers for tracking port usage
	      m_PortUsageBuffer = new BYTE[MaxPortsReported];
	      m_PortsToClaim = new BYTE[m_MaxPorts];
	      m_PortsToRelease = new BYTE[m_MaxPorts];
	      m_PortsAlreadyClaimed = new BYTE[m_MaxPorts];

	      for ( DWORD idx = 0; idx < m_MaxPorts; idx++ )
	      {
		      m_PortsToClaim[idx] = 0;
		      m_PortsToRelease[idx] = 0;
            m_PortUsageBuffer[idx] = 0;
            m_PortsAlreadyClaimed[idx] = 0;
	      }
	      retStatus = ComDBGetCurrentPortUsage( m_hComDB, m_PortUsageBuffer, m_MaxPorts, CDB_REPORT_BYTES, &MaxPortsReported );
      	TraceAssignedPorts();
      }
   }

	return retStatus;
}

DWORD CComOnlyAdapter::ResizePortUsageBuffers(DWORD PortNum)
{
	DWORD retStatus;
	BYTE *	tmpPortUsageBuffer = NULL;
	BYTE *	tmpPortsToClaim = NULL;
	BYTE *	tmpPortsToRelease = NULL;
   BYTE *   tmpPortsAlreadyClaimed = NULL;

	DWORD		tmpMaxPorts = 0;
	DWORD		idx = 0;
	
   TRACE(TEXT("DGCONFIG: Entering CComOnlyAdapter::ResizePortUsageBuffers MaxPorts=%d\n"),m_MaxPorts);
	if ( m_hComDB == NULL )
	{
		retStatus = ERROR_INVALID_HANDLE;	
		goto Exit;
	}		
	if ( m_MaxPorts >= COMDB_MAX_PORTS_ARBITRATED )
	{
		retStatus = ERROR_BAD_LENGTH;
		goto Exit;
	}

	tmpMaxPorts = m_MaxPorts;
	tmpPortUsageBuffer = new BYTE[m_MaxPorts];
	tmpPortsToClaim = new BYTE[m_MaxPorts];
	tmpPortsToRelease = new BYTE[m_MaxPorts];
	tmpPortsAlreadyClaimed = new BYTE[m_MaxPorts];

	for ( idx = 0; idx < tmpMaxPorts; idx++ )
	{
		tmpPortsToClaim[idx] = m_PortsToClaim[idx];
		tmpPortsToRelease[idx] = m_PortsToRelease[idx];
      tmpPortUsageBuffer[idx] = m_PortUsageBuffer[idx];
      tmpPortsAlreadyClaimed[idx] = m_PortsAlreadyClaimed[idx];
	}

	{
	   // NewSize - must be a multiple of 1024, with a max of COMDB_MAX_PORTS_ARBITRATED
		DWORD NewSize = (PortNum + 1024) & ~0x3ff;
		if ( NewSize > COMDB_MAX_PORTS_ARBITRATED )
			NewSize = COMDB_MAX_PORTS_ARBITRATED;
		retStatus = ComDBResizeDatabase( m_hComDB, NewSize );
	   TRACE(TEXT("DGCONFIG: ComDBResizeDatabase returned status %x with %d MaxPorts\n"),retStatus,NewSize);

		if ( retStatus == ERROR_SUCCESS )
		{
			InitPortUsageBuffers();
			for ( idx = 0; idx < tmpMaxPorts; idx++ )
			{
				m_PortsToClaim[idx] = tmpPortsToClaim[idx];
				m_PortsToRelease[idx] = tmpPortsToRelease[idx];
		      m_PortUsageBuffer[idx] = tmpPortUsageBuffer[idx];
		      m_PortsAlreadyClaimed[idx] = tmpPortsAlreadyClaimed[idx];
			}
		}
	}
Exit:
   TRACE(TEXT("CComOnlyAdapter::ResizePortUsageBuffers Exit label\n"));

	if ( tmpPortUsageBuffer != NULL ) delete [] tmpPortUsageBuffer;
	if ( tmpPortsToClaim != NULL )     delete [] tmpPortsToClaim;
	if ( tmpPortsToRelease != NULL )   delete [] tmpPortsToRelease;
	if ( tmpPortsAlreadyClaimed != NULL )   delete [] tmpPortsAlreadyClaimed;

   TRACE(TEXT("DGCONFIG: Leaving CComOnlyAdapter::ResizePortUsageBuffers MaxPorts=%d RetStatus=%x\n"),m_MaxPorts,retStatus);
	return retStatus;
}

BOOL CComOnlyAdapter::IsNumberAvailable(DWORD const ComNumber)
{
   BOOL Success = FALSE;
	if ( (ComNumber != 0) && (ComNumber <= m_MaxPorts) )
      Success = (0 == m_PortUsageBuffer[ComNumber-1]);
   return Success;
}

BOOL CComOnlyAdapter::AddPortComNumber(DWORD ComNumber, BOOL fOverride)
{
   BOOL Success = FALSE;
	if ( ( ComNumber != 0 ) && ( m_hComDB != NULL ) && (ComNumber <= m_MaxPorts) )
   {
		if (ERROR_SUCCESS == ComDBClaimPort( m_hComDB, ComNumber, fOverride, NULL ))
      {
   		m_PortUsageBuffer[ComNumber-1] = 1;
         Success = TRUE;
      }
   }

   return Success;
}

BOOL CComOnlyAdapter::AddPortComNumberDeferred(DWORD ComNumber)
{
   BOOL Success = FALSE;
	if ( ( m_hComDB != NULL ) && ( ComNumber != 0 ) && (ComNumber <= m_MaxPorts) )
	{
		m_PortsToClaim[ComNumber-1] = 1;
		m_PortUsageBuffer[ComNumber-1] = 1;
		m_PortsToRelease[ComNumber-1] = 0;
      Success = TRUE;
	}
	return Success;
}

BOOL CComOnlyAdapter::FreePortComNumber(DWORD ComNumber)
{
   BOOL Success = FALSE;
	if ( ( m_hComDB != NULL ) && ( ComNumber != 0 ) )
	{
	   ComDBReleasePort( m_hComDB, ComNumber );
		m_PortUsageBuffer[ComNumber-1] = 0;
	}
	return Success;
}

BOOL  CComOnlyAdapter::FreePortComNumberDeferred(DWORD ComNumber)
{
   BOOL Success = FALSE;
	if ( ( m_hComDB != NULL ) && ( ComNumber != 0 )  && (ComNumber <= m_MaxPorts) )
	{
		m_PortsToRelease[ComNumber-1] = 1;
		m_PortUsageBuffer[ComNumber-1] = 0;
		m_PortsToClaim[ComNumber-1]=0;
      Success = TRUE;
	}
	return Success;
}

#define PROFILE_PORTS TEXT("Ports")
#define PROFILE_PORTS_DEFAULT TEXT("9600,n,8,1")

void CComOnlyAdapter::UpdateDeferredPortNameUsage()
{
   CString DigiName;

   TraceAssignedPorts();

   if (m_hComDB != NULL)
   {
	   for ( DWORD idx = 0; idx < m_MaxPorts; idx++ )
	   {
		   if ( m_PortsToClaim[idx] != 0 )
		   {	
			   // If it's not on the already claimed list, claim the number
            if (0 == m_PortsAlreadyClaimed[idx])
            {
               TRACE("CComOnlyAdapter::UpdateDeferredPortNameUsage:Claiming COM%d\n",idx+1);
		         ComDBClaimPort( m_hComDB, idx+1, TRUE, NULL );
               // Add the entry to the WIN.INI file -- mapped to the registry.
               // This is the entry used by the printer applet (\\HKLM\Software\...\CurrentVersion\Ports).
               DigiName.Format("COM%d:", idx+1);
               WriteProfileString(PROFILE_PORTS, DigiName, PROFILE_PORTS_DEFAULT);
            }
            else
            {
               TRACE("CComOnlyAdapter::UpdateDeferredPortNameUsage: COM%d already claimed, don't claim\n",idx+1);
            }
		   }
		   else
		   {
			   // Release the port, if necessary
			   if ( m_PortsToRelease[idx] != 0 )
			   {
               // Only release it if the number is currently claimed by our config
               if (0 != m_PortsAlreadyClaimed[idx])
               {
                  TRACE("CComOnlyAdapter::UpdateDeferredPortNameUsage:Releasing COM%d\n",idx+1);
	   			   ComDBReleasePort( m_hComDB, idx+1 );

                  // Remove the entry from the WIN.INI file -- mapped to the registry.
                  // This is the entry used by the printer applet (\\HKLM\Software\...\CurrentVersion\Ports).
                  DigiName.Format("COM%d:", idx+1);
                  WriteProfileString(PROFILE_PORTS, DigiName, NULL); // NULL means delete entry
               }
               else
               {
                  TRACE("CComOnlyAdapter::UpdateDeferredPortNameUsage: COM%d was not already claimed, don't release\n",idx+1);
               }
            }
		   }	
	   }
   }
}

BOOL CComOnlyAdapter::AddAlreadyClaimedPortNumber(DWORD ComNumber)
{

   TRACE("CComOnlyAdapter::AddAlreadyClaimedPortNumber\n");

   BOOL Success = FALSE;
   if ( (0 != ComNumber)  && (ComNumber <= m_MaxPorts) )
   {
      m_PortsAlreadyClaimed[ComNumber-1] = 1;
      Success = TRUE;
   }
   return Success;
}

BOOL CComOnlyAdapter::IsNumberAlreadyClaimed(DWORD ComNumber)
{
   BOOL Success = FALSE;
   if ( (0 != ComNumber)  && (ComNumber <= m_MaxPorts) )
      Success = (0 != m_PortsAlreadyClaimed[ComNumber-1]);

   return Success;
}

BOOL CComOnlyAdapter::IsNumberDeferredClaim(DWORD ComNumber)
{
   BOOL Success = FALSE;
   if ( (0 != ComNumber)  && (ComNumber <= m_MaxPorts) )
      Success = (0 != m_PortsToClaim[ComNumber-1]);
   return Success;
}

BOOL CComOnlyAdapter::IsNumberInMaxRange(DWORD ComNumber)
{
   return ( (ComNumber > m_MaxPorts) && (ComNumber <= COMDB_MAX_PORTS_ARBITRATED) );
}

// NOTE: This method assumes that there is a one to one mapping of line/port/chan for the adapter
//       In other words, it will only work for Xp (Hercules) or Acceleport RAS (Chaska)
BOOL CComOnlyAdapter::ReNumberPortComNames(DWORD StartPort, DWORD StartCom)
{
   BOOL Success = FALSE;

   CPort * pPort = NULL;

   TRACE("CComOnlyAdapter::ReNumberPortComNames:Enter\n");

   // Pend the release of these com numbers so we can use them as part of the range
   {
      SEnumDescendentsCriteria EnumCriteria(DXB_REGVAL_PORT_TYPE_STD_COM);
      while ( (pPort = (CPort *)EnumDescendents(EnumCriteria)) != NULL)
         if ((DWORD)(pPort->GetLineNumber()) >=  StartPort)
            FreePortComNumberDeferred(pPort->GetComNumber());
   }

   {
      DWORD PortIndex, MaxPorts;
      SEnumDescendentsCriteria EnumCriteria(DXB_REGVAL_PORT_TYPE_STD_COM);
      DWORD PendingComNumbers[COMDB_MAX_PORTS_ARBITRATED] = {0}; // Practical maximum
      DWORD WorkingComNumber = StartCom;
      MaxPorts = (DWORD) GetAdapterMaxPorts();

      // Figure out a range of available com numbers for this port forward
      for (PortIndex = StartPort; PortIndex < MaxPorts; PortIndex++)
      {
         while (!IsNumberAvailable(WorkingComNumber))
         {
            WorkingComNumber++;
            if (WorkingComNumber >= COMDB_MAX_PORTS_ARBITRATED)
            {
               // None are available!
               TRACE("CComOnlyAdapter::ReNumberPortComNames:Failure, exceeded maximum available com number (4096)\n");
               goto ErrorExit;
            }
         }

         PendingComNumbers[PortIndex] = WorkingComNumber;
         WorkingComNumber++;
      }

      // Pend the claim of com numbers and config the ports with their new number
      while ( (pPort = (CPort *)EnumDescendents(EnumCriteria)) != NULL)
      {
         if ( (PortIndex = pPort->GetLineNumber()) >= StartPort)
         {
            // Update the port's com number and reserve it with the com db
            pPort->SetComNumber(PendingComNumbers[PortIndex]);
            AddPortComNumberDeferred(PendingComNumbers[PortIndex]);
         }
      }
   }

   Success = TRUE;

ErrorExit:
   return Success;
}

#endif // #if defined NT5
