/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsPrintSettingsX.h"
#include "nsObjCExceptions.h"

#include "plbase64.h"
#include "plstr.h"

#include "nsCocoaUtils.h"

#include "mozilla/Preferences.h"

using namespace mozilla;

#define MAC_OS_X_PAGE_SETUP_PREFNAME    "print.macosx.pagesetup-2"
#define COCOA_PAPER_UNITS_PER_INCH      72.0

NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsX, nsPrintSettings, nsPrintSettingsX)

nsPrintSettingsX::nsPrintSettingsX()
  : mAdjustedPaperWidth{0.0}
  , mAdjustedPaperHeight{0.0}
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  mPrintInfo = [[NSPrintInfo sharedPrintInfo] copy];
  mWidthScale = COCOA_PAPER_UNITS_PER_INCH;
  mHeightScale = COCOA_PAPER_UNITS_PER_INCH;

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

nsPrintSettingsX::nsPrintSettingsX(const nsPrintSettingsX& src)
{
  *this = src;
}

nsPrintSettingsX::~nsPrintSettingsX()
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  [mPrintInfo release];

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

nsPrintSettingsX& nsPrintSettingsX::operator=(const nsPrintSettingsX& rhs)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;

  if (this == &rhs) {
    return *this;
  }

  nsPrintSettings::operator=(rhs);

  [mPrintInfo release];
  mPrintInfo = [rhs.mPrintInfo copy];

  return *this;

  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(*this);
}

nsresult nsPrintSettingsX::Init()
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  InitUnwriteableMargin();
  InitAdjustedPaperSize();

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

// Should be called whenever the page format changes.
NS_IMETHODIMP nsPrintSettingsX::InitUnwriteableMargin()
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  PMPaper paper;
  PMPaperMargins paperMargin;
  PMPageFormat pageFormat = GetPMPageFormat();
  ::PMGetPageFormatPaper(pageFormat, &paper);
  ::PMPaperGetMargins(paper, &paperMargin);
  mUnwriteableMargin.top    = NS_POINTS_TO_INT_TWIPS(paperMargin.top);
  mUnwriteableMargin.left   = NS_POINTS_TO_INT_TWIPS(paperMargin.left);
  mUnwriteableMargin.bottom = NS_POINTS_TO_INT_TWIPS(paperMargin.bottom);
  mUnwriteableMargin.right  = NS_POINTS_TO_INT_TWIPS(paperMargin.right);

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsPrintSettingsX::InitAdjustedPaperSize()
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  PMPageFormat pageFormat = GetPMPageFormat();

  PMRect paperRect;
  ::PMGetAdjustedPaperRect(pageFormat, &paperRect);

  mAdjustedPaperWidth = paperRect.right - paperRect.left;
  mAdjustedPaperHeight = paperRect.bottom - paperRect.top;

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

void
nsPrintSettingsX::SetCocoaPrintInfo(NSPrintInfo* aPrintInfo)
{
  if (mPrintInfo != aPrintInfo) {
    [mPrintInfo release];
    mPrintInfo = [aPrintInfo retain];
  }
}

NS_IMETHODIMP nsPrintSettingsX::ReadPageFormatFromPrefs()
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  nsAutoCString encodedData;
  nsresult rv =
    Preferences::GetCString(MAC_OS_X_PAGE_SETUP_PREFNAME, encodedData);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // decode the base64
  char* decodedData = PL_Base64Decode(encodedData.get(), encodedData.Length(), nullptr);
  NSData* data = [NSData dataWithBytes:decodedData length:strlen(decodedData)];
  if (!data)
    return NS_ERROR_FAILURE;

  PMPageFormat newPageFormat;
  OSStatus status = ::PMPageFormatCreateWithDataRepresentation((CFDataRef)data, &newPageFormat);
  if (status == noErr) {
    SetPMPageFormat(newPageFormat);
  }
  InitUnwriteableMargin();

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsPrintSettingsX::WritePageFormatToPrefs()
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  PMPageFormat pageFormat = GetPMPageFormat();
  if (pageFormat == kPMNoPageFormat)
    return NS_ERROR_NOT_INITIALIZED;

  NSData* data = nil;
  OSStatus err = ::PMPageFormatCreateDataRepresentation(pageFormat, (CFDataRef*)&data, kPMDataFormatXMLCompressed);
  if (err != noErr)
    return NS_ERROR_FAILURE;

  nsAutoCString encodedData;
  encodedData.Adopt(PL_Base64Encode((char*)[data bytes], [data length], nullptr));
  if (!encodedData.get())
    return NS_ERROR_OUT_OF_MEMORY;

  return Preferences::SetCString(MAC_OS_X_PAGE_SETUP_PREFNAME, encodedData);

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

nsresult nsPrintSettingsX::_Clone(nsIPrintSettings **_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = nullptr;

  nsPrintSettingsX *newSettings = new nsPrintSettingsX(*this);
  if (!newSettings)
    return NS_ERROR_FAILURE;
  *_retval = newSettings;
  NS_ADDREF(*_retval);
  return NS_OK;
}

NS_IMETHODIMP nsPrintSettingsX::_Assign(nsIPrintSettings *aPS)
{
  nsPrintSettingsX *printSettingsX = static_cast<nsPrintSettingsX*>(aPS);
  if (!printSettingsX)
    return NS_ERROR_UNEXPECTED;
  *this = *printSettingsX;
  return NS_OK;
}

PMPrintSettings
nsPrintSettingsX::GetPMPrintSettings()
{
  return static_cast<PMPrintSettings>([mPrintInfo PMPrintSettings]);
}

PMPrintSession
nsPrintSettingsX::GetPMPrintSession()
{
  return static_cast<PMPrintSession>([mPrintInfo PMPrintSession]);
}

PMPageFormat
nsPrintSettingsX::GetPMPageFormat()
{
  return static_cast<PMPageFormat>([mPrintInfo PMPageFormat]);
}

void
nsPrintSettingsX::SetPMPageFormat(PMPageFormat aPageFormat)
{
  PMPageFormat oldPageFormat = GetPMPageFormat();
  ::PMCopyPageFormat(aPageFormat, oldPageFormat);
  [mPrintInfo updateFromPMPageFormat];
}

void
nsPrintSettingsX::SetInchesScale(float aWidthScale, float aHeightScale)
{
  if (aWidthScale > 0 && aHeightScale > 0) {
    mWidthScale = aWidthScale;
    mHeightScale = aHeightScale;
  }
}

void
nsPrintSettingsX::GetInchesScale(float *aWidthScale, float *aHeightScale)
{
  *aWidthScale = mWidthScale;
  *aHeightScale = mHeightScale;
}

NS_IMETHODIMP nsPrintSettingsX::SetPaperWidth(double aPaperWidth)
{
  mPaperWidth = aPaperWidth;
  mAdjustedPaperWidth = aPaperWidth * mWidthScale;
  return NS_OK;
}

NS_IMETHODIMP nsPrintSettingsX::SetPaperHeight(double aPaperHeight)
{
  mPaperHeight = aPaperHeight;
  mAdjustedPaperHeight = aPaperHeight * mHeightScale;
  return NS_OK;
}

NS_IMETHODIMP
nsPrintSettingsX::GetEffectivePageSize(double *aWidth, double *aHeight)
{
  *aWidth  = NS_INCHES_TO_TWIPS(mAdjustedPaperWidth / mWidthScale);
  *aHeight = NS_INCHES_TO_TWIPS(mAdjustedPaperHeight / mHeightScale);
  return NS_OK;
}

void
nsPrintSettingsX::GetFilePageSize(double *aWidth, double *aHeight)
{
  double height, width;
  if (kPaperSizeInches == GetCocoaUnit(mPaperSizeUnit)) {
    width  = NS_INCHES_TO_TWIPS(mAdjustedPaperWidth / mWidthScale);
    height = NS_INCHES_TO_TWIPS(mAdjustedPaperHeight / mHeightScale);
  } else {
    width  = NS_MILLIMETERS_TO_TWIPS(mAdjustedPaperWidth / mWidthScale);
    height = NS_MILLIMETERS_TO_TWIPS(mAdjustedPaperHeight / mHeightScale);
  }
  width /= TWIPS_PER_POINT_FLOAT;
  height /= TWIPS_PER_POINT_FLOAT;

  *aWidth = width;
  *aHeight = height;
}

void nsPrintSettingsX::SetAdjustedPaperSize(double aWidth, double aHeight)
{
  mAdjustedPaperWidth = aWidth;
  mAdjustedPaperHeight = aHeight;
}

void nsPrintSettingsX::GetAdjustedPaperSize(double *aWidth, double *aHeight)
{
  *aWidth = mAdjustedPaperWidth;
  *aHeight = mAdjustedPaperHeight;
}

NS_IMETHODIMP
nsPrintSettingsX::SetScaling(double aScaling)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Only use NSPrintInfo data in the parent process. The
  // child process' instance is not needed or used.
  if (XRE_IsParentProcess()) {
    NSMutableDictionary* printInfoDict = [mPrintInfo dictionary];
    [printInfoDict setObject: [NSNumber numberWithFloat: aScaling]
     forKey: NSPrintScalingFactor];
    NSPrintInfo* newPrintInfo =
        [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
    if (NS_WARN_IF(!newPrintInfo)) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    SetCocoaPrintInfo(newPrintInfo);
    [newPrintInfo release];
  } else {
    nsPrintSettings::SetScaling(aScaling);
  }

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP
nsPrintSettingsX::GetScaling(double *aScaling)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Only use NSPrintInfo data in the parent process. The
  // child process' instance is not needed or used.
  if (XRE_IsParentProcess()) {
    NSDictionary* printInfoDict = [mPrintInfo dictionary];

    *aScaling =
      [[printInfoDict objectForKey: NSPrintScalingFactor] doubleValue];

    // Limit scaling precision to whole number percent values
    *aScaling = round(*aScaling * 100.0) / 100.0;
  } else {
    nsPrintSettings::GetScaling(aScaling);
  }

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP
nsPrintSettingsX::SetToFileName(const nsAString& aToFileName)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  if (XRE_IsContentProcess() &&
      Preferences::GetBool("print.print_via_parent")) {
    // On content sandbox, NSPrintJobSavingURL will returns error since
    // sandbox disallows file access.
    return nsPrintSettings::SetToFileName(aToFileName);
  }

  NSMutableDictionary* printInfoDict = [mPrintInfo dictionary];

  if (!aToFileName.IsEmpty()) {
    NSURL* jobSavingURL =
        [NSURL fileURLWithPath: nsCocoaUtils::ToNSString(aToFileName)];
    if (jobSavingURL) {
      [printInfoDict setObject: NSPrintSaveJob forKey: NSPrintJobDisposition];
      [printInfoDict setObject: jobSavingURL forKey: NSPrintJobSavingURL];
    }
    mToFileName = aToFileName;
  } else {
    mToFileName.Truncate();
  }

  NSPrintInfo* newPrintInfo =
      [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
  if (NS_WARN_IF(!newPrintInfo)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  SetCocoaPrintInfo(newPrintInfo);
  [newPrintInfo release];
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP
nsPrintSettingsX::GetOrientation(int32_t *aOrientation)
{
  // Only use NSPrintInfo data in the parent process. The
  // child process' instance is not needed or used.
  if (XRE_IsParentProcess()) {
    if ([mPrintInfo orientation] == NS_PAPER_ORIENTATION_PORTRAIT) {
      *aOrientation = nsIPrintSettings::kPortraitOrientation;
    } else {
      *aOrientation = nsIPrintSettings::kLandscapeOrientation;
    }
  } else {
    nsPrintSettings::GetOrientation(aOrientation);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsPrintSettingsX::SetOrientation(int32_t aOrientation)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Only use NSPrintInfo data in the parent process. The
  // child process' instance is not needed or used.
  if (XRE_IsParentProcess()) {
    NSMutableDictionary* printInfoDict = [mPrintInfo dictionary];
    if (aOrientation == nsIPrintSettings::kPortraitOrientation) {
      [printInfoDict setObject: [NSNumber numberWithInt: NS_PAPER_ORIENTATION_PORTRAIT]
       forKey: NSPrintOrientation];
    } else {
      [printInfoDict setObject: [NSNumber numberWithInt: NS_PAPER_ORIENTATION_LANDSCAPE]
       forKey: NSPrintOrientation];
    }
    NSPrintInfo* newPrintInfo =
        [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
    if (NS_WARN_IF(!newPrintInfo)) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    SetCocoaPrintInfo(newPrintInfo);
    [newPrintInfo release];
  } else {
    nsPrintSettings::SetOrientation(aOrientation);
  }

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP
nsPrintSettingsX::SetUnwriteableMarginTop(double aUnwriteableMarginTop)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  nsPrintSettings::SetUnwriteableMarginTop(aUnwriteableMarginTop);

  // Only use NSPrintInfo data in the parent process. The
  // child process' instance is not needed or used.
  if (XRE_IsParentProcess()) {
    NSMutableDictionary* printInfoDict = [mPrintInfo dictionary];
    [printInfoDict setObject :
        [NSNumber numberWithDouble: aUnwriteableMarginTop]
        forKey : NSPrintTopMargin];
    NSPrintInfo* newPrintInfo =
        [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
    if (NS_WARN_IF(!newPrintInfo)) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    SetCocoaPrintInfo(newPrintInfo);
    [newPrintInfo release];
  }

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP
nsPrintSettingsX::SetUnwriteableMarginLeft(double aUnwriteableMarginLeft)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  nsPrintSettings::SetUnwriteableMarginLeft(aUnwriteableMarginLeft);

  // Only use NSPrintInfo data in the parent process. The
  // child process' instance is not needed or used.
  if (XRE_IsParentProcess()) {
    NSMutableDictionary* printInfoDict = [mPrintInfo dictionary];
    [printInfoDict setObject : [NSNumber numberWithDouble: aUnwriteableMarginLeft]
     forKey : NSPrintLeftMargin];
    NSPrintInfo* newPrintInfo =
        [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
    if (NS_WARN_IF(!newPrintInfo)) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    SetCocoaPrintInfo(newPrintInfo);
    [newPrintInfo release];
  }

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP
nsPrintSettingsX::SetUnwriteableMarginBottom(double aUnwriteableMarginBottom)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  nsPrintSettings::SetUnwriteableMarginBottom(aUnwriteableMarginBottom);

  // Only use NSPrintInfo data in the parent process. The
  // child process' instance is not needed or used.
  if (XRE_IsParentProcess()) {
    NSMutableDictionary* printInfoDict = [mPrintInfo dictionary];
    [printInfoDict setObject : [NSNumber numberWithDouble: aUnwriteableMarginBottom]
     forKey : NSPrintBottomMargin];
    NSPrintInfo* newPrintInfo =
        [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
    if (NS_WARN_IF(!newPrintInfo)) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    SetCocoaPrintInfo(newPrintInfo);
    [newPrintInfo release];
  }

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP
nsPrintSettingsX::SetUnwriteableMarginRight(double aUnwriteableMarginRight)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  nsPrintSettings::SetUnwriteableMarginRight(aUnwriteableMarginRight);

  // Only use NSPrintInfo data in the parent process. The
  // child process' instance is not needed or used.
  if (XRE_IsParentProcess()) {
    NSMutableDictionary* printInfoDict = [mPrintInfo dictionary];
    [printInfoDict setObject : [NSNumber numberWithDouble: aUnwriteableMarginRight]
     forKey : NSPrintRightMargin];
    NSPrintInfo* newPrintInfo =
        [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
    if (NS_WARN_IF(!newPrintInfo)) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    SetCocoaPrintInfo(newPrintInfo);
    [newPrintInfo release];
  }

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

int
nsPrintSettingsX::GetCocoaUnit(int16_t aGeckoUnit)
{
  if (aGeckoUnit == kPaperSizeMillimeters)
    return kPaperSizeMillimeters;
  else
    return kPaperSizeInches;
}

nsresult nsPrintSettingsX::SetCocoaPaperSize(double aWidth, double aHeight)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NSSize paperSize;
  NSMutableDictionary* printInfoDict = [mPrintInfo dictionary];
  if ([mPrintInfo orientation] == NS_PAPER_ORIENTATION_PORTRAIT) {
    // switch widths and heights
    paperSize = NSMakeSize(aWidth, aHeight);
    [printInfoDict setObject: [NSValue valueWithSize: paperSize]
     forKey: NSPrintPaperSize];
  } else {
    paperSize = NSMakeSize(aHeight, aWidth);
    [printInfoDict setObject: [NSValue valueWithSize: paperSize]
     forKey: NSPrintPaperSize];
  }
  NSPrintInfo* newPrintInfo =
      [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
  if (NS_WARN_IF(!newPrintInfo)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  SetCocoaPrintInfo(newPrintInfo);
  [newPrintInfo release];
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
