/*
 * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 */
/*
    File:       win32main.cpp

    Contains:   main function to drive streaming server on win32.


*/

#include "getopt.h"
#include "FilePrefsSource.h"

#include "RunServer.h"
#include "QTSServer.h"
#include "QTSSExpirationDate.h"
#include "GenerateXMLPrefs.h"

//
// Data
static FilePrefsSource sPrefsSource(true); // Allow dups
static XMLPrefsParser* sXMLParser = NULL;
static FilePrefsSource sMessagesSource;
static UInt16 sPort = 0; //port can be set on the command line
static int sStatsUpdateInterval = 0;
static SERVICE_STATUS_HANDLE sServiceStatusHandle = 0;
static QTSS_ServerState sInitialState = qtssRunningState;

//
// Functions
static void ReportStatus(DWORD inCurrentState, DWORD inExitCode);
static void InstallService(char* inServiceName);
static void RemoveService(char *inServiceName);
static void RunAsService(char* inServiceName);
void WINAPI ServiceControl(DWORD);
void WINAPI ServiceMain(DWORD argc, LPTSTR *argv);


int main(int argc, char * argv[]) 
{
    extern char* optarg;
    
    //First thing to do is to read command-line arguments.
    int ch;
    
    char* theConfigFilePath = "c:\\Program Files\\Darwin Streaming Server\\streamingserver.cfg";
    char* theXMLFilePath = "c:\\Program Files\\Darwin Streaming Server\\streamingserver.xml";
    Bool16 notAService = false;
    Bool16 theXMLPrefsExist = true;

#if _DEBUG
    char* compileType = "Compile_Flags/_DEBUG; ";
#else
   char* compileType = "";
#endif

    while ((ch = getopt(argc,argv, "vdxp:o:c:irsS:I")) != EOF) // opt: means requires option
    {
        switch(ch)
        {
            case 'v':

                qtss_printf("%s/%s ( Build/%s; Platform/%s; %s%s) Built on: %s\n",QTSServerInterface::GetServerName().Ptr,
                                        QTSServerInterface::GetServerVersion().Ptr,
                                        QTSServerInterface::GetServerBuild().Ptr,
                                        QTSServerInterface::GetServerPlatform().Ptr,
                                        compileType,
                                        QTSServerInterface::GetServerComment().Ptr,
                                        QTSServerInterface::GetServerBuildDate().Ptr);

                qtss_printf("usage: %s [ -d | -p port | -v | -c /myconfigpath.xml | -o /myconfigpath.conf | -x | -S numseconds | -I | -h ]\n", QTSServerInterface::GetServerName().Ptr);
                qtss_printf("-d: Don't run as a Win32 Service\n");
                qtss_printf("-p XXX: Specify the default RTSP listening port of the server\n");
                qtss_printf("-c c:\\myconfigpath.xml: Specify a config file path\n");
                qtss_printf("-o c:\\myconfigpath.conf: Specify a DSS 1.x / 2.x config file path\n");
                qtss_printf("-x: Force create new .xml config file from 1.x / 2.x config\n");
                qtss_printf("-i: Install the Darwin Streaming Server service\n");
                qtss_printf("-r: Remove the Darwin Streaming Server service\n");
                qtss_printf("-s: Start the Darwin Streaming Server service\n");
                qtss_printf("-S n: Display server stats in the console every \"n\" seconds\n");
                qtss_printf("-I: Start the server in the idle state\n");
                ::exit(0);  
            case 'd':
                notAService = true;
                break;
            case 'p':
                Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt.
                sPort = ::atoi(optarg);
                break;
            case 'c':
                Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt.
                theXMLFilePath = optarg;
                break;
            case 'S':
                Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt.
                sStatsUpdateInterval = ::atoi(optarg);
                break;
            case 'o':
                Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt.
                theConfigFilePath = optarg;
                break;
            case 'x':
                theXMLPrefsExist = false; // Force us to generate a new XML prefs file
                break;
            case 'i':
                qtss_printf("Installing the Darwin Streaming Server service...\n");
                ::InstallService("Darwin Streaming Server");
                qtss_printf("Starting the Darwin Streaming Server service...\n");
                ::RunAsService("Darwin Streaming Server");
                ::exit(0);
                break;
            case 'r':
                qtss_printf("Removing the Darwin Streaming Server service...\n");
                ::RemoveService("Darwin Streaming Server");
                ::exit(0);
            case 's':
                qtss_printf("Starting the Darwin Streaming Server service...\n");
                ::RunAsService("Darwin Streaming Server");
                ::exit(0);
            case 'I':
                sInitialState = qtssIdleState;
                break;
            default:
                break;
        }
    }
    
    //
    // Check expiration date
    
    QTSSExpirationDate::PrintExpirationDate();
    if (QTSSExpirationDate::IsSoftwareExpired())
    {
        qtss_printf("Streaming Server has expired\n");
        ::exit(0);
    }

    //
    // Create an XML prefs parser object using the specified path
    sXMLParser = new XMLPrefsParser(theXMLFilePath);
    
    //
    // Check to see if the XML file exists as a directory. If it does,
    // just bail because we do not want to overwrite a directory
    if (sXMLParser->DoesFileExistAsDirectory())
    {
        qtss_printf("Directory located at location where streaming server prefs file should be.\n");
        ::exit(0);
    }
    
    if (!sXMLParser->CanWriteFile())
    {
        qtss_printf("Cannot write to the streaming server prefs file.\n");
        ::exit(0);
    }

    // If we aren't forced to create a new XML prefs file, whether
    // we do or not depends solely on whether the XML prefs file exists currently.
    if (theXMLPrefsExist)
        theXMLPrefsExist = sXMLParser->DoesFileExist();
    
    if (!theXMLPrefsExist)
    {
        //
        //Construct a Prefs Source object to get server preferences
        
        int prefsErr = sPrefsSource.InitFromConfigFile(theConfigFilePath);
        if ( prefsErr )
            qtss_printf("Could not load configuration file at %s.\n Generating a new prefs file at %s\n", theConfigFilePath, theXMLFilePath);

        //
        // Generate a brand-new XML prefs file out of the old prefs
        int xmlGenerateErr = GenerateAllXMLPrefs(&sPrefsSource, sXMLParser);
        if (xmlGenerateErr)
        {
            qtss_printf("Fatal Error: Could not create new prefs file at: %s. (%d)\n", theConfigFilePath, OSThread::GetErrno());
            ::exit(-1);
        }       
    }

    //
    // Parse the configs from the XML file
    int xmlParseErr = sXMLParser->Parse();
    if (xmlParseErr)
    {
        qtss_printf("Fatal Error: Could not load configuration file at %s. (%d)\n", theXMLFilePath, OSThread::GetErrno());
        ::exit(-1);
    }

    //
    // Construct a messages source object
    sMessagesSource.InitFromConfigFile("qtssmessages.txt");

    //
    // Start Win32 DLLs
    WORD wsVersion = MAKEWORD(1, 1);
    WSADATA wsData;
    (void)::WSAStartup(wsVersion, &wsData);
    
    if (notAService)
    {
        // If we're running off the command-line, don't do the service initiation crap.
        ::StartServer(sXMLParser, &sMessagesSource, sPort, sStatsUpdateInterval, sInitialState, false); // No stats update interval for now
        ::RunServer();
        ::exit(0);
    }
    
    SERVICE_TABLE_ENTRY dispatchTable[] =
    {
        { "", ServiceMain },
        { NULL, NULL }
    };

    //
    // In case someone runs the server improperly, print out a friendly message.
    qtss_printf("Darwin Streaming Server must either be started from the DOS Console\n");
    qtss_printf("using the -d command-line option, or using the Service Control Manager\n\n");
    qtss_printf("Waiting for the Service Control Manager to start Darwin Streaming Server...\n");
    BOOL theErr = ::StartServiceCtrlDispatcher(dispatchTable);
    if (!theErr)
    {
        qtss_printf("Fatal Error: Couldn't start Service\n");
        ::exit(-1);
    }

    return (0);
}
    
    
void __stdcall ServiceMain(DWORD /*argc*/, LPTSTR *argv)
{
    char* theServerName = argv[0];

    sServiceStatusHandle = ::RegisterServiceCtrlHandler( theServerName, &ServiceControl );
    if (sServiceStatusHandle == 0)
    {
        qtss_printf("Failure registering service handler");
        return;
    }

    //
    // Report our status
    ::ReportStatus( SERVICE_START_PENDING, NO_ERROR );
    
    //
    // Start & Run the server - no stats update interval for now
    if (::StartServer(sXMLParser, &sMessagesSource, sPort, sStatsUpdateInterval, sInitialState, false) != qtssFatalErrorState)
    {
        ::ReportStatus( SERVICE_RUNNING, NO_ERROR );
        ::RunServer(); // This function won't return until the server has died
    
        //
        // Ok, server is done...
        ::ReportStatus( SERVICE_STOPPED, NO_ERROR );
    }
    else
        ::ReportStatus( SERVICE_STOPPED, ERROR_BAD_COMMAND ); // I dunno... report some error
    
}

void WINAPI ServiceControl(DWORD inControlCode)
{
    QTSS_ServerState theState;
    QTSServerInterface* theServer = QTSServerInterface::GetServer();
    DWORD theStatusReport = SERVICE_START_PENDING;

    if (theServer != NULL)
        theState = theServer->GetServerState();
    else
        theState = qtssStartingUpState;

   switch(inControlCode)
    {
        // Stop the service.
        //
        case SERVICE_CONTROL_STOP:
        case SERVICE_CONTROL_SHUTDOWN:
        {
            if (theState == qtssStartingUpState)
                break;
                
            //
            // Signal the server to shut down.
            theState = qtssShuttingDownState;
            if (theServer != NULL)
                theServer->SetValue(qtssSvrState, 0, &theState, sizeof(theState));
            break;
        }
        case SERVICE_CONTROL_PAUSE:
        {
            if (theState != qtssRunningState)
                break;
                
            //
            // Signal the server to refuse new connections.
            theState = qtssRefusingConnectionsState;
            if (theServer != NULL)
                theServer->SetValue(qtssSvrState, 0, &theState, sizeof(theState));
            break;
        }
        case SERVICE_CONTROL_CONTINUE:
        {
            if (theState != qtssRefusingConnectionsState)
                break;
                
            //
            // Signal the server to refuse new connections.
            theState = qtssRefusingConnectionsState;
            if (theServer != NULL)
                theServer->SetValue(qtssSvrState, 0, &theState, sizeof(theState));
            break;
        }
        case SERVICE_CONTROL_INTERROGATE:
            break; // Just update our status
            
        default:
            break;
    }

    if (theServer != NULL)
    {
        theState = theServer->GetServerState();
        
        //
        // Convert a QTSS state to a Win32 Service state
        switch (theState)
        {
            case qtssStartingUpState:           theStatusReport = SERVICE_START_PENDING;    break;
            case qtssRunningState:              theStatusReport = SERVICE_RUNNING;          break;
            case qtssRefusingConnectionsState:  theStatusReport = SERVICE_PAUSED;           break;
            case qtssFatalErrorState:           theStatusReport = SERVICE_STOP_PENDING;     break;
            case qtssShuttingDownState:         theStatusReport = SERVICE_STOP_PENDING;     break;
            default:                            theStatusReport = SERVICE_RUNNING;          break;
        }
    }
    else
        theStatusReport = SERVICE_START_PENDING;

    qtss_printf("Reporting status from ServiceControl function\n");
    ::ReportStatus(theStatusReport, NO_ERROR);
}

void ReportStatus(DWORD inCurrentState, DWORD inExitCode)
{
    static Bool16 sFirstTime = 1;
    static UInt32 sCheckpoint = 0;
    static SERVICE_STATUS sStatus;
    
    if(sFirstTime)
    {
        sFirstTime = false;
        
        //
        // Setup the status structure
        sStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
        sStatus.dwCurrentState = SERVICE_START_PENDING;
        //sStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
        sStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
        sStatus.dwWin32ExitCode = 0;
        sStatus.dwServiceSpecificExitCode = 0;
        sStatus.dwCheckPoint = 0;
        sStatus.dwWaitHint = 0;
    }

    if (sStatus.dwCurrentState == SERVICE_START_PENDING)
        sStatus.dwCheckPoint = ++sCheckpoint;
    else
        sStatus.dwCheckPoint = 0;
    
    sStatus.dwCurrentState = inCurrentState;
    sStatus.dwServiceSpecificExitCode = inExitCode;
    BOOL theErr = SetServiceStatus(sServiceStatusHandle, &sStatus);
    if (theErr == 0)
    {
        DWORD theerrvalue = ::GetLastError();
    }
}

void RunAsService(char* inServiceName)
{
    SC_HANDLE   theService;
    SC_HANDLE   theSCManager;
    
    theSCManager = ::OpenSCManager(
                        NULL,                   // machine (NULL == local)
                        NULL,                   // database (NULL == default)
                        SC_MANAGER_ALL_ACCESS   // access required
                        );
    if (!theSCManager)
        return;

    theService = ::OpenService(
        theSCManager,               // SCManager database
        inServiceName,               // name of service
        SERVICE_ALL_ACCESS );

    SERVICE_STATUS lpServiceStatus;

    if (theService)
    {   const SInt32 kNotRunning = 1062;
        Bool16 stopped = ::ControlService(theService, SERVICE_CONTROL_STOP, &lpServiceStatus);
        if(!stopped && ( (SInt32) ::GetLastError() != kNotRunning) )
            qtss_printf("Stopping Service Error: %d\n", ::GetLastError());

        Bool16 started = ::StartService(theService, 0, NULL);
        if(!started)
            qtss_printf("Starting Service Error: %d\n", ::GetLastError());
        
        ::CloseServiceHandle(theService);
    }

    ::CloseServiceHandle(theSCManager);
}


void InstallService(char* inServiceName)
{
    SC_HANDLE   theService;
    SC_HANDLE   theSCManager;

    TCHAR thePath[512];
    TCHAR theQuotedPath[522];

    BOOL theErr = ::GetModuleFileName( NULL, thePath, 512 );
    if (!theErr)
        return;

    qtss_sprintf(theQuotedPath, "\"%s\"", thePath);

    theSCManager = ::OpenSCManager(
                        NULL,                   // machine (NULL == local)
                        NULL,                   // database (NULL == default)
                        SC_MANAGER_ALL_ACCESS   // access required
                        );
    if (!theSCManager)
    {
        qtss_printf("Failed to install Darwin Streaming Server Service\n");
        return;
    }

    theService = CreateService(
        theSCManager,               // SCManager database
        inServiceName,               // name of service
        inServiceName,               // name to display
        SERVICE_ALL_ACCESS,         // desired access
        SERVICE_WIN32_OWN_PROCESS,  // service type
        SERVICE_AUTO_START,       // start type
        SERVICE_ERROR_NORMAL,       // error control type
        theQuotedPath,               // service's binary
        NULL,                       // no load ordering group
        NULL,                       // no tag identifier
        NULL,       // dependencies
        NULL,                       // LocalSystem account
        NULL);                      // no password

    if (theService)
    {
        ::CloseServiceHandle(theService);
        qtss_printf("Installed Darwin Streaming Server Service\n");
    }
    else
        qtss_printf("Failed to install Darwin Streaming Server Service\n");

    ::CloseServiceHandle(theSCManager);
}

void RemoveService(char *inServiceName)
{
    SC_HANDLE   theSCManager;
    SC_HANDLE   theService;

    theSCManager = ::OpenSCManager(
                        NULL,                   // machine (NULL == local)
                        NULL,                   // database (NULL == default)
                        SC_MANAGER_ALL_ACCESS   // access required
                        );
    if (!theSCManager)
    {
        qtss_printf("Failed to remove Darwin Streaming Server Service\n");
        return;
    }

    theService = ::OpenService(theSCManager, inServiceName, SERVICE_ALL_ACCESS);
    if (theService != NULL)
    {
        Bool16 stopped = ::ControlService(theService, SERVICE_CONTROL_STOP, NULL);
        if(!stopped)
            qtss_printf("Stopping Service Error: %d\n", ::GetLastError());

        (void)::DeleteService(theService);
        ::CloseServiceHandle(theService);
        qtss_printf("Removed Darwin Streaming Server Service\n");
    }
    else
        qtss_printf("Failed to remove Darwin Streaming Server Service\n");

    ::CloseServiceHandle(theSCManager);
}
