/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Mike McCabe <mccabe@netscape.com>
 *   John Bandhauer <jband@netscape.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/* Implementation of xptiManifest. */

#include "xptiprivate.h"
#include "nsManifestLineReader.h"
#include "nsString.h"

static const char g_Disclaimer[] = "# Generated file. ** DO NOT EDIT! **";

static const char g_TOKEN_Files[]          = "Files";
static const char g_TOKEN_ArchiveItems[]   = "ArchiveItems";
static const char g_TOKEN_Interfaces[]     = "Interfaces";
static const char g_TOKEN_Header[]         = "Header";
static const char g_TOKEN_Version[]        = "Version";
static const char g_TOKEN_AppDir[]         = "AppDir";
static const char g_TOKEN_Directories[]    = "Directories";

static const int  g_VERSION_MAJOR          = 2;
static const int  g_VERSION_MINOR          = 0;

/***************************************************************************/

static PRBool 
GetCurrentAppDirString(xptiInterfaceInfoManager* aMgr, nsACString &aStr)
{
    nsCOMPtr<nsILocalFile> appDir;
    aMgr->GetApplicationDir(getter_AddRefs(appDir));
    if(appDir)
        return NS_SUCCEEDED(appDir->GetPersistentDescriptor(aStr));
    return PR_FALSE;
}

static PRBool 
CurrentAppDirMatchesPersistentDescriptor(xptiInterfaceInfoManager* aMgr, 
                                         const char *inStr)
{
    nsCOMPtr<nsILocalFile> appDir;
    aMgr->GetApplicationDir(getter_AddRefs(appDir));

    nsCOMPtr<nsILocalFile> descDir;
    nsresult rv = NS_NewNativeLocalFile(EmptyCString(), PR_FALSE, getter_AddRefs(descDir));
    if(NS_FAILED(rv))
        return PR_FALSE;

    rv = descDir->SetPersistentDescriptor(nsDependentCString(inStr));
    if(NS_FAILED(rv))
        return PR_FALSE;
    
    PRBool matches;
    rv = appDir->Equals(descDir, &matches);
    return NS_SUCCEEDED(rv) && matches;
}

PR_STATIC_CALLBACK(PLDHashOperator)
xpti_InterfaceWriter(PLDHashTable *table, PLDHashEntryHdr *hdr,
                     PRUint32 number, void *arg)
{
    xptiInterfaceEntry* entry = ((xptiHashEntry*)hdr)->value;
    PRFileDesc* fd = (PRFileDesc*)  arg;

    char iidStr[NSID_LENGTH];
    entry->GetTheIID()->ToProvidedString(iidStr);

    const xptiTypelib& typelib = entry->GetTypelibRecord();

    PRBool success =  !!PR_fprintf(fd, "%d,%s,%s,%d,%d,%d\n",
                                   (int) number,
                                   entry->GetTheName(),
                                   iidStr,
                                   (int) typelib.GetFileIndex(),
                                   (int) (typelib.IsZip() ? 
                                   typelib.GetZipItemIndex() : -1),
                                   (int) entry->GetScriptableFlag());

    return success ? PL_DHASH_NEXT : PL_DHASH_STOP;
}


// static
PRBool xptiManifest::Write(xptiInterfaceInfoManager* aMgr,
                           xptiWorkingSet*           aWorkingSet)
{

    PRBool succeeded = PR_FALSE;
    PRFileDesc* fd = nsnull;
    PRUint32 i;
    PRUint32 size32;
    PRIntn interfaceCount = 0;
    nsCAutoString appDirString;
    
    nsCOMPtr<nsILocalFile> tempFile;
    if(!aMgr->GetCloneOfManifestLocation(getter_AddRefs(tempFile)) || !tempFile)
        return PR_FALSE;

    nsCAutoString originalLeafName;
    tempFile->GetNativeLeafName(originalLeafName);

    nsCAutoString leafName;
    leafName.Assign(originalLeafName + NS_LITERAL_CSTRING(".tmp"));

    tempFile->SetNativeLeafName(leafName);

    // All exits via "goto out;" from here on...
    if(NS_FAILED(tempFile->
                 OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
                                  0666, &fd)) || !fd)
    {
        goto out;
    }

    // write file header comments

    if(!PR_fprintf(fd, "%s\n", g_Disclaimer))
        goto out;

    // write the [Header] block, version number, and appdir.

    if(!PR_fprintf(fd, "\n[%s,%d]\n", g_TOKEN_Header, 2))
        goto out;

    if(!PR_fprintf(fd, "%d,%s,%d,%d\n", 
                       0, g_TOKEN_Version, g_VERSION_MAJOR, g_VERSION_MINOR))
        goto out;

    GetCurrentAppDirString(aMgr, appDirString);
    if(appDirString.IsEmpty())
        goto out;

    if(!PR_fprintf(fd, "%d,%s,%s\n", 
                       1, g_TOKEN_AppDir, appDirString.get()))
        goto out;

    // write Directories list

    if(!PR_fprintf(fd, "\n[%s,%d]\n", 
                       g_TOKEN_Directories, 
                       (int) aWorkingSet->GetDirectoryCount()))
        goto out;

    for(i = 0; i < aWorkingSet->GetDirectoryCount(); i++)
    {
        nsCOMPtr<nsILocalFile> dir;        
        nsCAutoString str;

        aWorkingSet->GetDirectoryAt(i, getter_AddRefs(dir));
        if(!dir)
            goto out;

        dir->GetPersistentDescriptor(str);
        if(str.IsEmpty())
            goto out;
        
        if(!PR_fprintf(fd, "%d,%s\n", (int) i, str.get()))
            goto out;
    }

    // write Files list

    if(!PR_fprintf(fd, "\n[%s,%d]\n", 
                       g_TOKEN_Files, 
                       (int) aWorkingSet->GetFileCount()))
        goto out;

    for(i = 0; i < aWorkingSet->GetFileCount(); i++)
    {
        const xptiFile& file = aWorkingSet->GetFileAt(i);

        LL_L2UI(size32, file.GetSize());
    
        if(!PR_fprintf(fd, "%d,%s,%d,%u,%lld\n",
                           (int) i,
                           file.GetName(),
                           (int) file.GetDirectory(),
                           size32, PRInt64(file.GetDate())))
        goto out;
    }

    // write ArchiveItems list

    if(!PR_fprintf(fd, "\n[%s,%d]\n", 
                       g_TOKEN_ArchiveItems, 
                       (int) aWorkingSet->GetZipItemCount()))
        goto out;

    for(i = 0; i < aWorkingSet->GetZipItemCount(); i++)
    {
        if(!PR_fprintf(fd, "%d,%s\n",
                           (int) i,
                           aWorkingSet->GetZipItemAt(i).GetName()))
        goto out;
    }

    // write the Interfaces list

    interfaceCount = aWorkingSet->mNameTable->entryCount;

    if(!PR_fprintf(fd, "\n[%s,%d]\n", 
                       g_TOKEN_Interfaces, 
                       (int) interfaceCount))
        goto out;

    if(interfaceCount != (PRIntn)
        PL_DHashTableEnumerate(aWorkingSet->mNameTable, 
                               xpti_InterfaceWriter, fd))
        goto out;


    if(PR_SUCCESS == PR_Close(fd))
    {
        succeeded = PR_TRUE;
    }
    fd = nsnull;

out:
    if(fd)
        PR_Close(fd);
    
    if(succeeded)
    {
        // delete the old file and rename this
        nsCOMPtr<nsILocalFile> mainFile;
        if(!aMgr->GetCloneOfManifestLocation(getter_AddRefs(mainFile)) || !mainFile)
            return PR_FALSE;
    
        PRBool exists;
        if(NS_FAILED(mainFile->Exists(&exists)))
            return PR_FALSE;

        if(exists && NS_FAILED(mainFile->Remove(PR_FALSE)))
            return PR_FALSE;
    
        nsCOMPtr<nsIFile> parent;
        mainFile->GetParent(getter_AddRefs(parent));
            
        // MoveTo means rename.
        if(NS_FAILED(tempFile->MoveToNative(parent, originalLeafName)))
            return PR_FALSE;
    }

    return succeeded;
}        

/***************************************************************************/
/***************************************************************************/

static char* 
ReadManifestIntoMemory(xptiInterfaceInfoManager* aMgr,
                       PRUint32* pLength)
{
    PRFileDesc* fd = nsnull;
    PRInt32 flen;
    PRInt64 fileSize;
    char* whole = nsnull;
    PRBool success = PR_FALSE;

    nsCOMPtr<nsILocalFile> aFile;
    if(!aMgr->GetCloneOfManifestLocation(getter_AddRefs(aFile)) || !aFile)
        return nsnull;

    if(NS_FAILED(aFile->GetFileSize(&fileSize)) || !(flen = nsInt64(fileSize)))
        return nsnull;

    whole = new char[flen];
    if (!whole)
        return nsnull;

    // All exits from on here should be via 'goto out' 

    if(NS_FAILED(aFile->OpenNSPRFileDesc(PR_RDONLY, 0444, &fd)) || !fd)
        goto out;

    if(flen > PR_Read(fd, whole, flen))
        goto out;

    success = PR_TRUE;

 out:
    if(fd)
        PR_Close(fd);

    if(!success)     
    {
        delete [] whole;
        return nsnull;
    }

    *pLength = flen;
    return whole;    
}

static
PRBool ReadSectionHeader(nsManifestLineReader& reader, 
                         const char *token, int minCount, int* count)
{
    while(1)
    {
        if(!reader.NextLine())
            break;
        if(*reader.LinePtr() == '[')
        {
            char* p = reader.LinePtr() + (reader.LineLength() - 1);
            if(*p != ']')
                break;
            *p = 0;

            char* values[2];
            int lengths[2];
            if(2 != reader.ParseLine(values, lengths, 2))
                break;

            // ignore the leading '['
            if(0 != PL_strcmp(values[0]+1, token))
                break;

            if((*count = atoi(values[1])) < minCount)
                break;
            
            return PR_TRUE;
        }
    }
    return PR_FALSE;
}


// static
PRBool xptiManifest::Read(xptiInterfaceInfoManager* aMgr,
                          xptiWorkingSet*           aWorkingSet)
{
    int i;
    char* whole = nsnull;
    PRBool succeeded = PR_FALSE;
    PRUint32 flen;
    nsManifestLineReader reader;
    xptiHashEntry* hashEntry;
    int headerCount = 0;
    int dirCount = 0;
    int fileCount = 0;
    int zipItemCount = -1;
    int interfaceCount = 0;
    int dir;
    int flags;
    char* values[6];    // 6 is currently the max items we need to parse
    int lengths[6];
    PRUint32 size32;
    PRInt64 size;
    PRInt64 date;

    whole = ReadManifestIntoMemory(aMgr, &flen);
    if(!whole)
        return PR_FALSE;

    reader.Init(whole, flen);

    // All exits from here on should be via 'goto out' 
    
    // Look for "Header" section

    // This version accepts only version 1,0. We also freak if the header
    // has more than one entry. The rationale is that we want to force an
    // autoreg if the xpti.dat file was written by *any* other version of
    // the software. Future versions may wish to support updating older
    // manifests in some interesting way.

    if(!ReadSectionHeader(reader, g_TOKEN_Header, 2, &headerCount))
        goto out;

    if(headerCount != 2)
        goto out;

    // Verify the version number

    if(!reader.NextLine())
        goto out;

    // index,VersionLiteral,major,minor
    if(4 != reader.ParseLine(values, lengths, 4))
        goto out;

    // index
    if(0 != atoi(values[0]))
        goto out;

    // VersionLiteral
    if(0 != PL_strcmp(values[1], g_TOKEN_Version))
        goto out;

    // major
    if(g_VERSION_MAJOR != atoi(values[2]))
        goto out;

    // minor
    if(g_VERSION_MINOR != atoi(values[3]))
        goto out;

    // Verify the application directory

    if(!reader.NextLine())
        goto out;

    // index,AppDirLiteral,directoryname
    if(3 != reader.ParseLine(values, lengths, 3))
        goto out;

    // index
    if(1 != atoi(values[0]))
        goto out;

    // AppDirLiteral
    if(0 != PL_strcmp(values[1], g_TOKEN_AppDir))
        goto out;

    if(!CurrentAppDirMatchesPersistentDescriptor(aMgr, values[2]))
        goto out;

    // Look for "Directories" section

    if(!ReadSectionHeader(reader, g_TOKEN_Directories, 1, &dirCount))
        goto out;
    else
    {
        // To validate that the directory list matches the current search path
        // we first confirm that the list lengths match.

        nsCOMPtr<nsISupportsArray> searchPath;
        aMgr->GetSearchPath(getter_AddRefs(searchPath));

        PRUint32 searchPathCount;
        searchPath->Count(&searchPathCount);
        
        if(dirCount != (int) searchPathCount)
            goto out;
    }

    // Read the directory records

    for(i = 0; i < dirCount; ++i)
    {
        if(!reader.NextLine())
            goto out;
       
        // index,directoryname
        if(2 != reader.ParseLine(values, lengths, 2))
            goto out;

        // index
        if(i != atoi(values[0]))
            goto out;

        // directoryname
        if(!aWorkingSet->DirectoryAtMatchesPersistentDescriptor(i, values[1]))
            goto out;    
    }

    // Look for "Files" section

    if(!ReadSectionHeader(reader, g_TOKEN_Files, 1, &fileCount))
        goto out;


    // Alloc room in the WorkingSet for the filearray.

    if(!aWorkingSet->NewFileArray(fileCount))   
        goto out;    

    // Read the file records

    for(i = 0; i < fileCount; ++i)
    {
        if(!reader.NextLine())
            goto out;

        // index,filename,dirIndex,dilesSize,filesDate
        if(5 != reader.ParseLine(values, lengths, 5))
            goto out;

        // index
        if(i != atoi(values[0]))
            goto out;

        // filename
        if(!*values[1])
            goto out;

        // dirIndex
        dir = atoi(values[2]);
        if(dir < 0 || dir > dirCount)
            goto out;

        // fileSize
        size32 = atoi(values[3]);
        if(size32 <= 0)
            goto out;
        LL_UI2L(size, size32);

        // fileDate
        date = nsCRT::atoll(values[4]);
        if(LL_IS_ZERO(date))
            goto out;
        
        // Append a new file record to the array.

        aWorkingSet->AppendFile(
            xptiFile(nsInt64(size), nsInt64(date), dir, values[1], aWorkingSet));
    }

    // Look for "ZipItems" section

    if(!ReadSectionHeader(reader, g_TOKEN_ArchiveItems, 0, &zipItemCount))
        goto out;

    // Alloc room in the WorkingSet for the zipItemarray.

    if(zipItemCount)
        if(!aWorkingSet->NewZipItemArray(zipItemCount))   
            goto out;    

    // Read the zipItem records

    for(i = 0; i < zipItemCount; ++i)
    {
        if(!reader.NextLine())
            goto out;

        // index,filename
        if(2 != reader.ParseLine(values, lengths, 2))
            goto out;

        // index
        if(i != atoi(values[0]))
            goto out;

        // filename
        if(!*values[1])
            goto out;
        
        // Append a new zipItem record to the array.

        aWorkingSet->AppendZipItem(xptiZipItem(values[1], aWorkingSet));
    }

    // Look for "Interfaces" section

    if(!ReadSectionHeader(reader, g_TOKEN_Interfaces, 1, &interfaceCount))
        goto out;

    // Read the interface records

    for(i = 0; i < interfaceCount; ++i)
    {
        int fileIndex;
        int zipItemIndex;
        nsIID iid;
        xptiInterfaceEntry* entry;
        xptiTypelib typelibRecord;

        if(!reader.NextLine())
            goto out;

        // index,interfaceName,iid,fileIndex,zipIndex,flags
        if(6 != reader.ParseLine(values, lengths, 6))
            goto out;

        // index
        if(i != atoi(values[0]))
            goto out;

        // interfaceName
        if(!*values[1])
            goto out;

        // iid
        if(!iid.Parse(values[2]))
            goto out;

        // fileIndex
        fileIndex = atoi(values[3]);
        if(fileIndex < 0 || fileIndex >= fileCount)
            goto out;

        // zipIndex (NOTE: -1 is a valid value)
        zipItemIndex = atoi(values[4]);
        if(zipItemIndex < -1 || zipItemIndex >= zipItemCount)
            goto out;

        // flags
        flags = atoi(values[5]);
        if(flags != 0 && flags != 1)
            goto out;
        
        // Build an InterfaceInfo and hook it in.

        if(zipItemIndex == -1)
            typelibRecord.Init(fileIndex);
        else
            typelibRecord.Init(fileIndex, zipItemIndex);
        
        entry = xptiInterfaceEntry::NewEntry(values[1], lengths[1],
                                             iid, typelibRecord, 
                                             aWorkingSet);
        if(!entry)
            goto out;    
        
        entry->SetScriptableFlag(flags==1);

        // Add our entry to the iid hashtable.

        hashEntry = (xptiHashEntry*)
            PL_DHashTableOperate(aWorkingSet->mNameTable, 
                                 entry->GetTheName(), PL_DHASH_ADD);
        if(hashEntry)
            hashEntry->value = entry;
    
        // Add our entry to the name hashtable.

        hashEntry = (xptiHashEntry*)
            PL_DHashTableOperate(aWorkingSet->mIIDTable, 
                                 entry->GetTheIID(), PL_DHASH_ADD);
        if(hashEntry)
            hashEntry->value = entry;
    }

    // success!

    succeeded = PR_TRUE;

 out:
    if(whole)
        delete [] whole;

    if(!succeeded)
    {
        // Cleanup the WorkingSet on failure.
        aWorkingSet->InvalidateInterfaceInfos();
        aWorkingSet->ClearHashTables();
        aWorkingSet->ClearFiles();
    }
    return succeeded;
}        

// static 
PRBool xptiManifest::Delete(xptiInterfaceInfoManager* aMgr)
{
    nsCOMPtr<nsILocalFile> aFile;
    if(!aMgr->GetCloneOfManifestLocation(getter_AddRefs(aFile)) || !aFile)
        return PR_FALSE;

    PRBool exists;
    if(NS_FAILED(aFile->Exists(&exists)))
        return PR_FALSE;

    if(exists && NS_FAILED(aFile->Remove(PR_FALSE)))
        return PR_FALSE;
    
    return PR_TRUE;
}

