// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "chrome/browser/web_applications/web_app_mac.h"

#import <Cocoa/Cocoa.h>

#include "base/file_util.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/foundation_util.h"
#include "base/mac/mac_util.h"
#include "base/memory/scoped_nsobject.h"
#include "base/scoped_temp_dir.h"
#include "base/sys_string_conversions.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/common/chrome_paths_internal.h"
#include "chrome/common/mac/app_mode_common.h"
#include "content/public/browser/browser_thread.h"
#include "grit/chromium_strings.h"
#include "skia/ext/skia_utils_mac.h"
#include "third_party/icon_family/IconFamily.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/gfx/image/image_skia.h"

namespace {

// Creates a NSBitmapImageRep from |bitmap|.
NSBitmapImageRep* SkBitmapToImageRep(const SkBitmap& bitmap) {
  base::mac::ScopedCFTypeRef<CGColorSpaceRef> color_space(
      CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
  NSImage* image = gfx::SkBitmapToNSImageWithColorSpace(
    bitmap, color_space.get());
  return base::mac::ObjCCast<NSBitmapImageRep>(
      [[image representations] lastObject]);
}

// Adds |image_rep| to |icon_family|. Returns true on success, false on failure.
bool AddBitmapImageRepToIconFamily(IconFamily* icon_family,
                                   NSBitmapImageRep* image_rep) {
  NSSize size = [image_rep size];
  if (size.width != size.height)
    return false;

  switch (static_cast<int>(size.width)) {
    case 512:
      return [icon_family setIconFamilyElement:kIconServices512PixelDataARGB
                            fromBitmapImageRep:image_rep];
    case 256:
      return [icon_family setIconFamilyElement:kIconServices256PixelDataARGB
                            fromBitmapImageRep:image_rep];
    case 128:
      return [icon_family setIconFamilyElement:kThumbnail32BitData
                            fromBitmapImageRep:image_rep] &&
             [icon_family setIconFamilyElement:kThumbnail8BitMask
                            fromBitmapImageRep:image_rep];
    case 32:
      return [icon_family setIconFamilyElement:kLarge32BitData
                            fromBitmapImageRep:image_rep] &&
             [icon_family setIconFamilyElement:kLarge8BitData
                            fromBitmapImageRep:image_rep] &&
             [icon_family setIconFamilyElement:kLarge8BitMask
                            fromBitmapImageRep:image_rep] &&
             [icon_family setIconFamilyElement:kLarge1BitMask
                            fromBitmapImageRep:image_rep];
    case 16:
      return [icon_family setIconFamilyElement:kSmall32BitData
                            fromBitmapImageRep:image_rep] &&
             [icon_family setIconFamilyElement:kSmall8BitData
                            fromBitmapImageRep:image_rep] &&
             [icon_family setIconFamilyElement:kSmall8BitMask
                            fromBitmapImageRep:image_rep] &&
             [icon_family setIconFamilyElement:kSmall1BitMask
                            fromBitmapImageRep:image_rep];
    default:
      return false;
  }
}

}  // namespace


namespace web_app {

WebAppShortcutCreator::WebAppShortcutCreator(
    const FilePath& user_data_dir,
    const ShellIntegration::ShortcutInfo& shortcut_info,
    const string16& chrome_bundle_id)
    : user_data_dir_(user_data_dir),
      info_(shortcut_info),
      chrome_bundle_id_(chrome_bundle_id) {
}

WebAppShortcutCreator::~WebAppShortcutCreator() {
}

bool WebAppShortcutCreator::CreateShortcut() {
  FilePath app_name = internals::GetSanitizedFileName(info_.title);
  FilePath app_file_name = app_name.ReplaceExtension("app");
  ScopedTempDir scoped_temp_dir;
  if (!scoped_temp_dir.CreateUniqueTempDir())
    return false;
  FilePath staging_path = scoped_temp_dir.path().Append(app_file_name);

  // Update the app's plist and icon in a temp directory. This works around
  // a Finder bug where the app's icon doesn't properly update.
  if (!file_util::CopyDirectory(GetAppLoaderPath(), staging_path, true)) {
    LOG(ERROR) << "Copying app to staging path: " << staging_path.value()
               << " failed";
    return false;
  }

  if (!UpdatePlist(staging_path))
    return false;

  if (!UpdateIcon(staging_path))
    return false;

  FilePath dst_path = GetDestinationPath(app_file_name);
  if (!file_util::CopyDirectory(staging_path, dst_path, true)) {
    LOG(ERROR) << "Copying app to dst path: " << dst_path.value() << " failed";
    return false;
  }

  RevealGeneratedBundleInFinder(dst_path);

  return true;
}

FilePath WebAppShortcutCreator::GetAppLoaderPath() const {
  return base::mac::PathForFrameworkBundleResource(
      base::mac::NSToCFCast(@"app_mode_loader.app"));
}

FilePath WebAppShortcutCreator::GetDestinationPath(
    const FilePath& app_file_name) const {
  FilePath path;
  if (base::mac::GetLocalDirectory(NSApplicationDirectory, &path) &&
      file_util::PathIsWritable(path)) {
    return path;
  }

  if (base::mac::GetUserDirectory(NSApplicationDirectory, &path))
    return path;

  return FilePath();
}

bool WebAppShortcutCreator::UpdatePlist(const FilePath& app_path) const {
  NSString* plist_path = base::mac::FilePathToNSString(
      app_path.Append("Contents").Append("Info.plist"));

  NSString* extension_id = base::SysUTF8ToNSString(info_.extension_id);
  NSString* extension_title = base::SysUTF16ToNSString(info_.title);
  NSString* extension_url = base::SysUTF8ToNSString(info_.url.spec());
  NSString* chrome_bundle_id = base::SysUTF16ToNSString(chrome_bundle_id_);
  NSDictionary* replacement_dict =
      [NSDictionary dictionaryWithObjectsAndKeys:
          extension_id, app_mode::kShortcutIdPlaceholder,
          extension_title, app_mode::kShortcutNamePlaceholder,
          extension_url, app_mode::kShortcutURLPlaceholder,
          chrome_bundle_id, app_mode::kShortcutBrowserBundleIDPlaceholder,
          nil];

  NSMutableDictionary* plist =
      [NSMutableDictionary dictionaryWithContentsOfFile:plist_path];
  NSArray* keys = [plist allKeys];

  // 1. Fill in variables.
  for (id key in keys) {
    NSString* value = [plist valueForKey:key];
    if (![value isKindOfClass:[NSString class]] || [value length] < 2)
      continue;

    // Remove leading and trailing '@'s.
    NSString* variable =
        [value substringWithRange:NSMakeRange(1, [value length] - 2)];

    NSString* substitution = [replacement_dict valueForKey:variable];
    if (substitution)
      [plist setObject:substitution forKey:key];
  }

  // 2. Fill in other values.
  [plist setObject:GetBundleIdentifier(plist)
            forKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)];
  [plist setObject:base::mac::FilePathToNSString(user_data_dir_)
            forKey:app_mode::kCrAppModeUserDataDirKey];
  [plist setObject:base::mac::FilePathToNSString(info_.extension_path)
            forKey:app_mode::kCrAppModeExtensionPathKey];
  return [plist writeToFile:plist_path atomically:YES];
}

bool WebAppShortcutCreator::UpdateIcon(const FilePath& app_path) const {
  if (info_.favicon.IsEmpty())
    return true;

  scoped_nsobject<IconFamily> icon_family([[IconFamily alloc] init]);
  bool image_added = false;
  info_.favicon.ToImageSkia()->EnsureRepsForSupportedScaleFactors();
  std::vector<gfx::ImageSkiaRep> image_reps =
      info_.favicon.ToImageSkia()->image_reps();
  for (size_t i = 0; i < image_reps.size(); ++i) {
    NSBitmapImageRep* image_rep = SkBitmapToImageRep(
        image_reps[i].sk_bitmap());
    if (!image_rep)
      continue;

    // Missing an icon size is not fatal so don't fail if adding the bitmap
    // doesn't work.
    if (!AddBitmapImageRepToIconFamily(icon_family, image_rep))
      continue;

    image_added = true;
  }

  if (!image_added)
    return false;

  FilePath resources_path = app_path.Append("Contents").Append("Resources");
  if (!file_util::CreateDirectory(resources_path))
    return false;
  FilePath icon_path = resources_path.Append("app.icns");
  return [icon_family writeToFile:base::mac::FilePathToNSString(icon_path)];
}

NSString* WebAppShortcutCreator::GetBundleIdentifier(NSDictionary* plist) const
{
  NSString* bundle_id_template =
    base::mac::ObjCCast<NSString>(
        [plist objectForKey:base::mac::CFToNSCast(kCFBundleIdentifierKey)]);
  NSString* extension_id = base::SysUTF8ToNSString(info_.extension_id);
  NSString* placeholder =
      [NSString stringWithFormat:@"@%@@", app_mode::kShortcutIdPlaceholder];
  NSString* bundle_id =
      [bundle_id_template
          stringByReplacingOccurrencesOfString:placeholder
                                    withString:extension_id];
  return bundle_id;
}

void WebAppShortcutCreator::RevealGeneratedBundleInFinder(
    const FilePath& generated_bundle) const {
  [[NSWorkspace sharedWorkspace]
                    selectFile:base::mac::FilePathToNSString(generated_bundle)
      inFileViewerRootedAtPath:nil];
}

}  // namespace

namespace web_app {
namespace internals {

bool CreatePlatformShortcuts(
    const FilePath& web_app_path,
    const ShellIntegration::ShortcutInfo& shortcut_info) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
  string16 bundle_id = UTF8ToUTF16(base::mac::BaseBundleID());
  WebAppShortcutCreator shortcut_creator(web_app_path, shortcut_info,
                            bundle_id);
  return shortcut_creator.CreateShortcut();
}

void DeletePlatformShortcuts(
    const FilePath& web_app_path,
    const ShellIntegration::ShortcutInfo& shortcut_info) {
  // TODO(benwells): Implement this when shortcuts / weblings are enabled on
  // mac.
}

}  // namespace internals
}  // namespace web_app