// Copyright (c) 2011 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/ui/cocoa/first_run_dialog.h"

#include "base/mac/mac_util.h"
#include "base/memory/ref_counted.h"
#import "base/memory/scoped_nsobject.h"
#include "base/message_loop.h"
#include "base/sys_string_conversions.h"
#include "chrome/browser/first_run/first_run.h"
#include "chrome/browser/first_run/first_run_dialog.h"
#include "chrome/browser/google/google_util.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_model.h"
#import "chrome/browser/ui/cocoa/search_engine_dialog_controller.h"
#include "chrome/common/url_constants.h"
#include "googleurl/src/gurl.h"
#include "grit/locale_settings.h"
#import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
#include "ui/base/l10n/l10n_util_mac.h"

#if defined(GOOGLE_CHROME_BUILD)
#import "chrome/app/breakpad_mac.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/common/pref_names.h"
#include "chrome/installer/util/google_update_settings.h"
#endif

@interface FirstRunDialogController (PrivateMethods)
// Show the dialog.
- (void)show;
@end

namespace {

// Compare function for -[NSArray sortedArrayUsingFunction:context:] that
// sorts the views in Y order bottom up.
NSInteger CompareFrameY(id view1, id view2, void* context) {
  CGFloat y1 = NSMinY([view1 frame]);
  CGFloat y2 = NSMinY([view2 frame]);
  if (y1 < y2)
    return NSOrderedAscending;
  else if (y1 > y2)
    return NSOrderedDescending;
  else
    return NSOrderedSame;
}

class FirstRunShowBridge : public base::RefCounted<FirstRunShowBridge> {
 public:
  FirstRunShowBridge(FirstRunDialogController* controller);

  void ShowDialog();
 private:
  FirstRunDialogController* controller_;
};

FirstRunShowBridge::FirstRunShowBridge(
    FirstRunDialogController* controller) : controller_(controller) {
}

void FirstRunShowBridge::ShowDialog() {
  [controller_ show];
  MessageLoop::current()->QuitNow();
}

// Show the search engine selection dialog.
void ShowSearchEngineSelectionDialog(Profile* profile,
                                     bool randomize_search_engine_experiment) {
  scoped_nsobject<SearchEngineDialogController> dialog(
      [[SearchEngineDialogController alloc] init]);
  [dialog.get() setProfile:profile];
  [dialog.get() setRandomize:randomize_search_engine_experiment];

  [dialog.get() showWindow:nil];
}

// Show the first run UI.
void ShowFirstRun(Profile* profile) {
#if defined(GOOGLE_CHROME_BUILD)
  // The purpose of the dialog is to ask the user to enable stats and crash
  // reporting. This setting may be controlled through configuration management
  // in enterprise scenarios. If that is the case, skip the dialog entirely, as
  // it's not worth bothering the user for only the default browser question
  // (which is likely to be forced in enterprise deployments anyway).
  const PrefService::Preference* metrics_reporting_pref =
      g_browser_process->local_state()->FindPreference(
          prefs::kMetricsReportingEnabled);
  if (!metrics_reporting_pref || !metrics_reporting_pref->IsManaged()) {
    scoped_nsobject<FirstRunDialogController> dialog(
        [[FirstRunDialogController alloc] init]);

    [dialog.get() showWindow:nil];

    // If the dialog asked the user to opt-in for stats and crash reporting,
    // record the decision and enable the crash reporter if appropriate.
    bool stats_enabled = [dialog.get() statsEnabled];
    GoogleUpdateSettings::SetCollectStatsConsent(stats_enabled);

    // Breakpad is normally enabled very early in the startup process.  However,
    // on the first run it may not have been enabled due to the missing opt-in
    // from the user.  If the user agreed now, enable breakpad if necessary.
    if (!IsCrashReporterEnabled() && stats_enabled) {
      InitCrashReporter();
      InitCrashProcessInfo();
    }

    // If selected set as default browser.
    BOOL make_default_browser = [dialog.get() makeDefaultBrowser];
    if (make_default_browser) {
      bool success = ShellIntegration::SetAsDefaultBrowser();
      DCHECK(success);
    }
  }
#else  // GOOGLE_CHROME_BUILD
  // We don't show the dialog in Chromium.
#endif  // GOOGLE_CHROME_BUILD

  FirstRun::CreateSentinel();

  // Set preference to show first run bubble and welcome page.
  // Don't display the minimal bubble if there is no default search provider.
  TemplateURLModel* search_engines_model = profile->GetTemplateURLModel();
  if (search_engines_model &&
      search_engines_model->GetDefaultSearchProvider()) {
    FirstRun::SetShowFirstRunBubblePref(true);
  }
  FirstRun::SetShowWelcomePagePref();
}

}  // namespace

namespace first_run {

void ShowFirstRunDialog(Profile* profile,
                        bool randomize_search_engine_experiment) {
  // If the default search is not managed via policy, ask the user to
  // choose a default.
  TemplateURLModel* model = profile->GetTemplateURLModel();
  if (!FirstRun::SearchEngineSelectorDisallowed() ||
      (model && !model->is_default_search_managed())) {
    ShowSearchEngineSelectionDialog(profile,
                                    randomize_search_engine_experiment);
  }
  ShowFirstRun(profile);
}

}  // namespace first_run

@implementation FirstRunDialogController

@synthesize statsEnabled = statsEnabled_;
@synthesize makeDefaultBrowser = makeDefaultBrowser_;

- (id)init {
  NSString* nibpath =
      [base::mac::MainAppBundle() pathForResource:@"FirstRunDialog"
                                          ofType:@"nib"];
  self = [super initWithWindowNibPath:nibpath owner:self];
  if (self != nil) {
    // Bound to the dialog checkbox, default to true.
    makeDefaultBrowser_ = YES;
  }
  return self;
}

- (void)dealloc {
  [super dealloc];
}

- (IBAction)showWindow:(id)sender {
  // The main MessageLoop has not yet run, but has been spun. If we call
  // -[NSApplication runModalForWindow:] we will hang <http://crbug.com/54248>.
  // Therefore the main MessageLoop is run so things work.

  scoped_refptr<FirstRunShowBridge> bridge(new FirstRunShowBridge(self));
  MessageLoop::current()->PostTask(
      FROM_HERE,
      NewRunnableMethod(bridge.get(),
                        &FirstRunShowBridge::ShowDialog));
  MessageLoop::current()->Run();
}

- (void)show {
  NSWindow* win = [self window];

  if (!platform_util::CanSetAsDefaultBrowser()) {
    [setAsDefaultCheckbox_ setHidden:YES];
    makeDefaultBrowser_ = NO;
  }

  // Only support the sizing the window once.
  DCHECK(!beenSized_) << "ShowWindow was called twice?";
  if (!beenSized_) {
    beenSized_ = YES;
    DCHECK_GT([objectsToSize_ count], 0U);

    // Size everything to fit, collecting the widest growth needed (XIB provides
    // the min size, i.e.-never shrink, just grow).
    CGFloat largestWidthChange = 0.0;
    for (NSView* view in objectsToSize_) {
      DCHECK_NE(statsCheckbox_, view) << "Stats checkbox shouldn't be in list";
      if (![view isHidden]) {
        NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:view];
        DCHECK_EQ(delta.height, 0.0)
            << "Didn't expect anything to change heights";
        if (largestWidthChange < delta.width)
          largestWidthChange = delta.width;
      }
    }

    // Make the window wide enough to fit everything.
    if (largestWidthChange > 0.0) {
      NSView* contentView = [win contentView];
      NSRect windowFrame = [contentView convertRect:[win frame] fromView:nil];
      windowFrame.size.width += largestWidthChange;
      windowFrame = [contentView convertRect:windowFrame toView:nil];
      [win setFrame:windowFrame display:NO];
    }

    // The stats checkbox gets some really long text, so it gets word wrapped
    // and then sized.
    DCHECK(statsCheckbox_);
    CGFloat statsCheckboxHeightChange = 0.0;
    [GTMUILocalizerAndLayoutTweaker wrapButtonTitleForWidth:statsCheckbox_];
    statsCheckboxHeightChange =
        [GTMUILocalizerAndLayoutTweaker sizeToFitView:statsCheckbox_].height;

    // Walk bottom up shuffling for all the hidden views.
    NSArray* subViews =
        [[[win contentView] subviews] sortedArrayUsingFunction:CompareFrameY
                                                       context:NULL];
    CGFloat moveDown = 0.0;
    NSUInteger numSubViews = [subViews count];
    for (NSUInteger idx = 0 ; idx < numSubViews ; ++idx) {
      NSView* view = [subViews objectAtIndex:idx];

      // If the view is hidden, collect the amount to move everything above it
      // down, if it's not hidden, apply any shift down.
      if ([view isHidden]) {
        DCHECK_GT((numSubViews - 1), idx)
            << "Don't support top view being hidden";
        NSView* nextView = [subViews objectAtIndex:(idx + 1)];
        CGFloat viewBottom = [view frame].origin.y;
        CGFloat nextViewBottom = [nextView frame].origin.y;
        moveDown += nextViewBottom - viewBottom;
      } else {
        if (moveDown != 0.0) {
          NSPoint origin = [view frame].origin;
          origin.y -= moveDown;
          [view setFrameOrigin:origin];
        }
      }
      // Special case, if this is the stats checkbox, everything above it needs
      // to get moved up by the amount it changed height.
      if (view == statsCheckbox_) {
        moveDown -= statsCheckboxHeightChange;
      }
    }

    // Resize the window for any height change from hidden views, etc.
    if (moveDown != 0.0) {
      NSView* contentView = [win contentView];
      [contentView setAutoresizesSubviews:NO];
      NSRect windowFrame = [contentView convertRect:[win frame] fromView:nil];
      windowFrame.size.height -= moveDown;
      windowFrame = [contentView convertRect:windowFrame toView:nil];
      [win setFrame:windowFrame display:NO];
      [contentView setAutoresizesSubviews:YES];
    }

  }

  // Neat weirdness in the below code - the Application menu stays enabled
  // while the window is open but selecting items from it (e.g. Quit) has
  // no effect.  I'm guessing that this is an artifact of us being a
  // background-only application at this stage and displaying a modal
  // window.

  // Display dialog.
  [win center];
  [NSApp runModalForWindow:win];
}

- (IBAction)ok:(id)sender {
  [[self window] close];
  [NSApp stopModal];
}

- (IBAction)learnMore:(id)sender {
  GURL url = google_util::AppendGoogleLocaleParam(
      GURL(chrome::kLearnMoreReportingURL));
  NSString* urlStr = base::SysUTF8ToNSString(url.spec());;
  NSURL* learnMoreUrl = [NSURL URLWithString:urlStr];
  [[NSWorkspace sharedWorkspace] openURL:learnMoreUrl];
}

@end