// 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.

#include "ash/monitor/multi_monitor_manager.h"

#include <string>
#include <vector>

#include "base/command_line.h"
#include "base/stl_util.h"
#include "base/string_split.h"
#include "ui/aura/aura_switches.h"
#include "ui/aura/env.h"
#include "ui/aura/root_window.h"
#include "ui/aura/root_window_host.h"
#include "ui/aura/window_property.h"
#include "ui/gfx/monitor.h"
#include "ui/gfx/rect.h"

DECLARE_WINDOW_PROPERTY_TYPE(int);

namespace ash {
namespace internal {
namespace {
gfx::Monitor& GetInvalidMonitor() {
  static gfx::Monitor* invalid_monitor = new gfx::Monitor();
  return *invalid_monitor;
}
}  // namespace

using aura::RootWindow;
using aura::Window;
using gfx::Monitor;
using std::string;
using std::vector;

DEFINE_WINDOW_PROPERTY_KEY(int, kMonitorIdKey, -1);

MultiMonitorManager::MultiMonitorManager() {
  Init();
}

MultiMonitorManager::~MultiMonitorManager() {
}

// static
void MultiMonitorManager::AddRemoveMonitor() {
  MultiMonitorManager* manager = static_cast<MultiMonitorManager*>(
      aura::Env::GetInstance()->monitor_manager());
  manager->AddRemoveMonitorImpl();
}

void MultiMonitorManager::CycleMonitor() {
  MultiMonitorManager* manager = static_cast<MultiMonitorManager*>(
      aura::Env::GetInstance()->monitor_manager());
  manager->CycleMonitorImpl();
}

  void MultiMonitorManager::ToggleMonitorScale() {
  MultiMonitorManager* manager = static_cast<MultiMonitorManager*>(
      aura::Env::GetInstance()->monitor_manager());
  manager->ScaleMonitorImpl();
}

void MultiMonitorManager::OnNativeMonitorsChanged(
    const std::vector<Monitor>& new_monitors) {
  size_t min = std::min(monitors_.size(), new_monitors.size());

  // For m19, we only care about 1st monitor as primary, and
  // don't differentiate the rest of monitors as all secondary
  // monitors have the same content. ID for primary monitor stays the same
  // because we never remove it, we don't update IDs for other monitors
  // , for now, because they're the same.
  // TODO(oshima): Fix this so that we can differentiate outputs
  // and keep a content on one monitor stays on the same monitor
  // when a monitor is added or removed.
  for (size_t i = 0; i < min; ++i) {
    Monitor& current_monitor = monitors_[i];
    const Monitor& new_monitor = new_monitors[i];
    if (current_monitor.bounds_in_pixel() != new_monitor.bounds_in_pixel()) {
      current_monitor.SetScaleAndBounds(new_monitor.device_scale_factor(),
                                        new_monitor.bounds_in_pixel());
      NotifyBoundsChanged(current_monitor);
    }
  }

  if (monitors_.size() < new_monitors.size()) {
    // New monitors added
    for (size_t i = min; i < new_monitors.size(); ++i) {
      const gfx::Monitor& new_monitor = new_monitors[i];
      monitors_.push_back(Monitor(new_monitor.id()));
      gfx::Monitor& monitor = monitors_.back();
      monitor.SetScaleAndBounds(new_monitor.device_scale_factor(),
                                new_monitor.bounds_in_pixel());
      NotifyMonitorAdded(monitor);
    }
  } else {
    // Monitors are removed. We keep the monitor for the primary
    // monitor (at index 0) because it needs the monitor information
    // even if it doesn't exit.
    while (monitors_.size() > new_monitors.size() && monitors_.size() > 1) {
      Monitors::reverse_iterator iter = monitors_.rbegin();
      NotifyMonitorRemoved(*iter);
      monitors_.erase(iter.base() - 1);
    }
  }
}

RootWindow* MultiMonitorManager::CreateRootWindowForMonitor(
    const Monitor& monitor) {
  RootWindow* root_window = new RootWindow(monitor.bounds_in_pixel());
  // No need to remove RootWindowObserver because
  // the MonitorManager object outlives RootWindow objects.
  root_window->AddRootWindowObserver(this);
  root_window->SetProperty(kMonitorIdKey, monitor.id());
  root_window->Init();
  return root_window;
}

const Monitor& MultiMonitorManager::GetMonitorAt(size_t index) {
  return index < monitors_.size() ? monitors_[index] : GetInvalidMonitor();
}

size_t MultiMonitorManager::GetNumMonitors() const {
  return monitors_.size();
}

const Monitor& MultiMonitorManager::GetMonitorNearestWindow(
    const Window* window) const {
  if (!window) {
    MultiMonitorManager* manager = const_cast<MultiMonitorManager*>(this);
    return manager->GetMonitorAt(0);
  }
  const RootWindow* root = window->GetRootWindow();
  MultiMonitorManager* that = const_cast<MultiMonitorManager*>(this);
  return root ?
      that->FindMonitorById(root->GetProperty(kMonitorIdKey)) :
      GetInvalidMonitor();
}

const Monitor& MultiMonitorManager::GetMonitorNearestPoint(
    const gfx::Point& point) const {
  // TODO(oshima): For m19, mouse is constrained within
  // the primary window.
  MultiMonitorManager* manager = const_cast<MultiMonitorManager*>(this);
  return manager->GetMonitorAt(0);
}

void MultiMonitorManager::OnRootWindowResized(const aura::RootWindow* root,
                                              const gfx::Size& old_size) {
  if (!use_fullscreen_host_window()) {
    int monitor_id = root->GetProperty(kMonitorIdKey);
    Monitor& monitor = FindMonitorById(monitor_id);
    monitor.SetSize(root->GetHostSize());
    NotifyBoundsChanged(monitor);
  }
}

bool MultiMonitorManager::UpdateWorkAreaOfMonitorNearestWindow(
    const aura::Window* window,
    const gfx::Insets& insets) {
  const RootWindow* root = window->GetRootWindow();
  Monitor& monitor = FindMonitorById(root->GetProperty(kMonitorIdKey));
  gfx::Rect old_work_area = monitor.work_area();
  monitor.UpdateWorkAreaFromInsets(insets);
  return old_work_area != monitor.work_area();
}

void MultiMonitorManager::Init() {
  // TODO(oshima): Move this logic to MonitorChangeObserver.
  const string size_str = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
      switches::kAuraHostWindowSize);
  vector<string> parts;
  base::SplitString(size_str, ',', &parts);
  for (vector<string>::const_iterator iter = parts.begin();
       iter != parts.end(); ++iter) {
    monitors_.push_back(CreateMonitorFromSpec(*iter));
  }
  if (monitors_.empty())
    monitors_.push_back(CreateMonitorFromSpec("" /* default */));
}

void MultiMonitorManager::AddRemoveMonitorImpl() {
  std::vector<Monitor> new_monitors;
  if (monitors_.size() > 1) {
    // Remove if there is more than one monitor.
    int count = monitors_.size() - 1;
    for (Monitors::const_iterator iter = monitors_.begin(); count-- > 0; ++iter)
      new_monitors.push_back(*iter);
  } else {
    // Add if there is only one monitor.
    new_monitors.push_back(monitors_[0]);
    new_monitors.push_back(CreateMonitorFromSpec("50+50-1280x768"));
  }
  if (new_monitors.size())
    OnNativeMonitorsChanged(new_monitors);
}

void MultiMonitorManager::CycleMonitorImpl() {
  if (monitors_.size() > 1) {
    std::vector<Monitor> new_monitors;
    for (Monitors::const_iterator iter = monitors_.begin() + 1;
         iter != monitors_.end(); ++iter) {
      gfx::Monitor monitor = *iter;
      new_monitors.push_back(monitor);
    }
    new_monitors.push_back(monitors_.front());
    OnNativeMonitorsChanged(new_monitors);
  }
}

void MultiMonitorManager::ScaleMonitorImpl() {
  if (monitors_.size() > 0) {
    std::vector<Monitor> new_monitors;
    for (Monitors::const_iterator iter = monitors_.begin();
         iter != monitors_.end(); ++iter) {
      gfx::Monitor monitor = *iter;
      float factor = monitor.device_scale_factor() == 1.0f ? 2.0f : 1.0f;
      monitor.SetScaleAndBounds(
          factor, gfx::Rect(monitor.bounds_in_pixel().origin(),
                            monitor.size().Scale(factor)));
      new_monitors.push_back(monitor);
    }
    OnNativeMonitorsChanged(new_monitors);
  }
}

gfx::Monitor& MultiMonitorManager::FindMonitorById(int id) {
  for (Monitors::iterator iter = monitors_.begin();
       iter != monitors_.end(); ++iter) {
    if ((*iter).id() == id)
      return *iter;
  }
  DLOG(FATAL) << "Could not find monitor by id:" << id;
  return GetInvalidMonitor();
}

}  // namespace internal
}  // namespace ash