/*****************************************************************************
* Copyright (c) 1990-99 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
*
*****************************************************************************/


// PortSerial.cpp: implementation of the CPortSerial class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "PortSerial.h"
#include "SerialCommNameHelpers.h"

#include "PropStdComName.h"

#if defined USE_OLD_NT4_COMM_NAMING
#include "PropSerialCommName.h"
#endif // #if defined USE_OLD_NT4_COMM_NAMING

#ifdef NT5
#include "DriverServices.h"
#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(CPortSerial, CPort)

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

      // Solve our identity crisis
      SetIdentity(DXB_REGVAL_PORT_TYPE_SERIAL);

      // 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);
      CAdapter * pAdapter = (CAdapter *)(pLine->GetParent());
      AffirmThrow(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 == pAdapter->GetAdapterMaxLogicalChansPerLine())
      {
         strDisplayName.Format(IDS_DisplayName_PortFormat, (pLine->GetLineNumber() + 1));
      }
      else
      {
         strDisplayName.Format(IDS_DisplayName_PortFormat, (GetPortNumber() + 1));
      }

      SetDisplayName(strDisplayName);

      #if defined ALLOW_COM_RENAMING_FOR_DENSE
         SetAbility(CConfigObject::DOPROPERTIES);
      #else // #if defined ALLOW_COM_RENAMING_FOR_DENSE
         // Disable this at the top level for serial ports, std com ports do allow renaming
         SetAbility(CConfigObject::NOABILITIES);
      #endif // #if defined ALLOW_COM_RENAMING_FOR_DENSE

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

      #if !defined NT5
         // For NT5, the port class installer takes care of the com names
         AffirmThrow(InitSerialCommNameCache());
      #endif // #if !defined NT5
   }
   catch (CAffirmException ae)
   {
   }
}

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

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

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


   try
   {
      AffirmThrow(CPort::GetParameters());
      #if defined NT5
         m_strDosDevices.Empty();   // Clear this out, we'll derive it on demand
      #else // #if defined NT5

         AffirmThrow(RegReadStrValue(m_hKey, DXB_REGVAL_ASYN_DOSDEVICES, m_strDosDevices));
         CString strComNumber = m_strDosDevices.Right( m_strDosDevices.GetLength() - ((sizeof COM_NAME_PREFIX) - 1) );
         int nComNumber = atoi(strComNumber);
         SetComNumber(nComNumber);

      #endif // #if defined NT5

      fSuccess = TRUE;
   }

   catch (CAffirmException ae)
   {
   }

   return fSuccess;
}

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

   try
   {

      #if !defined NT5
         if (m_strDosDevices.IsEmpty())
         {
            // Generate a name for the first time... Also keep track of the number
            AffirmThrow(CreateUniqueSerialCommName(m_strDosDevices));
            CString strComNumber = m_strDosDevices.Right( m_strDosDevices.GetLength() - ((sizeof COM_NAME_PREFIX) - 1) );
            int nComNumber = atoi(strComNumber);
            SetComNumber(nComNumber);
         }

         AffirmThrow(CPort::SetParameters());

         AffirmThrow(WriteSerialCommName(m_strDosDevices));

         AffirmThrow(FlushSerialCommNameCache());

         AffirmThrow(RegWriteStrValue(m_hKey, DXB_REGVAL_ASYN_DOSDEVICES, m_strDosDevices));

         // Update the print spooler Registry section so our
         // ports show up in printer property sheets
         CString spoolerEntry = m_strDosDevices + TEXT(":");
         WriteProfileString(TEXT("Ports"), spoolerEntry, TEXT("9600,n,8,1"));

      #else // #if !defined NT5
         // If we're NT5, we do not derive the COM names
         AffirmThrow(CPort::SetParameters());

         // For NT5 we need s/dxb channel indexes, hardware ID and Internal Modem flag

         TRACE("CPortSerial::SetParameters\n");
         // Port<-Service<-Protocol<-Line<-Adapter
         CLine* pLine = (CLine*)(GetParent()->GetParent()->GetParent());
         AffirmThrow(pLine != NULL);
         CAdapter* pAdapter = (CAdapter*)(pLine->GetParent());
         AffirmThrow(pAdapter != NULL);

         int nSdxbIndex = 0;
         int MaxLogicalChansPerLine = pAdapter->GetAdapterMaxLogicalChansPerLine();
         if (0 != MaxLogicalChansPerLine)
         {
            int LineNumber = pLine->GetLineNumber();
            int PortNumber = GetPortNumber();
            nSdxbIndex = (LineNumber * MaxLogicalChansPerLine) + PortNumber;
            TRACE("CPortSerial::SetParameters(): MaxLogicalChansPerLine:%d, LineNumber:%d, PortNumber:%d, SdxbIndex:%d\n",MaxLogicalChansPerLine,LineNumber,PortNumber,nSdxbIndex);
         }
         else
         {
            // The inf didn't specify this for the board
            // Use contiguous indexes, this is the fallback
   
            // enumerate all of the leaves at our level under our adapter
            SEnumDescendentsCriteria EnumCriteria(GetIdentity());
            CPortSerial * pPort = NULL;
            while ((pPort = (CPortSerial *)pAdapter->EnumDescendents(EnumCriteria)) != NULL)
            {
               if (pPort == this) 
                  break;
               else
                  nSdxbIndex++;
            }
         }
         AffirmThrow(RegWriteDWordValue(m_hKey,  DXB_REGVAL_ASYN_SDXB_PORT, nSdxbIndex ));
         AffirmThrow(RegWriteStrValue(m_hKey, DXB_REGVAL_PDO_HDWID, DXB_HDWID_ASYN));

         int nHasModems = pAdapter->AdapterHasModems();
         AffirmThrow(RegWriteDWordValue(m_hKey,  DXB_REGVAL_ASYN_MODEMDEVICE, nHasModems ));

         #if defined ALLOW_NT5_COMM_NAMING
         // NOTE: Specific derived classed implement this functionality, if we generally
         // want com renaming available for w2k, we should do it here instead

         // Find out if the port has previously been installed. The instance
         // would have been written by the Client CoInstaller
         CString strInstance;
         AffirmThrow(RegReadStrValue(m_hKey, DXB_REGVAL_REGINSTANCE, strInstance));
         // The port has been previously installed. Update the user assigned port name
         // under System\Enum\DIGI_ASYN\<PORTNAME> 
         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 )
               {
                  RegWriteStrValue(hDeviceKey,DXB_REGVAL_ASYN_PORTNAME,m_strDosDevices);
                  RegCloseKey(hDeviceKey);
               }

               CString strFriendlyName;
               strFriendlyName.Format(IDS_FriendlyNameFormat,m_strDosDevices);
               TRACE("Friendly Name = %s\n",strFriendlyName);
               SetupDiSetDeviceRegistryProperty(hDeviceInfo, &spd, SPDRP_FRIENDLYNAME,(BYTE *)((LPCSTR)strFriendlyName), (lstrlen(strFriendlyName) + 1) * sizeof(WCHAR));
        
            }
            SetupDiDestroyDeviceInfoList( hDeviceInfo );
         }

         #endif // #if defined ALLOW_NT5_COMM_NAMING

      #endif // #if !defined NT5

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

   return fSuccess;
}

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

   try
   {
      // For NT5 we do not manipulate the COM names
      #if !defined NT5
         Affirm(RemoveSerialCommName(m_strDosDevices));
         Affirm(RegDelValue(m_hKey, DXB_REGVAL_ASYN_DOSDEVICES));
         // Remove the port from the print spooler Registry section
         // Note that this fcn. is not called when ports are renamed
         // or removed, so I've never seen this code execute.  Hmm.
         CString spoolerEntry = m_strDosDevices + TEXT(":");
         WriteProfileString(TEXT("Ports"), spoolerEntry, NULL);
      #endif // #if !defined NT5

      #ifdef NT5
        CString strInstance;
        // this value would have been written by the coinstaller for the client
        // driver, when the driver got installed.
        RegReadStrValue(m_hKey, DXB_REGVAL_REGINSTANCE, strInstance);

        TRACE("CPortSerial::RemoveParameters():strInstance:%s\n", strInstance);

        if (!strInstance.IsEmpty())
        {
          DeleteService(strInstance,TRUE);
          strInstance.Empty();
          RegWriteStrValue(m_hKey, DXB_REGVAL_REGINSTANCE, strInstance);
        }
      #endif

      AffirmThrow(CPort::RemoveParameters(fRemoveKey));

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

   return fSuccess;
}

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

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

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

   return fSuccess;
}

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

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

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

   return fSuccess;
}

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

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

   return fSuccess;
}

#if defined NT5
// For NT5, we need to go to great lengths to find our com name
// This code ends up finding the name under the enum key
// The caller has taken the bulk of the time hit by generating a HDEVINFO on our behalf...
void CPortSerial::GetCommNameFromDevInfo(HDEVINFO const hInfo)
{
   // May have it already, don't do it again...
   if (m_strDosDevices.IsEmpty())
   {
      CString strInstance;
      try
      {
         AffirmThrow(RegReadStrValue(m_hKey, DXB_REGVAL_REGINSTANCE, strInstance));
         if (!strInstance.IsEmpty())
         {
            TRACE("CPortSerial::GetCommNameFromDevInfo:Finding com name...\n");
            SP_DEVINFO_DATA spd;
            spd.cbSize = sizeof(SP_DEVINFO_DATA);
            if (FindDevice(hInfo, strInstance, &spd))
            {
               HKEY hDeviceKey = SetupDiOpenDevRegKey( hInfo, &spd, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_ALL_ACCESS); 
               if ( hDeviceKey != NULL )
               {
                  AffirmThrow(RegReadStrValue(hDeviceKey, _T("PortName"), m_strDosDevices));
                  RegCloseKey(hDeviceKey);
               }
            }
            TRACE("   Done...\n");
         }
      }
      catch (CAffirmException ae)
      {
      }
   }
}
#endif // #if defined NT5

CString CPortSerial::GetInformationText(void)
{
   CString strInfo = TEXT("");

   try
   {
      #if defined NT5
         // For NT5, We do the derivation of com name on demand since it's so slow....
         // The HDEVINFO takes a long time to generate (3 seconds),
         CWaitCursor wait;

         // This is only empty if none of the port objects have been asked for a display name...
         if (m_strDosDevices.IsEmpty())
         {
            // We need to generate the HDEVINFO, take the time hit now (but only once)
            TRACE("CPortSerial::GetInformationText:Creating a new device info list...\n");
            HDEVINFO hDeviceInfo;
            hDeviceInfo = GetDevInfo( PORTCLASS );
            AffirmThrow( hDeviceInfo != INVALID_HANDLE_VALUE );
            TRACE("   Done...\n");

            // Tell the siblings to get their com names
            // Note that this instance is included in the enumeration
            {
               TRACE("CPortSerial::GetInformationText:Running through the list of siblings...\n");
               // Port<-Service<-Protocol<-Line<-Adapter
               CAdapter* pAdapter = (CAdapter*)(GetParent()->GetParent()->GetParent()->GetParent());
               AffirmThrow(pAdapter != NULL);
               SEnumDescendentsCriteria EnumCriteria(GetIdentity());
               CPortSerial * pPort;
               while ((pPort = (CPortSerial *)pAdapter->EnumDescendents(EnumCriteria)) != NULL)
                  pPort->GetCommNameFromDevInfo(hDeviceInfo);

               TRACE("   Done...\n");
            }
            // Delete the info
            SetupDiDestroyDeviceInfoList( hDeviceInfo );
         }
      #endif // #if defined NT5

      // For NT4, we've already got the com name in memory, for NT5, we've just derived it...
      strInfo.Format(IDS_Information_PortNameFormat, m_strDosDevices);
   }
   catch (CAffirmException ae)
   {
   }

   return strInfo;
}

BOOL CPortSerial::DoProperties(void)
{
   BOOL fThingsHaveChanged = FALSE;
   #if !defined NT5
      try
      {
         CPropertySheet PropSheet(GetDisplayName());

         #if defined USE_OLD_NT4_COMM_NAMING
            CPropSerialCommName PropSerialCommName;
            PropSerialCommName.m_strSerialCommName = m_strDosDevices;
            PropSheet.AddPage(&PropSerialCommName);
         #else // #if defined USE_OLD_NT4_COMM_NAMING
            DWORD nExistingComNumber = 0;
            CPropStdComName PropStdComName;
            {
               // figure out the com number from m_strDosDevices
               CString strComNumber = m_strDosDevices.Right( m_strDosDevices.GetLength() - ((sizeof COM_NAME_PREFIX) - 1) );
               int nComNumber = atoi(strComNumber);
               PropStdComName.SetExistingComNumber(nComNumber);
               nExistingComNumber = nComNumber; // Used later to detect com name change

               // 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;
               POSITION pos = NULL;
               while ( EnumerateInUseSerialCommNames(pos, strComName) )
               {
                  strComNumber = m_strDosDevices.Right( strComName.GetLength() - ((sizeof COM_NAME_PREFIX) - 1) );
                  if (0 != (nComNumber = atoi(strComNumber)))
                     PropStdComName.SetComNumberStatus(nComNumber, COM_NUMBER_SYSTEM_CONFLICT);
                  if (NULL == pos)
                     break;
               }
            }
            PropSheet.AddPage(&PropStdComName);
         #endif // #if defined USE_OLD_NT4_COMM_NAMING

         PropSheet.m_psh.dwFlags |= PSH_NOAPPLYNOW;
         if (PropSheet.DoModal() == IDOK)
         {
            #if defined USE_OLD_NT4_COMM_NAMING
               if (m_strDosDevices != PropSerialCommName.m_strSerialCommName)
               {
                  AffirmThrow(RemoveSerialCommName(m_strDosDevices));
                  m_strDosDevices = PropSerialCommName.m_strSerialCommName;
                  AffirmThrow(WriteSerialCommName(m_strDosDevices));
                  fThingsHaveChanged = TRUE;
               }
            #else // #if defined USE_OLD_NT4_COMM_NAMING
               if (nExistingComNumber != PropStdComName.GetSelectedComNumber())
               {
                  int nNewComNumber = PropStdComName.GetSelectedComNumber();
                  // Remove the old name
                  AffirmThrow(RemoveSerialCommName(m_strDosDevices));
                  // Generate the new name
                  m_strDosDevices.Format(IDS_FORMAT_COM_NAME, nNewComNumber);
                  AffirmThrow(WriteSerialCommName(m_strDosDevices));
                  SetComNumber(nNewComNumber);
                  fThingsHaveChanged = TRUE;
               }
            #endif // #if defined USE_OLD_NT4_COMM_NAMING
         }
      }
      catch (CAffirmException ae)
      {
      }

   #endif // #if !defined NT5

   return fThingsHaveChanged;
}
