/*****************************************************************************
* 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
*
*****************************************************************************/


// PortStdCom.cpp: implementation of the CPortStdCom class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "PortStdCom.h"
#include "PortSerial.h"
#include "SerialCommNameHelpers.h"
#include "PropSerialCommName.h"
#include "PageStdComAdvanced.h"
#include "PropStdComName.h"

#ifdef NT5
#include "adapter.h"
#include "line.h"
#endif

#include "Resource.h"

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

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

IMPLEMENT_DYNAMIC(CPortStdCom, CPortSerial)

CPortStdCom::CPortStdCom(HKEY const hKeyRoot, CString const strRegPath, CConfigObject const * const pParent) : CPortSerial(hKeyRoot, strRegPath, pParent)
{
   try
   {
      // Get parameters
      AffirmThrow(GetParameters());

      // Solve our identity crisis
      SetIdentity(DXB_REGVAL_PORT_TYPE_STD_COM);
      // Set our display name
      CString strDisplayName;
      // We need to look at our parents...
      // Port<-Service<-Protocol<-Line<-Adapter
      CLine * pLine = (CLine *)(GetParent()->GetParent()->GetParent());
      AffirmThrow(pLine != NULL);

      // There is linkage here! Our parent MUST be a CComOnlyAdapter
      //   We can't check here because the adapter's constructor hasn't completed yet!!!
      //   Later on we will ASSERT if our adapter is not a CComOnlyAdapter
      m_pAdapter = (CComOnlyAdapter *)(pLine->GetParent());
      AffirmThrow(m_pAdapter != NULL);

      // If lines and ports are one to one, we need to expose the line number here,
      // If there are multiple ports per line, then we use the port number
      if ( 1 == m_pAdapter->GetAdapterMaxLogicalChansPerLine())
      {
         strDisplayName.Format(IDS_DisplayName_PortFormat, (GetLineNumber() + 1));
      }
      else
      {
         strDisplayName.Format(IDS_DisplayName_PortFormat, (GetPortNumber() + 1));
      }

      SetDisplayName(strDisplayName);

      // Set what we can do
      SetAbility(CConfigObject::DOPROPERTIES);

      // Get our static config data
      AffirmThrow(GetConfigData());

#if !defined NT5
      AffirmThrow(InitSerialCommNameCache());
#endif // #if !defined NT5

   }
   catch (CAffirmException ae)
   {
   }
}

CPortStdCom::~CPortStdCom()
{
   try
   {
   }
   catch (CAffirmException ae)
   {
   }
}

//////////////////////////////////////////////////////////////////////
// Get/Set parameters
//////////////////////////////////////////////////////////////////////

BOOL CPortStdCom::GetParameters(void)
{
   BOOL fSuccess = FALSE;

   try
   {
      AffirmThrow(CPortSerial::GetParameters()); // Things like com name...
      AffirmThrow(RegReadDWordValue(m_hKey, DXB_REGVAL_ASYN_CLOSETIMEOUT, &m_nCloseTimeout, DXB_REGVAL_ASYN_CLOSETIMEOUT_DEFAULT));
      AffirmThrow(RegReadDWordValue(m_hKey, DXB_REGVAL_ASYN_COMPLETEWHENSENT, &m_nCompleteWhenSent, DXB_REGVAL_ASYN_COMPLETEWHENSENT_DEFAULT));
      AffirmThrow(RegReadDWordValue(m_hKey, DXB_REGVAL_SERIAL_ALT_PIN, &m_nAltPin, DXB_REGVAL_SERIAL_ALT_PIN_DEFAULT));

#if defined NT5
      AffirmThrow(RegReadDWordValue(m_hKey, DXB_REGVAL_COM_NUMBER, &m_nComNumber, DXB_REGVAL_COM_NUMBER_DEFAULT));
#endif

      fSuccess = TRUE;
   }

   catch (CAffirmException ae)
   {
   }

   return fSuccess;
}

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

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

      AffirmThrow(RegWriteDWordValue(m_hKey, DXB_REGVAL_ASYN_CLOSETIMEOUT, m_nCloseTimeout));
      AffirmThrow(RegWriteDWordValue(m_hKey, DXB_REGVAL_ASYN_COMPLETEWHENSENT, m_nCompleteWhenSent));
      AffirmThrow(RegWriteDWordValue(m_hKey, DXB_REGVAL_SERIAL_ALT_PIN, m_nAltPin));
      // Adapter object will write this on our behalf...
#if defined NT5
      TRACE("CPortStdCom::SetParameters:Enter:Writing Com Number %d\n", m_nComNumber);
      AffirmThrow(RegWriteDWordValue(m_hKey, DXB_REGVAL_COM_NUMBER, m_nComNumber));

      // If the port has been previously installed, write the friendly name
      // The initial install skips this step.
      CString strInstance;
      AffirmThrow(RegReadStrValue(m_hKey, DXB_REGVAL_REGINSTANCE, strInstance));
      if (!strInstance.IsEmpty())
      {
         HDEVINFO hDeviceInfo = GetDevInfo( PORTCLASS );
         AffirmThrow( hDeviceInfo != INVALID_HANDLE_VALUE );
         SP_DEVINFO_DATA spd;
         spd.cbSize = sizeof(SP_DEVINFO_DATA);
         if (FindDevice(hDeviceInfo, strInstance, &spd))
         {
            m_strDosDevices.Format(IDS_FORMAT_COM_NAME, GetComNumber());
            HKEY   hDeviceKey = SetupDiOpenDevRegKey( hDeviceInfo, &spd, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_ALL_ACCESS); 
            if ( hDeviceKey != NULL )
            {
               // System\Enum\DIGI_ASYN\<PORTNAME> 
               RegWriteStrValue(hDeviceKey,DXB_REGVAL_ASYN_PORTNAME,m_strDosDevices);
               RegCloseKey(hDeviceKey);
            }	

            CString strFriendlyName;
            strFriendlyName.Format(IDS_FriendlyNameFormat,m_strDosDevices);
            TRACE("Friendly Name = %s\n",strFriendlyName);
            // Friendly name must be written with this API
            SetupDiSetDeviceRegistryProperty(hDeviceInfo, &spd, SPDRP_FRIENDLYNAME,(BYTE *)((LPCSTR)strFriendlyName), (lstrlen(strFriendlyName) + 1) * sizeof(WCHAR));
     
         }
         SetupDiDestroyDeviceInfoList( hDeviceInfo );
      }
#endif

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

   return fSuccess;
}

BOOL CPortStdCom::RemoveParameters(BOOL fRemoveKey /* = FALSE */)
{
   BOOL fSuccess = FALSE;

   try
   {
      AffirmThrow(RegDelValue(m_hKey, DXB_REGVAL_ASYN_CLOSETIMEOUT));
      AffirmThrow(RegDelValue(m_hKey, DXB_REGVAL_ASYN_COMPLETEWHENSENT));
      AffirmThrow(RegDelValue(m_hKey, DXB_REGVAL_SERIAL_ALT_PIN));
#if defined NT5
      AffirmThrow(RegDelValue(m_hKey, DXB_REGVAL_COM_NUMBER));
#endif
      AffirmThrow(CPortSerial::RemoveParameters(fRemoveKey));

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

   return fSuccess;
}

BOOL CPortStdCom::CopyParameters(CConfigObject const * const pFrom)
{
   BOOL fSuccess = FALSE;
   try
   {
      AffirmThrow(pFrom != NULL);
      AffirmThrow( pFrom->IsKindOf( RUNTIME_CLASS( CPortStdCom ) ) );

      CPortStdCom * pFromPort = (CPortStdCom *) pFrom;

      // Copies only the advanced parameters
      m_nCloseTimeout = pFromPort->m_nCloseTimeout;
      m_nCompleteWhenSent = pFromPort->m_nCompleteWhenSent;
      m_nAltPin = pFromPort->m_nAltPin;
      // Don't copy the com number!

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

   return fSuccess;
}

//////////////////////////////////////////////////////////////////////
// Config data
//////////////////////////////////////////////////////////////////////

BOOL CPortStdCom::GetConfigData(void)
{
   BOOL fSuccess = FALSE;

   try
   {
      AffirmThrow(CPortSerial::GetConfigData());
      fSuccess = TRUE;
   }
   catch (CAffirmException ae)
   {
   }

   return fSuccess;
}

//////////////////////////////////////////////////////////////////////
// UI config methods
//////////////////////////////////////////////////////////////////////

BOOL CPortStdCom::AddWizardPages(CConfigWizSheet * const pSheet)
{
   BOOL fSuccess = FALSE;

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

   return fSuccess;
}

BOOL CPortStdCom::NextWizardPage(int const nState)
{
   BOOL fSuccess = FALSE;

   try
   {
      AffirmThrow(FALSE);
      fSuccess = TRUE;
   }
   catch (CAffirmException ae)
   {
   }

   return fSuccess;
}

CString CPortStdCom::GetInformationText(void)
{
   CString strInfo = TEXT("");
   CString strStdComInfo = TEXT("");
   CString strPortSerialInfo = TEXT("");
   CString strOn((LPCSTR)IDS_INFO_ON);
   CString strOff((LPCSTR)IDS_INFO_OFF);
   CString strInfinite((LPCSTR)IDS_INFO_INFINITE);

#define DXB_REGVAL_ASYN_CLOSETIMEOUT_INFINITE 0xffffffff

   try
   {
      // Derive the advanced parameters information
      CString strTimeout;
      strTimeout.Format("%d", m_nCloseTimeout); // May or may not be used...
      strStdComInfo.Format(IDS_Information_StdCom,
         ((m_nCloseTimeout == DXB_REGVAL_ASYN_CLOSETIMEOUT_INFINITE) ? strInfinite : strTimeout),
         ((0 != m_nCompleteWhenSent) ? strOn : strOff),
         ((0 != m_nAltPin) ? strOn : strOff)
      );

#if defined NT5
      // For NT5 we're using a number stored in our adapter key, format a display name
      if (0 != m_nComNumber)
         strPortSerialInfo.Format(IDS_FORMAT_COM_NAME, m_nComNumber);
#else // #if defined NT5
      // PortSerial has the com name...
      strPortSerialInfo = CPortSerial::GetInformationText();
#endif // #if defined NT5

      if (strPortSerialInfo.IsEmpty())
         strInfo = strStdComInfo;
      else
         strInfo = strPortSerialInfo + "\n\n" + strStdComInfo;
   
   }
   catch (CAffirmException ae)
   {
   }

   return strInfo;
}

BOOL CPortStdCom::DoProperties(void)
{
   // NOTE: NOTE: There is similar prop page handling code in CPortSerial::DoProperties()

   BOOL fThingsHaveChanged = FALSE;

   try
   {
      CPropertySheet PropSheet(GetDisplayName());

      #if !defined NT5
	  DWORD nComNumber, nExistingComNumber = 0;
      CPropStdComName PropStdComName;
      {
		 nExistingComNumber = GetComNumber(); // Used later to detect com name change
         PropStdComName.SetExistingComNumber(nExistingComNumber);

         // Derive the status for all of the com ports in the system
         // Trundle through the com name cache and forward the status to the prop page
         CString strComName, strComNumber;
         POSITION pos = NULL;
         //Initialize pos to start of CMap
         EnumerateInUseSerialCommNames(pos, strComName);
         while ( pos != NULL)
         {
            // This number is in use.
            EnumerateInUseSerialCommNames(pos, strComName);
            strComNumber = strComName.Right( strComName.GetLength() - ((sizeof COM_NAME_PREFIX) - 1) );
            if ((0 != (nComNumber = atoi(strComNumber))) && (nComNumber != nExistingComNumber))
            {
               // The conversion was good, check if a port above us is using it
               if (m_pAdapter->IsNumberConfiguredForPortAbove(GetLineNumber(),nComNumber))
               {
                  // These numbers are available to start off an enumeration, indicate this.
                  PropStdComName.SetComNumberStatus(nComNumber, COM_NUMBER_IN_USE_ABOVE_CURRENT_PORT);
               }
               else
               {
                  PropStdComName.SetComNumberStatus(nComNumber, COM_NUMBER_SYSTEM_CONFLICT);
               }
            }
         }
      }
      PropSheet.AddPage(&PropStdComName);
      #endif // #if !defined NT5

      CPageStdComAdvanced PageStdComAdvanced;
      // In the Registry Close timeout has a range of 0 - 3600 seconds, -1 (0xffffffff) is infinite
      // In the prop page, this range is represented by a bool and an int
      PageStdComAdvanced.m_fCloseTimeoutEnable = (m_nCloseTimeout != DXB_REGVAL_ASYN_CLOSETIMEOUT_INFINITE);
      PageStdComAdvanced.m_CloseTimeout = (m_nCloseTimeout == DXB_REGVAL_ASYN_CLOSETIMEOUT_INFINITE) ? DXB_REGVAL_ASYN_CLOSETIMEOUT_DEFAULT : m_nCloseTimeout;
      PageStdComAdvanced.m_fAltPin = (0 != m_nAltPin);
      PageStdComAdvanced.m_fCompleteWhenSent = (0 != m_nCompleteWhenSent);
      PropSheet.AddPage(&PageStdComAdvanced);

      DWORD WorkingComNumber;

      #if defined NT5
      CPropStdComName PropStdComName;
      PropStdComName.SetExistingComNumber(m_nComNumber);
      // Fill in the status for the claimed ports, the ones that we don't mark are either available or
      // above range. If above range we'll assume they're free and resize the com db if the user selects

      // Enforce the linkage between these two classes before we call anything specific
      AffirmThrow( m_pAdapter->IsKindOf( RUNTIME_CLASS( CComOnlyAdapter ) ) );

      for (WorkingComNumber = 1; WorkingComNumber <= m_pAdapter->GetCurrentMaxRange(); WorkingComNumber++)
      {
         if (!m_pAdapter->IsNumberAvailable(WorkingComNumber) && (m_nComNumber != WorkingComNumber))
         {
            if (m_pAdapter->IsNumberConfiguredForPortAbove(GetLineNumber(),WorkingComNumber))
            {
               // These numbers are available to start off an enumeration, indicate this
               PropStdComName.SetComNumberStatus(WorkingComNumber, COM_NUMBER_IN_USE_ABOVE_CURRENT_PORT);
            }
            else
            {
               PropStdComName.SetComNumberStatus(WorkingComNumber, COM_NUMBER_SYSTEM_CONFLICT);
            }
         }
      }

      PropSheet.AddPage(&PropStdComName);
      #endif // #if defined NT5

      PropSheet.m_psh.dwFlags |= PSH_NOAPPLYNOW;
      if (PropSheet.DoModal() == IDOK)
      {
         BOOL fEnumerateFromHere = PropStdComName.GetRenumberFromHere();
         WorkingComNumber = PropStdComName.GetSelectedComNumber();
		 TRACE("GetSelectedComNumber: %d\n", WorkingComNumber);

         #if defined NT5
         if (fEnumerateFromHere)
         {
            // Enumerate, change in com name doesn't matter, just use it
            m_pAdapter->ReNumberPortComNames(GetLineNumber(), WorkingComNumber);
            fThingsHaveChanged = TRUE;
         }
         else
         {
            if (m_nComNumber != WorkingComNumber)
            {
               // Com name is different, only change it
               m_pAdapter->FreePortComNumberDeferred(m_nComNumber);
               m_pAdapter->AddPortComNumberDeferred(WorkingComNumber);
               m_nComNumber = WorkingComNumber;
               fThingsHaveChanged = TRUE;
            }
         }

         #else // #if defined NT5

         // For NT4, we do the enumerate right here since the com list has global scope for the port, not adapter...
         if (fEnumerateFromHere)
         {
            DWORD nPendingComNums[MAX_NAMES_IN_LIST] = {0};
            DWORD nProposedCom = WorkingComNumber; // Start with this port's number.
			TRACE("Enumerate Start: %d\n", WorkingComNumber);
            // Note: for these types of ports, there is a one to one mapping of lines and ports
            nPendingComNums[GetLineNumber()] = nProposedCom; // This one's already been verified
            // This is the range (+1 to skip this port), derive a new set of numbers, we'll validate, then apply.
            for (int nPort = (GetLineNumber() + 1); nPort < m_pAdapter->GetAdapterMaxPorts(); nPort++)
            {
               nProposedCom++; // We've used this already above, inc to the next candidate
               // proposed number available and within range
               while ( !( (COM_NUMBER_FREE == PropStdComName.GetComNumberStatus(nProposedCom)) ||
                          (COM_NUMBER_IN_USE_ABOVE_CURRENT_PORT == PropStdComName.GetComNumberStatus(nProposedCom))
                          ) &&
                          ( nProposedCom < MAX_NAMES_IN_LIST ) )
               {
                  nProposedCom++;
               }
               nPendingComNums[nPort] = nProposedCom;
            }

            // Enumerate to free, must free first so we can claim an overlapping range
            {
               SEnumDescendentsCriteria EnumCriteria(GetIdentity());
               CPortStdCom * pPort = NULL;
               while ((pPort = (CPortStdCom *)m_pAdapter->EnumDescendents(EnumCriteria)) != NULL)
               {
                  nPort = pPort->GetLineNumber();
                  if ( nPort >= GetLineNumber() )
                  {
                     // Free the com number we've already got...
                     AffirmThrow(RemoveSerialCommName(pPort->m_strDosDevices));
                     // Update the port's number
                     pPort->m_strDosDevices.Format(IDS_FORMAT_COM_NAME, nPendingComNums[nPort]);
                     pPort->SetComNumber(nPendingComNums[nPort]);
                  }
               }
            }
            // Enumerate to claim
            {
               SEnumDescendentsCriteria EnumCriteria(GetIdentity());
               CPortStdCom * pPort = NULL;
               while ((pPort = (CPortStdCom *)m_pAdapter->EnumDescendents(EnumCriteria)) != NULL)
               {
                  if ( pPort->GetLineNumber() >= GetLineNumber() )
                  {
                     // claim the new com number...
                     AffirmThrow(WriteSerialCommName(pPort->m_strDosDevices));
                  }
               }
            }
            fThingsHaveChanged = TRUE;
         }
         else
         {
            if (nExistingComNumber != WorkingComNumber)
            {
               // Remove the old name
               AffirmThrow(RemoveSerialCommName(m_strDosDevices));
               // Generate the new name
               m_strDosDevices.Format(IDS_FORMAT_COM_NAME, WorkingComNumber);
               AffirmThrow(WriteSerialCommName(m_strDosDevices));
               // Make sure the port knows the new number...
               SetComNumber(WorkingComNumber);

               fThingsHaveChanged = TRUE;
            }
         }
         #endif // #if defined NT5

         // The page has a bool and an integer to represent this value
         DWORD nTmpTimeout = PageStdComAdvanced.m_fCloseTimeoutEnable ? PageStdComAdvanced.m_CloseTimeout : DXB_REGVAL_ASYN_CLOSETIMEOUT_INFINITE;
         if (nTmpTimeout != m_nCloseTimeout)
         {
            m_nCloseTimeout = nTmpTimeout;
            fThingsHaveChanged = TRUE;
         }

         if (  (PageStdComAdvanced.m_fAltPin != (0 != m_nAltPin)) ||
               (PageStdComAdvanced.m_fCompleteWhenSent != (0 != m_nCompleteWhenSent))
            )
         {
            m_nCloseTimeout = PageStdComAdvanced.m_CloseTimeout;
            m_nAltPin = PageStdComAdvanced.m_fAltPin ? 1 : 0 ;
            m_nCompleteWhenSent = PageStdComAdvanced.m_fCompleteWhenSent ? 1 : 0 ;
            fThingsHaveChanged = TRUE;
         }

         // Do this last, that way our params are updated...
         if (PageStdComAdvanced.m_fAllPorts)
         {
            // Need to enumerate all of the ports and copy in this instance's advanced params
            AffirmThrow(m_pAdapter != NULL);
            SEnumDescendentsCriteria EnumCriteria(GetIdentity());
            CPortStdCom * pPort = NULL;
            while ((pPort = (CPortStdCom *)m_pAdapter->EnumDescendents(EnumCriteria)) != NULL)
            {
               if (this != pPort)
                  pPort->CopyParameters(this);
            }
            fThingsHaveChanged = TRUE;
         }
      }
   }
   catch (CAffirmException ae)
   {
   }

   return fThingsHaveChanged;
}
