/**********************************************************************

   Audacity - A Digital Audio Editor
   Copyright 1999-2009 Audacity Team
   License: GPL v2 - see LICENSE.txt

   Dominic Mazzoni
   Dan Horgan

******************************************************************//**

\class ScreenshotCommand
\brief Implements a command for capturing various areas of the screen or
project window.

*//*******************************************************************/

#include "ScreenshotCommand.h"
#include "CommandTargets.h"
#include "../AudacityApp.h"
#include "../Project.h"
#include <wx/toplevel.h>
#include <wx/dcscreen.h>
#include <wx/dcmemory.h>
#include <wx/settings.h>
#include <wx/bitmap.h>

#include "../TrackPanel.h"
#include "../toolbars/ToolManager.h"
#include "../toolbars/ToolBar.h"
#include "../toolbars/ControlToolBar.h"
#include "../toolbars/DeviceToolBar.h"
#include "../toolbars/EditToolBar.h"
#include "../toolbars/MeterToolBar.h"
#include "../toolbars/MixerToolBar.h"
#include "../toolbars/SelectionBar.h"
#include "../toolbars/ToolsToolBar.h"
#include "../toolbars/TranscriptionToolBar.h"
#include "../widgets/Ruler.h"

wxTopLevelWindow *ScreenshotCommand::GetFrontWindow(AudacityProject *project)
{
   wxWindow *front = NULL;
   wxWindow *proj = wxGetTopLevelParent(project);

   // This is kind of an odd hack.  There's no method to enumerate all
   // possible windows, so we search the whole screen for any windows
   // that are not this one and not the given Audacity project and
   // if we find anything, we assume that's the dialog the user wants
   // to capture.

   int width, height, x, y;
   wxDisplaySize(&width, &height);
   for (x = 0; x < width; x += 50) {
      for (y = 0; y < height; y += 50) {
         wxWindow *win = wxFindWindowAtPoint(wxPoint(x, y));
         if (win) {
            win = wxGetTopLevelParent(win);
            if (win != mIgnore && win != proj) {
               front = win;
               break;
            }
         }
      }
   }

   if (!front || !front->IsTopLevel()) {
      return (wxTopLevelWindow *)proj;
   }

   return (wxTopLevelWindow *)front;
}

wxRect ScreenshotCommand::GetBackgroundRect()
{
   wxRect r;

   r.x = 16;
   r.y = 16;
   r.width = r.x * 2;
   r.height = r.y * 2;

   return r;
}

static void Yield()
{
   int cnt;
   for (cnt = 10; cnt && !wxGetApp().Yield(true); cnt--) {
      wxMilliSleep(10);
   }
   wxMilliSleep(200);
   for (cnt = 10; cnt && !wxGetApp().Yield(true); cnt--) {
      wxMilliSleep(10);
   }
}

void ScreenshotCommand::Capture(wxString filename,
                          wxWindow *window,
                          int x, int y, int width, int height,
                          bool bg)
{
   if (window) {
      if (window->IsTopLevel()) {
         window->Raise();
      }
      else {
         wxGetTopLevelParent(window)->Raise();
      }
   }

   Yield();

   int screenW, screenH;
   wxDisplaySize(&screenW, &screenH);
   wxBitmap full(screenW, screenH);

   wxScreenDC screenDC;
   wxMemoryDC fullDC;

   // We grab the whole screen image since there seems to be a problem with
   // using non-zero source coordinates on OSX.  (as of wx2.8.9)
   fullDC.SelectObject(full);
   fullDC.Blit(0, 0, screenW, screenH, &screenDC, 0, 0);
   fullDC.SelectObject(wxNullBitmap);

   wxRect r(x, y, width, height);

   // Ensure within bounds (x/y are negative on Windows when maximized)
   r.Intersect(wxRect(0, 0, screenW, screenH));

   // Convert to screen coordinates if needed
   if (window && window->GetParent() && !window->IsTopLevel()) {
      r.SetPosition(window->GetParent()->ClientToScreen(r.GetPosition()));
   }

   // Extract the actual image
   wxBitmap part = full.GetSubBitmap(r);

   // Add a background
   if (bg && mBackground) {
      wxRect b = GetBackgroundRect();

      wxBitmap back(width + b.width, height + b.height);
      fullDC.SelectObject(back);

      fullDC.SetBackground(wxBrush(mBackColor, wxSOLID));
      fullDC.Clear();

      fullDC.DrawBitmap(part, b.x, b.y);
      fullDC.SelectObject(wxNullBitmap);

      part = back;
   }

   // Save the final image
   wxImage image = part.ConvertToImage();
   if (image.SaveFile(filename)) {
      mOutput->Status(_("Saved ") + filename);
   }
   else {
      mOutput->Error(_("Error trying to save file: ") + filename);
   }

   ::wxBell();
}

void ScreenshotCommand::CaptureToolbar(ToolManager *man, int type, wxString name)
{
   bool visible = man->IsVisible(type);
   if (!visible) {
      man->ShowHide(type);
      Yield();
   }

   wxWindow *w = man->GetToolBar(type);
   int x = 0, y = 0;
   int width, height;

   w->ClientToScreen(&x, &y);
   w->GetParent()->ScreenToClient(&x, &y);
   w->GetClientSize(&width, &height);

   Capture(name, w, x, y, width, height);

   if (!visible) {
      man->ShowHide(type);
      if (mIgnore)
         mIgnore->Raise();
   }
}

void ScreenshotCommand::CaptureDock(wxWindow *win, wxString fileName)
{
   int x = 0, y = 0;
   int width, height;

   win->ClientToScreen(&x, &y);
   win->GetParent()->ScreenToClient(&x, &y);
   win->GetClientSize(&width, &height);

   Capture(fileName, win, x, y, width, height);
}

wxString ScreenshotCommandType::BuildName()
{
   return wxT("Screenshot");
}

void ScreenshotCommandType::BuildSignature(CommandSignature &signature)
{
   OptionValidator *captureModeValidator = new OptionValidator();
   captureModeValidator->AddOption(wxT("window"));
   captureModeValidator->AddOption(wxT("fullwindow"));
   captureModeValidator->AddOption(wxT("windowplus"));
   captureModeValidator->AddOption(wxT("fullscreen"));
   captureModeValidator->AddOption(wxT("toolbars"));
   captureModeValidator->AddOption(wxT("selectionbar"));
   captureModeValidator->AddOption(wxT("tools"));
   captureModeValidator->AddOption(wxT("transport"));
   captureModeValidator->AddOption(wxT("mixer"));
   captureModeValidator->AddOption(wxT("meter"));
   captureModeValidator->AddOption(wxT("edit"));
   captureModeValidator->AddOption(wxT("device"));
   captureModeValidator->AddOption(wxT("transcription"));
   captureModeValidator->AddOption(wxT("trackpanel"));
   captureModeValidator->AddOption(wxT("ruler"));
   captureModeValidator->AddOption(wxT("tracks"));
   captureModeValidator->AddOption(wxT("firsttrack"));
   captureModeValidator->AddOption(wxT("secondtrack"));

   OptionValidator *backgroundValidator = new OptionValidator();
   backgroundValidator->AddOption(wxT("Blue"));
   backgroundValidator->AddOption(wxT("White"));
   backgroundValidator->AddOption(wxT("None"));

   Validator *filePathValidator = new Validator();

   signature.AddParameter(wxT("CaptureMode"),
                          wxT("fullscreen"),
                          captureModeValidator);
   signature.AddParameter(wxT("Background"),
                          wxT("None"),
                          backgroundValidator);
   signature.AddParameter(wxT("FilePath"), wxT(""), filePathValidator);
}

Command *ScreenshotCommandType::Create(CommandOutputTarget *target)
{
   return new ScreenshotCommand(*this, target);
}

wxString ScreenshotCommand::MakeFileName(wxString path, wxString basename)
{
   wxFileName prefixPath;
   prefixPath.AssignDir(path);
   wxString prefix = prefixPath.GetPath
      (wxPATH_GET_VOLUME|wxPATH_GET_SEPARATOR);

   wxString filename;
   int i = 0;
   do {
      filename.Printf(wxT("%s%s%03d.png"),
                      prefix.c_str(), basename.c_str(), i);
      i++;
   } while (::wxFileExists(filename));

   return filename;
}

bool ScreenshotCommand::Apply(CommandExecutionContext context)
{
   // Read the parameters that were passed in
   wxString filePath    = GetString(wxT("FilePath"));
   wxString captureMode = GetString(wxT("CaptureMode"));
   wxString background  = GetString(wxT("Background"));

   // Build a suitable filename
   wxString fileName = MakeFileName(filePath, captureMode);

   if (background.IsSameAs(wxT("Blue")))
   {
      mBackground = true;
      mBackColor = wxColour(51, 102, 153);
   }
   else if (background.IsSameAs(wxT("White")))
   {
      mBackground = true;
      mBackColor = wxColour(255, 255, 255);
   }
   else
   {
      mBackground = false;
   }

   // Reset the toolbars to a known state
   context.proj->mToolManager->Reset();

   wxTopLevelWindow *w = GetFrontWindow(context.proj);
   if (!w)
   {
      return false;
   }

   if (captureMode.IsSameAs(wxT("window")))
   {
      int x = 0, y = 0;
      int width, height;

      w->ClientToScreen(&x, &y);
      w->GetClientSize(&width, &height);

      if (w != context.proj && w->GetTitle() != wxT("")) {
         fileName = MakeFileName(filePath,
               captureMode + (wxT("-") + w->GetTitle() + wxT("-")));
      }

      Capture(fileName, w, x, y, width, height);
   }
   else if (captureMode.IsSameAs(wxT("fullwindow"))
         || captureMode.IsSameAs(wxT("windowplus")))
   {

      wxRect r = w->GetRect();
      r.SetPosition(w->GetScreenPosition());
      r = w->GetScreenRect();

      if (w != context.proj && w->GetTitle() != wxT("")) {
         fileName = MakeFileName(filePath,
               captureMode + (wxT("-") + w->GetTitle() + wxT("-")));
      }

#if defined(__WXGTK__)
      // In wxGTK, we need to include decoration sizes
      r.width += (wxSystemSettings::GetMetric(wxSYS_BORDER_X, w) * 2);
      r.height += wxSystemSettings::GetMetric(wxSYS_CAPTION_Y, w) +
         wxSystemSettings::GetMetric(wxSYS_BORDER_Y, w);
#endif
      if (!mBackground && captureMode.IsSameAs(wxT("windowplus")))
      {
         // background colour not selected but we want a background
         wxRect b = GetBackgroundRect();
         r.x = (r.x - b.x) >= 0 ? (r.x - b.x): 0;
         r.y = (r.y - b.y) >= 0 ? (r.y - b.y): 0;
         r.width += b.width;
         r.height += b.height;
      }

      Capture(fileName, w, r.x, r.y, r.width, r.height, true);
   }
   else if (captureMode.IsSameAs(wxT("fullscreen")))
   {
      int width, height;
      wxDisplaySize(&width, &height);

      Capture(fileName, w, 0, 0, width, height);
   }
   else if (captureMode.IsSameAs(wxT("toolbars")))
   {
      CaptureDock(context.proj->mToolManager->GetTopDock(), fileName);
   }
   else if (captureMode.IsSameAs(wxT("selectionbar")))
   {
      CaptureDock(context.proj->mToolManager->GetBotDock(), fileName);
   }
   else if (captureMode.IsSameAs(wxT("tools")))
   {
      CaptureToolbar(context.proj->mToolManager, ToolsBarID, fileName);
   }
   else if (captureMode.IsSameAs(wxT("transport")))
   {
      CaptureToolbar(context.proj->mToolManager, TransportBarID, fileName);
   }
   else if (captureMode.IsSameAs(wxT("mixer")))
   {
      CaptureToolbar(context.proj->mToolManager, MixerBarID, fileName);
   }
   else if (captureMode.IsSameAs(wxT("meter")))
   {
      CaptureToolbar(context.proj->mToolManager, MeterBarID, fileName);
   }
   else if (captureMode.IsSameAs(wxT("edit")))
   {
      CaptureToolbar(context.proj->mToolManager, EditBarID, fileName);
   }
   else if (captureMode.IsSameAs(wxT("device")))
   {
      CaptureToolbar(context.proj->mToolManager, DeviceBarID, fileName);
   }
   else if (captureMode.IsSameAs(wxT("transcription")))
   {
      CaptureToolbar(context.proj->mToolManager, TranscriptionBarID, fileName);
   }
   else if (captureMode.IsSameAs(wxT("trackpanel")))
   {
      TrackPanel *panel = context.proj->mTrackPanel;
      AdornedRulerPanel *ruler = panel->mRuler;

      int h = ruler->GetRulerHeight();
      int x = 0, y = -h;
      int width, height;

      panel->ClientToScreen(&x, &y);
      panel->GetParent()->ScreenToClient(&x, &y);
      panel->GetClientSize(&width, &height);

      Capture(fileName, panel, x, y, width, height + h);
   }
   else if (captureMode.IsSameAs(wxT("ruler")))
   {
      TrackPanel *panel = context.proj->mTrackPanel;
      AdornedRulerPanel *ruler = panel->mRuler;

      int x = 0, y = 0;
      int width, height;

      ruler->ClientToScreen(&x, &y);
      ruler->GetParent()->ScreenToClient(&x, &y);
      ruler->GetClientSize(&width, &height);
      height = ruler->GetRulerHeight();

      Capture(fileName, ruler, x, y, width, height);
   }
   else if (captureMode.IsSameAs(wxT("tracks")))
   {
      TrackPanel *panel = context.proj->mTrackPanel;

      int x = 0, y = 0;
      int width, height;

      panel->ClientToScreen(&x, &y);
      panel->GetParent()->ScreenToClient(&x, &y);
      panel->GetClientSize(&width, &height);

      Capture(fileName, panel, x, y, width, height);
   }
   else if (captureMode.IsSameAs(wxT("firsttrack")))
   {
      TrackPanel *panel = context.proj->mTrackPanel;
      TrackListIterator iter(context.proj->GetTracks());
      Track * t = iter.First();
      if (!t) {
         return false;
      }
      wxRect r = panel->FindTrackRect(t, true);

      int x = 0, y = r.y - 3;
      int width, height;

      panel->ClientToScreen(&x, &y);
      panel->GetParent()->ScreenToClient(&x, &y);
      panel->GetClientSize(&width, &height);

      Capture(fileName, panel, x, y, width, r.height + 6);

   }
   else if (captureMode.IsSameAs(wxT("secondtrack")))
   {
      TrackPanel *panel = context.proj->mTrackPanel;
      TrackListIterator iter(context.proj->GetTracks());
      Track * t = iter.First();
      if (!t) {
         return false;
      }
      if (t->GetLinked()) {
         t = iter.Next();
      }
      t = iter.Next();
      if (!t) {
         return false;
      }
      wxRect r = panel->FindTrackRect(t, true);

      int x = 0, y = r.y - 3;
      int width, height;

      panel->ClientToScreen(&x, &y);
      panel->GetParent()->ScreenToClient(&x, &y);
      panel->GetClientSize(&width, &height);

      Capture(fileName, panel, x, y, width, r.height + 6);
   }
   else
   {
      // Invalid capture mode!
      return false;
   }

   return true;
}
