/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * 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 the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either 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 NPL, 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 NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

//
// Mike Pinkerton
// Netscape Communications
//
// See header file for details.
//
// Remaining Work:
// * only convert data to clipboard on an app switch.
//

#include "nsCOMPtr.h"
#include "nsClipboard.h"

#include "nsVoidArray.h"
#include "nsIClipboardOwner.h"
#include "nsString.h"
#include "nsIFormatConverter.h"
#include "nsMimeMapper.h"

#include "nsIComponentManager.h"
#include "nsISupportsPrimitives.h"
#include "nsXPIDLString.h"
#include "nsPrimitiveHelpers.h"
#include "nsIImageMac.h"
#include "nsMemory.h"

#include <Scrap.h>



//
// nsClipboard constructor
//
nsClipboard::nsClipboard() : nsBaseClipboard()
{
}

//
// nsClipboard destructor
//
nsClipboard::~nsClipboard()
{
}


//
// SetNativeClipboardData
//
// Take data off the transferrable and put it on the clipboard in as many formats
// as are registered.
//
// NOTE: This code could all live in ForceDataToClipboard() and this could be a NOOP. 
// If speed and large data sizes are an issue, we should move that code there and only
// do it on an app switch.
//
NS_IMETHODIMP
nsClipboard :: SetNativeClipboardData ( PRInt32 aWhichClipboard )
{
  if ( aWhichClipboard != kGlobalClipboard )
    return NS_ERROR_FAILURE;

  nsresult errCode = NS_OK;
  
  mIgnoreEmptyNotification = PR_TRUE;

  // make sure we have a good transferable
  if ( !mTransferable )
    return NS_ERROR_INVALID_ARG;
  
  nsMimeMapperMac theMapper;

#if TARGET_CARBON
  ::ClearCurrentScrap();
#else
  ::ZeroScrap();
#endif

  // get flavor list that includes all flavors that can be written (including ones 
  // obtained through conversion)
  nsCOMPtr<nsISupportsArray> flavorList;
  errCode = mTransferable->FlavorsTransferableCanExport ( getter_AddRefs(flavorList) );
  if ( NS_FAILED(errCode) )
    return NS_ERROR_FAILURE;

  // For each flavor present (either directly in the transferable or that its
  // converter knows about) put it on the clipboard. Luckily, GetTransferData() 
  // handles conversions for us, so we really don't need to know if a conversion
  // is required or not.
  PRUint32 cnt;
  flavorList->Count(&cnt);
  for ( PRUint32 i = 0; i < cnt; ++i ) {
    nsCOMPtr<nsISupports> genericFlavor;
    flavorList->GetElementAt ( i, getter_AddRefs(genericFlavor) );
    nsCOMPtr<nsISupportsString> currentFlavor ( do_QueryInterface(genericFlavor) );
    if ( currentFlavor ) {
      nsXPIDLCString flavorStr;
      currentFlavor->ToString( getter_Copies(flavorStr) );
      
      // find MacOS flavor
      ResType macOSFlavor = theMapper.MapMimeTypeToMacOSType(flavorStr);
    
      // get data. This takes converters into account. Different things happen for
      // different flavors, so do some special processing.
      void* data = nsnull;
      PRUint32 dataSize = 0;
      if ( strcmp(flavorStr,kUnicodeMime) == 0 ) {
        // we have unicode, put it on first as unicode
        nsCOMPtr<nsISupports> genericDataWrapper;
        errCode = mTransferable->GetTransferData ( flavorStr, getter_AddRefs(genericDataWrapper), &dataSize );
        nsPrimitiveHelpers::CreateDataFromPrimitive ( flavorStr, genericDataWrapper, &data, dataSize );
        errCode = PutOnClipboard ( macOSFlavor, data, dataSize );
        if ( NS_SUCCEEDED(errCode) ) {
          // we also need to put it on as 'TEXT' after doing the conversion to the platform charset.
          char* plainTextData = nsnull;
          PRUnichar* castedUnicode = NS_REINTERPRET_CAST(PRUnichar*, data);
          PRInt32 plainTextLen = 0;
          nsPrimitiveHelpers::ConvertUnicodeToPlatformPlainText ( castedUnicode, dataSize / 2, &plainTextData, &plainTextLen );
          if ( plainTextData ) {
            errCode = PutOnClipboard ( 'TEXT', plainTextData, plainTextLen );
            nsMemory::Free ( plainTextData ); 
          }
        }
      } // if unicode
      else if ( strcmp(flavorStr, kPNGImageMime) == 0 || strcmp(flavorStr, kJPEGImageMime) == 0 ||
                  strcmp(flavorStr, kGIFImageMime) == 0 ) {
        // we have an image, which is in the transferable as an nsIImage. Convert it
        // to PICT (PicHandle) and put those bits on the clipboard. The actual size
        // of the picture is the size of the handle, not sizeof(Picture).
        nsCOMPtr<nsISupports> imageSupports;
        errCode = mTransferable->GetTransferData ( flavorStr, getter_AddRefs(imageSupports), &dataSize );
        nsCOMPtr<nsIImageMac> image ( do_QueryInterface(imageSupports) );
        if ( image ) {
          PicHandle picture = nsnull;
          image->ConvertToPICT ( &picture );
          if ( picture ) {
            errCode = PutOnClipboard ( 'PICT', *picture, ::GetHandleSize((Handle)picture) );
            ::KillPicture ( picture );
          }
        }
        else
          NS_WARNING ( "Image isn't an nsIImageMac in transferable" );
      }
      else {
        // we don't know what we have. let's just assume it's unicode but doesn't need to be
        // translated to TEXT.
        nsCOMPtr<nsISupports> genericDataWrapper;
        errCode = mTransferable->GetTransferData ( flavorStr, getter_AddRefs(genericDataWrapper), &dataSize );
        nsPrimitiveHelpers::CreateDataFromPrimitive ( flavorStr, genericDataWrapper, &data, dataSize );
        errCode = PutOnClipboard ( macOSFlavor, data, dataSize );
      }
              
      nsMemory::Free ( data );
    }
  } // foreach flavor in transferable

  // write out the mapping data in a special flavor on the clipboard. |mappingLen|
  // includes the NULL terminator.
  short mappingLen = 0;
  const char* mapping = theMapper.ExportMapping(&mappingLen);
  if ( mapping && mappingLen ) {
    errCode = PutOnClipboard ( nsMimeMapperMac::MappingFlavor(), mapping, mappingLen );
    nsCRT::free ( NS_CONST_CAST(char*, mapping) );
  }
  
  return errCode;
  
} // SetNativeClipboardData


//
// PutOnClipboard
//
// Actually does the work of placing the data on the native clipboard 
// in the given flavor
//
nsresult
nsClipboard :: PutOnClipboard ( ResType inFlavor, const void* inData, PRInt32 inLen )
{
  nsresult errCode = NS_OK;
  
#if TARGET_CARBON
  ScrapRef scrap;
  ::GetCurrentScrap(&scrap);
  ::PutScrapFlavor( scrap, inFlavor, kScrapFlavorMaskNone, inLen, inData );
#else
  long numBytes = ::PutScrap ( inLen, inFlavor, inData );
  if ( numBytes != noErr )
    errCode = NS_ERROR_FAILURE;
#endif

  return errCode;
  
} // PutOnClipboard


//
// GetNativeClipboardData
//
// Take data off the native clip and put it on the transferable.
//
NS_IMETHODIMP
nsClipboard :: GetNativeClipboardData ( nsITransferable * aTransferable, PRInt32 aWhichClipboard )
{
  if ( aWhichClipboard != kGlobalClipboard )
    return NS_ERROR_FAILURE;

  nsresult errCode = NS_OK;

  // make sure we have a good transferable
  if ( !aTransferable )
    return NS_ERROR_INVALID_ARG;

  // get flavor list that includes all acceptable flavors (including ones obtained through
  // conversion)
  nsCOMPtr<nsISupportsArray> flavorList;
  errCode = aTransferable->FlavorsTransferableCanImport ( getter_AddRefs(flavorList) );
  if ( NS_FAILED(errCode) )
    return NS_ERROR_FAILURE;

  // create a mime mapper. It's ok for this to fail because the data may come from
  // another app which obviously wouldn't put our mime mapping data on the clipboard.
  char* mimeMapperData = nsnull;
  errCode = GetDataOffClipboard ( nsMimeMapperMac::MappingFlavor(), (void**)&mimeMapperData, 0 );
  nsMimeMapperMac theMapper ( mimeMapperData );
  if (mimeMapperData)
    nsCRT::free ( mimeMapperData );
 
  // Now walk down the list of flavors. When we find one that is actually on the
  // clipboard, copy out the data into the transferable in that format. SetTransferData()
  // implicitly handles conversions.
  PRBool dataFound = PR_FALSE;
  PRUint32 cnt;
  flavorList->Count(&cnt);
  for ( PRUint32 i = 0; i < cnt; ++i ) {
    nsCOMPtr<nsISupports> genericFlavor;
    flavorList->GetElementAt ( i, getter_AddRefs(genericFlavor) );
    nsCOMPtr<nsISupportsString> currentFlavor ( do_QueryInterface(genericFlavor) );
    if ( currentFlavor ) {
      nsXPIDLCString flavorStr;
      currentFlavor->ToString ( getter_Copies(flavorStr) );
      
      // find MacOS flavor (don't add if not present)
      ResType macOSFlavor = theMapper.MapMimeTypeToMacOSType(flavorStr, PR_FALSE);
    
      void* clipboardData = nsnull;
      PRInt32 dataSize = 0L;
      nsresult loadResult = GetDataOffClipboard ( macOSFlavor, &clipboardData, &dataSize );
      if ( NS_SUCCEEDED(loadResult) && clipboardData )
        dataFound = PR_TRUE;
      else {
        // if we are looking for text/unicode and we fail to find it on the clipboard first,
        // try again with text/plain. If that is present, convert it to unicode.
        if ( strcmp(flavorStr, kUnicodeMime) == 0 ) {
          loadResult = GetDataOffClipboard ( 'TEXT', &clipboardData, &dataSize );
          if ( NS_SUCCEEDED(loadResult) && clipboardData ) {
            const char* castedText = NS_REINTERPRET_CAST(char*, clipboardData);          
            PRUnichar* convertedText = nsnull;
            PRInt32 convertedTextLen = 0;
            nsPrimitiveHelpers::ConvertPlatformPlainTextToUnicode ( castedText, dataSize, 
                                                                      &convertedText, &convertedTextLen );
            if ( convertedText ) {
              // out with the old, in with the new 
              nsMemory::Free(clipboardData);
              clipboardData = convertedText;
              dataSize = convertedTextLen * 2;
              dataFound = PR_TRUE;
            }
          } // if plain text data on clipboard
        } // if looking for text/unicode   
      } // else we try one last ditch effort to find our data
      
      if ( dataFound ) {
        if ( strcmp(flavorStr, kPNGImageMime) == 0 || strcmp(flavorStr, kJPEGImageMime) == 0 ||
               strcmp(flavorStr, kGIFImageMime) == 0 ) {
          // someone asked for an image, so we have to convert from PICT to the desired
          // image format
          
          #ifdef DEBUG
          printf ( "----------- IMAGE REQUESTED ----------" );
          #endif
               
        } // if image requested
        else {
          // Assume that we have some form of textual data. The DOM only wants LF, so convert
          // from MacOS line endings to DOM line endings.
          nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks ( flavorStr, &clipboardData, &dataSize );
          
          // put it into the transferable
          nsCOMPtr<nsISupports> genericDataWrapper;
          nsPrimitiveHelpers::CreatePrimitiveForData ( flavorStr, clipboardData, dataSize, getter_AddRefs(genericDataWrapper) );        
          errCode = aTransferable->SetTransferData ( flavorStr, genericDataWrapper, dataSize );
        }
        
        nsMemory::Free ( clipboardData );
        
        // we found one, get out of this loop!
        break;        
      } // if flavor found on clipboard
    }
  } // foreach flavor
  
  return errCode;
}


//
// GetDataOffClipboard
//
// Actually does the work of retrieving the data from the native clipboard 
// with the given MacOS flavor
//
nsresult
nsClipboard :: GetDataOffClipboard ( ResType inMacFlavor, void** outData, PRInt32* outDataSize )
{
  if ( !outData || !inMacFlavor )
    return NS_ERROR_FAILURE;

  // set up default results.
  *outData = nsnull;
  if ( outDataSize )
      *outDataSize = 0;

  // check if it is on the clipboard
  long offsetUnused = 0;

#if TARGET_CARBON
  ScrapRef scrap;
  long dataSize;
  OSStatus err;

  err = ::GetCurrentScrap(&scrap);
  if (err != noErr) return NS_ERROR_FAILURE;
  err = ::GetScrapFlavorSize(scrap, inMacFlavor, &dataSize);
  if (err != noErr) return NS_ERROR_FAILURE;
  if (dataSize > 0) {
    char* dataBuff = (char*) nsMemory::Alloc(dataSize);
    if ( !dataBuff )
      return NS_ERROR_OUT_OF_MEMORY;
      
    // ::GetScrapFlavorData can be very expensive when a conversion
    // is required (say the OS does the conversion from TEXT to utxt). Be
    // sure to only call this when pasting. We no longer use it in 
    // CheckIfFlavorPresent() for this very reason.
    err = ::GetScrapFlavorData(scrap, inMacFlavor, &dataSize, dataBuff);
    NS_ASSERTION(err == noErr, "nsClipboard:: Error getting data off clipboard");
    if ( err ) {
      nsMemory::Free(dataBuff);
      return NS_ERROR_FAILURE;
    }

    // put it into the transferable
    if ( outDataSize )
      *outDataSize = dataSize;
    *outData = dataBuff;
  }
#else
  long clipResult = ::GetScrap(NULL, inMacFlavor, &offsetUnused);
  if ( clipResult > 0 ) {
    Handle dataHand = ::NewHandle(0);
    if ( !dataHand )
      return NS_ERROR_OUT_OF_MEMORY;
    long dataSize = ::GetScrap ( dataHand, inMacFlavor, &offsetUnused );
    NS_ASSERTION(dataSize > 0, "nsClipboard:: Error getting data off the clipboard, size negative");
    if ( dataSize > 0 ) {
      char* dataBuff = NS_REINTERPRET_CAST(char*, nsMemory::Alloc(dataSize));
      if ( !dataBuff )
        return NS_ERROR_OUT_OF_MEMORY;
      ::HLock(dataHand);
      ::BlockMoveData ( *dataHand, dataBuff, dataSize );
      ::HUnlock(dataHand);
      
      ::DisposeHandle(dataHand);
      
      if ( outDataSize )
        *outDataSize = dataSize;
      *outData = dataBuff;
    } 
    else
      return NS_ERROR_FAILURE;
  }
#endif /* TARGET_CARBON */
  return NS_OK;
  
} // GetDataOffClipboard


//
// HasDataMatchingFlavors
//
// Check the clipboard to see if we have any data that matches the given flavors. This
// does NOT actually fetch the data. The items in the flavor list are nsISupportsString's.
// 
// Handle the case where we ask for unicode and it's not there, but plain text is. We 
// say "yes" in that case, knowing that we will have to perform a conversion when we actually
// pull the data off the clipboard.
//
NS_IMETHODIMP
nsClipboard :: HasDataMatchingFlavors ( nsISupportsArray* aFlavorList, PRInt32 aWhichClipboard, PRBool * outResult ) 
{
  nsresult rv = NS_OK;
  *outResult = PR_FALSE;  // assume there is nothing there we want.
  if ( aWhichClipboard != kGlobalClipboard )
    return NS_OK;
  
  // create a mime mapper. It's ok for this to fail because the data may come from
  // another app which obviously wouldn't put our mime mapping data on the clipboard.
  char* mimeMapperData = nsnull;
  rv = GetDataOffClipboard ( nsMimeMapperMac::MappingFlavor(), (void**)&mimeMapperData, 0 );
  nsMimeMapperMac theMapper ( mimeMapperData );
  nsMemory::Free ( mimeMapperData );
  
  PRUint32 length;
  aFlavorList->Count(&length);
  for ( PRUint32 i = 0; i < length; ++i ) {
    nsCOMPtr<nsISupports> genericFlavor;
    aFlavorList->GetElementAt ( i, getter_AddRefs(genericFlavor) );
    nsCOMPtr<nsISupportsString> flavorWrapper ( do_QueryInterface(genericFlavor) );
    if ( flavorWrapper ) {
      nsXPIDLCString flavor;
      flavorWrapper->ToString ( getter_Copies(flavor) );
      
#ifdef NS_DEBUG
      if ( strcmp(flavor, kTextMime) == 0 )
        NS_WARNING ( "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD" );
#endif

      // now that we have the flavor (whew!), run it through the mime mapper. If
      // the mapper returns a null flavor, then it ain't there.
      ResType macFlavor = theMapper.MapMimeTypeToMacOSType ( flavor, PR_FALSE );
      if ( macFlavor ) {
        if ( CheckIfFlavorPresent(macFlavor) ) {
          *outResult = PR_TRUE;   // we found one!
          break;
        }
        else {
          // if the client asked for unicode and it wasn't present, check if we have TEXT.
          // We'll handle the actual data substitution in GetNativeClipboardData().
          if ( strcmp(flavor, kUnicodeMime) == 0 ) {
            if ( CheckIfFlavorPresent('TEXT') ) {
              *outResult = PR_TRUE;
              break;
            }
          }
        }
      }
    }  
  } // foreach flavor
  
  return NS_OK;
}


//
// CheckIfFlavorPresent
//
// A little utility routine for derminining if a given flavor is really there
//
PRBool
nsClipboard :: CheckIfFlavorPresent ( ResType inMacFlavor )
{
  PRBool retval = PR_FALSE;

#if TARGET_CARBON
  ScrapRef scrap = nsnull;
  OSStatus err = ::GetCurrentScrap(&scrap);
  if ( scrap ) {
  
    // the OS clipboard may contain promises and require conversions. Instead
    // of calling in those promises, we can use ::GetScrapFlavorInfoList() to
    // see the list of what could be there if we asked for it. This is really
    // fast. Iterate over the list, and if we find it, we're good to go.
    UInt32 flavorCount = 0;
    ::GetScrapFlavorCount ( scrap, &flavorCount );    
    ScrapFlavorInfo* flavorList = new ScrapFlavorInfo[flavorCount];
    if ( flavorList ) {
      err = ::GetScrapFlavorInfoList ( scrap, &flavorCount, flavorList );
      if ( !err && flavorList ) {
        for ( int i = 0; i < flavorCount; ++i ) {
          if ( flavorList[i].flavorType == inMacFlavor )
            retval = PR_TRUE;
        }
        delete flavorList;
      }
    }

  }
#else
  long offsetUnused = 0;
  long clipResult = ::GetScrap(NULL, inMacFlavor, &offsetUnused);
  if ( clipResult > 0 )   
    retval = PR_TRUE;   // we found one!
#endif

  return retval;
} // CheckIfFlavorPresent
