#include "headers.hxx"
#include "dspecup.hpp"
#include "Analysis.hpp"
#include "repair.hpp"
#include "AnalysisResults.hpp"
#include "resourceDspecup.h"
#include "CSVDSReader.hpp"
#include "constants.hpp"
#include "AdsiHelpers.hpp"
#include "guids.inc"

HRESULT
FindCsvFile(
            String& csvFilePath,
            String& csv409Path
           )
{
   LOG_FUNCTION(FindCsvFile);

   csvFilePath.erase();
   csv409Path.erase();
   
   HRESULT hr = S_OK;

   do
   {
      // look for dcpromo.csv and 409.csv file in system 
      // or current directory
      

      // check the default of
      // %windir%\system32\mui\dispspec\dcpromo.csv and
      // .\dcpromo.csv

      String csvname=L"dcpromo.csv";
      String sys32dir = Win::GetSystemDirectory();
      String csvPath  = sys32dir + L"\\debug\\adprep\\data\\" + csvname;

      if (FS::FileExists(csvPath))
      {
         csvFilePath = csvPath;
      }
      else
      {
         error=String::format(IDS_COULD_NOT_FIND_FILE,csvPath.c_str());
         hr=E_FAIL;
         break;
      }
      

      csvname=L"409.csv";
      csvPath  = sys32dir + L"\\debug\\adprep\\data\\" + csvname;
      
      if (FS::FileExists(csvPath))
      {
         csv409Path = csvPath;
      }
      else
      {
         error=String::format(IDS_COULD_NOT_FIND_FILE,csvPath.c_str());
         hr=E_FAIL;
         break;
      }
   }
   while(0);


   LOG_HRESULT(hr);
   LOG(csvFilePath);
   LOG(csv409Path);
   
   return hr;      
}


HRESULT 
InitializeADSI(
               const String   &targetDcName,
               String         &ldapPrefix,
               String         &rootContainerDn,
               String         &domainName,
               String         &completeDcName,
               SmartInterface<IADs>& rootDse
              )
{
   LOG_FUNCTION(InitializeADSI);

   HRESULT hr=S_OK;
   do
   {
      Computer targetDc(targetDcName);
      hr = targetDc.Refresh();

      if (FAILED(hr))
      {
         error = String::format(
               IDS_CANT_TARGET_MACHINE,
               targetDcName.c_str());
         break;
      }

      if (!targetDc.IsDomainController())
      {
         error=String::format(
               IDS_TARGET_IS_NOT_DC,
               targetDcName.c_str());
         hr=E_FAIL;
         break;
      }

      completeDcName = targetDc.GetActivePhysicalFullDnsName();
      ldapPrefix = L"LDAP://" + completeDcName + L"/";

      //
      // Find the DN of the configuration container.
      // 

      // Bind to the rootDSE object.  We will keep this binding handle
      // open for the duration of the analysis and repair phases in order
      // to keep a server session open.  If we decide to pass creds to the
      // AdsiOpenObject call in a later revision, then by keeping the
      // session open we will not need to pass the password to subsequent
      // AdsiOpenObject calls.
      
      hr = AdsiOpenObject<IADs>(ldapPrefix + L"RootDSE", rootDse);
      if (FAILED(hr))
      {
         error=String::format(
               IDS_UNABLE_TO_CONNECT_TO_DC,
               completeDcName.c_str());
         hr=E_FAIL;
         break;      
      }

      // read the configuration naming context.
      _variant_t variant;
      hr =
         rootDse->Get(
            AutoBstr(LDAP_OPATT_CONFIG_NAMING_CONTEXT_W),
            &variant);
      if (FAILED(hr))
      {
         LOG(L"can't read config NC");
                  
         error=String::format(IDS_UNABLE_TO_READ_DIRECTORY_INFO);
         break;   
      }

      String configNc = V_BSTR(&variant);

      ASSERT(!configNc.empty());   
      LOG(configNc);

      wchar_t *domain=wcschr(configNc.c_str(),L',');
      ASSERT(domain!=NULL);
      domainName=domain+1;

      rootContainerDn = L"CN=DisplaySpecifiers," + configNc;
   }
   while (0);

   LOG_HRESULT(hr);

   return hr;
}


HRESULT 
GetInitialInformation(  
                        String &targetDomainControllerName,
                        String &csvFilename,
                        String &csv409Name
                     )
{
   LOG_FUNCTION(GetInitialInformation);

   HRESULT hr = S_OK;
   do
   {
      
      //
      // find the dcpromo.csv file to use
      //
   
      hr = FindCsvFile(csvFilename, csv409Name);
      BREAK_ON_FAILED_HRESULT(hr);

      //
      // Determine the target domain controller
      //

      if (targetDomainControllerName.empty())
      {
         // no target specified, default to the current machine
   
         targetDomainControllerName =
            Win::GetComputerNameEx(ComputerNameDnsFullyQualified);
   
         if (targetDomainControllerName.empty())
         {
            // no DNS name?  that's not right...
            LOG(L"no default DNS computer name found. Using netbios name.");
            

            targetDomainControllerName = 
               Win::GetComputerNameEx(ComputerNameNetBIOS);
            ASSERT(!targetDomainControllerName.empty());
         }
      }
   } 
   while (0);
   
   LOG_HRESULT(hr);
   return hr;
}


///////////////////////////////////////////////////////////////////
// Function: cchLoadHrMsg
//
// Given an HRESULT error,
// it loads the string for the error. It returns the # of characters returned
int cchLoadHrMsg( HRESULT hr, String &message )
{
   if(hr == S_OK) return 0;

   wchar_t *msgPtr = NULL;

   // Try from the system table
   int cch = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 
                           NULL, 
                           hr,
                           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                           (LPWSTR)&msgPtr, 
                           0, 
                           NULL);


   if (!cch) 
   { 
      //try ads errors
      static HMODULE g_adsMod = 0;
      if (0 == g_adsMod)
      {
            g_adsMod = GetModuleHandle (L"activeds.dll");
      }

      cch = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE, 
                        g_adsMod, 
                        hr,
                        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                        (LPWSTR)&msgPtr, 
                        0, 
                        NULL);
   }

   if (!cch)
   {
      // Try NTSTATUS error codes

      hr = HRESULT_FROM_WIN32(RtlNtStatusToDosError(hr));

      cch = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 
                           NULL, 
                           hr,
                           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                           (LPWSTR)&msgPtr, 
                           0, 
                           NULL);
   }

   message.erase();

   if(cch!=0)
   {
      if(msgPtr==NULL) 
      {
         cch=0;
      }
      else
      {
         message=msgPtr;
         ::LocalFree(msgPtr);
      } 
   } 
   
   return cch;
}

// allocates error with src and a message formated from hr
void AllocError(HRESULT &hr,PWSTR *error,const String& src)
{
   ASSERT(error!=NULL);
   ASSERT(FAILED(hr));

   if (error==NULL) return; 

   // There is no use in formating a message if hr didn't fail
   if(!FAILED(hr)) return;
   
   String msg;
   
   if(hr!=E_FAIL)
   {
      // We ignore the error since it is possible that 
      // we don't find a message
      cchLoadHrMsg(hr,msg);
   }
   
   // Under any conditions(no mesage,E_FAIL or good message)
   // we print the hr. 
   msg+=String::format(L" (0x%1!x!).",hr);
   
   // we also add src
   msg=src+L" "+msg;


   *error=static_cast<PWSTR>(
                                 LocalAlloc
                                 (
                                    LMEM_FIXED,
                                    (msg.size()+1)*sizeof(wchar_t)
                                 )
                            );
   if(*error==NULL)
   {
      hr = Win32ToHresult(ERROR_NOT_ENOUGH_MEMORY);
   }
   else
   {
      wcscpy(*error,msg.c_str());
   }
   return;
}


HRESULT 
RunAnalysis
(
      GUID  guid,
      PWSTR logFilesPath,
      void *caleeStruct/*=NULL*/,
      progressFunction stepIt/*=NULL*/,
      progressFunction totalSteps/*=NULL*/
)
{
   LOG_FUNCTION(RunAnalysis);
   hResourceModuleHandle=::GetModuleHandle(NULL);
   HRESULT hr=S_OK;

   try
   {
      goodAnalysis=false;
      results.createContainers.clear();
      results.conflictingWhistlerObjects.clear();
      results.createWhistlerObjects.clear();
      results.createW2KObjects.clear();
      results.objectActions.clear();
      results.customizedValues.clear();
      results.extraneousValues.clear();

      hr = ::CoInitialize(0);
      ASSERT(SUCCEEDED(hr));
   

      do
      {
         String normalPath=FS::NormalizePath(logFilesPath);
         if (!FS::PathExists(normalPath) || FS::FileExists(normalPath))
         {
            hr=E_FAIL;
            error=String::load(IDS_NO_LOG_FILE_PATH);
            break;
         }

         hr=GetInitialInformation(
                                    targetDomainControllerName,
                                    csvFileName,
                                    csv409Name
                                 );

         BREAK_ON_FAILED_HRESULT(hr);

         hr=csvReaderIntl.read(csvFileName.c_str(),LOCALEIDS);
         BREAK_ON_FAILED_HRESULT(hr);
   
         hr=csvReader409.read(csv409Name.c_str(),LOCALE409);
         BREAK_ON_FAILED_HRESULT(hr);

         SmartInterface<IADs> rootDse(0);

         hr=InitializeADSI
            (
               targetDomainControllerName,
               ldapPrefix,
               rootContainerDn,
               domainName,
               completeDcName,
               rootDse
            );
         BREAK_ON_FAILED_HRESULT(hr);

         String reportName;

         GetWorkFileName(  
                              normalPath,
                              String::load(IDS_FILE_NAME_REPORT),
                              L"txt",
                              reportName
                        );

         Analysis analysis(
                              guid,
                              csvReader409, 
                              csvReaderIntl,
                              ldapPrefix,
                              rootContainerDn,
                              results,
                              reportName,
                              caleeStruct,
                              stepIt,
                              totalSteps
                           );
   
         hr=analysis.run();
         BREAK_ON_FAILED_HRESULT(hr);
      } while(0);

      CoUninitialize();

      if(SUCCEEDED(hr))
	  {
         goodAnalysis=true;
      }
   }
   catch( const std::bad_alloc& )
   {
     // Since we are in an out of memory condition.
     // we will not show allocate messages.
     hr=Win32ToHresult(ERROR_OUTOFMEMORY);
   }


   LOG_HRESULT(hr);
   return hr;

}




HRESULT 
RunRepair 
(
      PWSTR logFilesPath,
      void *caleeStruct/*=NULL*/,
      progressFunction stepIt/*=NULL*/,
      progressFunction totalSteps/*=NULL*/
)
{
   hResourceModuleHandle=::GetModuleHandle(NULL);
   LOG_FUNCTION(RunRepair);
   HRESULT hr=S_OK;


   try
   {
      hr = ::CoInitialize(0);
      ASSERT(SUCCEEDED(hr));

      do
      {
         String normalPath=FS::NormalizePath(logFilesPath);
         if (!FS::PathExists(normalPath) || FS::FileExists(normalPath))
         {
            hr=E_FAIL;
            error=String::load(IDS_NO_LOG_FILE_PATH);
            break;
         }

         if (!goodAnalysis)
         {
            hr=E_FAIL;
            error=String::load(IDS_NO_ANALYSIS);
            break;
         }

         String ldiffName;

         GetWorkFileName(
                              normalPath,
                              String::load(IDS_FILE_NAME_LDIF_ACTIONS),
                              L"ldf",
                              ldiffName
                        );
         BREAK_ON_FAILED_HRESULT(hr);

         String csvName;

         GetWorkFileName(
                              normalPath,
                              String::load(IDS_FILE_NAME_CSV_ACTIONS),
                              L"csv",
                              csvName
                        );
         BREAK_ON_FAILED_HRESULT(hr);
   
         String saveName;

         GetWorkFileName(
                              normalPath,
                              String::load(IDS_FILE_NAME_UNDO),
                              L"ldf",
                              saveName
                        );

         BREAK_ON_FAILED_HRESULT(hr);

         String logPath;

         Repair repair
         (
            csvReader409, 
            csvReaderIntl,
            domainName,
            rootContainerDn,
            results,
            ldiffName,
            csvName,
            saveName,
            normalPath,
            completeDcName,
            caleeStruct,
            stepIt,
            totalSteps
          );

         hr=repair.run();
         BREAK_ON_FAILED_HRESULT(hr);
      } while(0);

	  CoUninitialize();
   }
   catch( const std::bad_alloc& )
   {
     // Since we are in an out of memory condition.
     // we will not show allocate messages.
     hr=Win32ToHresult(ERROR_OUTOFMEMORY);
   }

   LOG_HRESULT(hr);
   return hr;
}

extern "C"
HRESULT 
UpgradeDisplaySpecifiers 
(
      PWSTR logFilesPath,
      GUID  *OperationGuid,
      BOOL dryRun,
      PWSTR *errorMsg,//=NULL
      void *caleeStruct,//=NULL
      progressFunction stepIt,//=NULL
      progressFunction totalSteps//=NULL
)
{
    LOG_FUNCTION(UpgradeDisplaySpecifiers);
    hResourceModuleHandle=::GetModuleHandle(NULL);
    HRESULT hr=S_OK;

    do
    {
        hr = ::CoInitialize(0);
        ASSERT(SUCCEEDED(hr));

        GUID guid;
        if(OperationGuid==NULL)
        {
          hr = E_INVALIDARG;
          error = String::format(IDS_NO_GUID);
          break;
        }

        guid=*OperationGuid;

        int sizeGuids=sizeof(guids)/sizeof(*guids);
        bool found=false;
        for(int t=0;(t<sizeGuids) && (!found);t++)
        {
            if (guids[t]==guid) found=true;
        }

        if(!found)
        {
          hr = E_INVALIDARG;
          error = String::format(IDS_NO_OPERATION_GUID);
          break;
        }

        hr=RunAnalysis(guid,logFilesPath,caleeStruct,stepIt,totalSteps);
        BREAK_ON_FAILED_HRESULT(hr);

        if(dryRun==false)
        {
            hr=RunRepair(logFilesPath,caleeStruct,stepIt,totalSteps);
            BREAK_ON_FAILED_HRESULT(hr);
        }
        CoUninitialize();

    } while(0);


	if(FAILED(hr))
	{
		AllocError(hr,errorMsg,error);
	}

    LOG_HRESULT(hr);
    return hr;
}