// Copyright 2008, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "chrome/browser/printing/print_job.h"

#include "base/message_loop.h"
#include "chrome/browser/printing/print_job_worker.h"
#include "chrome/browser/printing/printed_document.h"
#include "chrome/browser/printing/printed_page.h"

#ifdef _MSC_VER
#pragma warning(disable:4355)  // 'this' : used in base member initializer list
#endif

namespace printing {

PrintJob::PrintJob(PrintedPagesSource* source)
    : ui_message_loop_(MessageLoop::current()),
      worker_(new PrintJobWorker(this)),
      source_(source),
      is_job_pending_(false),
      is_print_dialog_box_shown_(false),
      is_blocking_(false),
      is_canceling_(false) {
}

PrintJob::PrintJob()
    : ui_message_loop_(MessageLoop::current()),
      worker_(),
      source_(NULL),
      settings_(),
      is_job_pending_(false),
      is_print_dialog_box_shown_(false),
      is_blocking_(false),
      is_canceling_(false) {
}

PrintJob::~PrintJob() {
  // The job should be finished (or at least canceled) when it is destroyed.
  DCHECK(!is_job_pending_);
  DCHECK(!is_print_dialog_box_shown_);
  DCHECK(!is_blocking_);
  DCHECK(!is_canceling_);
  DCHECK(worker_->message_loop() == NULL);
  DCHECK_EQ(ui_message_loop_, MessageLoop::current());
}

void PrintJob::Initialize(PrintJobWorkerOwner* job,
                          PrintedPagesSource* source) {
  DCHECK(!source_);
  DCHECK(!worker_.get());
  DCHECK(!is_job_pending_);
  DCHECK(!is_print_dialog_box_shown_);
  DCHECK(!is_blocking_);
  DCHECK(!is_canceling_);
  DCHECK(!document_.get());
  source_ = source;
  worker_.reset(job->DetachWorker(this));
  settings_ = job->settings();

  UpdatePrintedDocument(new PrintedDocument(settings_, source_, job->cookie()));

  // Don't forget to register to our own messages.
  NotificationService::current()->AddObserver(
      this, NOTIFY_PRINT_JOB_EVENT, Source<PrintJob>(this));
}

void PrintJob::Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details) {
  DCHECK_EQ(ui_message_loop_, MessageLoop::current());
  switch (type) {
    case NOTIFY_PRINTED_DOCUMENT_UPDATED: {
      DCHECK(Source<PrintedDocument>(source).ptr() ==
             document_.get());

      // This notification may happens even if no job is started (i.e. print
      // preview)
      if (is_job_pending_ == true &&
          Source<PrintedDocument>(source).ptr() == document_.get() &&
          Details<PrintedPage>(details).ptr() != NULL) {
        // Are we waiting for a page to print? The worker will know.
        worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
            worker_.get(), &PrintJobWorker::OnNewPage));
      }
      break;
    }
    case NOTIFY_PRINT_JOB_EVENT: {
      OnNotifyPrintJobEvent(*Details<JobEventDetails>(details).ptr());
      break;
    }
    default: {
      break;
    }
  }
}

void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
                               PrintingContext::Result result) {
  DCHECK(!is_job_pending_);
  DCHECK(!is_blocking_);

  if (!source_ || result == PrintingContext::FAILED) {
    // The source is gone, there's nothing to do.
    Cancel();
    return;
  }

  // Only create a new PrintedDocument if the settings have changed or if
  // there was no printed document.
  if (!document_.get() || !new_settings.Equals(settings_)) {
    UpdatePrintedDocument(new PrintedDocument(new_settings, source_,
                                              PrintSettings::NewCookie()));
  }

  JobEventDetails::Type type;
  if (is_print_dialog_box_shown_) {
    type = (result == PrintingContext::OK) ?
               JobEventDetails::USER_INIT_DONE :
               JobEventDetails::USER_INIT_CANCELED;
    // Dialog box is not shown anymore.
    is_print_dialog_box_shown_ = false;
  } else {
    DCHECK_EQ(result, PrintingContext::OK);
    type = JobEventDetails::DEFAULT_INIT_DONE;
  }
  scoped_refptr<JobEventDetails> details(
      new JobEventDetails(type, document_.get(), NULL));
  NotificationService::current()->Notify(
      NOTIFY_PRINT_JOB_EVENT,
      Source<PrintJob>(this),
      Details<JobEventDetails>(details.get()));
}

PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
  NOTREACHED();
  return NULL;
}

int PrintJob::cookie() const {
  if (!document_.get())
    // Always use an invalid cookie in this case.
    return 0;
  return document_->cookie();
}

void PrintJob::GetSettings(GetSettingsAskParam ask_user_for_settings,
                           HWND parent_window) {
  DCHECK_EQ(ui_message_loop_, MessageLoop::current());
  DCHECK(!is_job_pending_);
  DCHECK(!is_print_dialog_box_shown_);
  DCHECK(!is_blocking_);
  // Is not reentrant.
  if (is_job_pending_ || is_blocking_)
    return;

  // Lazy create the worker thread. There is one worker thread per print job.
  if (!worker_->message_loop()) {
    if (!worker_->Start())
      return;

    // Don't re-register if we were already registered.
    NotificationService::current()->AddObserver(
        this, NOTIFY_PRINT_JOB_EVENT, Source<PrintJob>(this));
  }

  int page_count = 0;
  if (document_.get()) {
    page_count = document_->page_count();
  }

  // Real work is done in PrintJobWorker::Init().
  is_print_dialog_box_shown_ = ask_user_for_settings == ASK_USER;
  worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
      worker_.get(), &PrintJobWorker::GetSettings, is_print_dialog_box_shown_,
      parent_window, page_count));
}

void PrintJob::StartPrinting() {
  DCHECK_EQ(ui_message_loop_, MessageLoop::current());
  DCHECK(worker_->message_loop());
  DCHECK(!is_job_pending_);
  DCHECK(!is_print_dialog_box_shown_);
  DCHECK(!is_blocking_);
  if (!worker_->message_loop() || is_job_pending_ || is_blocking_)
    return;

  // Real work is done in PrintJobWorker::StartPrinting().
  worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
      worker_.get(), &PrintJobWorker::StartPrinting, document_));
  // Set the flag right now.
  is_job_pending_ = true;

  // Tell everyone!
  scoped_refptr<JobEventDetails> details(
      new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL));
  NotificationService::current()->Notify(
      NOTIFY_PRINT_JOB_EVENT,
      Source<PrintJob>(this),
      Details<JobEventDetails>(details.get()));
}

void PrintJob::Stop() {
  DCHECK_EQ(ui_message_loop_, MessageLoop::current());

  // Be sure to live long enough.
  scoped_refptr<PrintJob> handle(this);

  MessageLoop* worker_loop = worker_->message_loop();
  if (worker_loop) {
    if (is_print_dialog_box_shown_) {
      // Make sure there is no dialog box.
      worker_loop->PostTask(FROM_HERE, NewRunnableMethod(
          worker_.get(), &PrintJobWorker::DismissDialog));
      is_print_dialog_box_shown_ = false;
    }
    // It will wait infinitely for the worker thread to quit.
    worker_->NonBlockingStop();
    is_job_pending_ = false;
    NotificationService::current()->RemoveObserver(
      this, NOTIFY_PRINT_JOB_EVENT, Source<PrintJob>(this));
  }
  // Flush the cached document.
  UpdatePrintedDocument(NULL);

  if (is_blocking_) {
    // Make sure we don't get stuck in an inner message loop.
    MessageLoop::current()->Quit();
    is_blocking_ = false;
  }
}

void PrintJob::Cancel() {
  if (is_canceling_)
    return;
  is_canceling_ = true;

  // Be sure to live long enough.
  scoped_refptr<PrintJob> handle(this);

  DCHECK_EQ(ui_message_loop_, MessageLoop::current());
  MessageLoop* worker_loop = worker_.get() ? worker_->message_loop() : NULL;
  if (worker_loop) {
    // Call this right now so it renders the context invalid. Do not use
    // InvokeLater since it would take too much time.
    worker_->Cancel();
  }
  // Make sure a Cancel() is broadcast.
  scoped_refptr<JobEventDetails> details(
      new JobEventDetails(JobEventDetails::FAILED, NULL, NULL));
  NotificationService::current()->Notify(
      NOTIFY_PRINT_JOB_EVENT,
      Source<PrintJob>(this),
      Details<JobEventDetails>(details.get()));
  Stop();
  is_canceling_ = false;
}

bool PrintJob::RequestMissingPages() {
  DCHECK_EQ(ui_message_loop_, MessageLoop::current());
  DCHECK(!is_print_dialog_box_shown_);
  DCHECK(!is_blocking_);
  if (!is_job_pending_ || is_print_dialog_box_shown_ || is_blocking_)
    return false;

  MessageLoop* worker_loop = worker_.get() ? worker_->message_loop() : NULL;
  if (!worker_loop)
    return false;

  worker_loop->PostTask(FROM_HERE, NewRunnableMethod(
      worker_.get(), &PrintJobWorker::RequestMissingPages));
  return true;
}

bool PrintJob::FlushJob(int timeout_ms) {
  if (!RequestMissingPages())
    return false;

  // Make sure the object outlive this message loop.
  scoped_refptr<PrintJob> handle(this);

  MessageLoop::QuitTask timeout_task;
  scoped_ptr<Timer> timeout;
  if (timeout_ms) {
    timeout.reset(MessageLoop::current()->timer_manager()->StartTimer(
        timeout_ms,
        &timeout_task,
        false));
  }

  is_blocking_ = true;
  // Stop() will eventually be called, which will get out of the inner message
  // loop. But, don't take it for granted and set a timer in case something goes
  // wrong.

  bool old_state = MessageLoop::current()->NestableTasksAllowed();
  MessageLoop::current()->SetNestableTasksAllowed(true);
  MessageLoop::current()->Run();
  // Restore task state.
  MessageLoop::current()->SetNestableTasksAllowed(old_state);

  if (timeout.get()) {
    MessageLoop::current()->timer_manager()->StopTimer(timeout.get());
  }
  return true;
}

void PrintJob::DisconnectSource() {
  source_ = NULL;
  if (document_.get())
    document_->DisconnectSource();
}

bool PrintJob::is_job_pending() const {
  return is_job_pending_;
}

bool PrintJob::is_print_dialog_box_shown() const {
  return is_print_dialog_box_shown_;
}

PrintedDocument* PrintJob::document() const {
  return document_.get();
}

void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) {
  if (document_.get() == new_document)
    return;
  // Unregisters.
  if (document_.get()) {
    NotificationService::current()->
        RemoveObserver(this,
                       NOTIFY_PRINTED_DOCUMENT_UPDATED,
                       Source<PrintedDocument>(document_.get()));
  }
  document_ = new_document;

  // Registers.
  if (document_.get()) {
    NotificationService::current()->
        AddObserver(this,
                    NOTIFY_PRINTED_DOCUMENT_UPDATED,
                    Source<PrintedDocument>(document_.get()));
    settings_ = document_->settings();
  }

  if (worker_.get() && worker_->message_loop()) {
    DCHECK(!is_job_pending_);
    // Sync the document with the worker.
    worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
        worker_.get(), &PrintJobWorker::OnDocumentChanged, document_));
  }
}

void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
  switch (event_details.type()) {
    case JobEventDetails::FAILED: {
      settings_.Clear();
      // Update internal state.
      is_print_dialog_box_shown_ = false;
      // No need to cancel since the worker already canceled itself.
      Stop();
      break;
    }
    case JobEventDetails::USER_INIT_DONE:
    case JobEventDetails::DEFAULT_INIT_DONE:
    case JobEventDetails::USER_INIT_CANCELED: {
      DCHECK_EQ(event_details.document(), document_.get());
      break;
    }
    case JobEventDetails::NEW_DOC:
    case JobEventDetails::NEW_PAGE:
    case JobEventDetails::PAGE_DONE:
    case JobEventDetails::JOB_DONE:
    case JobEventDetails::ALL_PAGES_REQUESTED: {
      // Don't care.
      break;
    }
    case JobEventDetails::DOC_DONE: {
      // This will call Stop() and broadcast a JOB_DONE message.
      MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
          this, &PrintJob::OnDocumentDone));
      break;
    }
    default: {
      NOTREACHED();
      break;
    }
  }
}

void PrintJob::OnDocumentDone() {
  // Be sure to live long enough. The instance could be destroyed by the
  // JOB_DONE broadcast.
  scoped_refptr<PrintJob> handle(this);

  // Stop the worker thread.
  Stop();

  scoped_refptr<JobEventDetails> details(
      new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL));
  NotificationService::current()->Notify(
      NOTIFY_PRINT_JOB_EVENT,
      Source<PrintJob>(this),
      Details<JobEventDetails>(details.get()));
}

// Takes settings_ ownership and will be deleted in the receiving thread.
JobEventDetails::JobEventDetails(Type type,
                                 PrintedDocument* document,
                                 PrintedPage* page)
    : document_(document),
      page_(page),
      type_(type) {
}

JobEventDetails::~JobEventDetails() {
}

PrintedDocument* JobEventDetails::document() const {
  return document_;
}

PrintedPage* JobEventDetails::page() const {
  return page_;
}

}  // namespace printing