/*
  Copyright 2008 Google Inc.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#include "floating_main_view_decorator.h"

#include <string>
#include <algorithm>
#include "logger.h"
#include "common.h"
#include "elements.h"
#include "gadget_consts.h"
#include "gadget.h"
#include "signals.h"
#include "slot.h"
#include "view.h"
#include "view_host_interface.h"
#include "div_element.h"
#include "img_element.h"
#include "messages.h"
#include "menu_interface.h"

namespace ggadget {

static const double kVDMainBorderWidth = 6;
static const double kVDMainBackgroundOpacity = 0.618;

class FloatingMainViewDecorator::Impl {
  struct ResizeBorderInfo {
    double x;           // relative x
    double y;           // relative y
    double pin_x;       // relative pin x
    double pin_y;       // relative pin y
    double width;       // pixel width < 0 means relative width = 1.0
    double height;      // pixel height < 0 means relative height = 1.0
    ViewInterface::CursorType cursor;
    ViewInterface::HitTest hittest;
  };

  enum ResizeBorderId {
    RESIZE_TOP = 0,
    RESIZE_LEFT,
    RESIZE_BOTTOM,
    RESIZE_RIGHT,
    RESIZE_TOP_LEFT,
    RESIZE_TOP_RIGHT,
    RESIZE_BOTTOM_LEFT,
    RESIZE_BOTTOM_RIGHT,
    NUMBER_OF_RESIZE_BORDERS
  };

  static const ResizeBorderInfo kResizeBordersInfo[];

 public:
  Impl(FloatingMainViewDecorator *owner, bool transparent_background)
    : owner_(owner),
      show_decorator_(false),
      transparent_(transparent_background),
      background_(new ImgElement(NULL, owner, NULL)),
      resize_border_(new DivElement(NULL, owner, NULL)),
      zoom_corner_(new DivElement(NULL, owner, NULL)) {
    background_->SetSrc(Variant(transparent_ ? kVDMainBackgroundTransparent :
                                kVDMainBackground));
    background_->SetOpacity(transparent_ ? 1 : kVDMainBackgroundOpacity);
    background_->SetVisible(false);
    background_->SetStretchMiddle(true);
    background_->EnableCanvasCache(true);
    background_->SetEnabled(false);
    owner->InsertDecoratorElement(background_, true);

    // Setup resize borders.
    for (size_t i = 0; i < NUMBER_OF_RESIZE_BORDERS; ++i) {
      BasicElement *elm =
          new BasicElement(resize_border_, owner, NULL, NULL, false);
      const ResizeBorderInfo *info = &kResizeBordersInfo[i];
      elm->SetRelativeX(info->x);
      elm->SetRelativeY(info->y);
      elm->SetRelativePinX(info->pin_x);
      elm->SetRelativePinY(info->pin_y);
      if (info->width > 0)
        elm->SetPixelWidth(info->width);
      else
        elm->SetRelativeWidth(1);
      if (info->height > 0)
        elm->SetPixelHeight(info->height);
      else
        elm->SetRelativeHeight(1);
      elm->SetCursor(info->cursor);
      elm->SetHitTest(info->hittest);
      resize_border_->GetChildren()->InsertElement(elm, NULL);
    }
    resize_border_->SetVisible(false);
    resize_border_->SetEnabled(true);
    owner->InsertDecoratorElement(resize_border_, true);

    // Setup zoom corner
    ImgElement *corner_img = new ImgElement(zoom_corner_, owner, NULL);
    corner_img->SetSrc(Variant(kVDBottomRightCorner));
    corner_img->SetVisible(true);
    corner_img->SetEnabled(false);
    corner_img->SetHitTest(ViewInterface::HT_BOTTOMRIGHT);
    corner_img->SetCursor(ViewInterface::CURSOR_SIZENWSE);
    zoom_corner_->GetChildren()->InsertElement(corner_img, NULL);
    zoom_corner_->SetVisible(false);
    zoom_corner_->SetPixelWidth(corner_img->GetSrcWidth());
    zoom_corner_->SetPixelHeight(corner_img->GetSrcHeight());
    zoom_corner_->SetRelativeX(1);
    zoom_corner_->SetRelativeY(1);
    zoom_corner_->SetRelativePinX(1);
    zoom_corner_->SetRelativePinY(1);
    zoom_corner_->SetHitTest(ViewInterface::HT_BOTTOMRIGHT);
    zoom_corner_->SetCursor(ViewInterface::CURSOR_SIZENWSE);
    owner->InsertDecoratorElement(zoom_corner_, true);

    if (!transparent_) {
      background_->SetVisible(true);
      background_->SetPixelX(0);
      background_->SetPixelY(0);
      background_->SetRelativeWidth(1);
      background_->SetRelativeHeight(1);
      resize_border_->SetPixelX(0);
      resize_border_->SetPixelY(0);
      resize_border_->SetRelativeWidth(1);
      resize_border_->SetRelativeHeight(1);
    }
  }

  void UpdateResizeBorder(bool horizontal_resizable, bool vertical_resizable) {
    Elements *children = resize_border_->GetChildren();
    bool both = horizontal_resizable && vertical_resizable;
    children->GetItemByIndex(RESIZE_TOP)->SetVisible(vertical_resizable);
    children->GetItemByIndex(RESIZE_BOTTOM)->SetVisible(vertical_resizable);
    children->GetItemByIndex(RESIZE_LEFT)->SetVisible(horizontal_resizable);
    children->GetItemByIndex(RESIZE_RIGHT)->SetVisible(horizontal_resizable);
    children->GetItemByIndex(RESIZE_TOP_LEFT)->SetVisible(both);
    children->GetItemByIndex(RESIZE_TOP_RIGHT)->SetVisible(both);
    children->GetItemByIndex(RESIZE_BOTTOM_LEFT)->SetVisible(both);
    children->GetItemByIndex(RESIZE_BOTTOM_RIGHT)->SetVisible(both);
  }

  void UpdateDecoratorVisibility() {
    if (show_decorator_) {
      ResizableMode resizable = owner_->GetChildViewResizable();
      if (resizable == RESIZABLE_TRUE || owner_->IsMinimized()) {
        // background will always be shown if it's not transparent.
        if (transparent_)
          background_->SetVisible(true);
        resize_border_->SetVisible(true);
        zoom_corner_->SetVisible(false);
        UpdateResizeBorder(true, resizable == RESIZABLE_TRUE);
      } else {
        // background for transparent mode is only visible when view is
        // resizable.
        if (transparent_)
          background_->SetVisible(false);
        resize_border_->SetVisible(false);
        zoom_corner_->SetVisible(true);
      }
    } else {
      if (transparent_)
        background_->SetVisible(false);
      resize_border_->SetVisible(false);
      zoom_corner_->SetVisible(false);
    }
  }

  void CollapseExpandMenuCallback(const char *) {
    owner_->SetMinimized(!owner_->IsMinimized());
  }

  void DockMenuCallback(const char *) {
    on_dock_signal_();
  }

  void OnZoomMenuCallback(const char *, double zoom) {
    owner_->SetChildViewScale(zoom == 0 ? 1.0 : zoom);
  }

 public:
  FloatingMainViewDecorator *owner_;

  bool show_decorator_;
  bool transparent_;
  ImgElement *background_;
  DivElement *resize_border_;
  DivElement *zoom_corner_;

  Signal0<void> on_dock_signal_;
};

const FloatingMainViewDecorator::Impl::ResizeBorderInfo
FloatingMainViewDecorator::Impl::kResizeBordersInfo[] = {
  { 0, 0, 0, 0, -1, kVDMainBorderWidth,
    ViewInterface::CURSOR_SIZENS, ViewInterface::HT_TOP },
  { 0, 0, 0, 0, kVDMainBorderWidth, -1,
    ViewInterface::CURSOR_SIZEWE, ViewInterface::HT_LEFT },
  { 0, 1, 0, 1, -1, kVDMainBorderWidth,
    ViewInterface::CURSOR_SIZENS, ViewInterface::HT_BOTTOM },
  { 1, 0, 1, 0, kVDMainBorderWidth, -1,
    ViewInterface::CURSOR_SIZEWE, ViewInterface::HT_RIGHT },
  { 0, 0, 0, 0, kVDMainBorderWidth, kVDMainBorderWidth,
    ViewInterface::CURSOR_SIZENWSE, ViewInterface::HT_TOPLEFT },
  { 1, 0, 1, 0, kVDMainBorderWidth, kVDMainBorderWidth,
    ViewInterface::CURSOR_SIZENESW, ViewInterface::HT_TOPRIGHT },
  { 0, 1, 0, 1, kVDMainBorderWidth, kVDMainBorderWidth,
    ViewInterface::CURSOR_SIZENESW, ViewInterface::HT_BOTTOMLEFT },
  { 1, 1, 1, 1, kVDMainBorderWidth, kVDMainBorderWidth,
    ViewInterface::CURSOR_SIZENWSE, ViewInterface::HT_BOTTOMRIGHT }
};

FloatingMainViewDecorator::FloatingMainViewDecorator(
    ViewHostInterface *host, bool transparent_background)
  : MainViewDecoratorBase(host, "main_view_floating", false, false,
                          transparent_background),
    impl_(new Impl(this, transparent_background)) {
}

FloatingMainViewDecorator::~FloatingMainViewDecorator() {
  delete impl_;
  impl_ = NULL;
}

Connection *FloatingMainViewDecorator::ConnectOnDock(Slot0<void> *slot) {
  return impl_->on_dock_signal_.Connect(slot);
}

void FloatingMainViewDecorator::SetResizable(ResizableMode resizable) {
  MainViewDecoratorBase::SetResizable(resizable);
  impl_->UpdateDecoratorVisibility();
}

void FloatingMainViewDecorator::DoLayout() {
  // Call through parent's DoLayout();
  MainViewDecoratorBase::DoLayout();

  if (impl_->transparent_) {
    double top, left, bottom, right;
    GetMargins(&top, &left, &bottom, &right);
    double width = GetWidth();
    double height = GetHeight();
    double margin = 0;
    if (GetChildViewResizable() == RESIZABLE_TRUE || IsMinimized())
      margin = kVDMainBorderWidth;

    impl_->background_->SetPixelX(left - margin);
    impl_->background_->SetPixelY(top - margin);
    impl_->background_->SetPixelWidth(width - left - right + margin * 2);
    impl_->background_->SetPixelHeight(height - top - bottom + margin * 2);
    impl_->resize_border_->SetPixelX(left - margin);
    impl_->resize_border_->SetPixelY(top - margin);
    impl_->resize_border_->SetPixelWidth(width - left - right + margin * 2);
    impl_->resize_border_->SetPixelHeight(height - top - bottom + margin * 2);
  }

  impl_->UpdateDecoratorVisibility();
}

void FloatingMainViewDecorator::GetMargins(double *top, double *left,
                                           double *bottom, double *right) const {
  ButtonBoxPosition button_position = GetButtonBoxPosition();
  ButtonBoxOrientation button_orientation = GetButtonBoxOrientation();

  *top = kVDMainBorderWidth;
  *left = kVDMainBorderWidth;
  *bottom = kVDMainBorderWidth;
  *right = kVDMainBorderWidth;

  double *btn_edge = NULL;
  double btn_margin = 0;
  double btn_width, btn_height;
  GetButtonBoxSize(&btn_width, &btn_height);
  if (button_orientation == HORIZONTAL) {
    btn_margin = btn_height;
    if (button_position == TOP_LEFT || button_position == TOP_RIGHT)
      btn_edge = top;
    else
      btn_edge = bottom;
  } else {
    btn_margin = btn_width;
    if (button_position == TOP_LEFT || button_position == BOTTOM_LEFT)
      btn_edge = left;
    else
      btn_edge = right;
  }

  if (impl_->transparent_)
    *btn_edge += btn_margin;
  else if (!IsMinimized())
    *btn_edge = btn_margin;
}

void FloatingMainViewDecorator::OnAddDecoratorMenuItems(MenuInterface *menu) {
  static const struct {
    const char *label;
    double zoom;
  } kZoomMenuItems[] = {
    { "MENU_ITEM_AUTO_FIT", 0 },
    { "MENU_ITEM_50P", 0.5 },
    { "MENU_ITEM_75P", 0.75 },
    { "MENU_ITEM_100P", 1.0 },
    { "MENU_ITEM_125P", 1.25 },
    { "MENU_ITEM_150P", 1.50 },
    { "MENU_ITEM_175P", 1.75 },
    { "MENU_ITEM_200P", 2.0 },
  };
  static const int kNumZoomMenuItems = 8;

  int priority = MenuInterface::MENU_ITEM_PRI_DECORATOR;
  menu->AddItem(
      GM_(IsMinimized() ? "MENU_ITEM_EXPAND" : "MENU_ITEM_COLLAPSE"), 0, 0,
      NewSlot(impl_, &Impl::CollapseExpandMenuCallback), priority);

  if (impl_->on_dock_signal_.HasActiveConnections()) {
    menu->AddItem(GM_("MENU_ITEM_DOCK_TO_SIDEBAR"), 0, 0,
                  NewSlot(impl_, &Impl::DockMenuCallback), priority);
  }

  if (!IsMinimized() && !IsPoppedOut()) {
    double scale = GetChildViewScale();
    int flags[kNumZoomMenuItems];
    bool has_checked = false;

    for (int i = 0; i < kNumZoomMenuItems; ++i) {
      flags[i] = 0;
      if (kZoomMenuItems[i].zoom == scale) {
        flags[i] = MenuInterface::MENU_ITEM_FLAG_CHECKED;
        has_checked = true;
      }
    }

    // Check "Auto Fit" item if the current scale doesn't match with any
    // other menu items.
    if (!has_checked)
      flags[0] = MenuInterface::MENU_ITEM_FLAG_CHECKED;

    MenuInterface *zoom = menu->AddPopup(GM_("MENU_ITEM_ZOOM"), priority);
    for (int i = 0; i < kNumZoomMenuItems; ++i) {
      zoom->AddItem(GM_(kZoomMenuItems[i].label), flags[i], 0,
                    NewSlot(impl_, &Impl::OnZoomMenuCallback,
                            kZoomMenuItems[i].zoom), priority);
    }
  }

  MainViewDecoratorBase::OnAddDecoratorMenuItems(menu);
}

void FloatingMainViewDecorator::OnShowDecorator() {
  impl_->show_decorator_ = true;
  impl_->UpdateDecoratorVisibility();
  SetButtonBoxVisible(true);
  GetViewHost()->EnableInputShapeMask(false);
}

void FloatingMainViewDecorator::OnHideDecorator() {
  impl_->show_decorator_ = false;
  impl_->UpdateDecoratorVisibility();
  SetButtonBoxVisible(false);
  GetViewHost()->EnableInputShapeMask(true);
}

} // namespace ggadget
