/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape 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/NPL/
 *
 * 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 Netscape are
 * Copyright (C) 1999 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 */
#include "nsIServiceManager.h"
#ifndef XPCOM_STANDALONE
#include "nsIPlatformCharset.h"
#include "nsICharsetConverterManager.h"
#include "nsIUnicodeEncoder.h"
#include "nsIUnicodeDecoder.h"
#include "nsIUnicodeEncoder.h"
#include "nsIUnicodeDecoder.h"
#include "nsIURLParser.h"
#endif /* XPCOM_STANDALONE */

#include "nsFileSpec.h"  // evil ftang hack

#include "nsLocalFile.h" // includes platform-specific headers

#include "nsString.h"
#include "nsCOMPtr.h"
#include "nsXPIDLString.h"

#ifndef XPCOM_STANDALONE
static NS_DEFINE_CID(kStdURLParserCID, NS_STANDARDURLPARSER_CID);
#endif


class nsFSStringConversion {
public:
     static void CleanUp();
     static nsresult UCSToNewFS( const PRUnichar* aIn, char** aOut);  
     static nsresult FSToNewUCS( const char* aIn, PRUnichar** aOut);  

private:
     static nsresult PrepareFSCharset();
     static nsresult PrepareEncoder();
     static nsresult PrepareDecoder();
     static nsString* mFSCharset;
#ifndef XPCOM_STANDALONE
     static nsIUnicodeEncoder* mEncoder;
     static nsIUnicodeDecoder* mDecoder;
#endif /* XPCOM_STANDALONE */
};

nsString* nsFSStringConversion::mFSCharset = nsnull;
#ifndef XPCOM_STANDALONE
nsIUnicodeEncoder* nsFSStringConversion::mEncoder = nsnull;
nsIUnicodeDecoder* nsFSStringConversion::mDecoder = nsnull;
#endif /* XPCOM_STANDALONE */

#define GET_UCS( func , arg)                                    \
{                                                               \
   char* tmp;                                                   \
   nsresult res;                                                \
   if(NS_SUCCEEDED(res = (func)(&tmp))) {                       \
     if(NS_SUCCEEDED(res = nsFSStringConversion::FSToNewUCS(tmp, (arg)))){ \
       nsMemory::Free(tmp);                                     \
     }                                                          \
   }                                                            \
   return res;                                                  \
}
#define VOID_SET_UCS( func , arg, assertion_msg)                \
 {                                                              \
   char* tmp;                                                   \
   nsresult res;                                                \
   if(NS_SUCCEEDED(res = nsFSStringConversion::UCSToNewFS((arg), &tmp))) { \
     (func)(tmp);                                               \
     nsMemory::Free(tmp);                                       \
   }                                                            \
   NS_ASSERTION(NS_SUCCEEDED(res), assertion_msg);              \
}
#define SET_UCS( func , arg)                                    \
 {                                                              \
   char* tmp;                                                   \
   nsresult res;                                                \
   if(NS_SUCCEEDED(res = nsFSStringConversion::UCSToNewFS((arg), &tmp))) { \
     res = (func)(tmp);                                         \
     nsMemory::Free(tmp);                                       \
   }                                                            \
   return res;                                                  \
}
#define SET_UCS_2ARGS_2( func , arg1, arg2)                     \
 {                                                              \
   char* tmp;                                                   \
   nsresult res;                                                \
   if(NS_SUCCEEDED(res = nsFSStringConversion::UCSToNewFS((arg2), &tmp))){ \
     res = (func)(arg1, tmp);                                   \
     nsMemory::Free(tmp);                                       \
   }                                                            \
   return res;                                                  \
}
#define SET_UCS_2ARGS_1( func , path, followLinks, result)      \
 {                                                              \
   char* tmp;                                                   \
   nsresult res;                                                \
   if(NS_SUCCEEDED(res = nsFSStringConversion::UCSToNewFS((path), &tmp))){ \
     res = (func)(tmp, followLinks, result);                    \
     nsMemory::Free(tmp);                                       \
   }                                                            \
   return res;                                                  \
}

void
nsFSStringConversion::CleanUp()
{
#ifndef XPCOM_STANDALONE
  NS_IF_RELEASE(mEncoder);
  NS_IF_RELEASE(mDecoder);
#endif /* XPCOM_STANDALONE */
}

/* static */ nsresult
nsFSStringConversion::PrepareFSCharset()
{
   nsresult res = NS_ERROR_NOT_IMPLEMENTED;
#ifndef XPCOM_STANDALONE
   res = NS_OK;
   if(!mFSCharset)
   { 
     // lazy eval of the file system charset
     NS_WITH_SERVICE(nsIPlatformCharset, pcharset, NS_PLATFORMCHARSET_CONTRACTID, &res);
     if (NS_SUCCEEDED(res) && !pcharset) res = NS_ERROR_NULL_POINTER;
     if (NS_SUCCEEDED(res)) {
        mFSCharset = new nsString();
        if (!mFSCharset)
          res = NS_ERROR_OUT_OF_MEMORY;
     }
     if(NS_SUCCEEDED(res)) {
        res = pcharset->GetCharset(kPlatformCharsetSel_FileName, *mFSCharset);
     } 
     if (NS_FAILED(res)) {
       NS_WARNING("cannot get platform charset");
     }
   }
#endif /* XPCOM_STANDALONE */
   return res;
}
/* static */ nsresult
nsFSStringConversion::PrepareEncoder()
{
   nsresult res = NS_ERROR_NOT_IMPLEMENTED;
#ifndef XPCOM_STANDALONE
   res = NS_OK;
   if(! mEncoder)
   {
       res = PrepareFSCharset();
       if(NS_SUCCEEDED(res)) {
           NS_WITH_SERVICE(nsICharsetConverterManager,
                ucmgr, NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res);
           NS_ASSERTION((NS_SUCCEEDED(res) && ucmgr), 
                   "cannot get charset converter manager ");
           if(NS_SUCCEEDED(res) && ucmgr) 
               res = ucmgr->GetUnicodeEncoder( mFSCharset, &mEncoder);
           NS_ASSERTION((NS_SUCCEEDED(res) && mEncoder), 
                   "cannot find the unicode encoder");
       }
   }
#endif /* XPCOM_STANDALONE */
   return res;
}
/* static */ nsresult
nsFSStringConversion::PrepareDecoder()
{
   nsresult res = NS_ERROR_NOT_IMPLEMENTED;
#ifndef XPCOM_STANDALONE
   res = NS_OK;
   if(! mDecoder)
   {
       res = PrepareFSCharset();
       if(NS_SUCCEEDED(res)) {
           NS_WITH_SERVICE(nsICharsetConverterManager,
                ucmgr, NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res);
           NS_ASSERTION((NS_SUCCEEDED(res) && ucmgr), 
                   "cannot get charset converter manager ");
           if(NS_SUCCEEDED(res) && ucmgr) 
               res = ucmgr->GetUnicodeDecoder( mFSCharset, &mDecoder);
           NS_ASSERTION((NS_SUCCEEDED(res) && mDecoder), 
                   "cannot find the unicode decoder");
       }
   }
#endif /* XPCOM_STANDALONE */
   return res;
}
/* static */ nsresult
nsFSStringConversion::UCSToNewFS( const PRUnichar* aIn, char** aOut)
{
   nsresult res = NS_ERROR_NOT_IMPLEMENTED;
#ifndef XPCOM_STANDALONE
   res = PrepareEncoder();
   if(NS_SUCCEEDED(res)) 
   {
        PRInt32 inLength = nsCRT::strlen(aIn);
        PRInt32 outLength;
        res= mEncoder->GetMaxLength(aIn, inLength,&outLength);
        if(NS_SUCCEEDED(res)) {
           *aOut = (char*)nsMemory::Alloc(outLength+1);
           if(nsnull != *aOut) {
              res = mEncoder->Convert(aIn, &inLength, *aOut,  &outLength);
              if(NS_SUCCEEDED(res)) {
                 (*aOut)[outLength] = '\0';
              } else {
                 nsMemory::Free(*aOut);
                 *aOut = nsnull;
              }
           } else {
              res = NS_ERROR_OUT_OF_MEMORY;
           }
        }
   }
#endif /* XPCOM_STANDALONE */
   return res;
}
/* static */ nsresult
nsFSStringConversion::FSToNewUCS( const char* aIn, PRUnichar** aOut)
{
   nsresult res = NS_ERROR_NOT_IMPLEMENTED;
#ifndef XPCOM_STANDALONE
   res = PrepareDecoder();
   if(NS_SUCCEEDED(res)) 
   {
        PRInt32 inLength = nsCRT::strlen(aIn);
        PRInt32 outLength;
        res= mDecoder->GetMaxLength(aIn, inLength,&outLength);
        if(NS_SUCCEEDED(res)) {
           *aOut = (PRUnichar*)nsMemory::Alloc(2*(outLength+1));
           if(nsnull != *aOut) {
              res = mDecoder->Convert(aIn, &inLength, *aOut,  &outLength);
              if(NS_SUCCEEDED(res)) {
                 (*aOut)[outLength] = '\0';
              } else {
                 nsMemory::Free(*aOut);
                 *aOut = nsnull;
              }
           } else {
              res = NS_ERROR_OUT_OF_MEMORY;
           }
        }
   }
#endif /* XPCOM_STANDALONE */
   return res;
}

void NS_StartupLocalFile()
{
#ifdef XP_WIN
  CoInitialize(NULL);  // FIX: we should probably move somewhere higher up during startup
#endif
}

void NS_ShutdownLocalFile()
{
#ifndef XPCOM_STANDALONE
  nsFSStringConversion::CleanUp();
#endif /* XPCOM_STANDALONE */

#ifdef XP_WIN
  CoUninitialize();
#endif

}

// Unicode interface Wrapper
NS_IMETHODIMP  
nsLocalFile::InitWithUnicodePath(const PRUnichar *filePath)
{
   SET_UCS(InitWithPath, filePath);
}
NS_IMETHODIMP  
nsLocalFile::AppendUnicode(const PRUnichar *node)
{
   SET_UCS( Append , node);
}
NS_IMETHODIMP  
nsLocalFile::AppendRelativeUnicodePath(const PRUnichar *node)
{
   SET_UCS( AppendRelativePath , node);
}
NS_IMETHODIMP  
nsLocalFile::GetUnicodeLeafName(PRUnichar **aLeafName)
{
   GET_UCS(GetLeafName, aLeafName);
}
NS_IMETHODIMP  
nsLocalFile::SetUnicodeLeafName(const PRUnichar * aLeafName)
{
   SET_UCS( SetLeafName , aLeafName);
}
NS_IMETHODIMP  
nsLocalFile::GetUnicodePath(PRUnichar **_retval)
{
   GET_UCS(GetPath, _retval);
}
NS_IMETHODIMP  
nsLocalFile::CopyToUnicode(nsIFile *newParentDir, const PRUnichar *newName)
{
   SET_UCS_2ARGS_2( CopyTo , newParentDir, newName);
}
NS_IMETHODIMP  
nsLocalFile::CopyToFollowingLinksUnicode(nsIFile *newParentDir, const PRUnichar *newName)
{
   SET_UCS_2ARGS_2( CopyToFollowingLinks , newParentDir, newName);
}
NS_IMETHODIMP  
nsLocalFile::MoveToUnicode(nsIFile *newParentDir, const PRUnichar *newName)
{
   SET_UCS_2ARGS_2( MoveTo , newParentDir, newName);
}
NS_IMETHODIMP
nsLocalFile::GetUnicodeTarget(PRUnichar **_retval)
{   
   GET_UCS(GetTarget, _retval);
}
nsresult 
NS_NewUnicodeLocalFile(const PRUnichar* path, PRBool followLinks, nsILocalFile* *result)
{
   SET_UCS_2ARGS_1( NS_NewLocalFile, path, followLinks, result)
}
// should work on Macintosh, Unix, and Win32.
#define kMaxFilenameLength 31  


NS_IMETHODIMP
nsLocalFile::CreateUnique(const char* suggestedName, PRUint32 type, PRUint32 attributes)
{
    nsresult rv = Create(type, attributes);
    
    if (NS_SUCCEEDED(rv)) return NS_OK;
    if (rv != NS_ERROR_FILE_ALREADY_EXISTS) return rv;

    char* leafName; 
    rv = GetLeafName(&leafName);

    if (NS_FAILED(rv)) return rv;

    char* lastDot = strrchr(leafName, '.');
    char suffix[kMaxFilenameLength + 1] = "";
    if (lastDot)
    {
        strncpy(suffix, lastDot, kMaxFilenameLength); // include '.'
        suffix[kMaxFilenameLength] = 0; // make sure it's null terminated
        *lastDot = '\0'; // strip suffix and dot.
    }

    // 27 should work on Macintosh, Unix, and Win32. 
    const int maxRootLength = 27 - nsCRT::strlen(suffix) - 1;

    if ((int)nsCRT::strlen(leafName) > (int)maxRootLength)
        leafName[maxRootLength] = '\0';

    for (short indx = 1; indx < 10000; indx++)
    {
        // start with "Picture-1.jpg" after "Picture.jpg" exists
        char newName[kMaxFilenameLength + 1];
        sprintf(newName, "%s-%d%s", leafName, indx, suffix);
        SetLeafName(newName);

        rv = Create(type, attributes);
    
        if (NS_SUCCEEDED(rv) || rv != NS_ERROR_FILE_ALREADY_EXISTS) 
        {
            nsMemory::Free(leafName);
            return rv;
        }
    }
 
    nsMemory::Free(leafName);
    // The disk is full, sort of
    return NS_ERROR_FILE_TOO_BIG;
}

nsresult nsLocalFile::ParseURL(const char* inURL, char **outHost, char **outDirectory,
                               char **outFileBaseName, char **outFileExtension)
{
    nsresult rv = NS_ERROR_NOT_IMPLEMENTED;

#ifndef XPCOM_STANDALONE
    NS_ENSURE_ARG(inURL);
    NS_ENSURE_ARG_POINTER(outHost);
    *outHost = nsnull;
    NS_ENSURE_ARG_POINTER(outDirectory);
    *outDirectory = nsnull;
    NS_ENSURE_ARG_POINTER(outFileBaseName);
    *outFileBaseName = nsnull;
    NS_ENSURE_ARG_POINTER(outFileExtension);
    *outFileExtension = nsnull;
    
    rv = NS_OK;    
    char* eSpec = nsnull;
    eSpec = nsCRT::strdup(inURL);
    if (!eSpec)
        return NS_ERROR_OUT_OF_MEMORY;

    // Skip leading spaces and control-characters
    char* fwdPtr= (char*) eSpec;
    while (fwdPtr && (*fwdPtr > '\0') && (*fwdPtr <= ' '))
        fwdPtr++;
    // Remove trailing spaces and control-characters
    if (fwdPtr) {
        char* bckPtr= (char*)fwdPtr + PL_strlen(fwdPtr) -1;
        if (*bckPtr > '\0' && *bckPtr <= ' ') {
            while ((bckPtr-fwdPtr) >= 0 && (*bckPtr <= ' ')) {
                bckPtr--;
            }
            *(bckPtr+1) = '\0';
        }
    }

    NS_WITH_SERVICE(nsIURLParser, parser, kStdURLParserCID, &rv);
    if (NS_FAILED(rv)) return rv;

    nsXPIDLCString ePath;
    nsXPIDLCString scheme, username, password, host;
    PRInt32 mPort;
    
    // Parse the spec
    rv = parser->ParseAtScheme(eSpec, getter_Copies(scheme), getter_Copies(username), 
                               getter_Copies(password), outHost, &mPort,
                               getter_Copies(ePath));

    // if this isn't a file: URL, then we can't deal                                            
    if (NS_FAILED(rv) || nsCRT::strcasecmp(scheme, "file") != 0) {
        CRTFREEIF(*outHost);
        return NS_ERROR_FAILURE;
    }
    
    nsXPIDLCString param, query, ref;

    // Now parse the path
    rv = parser->ParseAtDirectory(ePath, outDirectory, outFileBaseName, outFileExtension,
                                  getter_Copies(param), getter_Copies(query), getter_Copies(ref));
    if (NS_FAILED(rv)) {
        CRTFREEIF(*outDirectory);
        CRTFREEIF(*outFileBaseName);
        CRTFREEIF(*outFileExtension);
        return rv;
    }

    // If any of the components are non-NULL but empty, free them
    if (*outHost && !nsCRT::strlen(*outHost))
        CRTFREEIF(*outHost);  
    if (*outDirectory && !nsCRT::strlen(*outDirectory))
        CRTFREEIF(*outDirectory);  
    if (*outFileBaseName && !nsCRT::strlen(*outFileBaseName))
        CRTFREEIF(*outFileBaseName);  
    if (*outFileExtension && !nsCRT::strlen(*outFileExtension))
        CRTFREEIF(*outFileExtension);  
#endif /* XPCOM_STANDALONE */
                                      
    return NS_OK;
}
 

// E_V_I_L Below! E_V_I_L Below! E_V_I_L Below! E_V_I_L Below!


// FTANG need to move this crap out of here.  Mixing nsFileSpec here is
// E_V_I_L

// ==================================================================
//  nsFileSpec stuff . put here untill nsFileSpec get obsoleted     
// ==================================================================

void nsFileSpec::operator = (const nsString& inNativePath)
{
   char* tmp;                                                   
   nsresult res;                                                
   if(NS_SUCCEEDED(res = nsFSStringConversion::UCSToNewFS((inNativePath.GetUnicode()), &tmp))) { 
     *this = tmp;
     nsMemory::Free(tmp);                                    
   }                                                            
   mError = res;
   NS_ASSERTION(NS_SUCCEEDED(res), "nsFileSpec = filed");
}
nsFileSpec nsFileSpec::operator + (const nsString& inRelativeUnixPath) const
{
   nsFileSpec resultSpec;
   char* tmp;                                                   
   nsresult res;                                                
   if(NS_SUCCEEDED(res = nsFSStringConversion::UCSToNewFS((inRelativeUnixPath.GetUnicode()), &tmp))) { 
     resultSpec = *this;
     resultSpec += tmp;
     nsMemory::Free(tmp);                                    
   }                                                            
   NS_ASSERTION(NS_SUCCEEDED(res), "nsFileSpec + filed");
   return resultSpec;
}
void nsFileSpec::operator += (const nsString& inRelativeUnixPath)
{
   char* tmp;                                                   
   nsresult res;                                                
   if(NS_SUCCEEDED(res = nsFSStringConversion::UCSToNewFS((inRelativeUnixPath.GetUnicode()), &tmp))) { 
     *this += tmp;
     nsMemory::Free(tmp);                                    
   }                                                            
   mError = res;
   NS_ASSERTION(NS_SUCCEEDED(res), "nsFileSpec + filed");
}

void nsFileSpec::SetLeafName (const nsString& inLeafName)
{
  VOID_SET_UCS( SetLeafName , inLeafName.GetUnicode(),"nsFileSpec::SetLeafName failed");
}
void nsFileSpec::MakeUnique(const nsString& inSuggestedLeafName)
{
  VOID_SET_UCS( MakeUnique,inSuggestedLeafName.GetUnicode(),"nsFileSpec::MakeUnique failed");
}
nsresult nsFileSpec::Rename(const nsString& inNewName)
{
  SET_UCS( Rename , inNewName.GetUnicode());
}
nsresult nsFileSpec::Execute(const nsString& args) const
{
  SET_UCS( Execute , args.GetUnicode());
}
