// gc.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "AssertWithStack.cpp"

CSpUnicodeSupport   g_Unicode;

class CError : public ISpErrorLog
{
public:
    CError(const WCHAR * pszFileName)
    {
        m_pszFileName = pszFileName;
    }
    STDMETHODIMP QueryInterface(REFIID riid, void ** ppv)
    {
        if (riid == __uuidof(IUnknown) ||
            riid == __uuidof(ISpErrorLog))
        {
            *ppv = (ISpErrorLog *)this;
            return S_OK;
        }
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    STDMETHODIMP_(ULONG) AddRef()
    {
        return 2;
    }
    STDMETHODIMP_(ULONG) Release()
    {
        return 1;
    }
    // -- ISpErrorLog
    STDMETHODIMP AddError(const long lLine, HRESULT hr, const WCHAR * pszDescription, const WCHAR * pszHelpFile, DWORD dwHelpContext);

    // --- data members
    const WCHAR * m_pszFileName;
};

HRESULT CError::AddError(const long lLine, HRESULT hr, const WCHAR * pszDescription, const WCHAR * pszHelpFile, DWORD dwHelpContext)
{
    SPDBG_FUNC("CError::AddError");
    USES_CONVERSION;
    if (lLine > 0)
    {
        fprintf(stderr, "%s(%d) : %s\n", W2T(m_pszFileName), lLine, W2T(pszDescription));
    }
    else
    {
        fprintf(stderr, "%s(1) : %s\n", W2T(m_pszFileName), W2T(pszDescription));
    }
    return S_OK;
}

HRESULT ParseCommandLine(
    int argc, 
    char * argv[], 
    WCHAR ** pszInFileName,
    WCHAR ** pszOutFileName,
    WCHAR ** pszHeaderFileName)
{
    SPDBG_FUNC("ParseCommandLine");
    HRESULT hr = S_OK;
    
    // Our job is to come up with the three filenames from the command
    // line arguments. We'll store them locally in cotask strings, 
    // and return them at the end
    CSpDynamicString dstrInFileName;
    CSpDynamicString dstrOutFileName;
    CSpDynamicString dstrHeaderFileName;
    
    for (int i = 1; SUCCEEDED(hr) && i < argc; i++)
    {
        // If this param looks like it's an option
        if ((argv[i][0] == L'-') || (argv[i][0] == L'/'))
        {
            if (stricmp(&argv[i][1], "?") == 0)
            {
                // The invoker is asking for help, give it to them as if they specified an
                // invalid argument
                hr = E_INVALIDARG;
            }
            else if (i + 1 >= argc)
            {
                // The following arguments require an additional argument themsevles
                // so if we don't have one, we're done
                hr = E_INVALIDARG;
            }
            else if (stricmp(&argv[i][1], "o") == 0)
            {
                // Set the output filename if it hasn't already been set
                if (dstrOutFileName.Length() != 0)
                {
                    hr = E_INVALIDARG;
                }
                    
                dstrOutFileName = argv[++i];
            }
            else if (stricmp(&argv[i][1], "h") == 0)
            {
                // Set the header filename if it hasn't already been set
                if (dstrHeaderFileName.Length() != 0)
                {
                    hr = E_INVALIDARG;
                }
                
                dstrHeaderFileName = argv[++i];
            }
            else
            {
                // Unknown option, we'll need to display usage
                hr = E_INVALIDARG;
            }
        }
        else
        {
            // Set the filename if it hasn't already been set
            if (dstrInFileName.Length() != 0)
            {
                hr = E_INVALIDARG;
            }
            
            dstrInFileName = argv[i];
        }
    }

    // If we don't have an input file name, that's an error at this point
    if (SUCCEEDED(hr) && dstrInFileName.Length() == 0)
    {
        hr = E_INVALIDARG;
    }

    // If we don't already have an output file name, make one up
    // based on the input file name (replacing the .cfg if it's there)
    if (SUCCEEDED(hr) && dstrOutFileName.Length() == 0)
    {
        dstrOutFileName = dstrInFileName;
        
        if (dstrOutFileName.Length() >= 4 &&
            wcsicmp(((const WCHAR *)dstrOutFileName) + dstrOutFileName.Length() - 4, L".xml") == 0)
        {
            wcscpy(((WCHAR *)dstrOutFileName) + dstrOutFileName.Length() - 4, L".cfg");
        }
        else
        {
            dstrOutFileName.Append(L".cfg");
        }
    }

    // If we failed above, we need to display usage
    if (FAILED(hr))
    {
        fprintf(stderr, "%s [/o cfg_filename] [/h header_filename] input_filename\n", argv[0]);
    }
    else
    {
        // Pass back our filenames based on what we saw on the command line
        *pszInFileName = dstrInFileName.Detach();
        *pszOutFileName = dstrOutFileName.Detach();
        *pszHeaderFileName = dstrHeaderFileName.Detach();
    }
    
    return hr;
}

HRESULT OpenFile(
        const WCHAR * pszFileName, 
        DWORD dwDesiredAccess, 
        DWORD dwCreationDisposition, 
        const WCHAR * pszNewExtension, 
        CSpFileStream ** ppFileStream)
{
    SPDBG_FUNC("OpenFile");
    HRESULT hr = S_OK;
    
    // Try to open the file
    HANDLE hFile = g_Unicode.CreateFile(pszFileName, dwDesiredAccess, 0, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        // If that failed, try again appending the extension first
        if (pszNewExtension != NULL)
        {
            CSpDynamicString dstrFileNameWithExt;
            dstrFileNameWithExt.Append2(pszFileName, pszNewExtension);
            
            hFile = g_Unicode.CreateFile(dstrFileNameWithExt, dwDesiredAccess, 0, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
        }
    }
    
    // If we couldn't open the file, record the error
    if (hFile == INVALID_HANDLE_VALUE)
    {
        hr = SpHrFromLastWin32Error();
    }
    
    // Create a new filestream object for that file
    CSpFileStream * pFileStream;
    if (SUCCEEDED(hr))
    {
        *ppFileStream = new CSpFileStream(hFile);
        if (*ppFileStream == NULL)
        {
            hr = E_OUTOFMEMORY;
        }
    }
    
    // If we failed
    if (FAILED(hr))
    {
        // First we should let the invoker know that it failed
        USES_CONVERSION;
        fprintf(stderr, "Error: Error opening %s\n", W2T(pszFileName));
        
        if (hFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(hFile);
        }
    }
    
    return hr;
}

HRESULT Compile(const WCHAR * pszInFileName, const WCHAR * pszOutFileName, const WCHAR * pszHeaderFileName)
{
    SPDBG_FUNC("Compile");
    HRESULT hr;
    
    CComPtr<ISpGrammarCompiler> cpCompiler;
    hr = cpCompiler.CoCreateInstance(CLSID_SpGrammarCompiler);
    
    CComPtr<CSpFileStream> cpInStream;
    if (SUCCEEDED(hr))
    {
        hr = OpenFile(pszInFileName, GENERIC_READ, OPEN_EXISTING, L".xml", &cpInStream);
    }
    
    CComPtr<CSpFileStream> cpOutStream;
    if (SUCCEEDED(hr))
    {
        hr = OpenFile(pszOutFileName, GENERIC_WRITE, CREATE_ALWAYS, NULL, &cpOutStream);
    }
    
    CComPtr<CSpFileStream> cpHeaderStream;
    if (SUCCEEDED(hr) && pszHeaderFileName != NULL)
    {
        hr = OpenFile(pszHeaderFileName, GENERIC_WRITE, CREATE_ALWAYS, NULL, &cpHeaderStream);
    }
        
    if (SUCCEEDED(hr))
    {
        CError errorlog(pszInFileName);
        hr = cpCompiler->CompileStream(cpInStream, cpOutStream, cpHeaderStream, NULL, &errorlog, 0);
    }
    
    return hr;
}

HRESULT Execute(int argc, char * argv[])
{
    SPDBG_FUNC("Execute");
    HRESULT hr = S_OK;
    
    CSpDynamicString dstrInFileName, dstrOutFileName, dstrHeaderFileName;
    hr = ParseCommandLine(argc, argv, &dstrInFileName, &dstrOutFileName, &dstrHeaderFileName);
    
    if (SUCCEEDED(hr))
    {
        hr = Compile(dstrInFileName, dstrOutFileName, dstrHeaderFileName);
    }

    if (SUCCEEDED(hr))
    {
        fprintf(stderr, "Compilation successful!\n");
    }

    return hr;
}

int main(int argc, char* argv[])
{
    SPDBG_FUNC("main");
    HRESULT hr;
    hr = CoInitialize(NULL);
    
    if (SUCCEEDED(hr))
    {
        hr = Execute(argc, argv);
        CoUninitialize();
    }
    
    return FAILED(hr) ? -1 : 0;
}