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

#ifndef CHROMEOS_DISPLAY_OUTPUT_CONFIGURATOR_H_
#define CHROMEOS_DISPLAY_OUTPUT_CONFIGURATOR_H_

#include <map>
#include <string>
#include <vector>

#include "base/basictypes.h"
#include "base/event_types.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/observer_list.h"
#include "base/timer/timer.h"
#include "chromeos/chromeos_export.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

// Forward declarations for Xlib and Xrandr.
// This is so unused X definitions don't pollute the namespace.
typedef unsigned long XID;
typedef XID RROutput;
typedef XID RRCrtc;
typedef XID RRMode;

namespace chromeos {

// Used to describe the state of a multi-display configuration.
enum OutputState {
  STATE_INVALID,
  STATE_HEADLESS,
  STATE_SINGLE,
  STATE_DUAL_MIRROR,
  STATE_DUAL_EXTENDED,
};

// Video output types.
enum OutputType {
  OUTPUT_TYPE_NONE = 0,
  OUTPUT_TYPE_UNKNOWN = 1 << 0,
  OUTPUT_TYPE_INTERNAL = 1 << 1,
  OUTPUT_TYPE_VGA = 1 << 2,
  OUTPUT_TYPE_HDMI = 1 << 3,
  OUTPUT_TYPE_DVI = 1 << 4,
  OUTPUT_TYPE_DISPLAYPORT = 1 << 5,
  OUTPUT_TYPE_NETWORK = 1 << 6,
};

// Content protection methods applied on video output.
enum OutputProtectionMethod {
  OUTPUT_PROTECTION_METHOD_NONE = 0,
  OUTPUT_PROTECTION_METHOD_HDCP = 1 << 0,
};

// HDCP protection state.
enum HDCPState {
  HDCP_STATE_UNDESIRED,
  HDCP_STATE_DESIRED,
  HDCP_STATE_ENABLED
};

// This class interacts directly with the underlying Xrandr API to manipulate
// CTRCs and Outputs.
class CHROMEOS_EXPORT OutputConfigurator
    : public base::MessageLoop::Dispatcher,
      public base::MessagePumpObserver {
 public:
  typedef uint64_t OutputProtectionClientId;
  static const OutputProtectionClientId kInvalidClientId = 0;

  struct ModeInfo {
    ModeInfo();
    ModeInfo(int width, int height, bool interlaced, float refresh_rate);

    int width;
    int height;
    bool interlaced;
    float refresh_rate;
  };

  typedef std::map<RRMode, ModeInfo> ModeInfoMap;

  struct CoordinateTransformation {
    // Initialized to the identity transformation.
    CoordinateTransformation();

    float x_scale;
    float x_offset;
    float y_scale;
    float y_offset;
  };

  // Information about an output's current state.
  struct OutputSnapshot {
    OutputSnapshot();
    ~OutputSnapshot();

    RROutput output;

    // CRTC that should be used for this output. Not necessarily the CRTC
    // that XRandR reports is currently being used.
    RRCrtc crtc;

    // Mode currently being used by the output.
    RRMode current_mode;

    // "Best" mode supported by the output.
    RRMode native_mode;

    // Mode used when displaying the same desktop on multiple outputs.
    RRMode mirror_mode;

    // User-selected mode for the output.
    RRMode selected_mode;

    // Output's origin on the framebuffer.
    int x;
    int y;

    // Output's physical dimensions.
    uint64 width_mm;
    uint64 height_mm;

    // TODO(kcwu): Remove this. Check type == OUTPUT_TYPE_INTERNAL instead.
    bool is_internal;
    bool is_aspect_preserving_scaling;

    // The type of output.
    OutputType type;

    // Map from mode IDs to details about the corresponding modes.
    ModeInfoMap mode_infos;

    // XInput device ID or 0 if this output isn't a touchscreen.
    int touch_device_id;

    CoordinateTransformation transform;

    // Display id for this output.
    int64 display_id;

    bool has_display_id;

    // This output's index in the array returned by XRandR. Stable even as
    // outputs are connected or disconnected.
    int index;
  };

  class Observer {
   public:
    virtual ~Observer() {}

    // Called after the display mode has been changed. |output| contains the
    // just-applied configuration. Note that the X server is no longer grabbed
    // when this method is called, so the actual configuration could've changed
    // already.
    virtual void OnDisplayModeChanged(
        const std::vector<OutputSnapshot>& outputs) {}

    // Called after a display mode change attempt failed. |failed_new_state| is
    // the new state which the system failed to enter.
    virtual void OnDisplayModeChangeFailed(OutputState failed_new_state) {}
  };

  // Interface for classes that make decisions about which output state
  // should be used.
  class StateController {
   public:
    virtual ~StateController() {}

    // Called when displays are detected.
    virtual OutputState GetStateForDisplayIds(
        const std::vector<int64>& display_ids) const = 0;

    // Queries the resolution (|width|x|height|) in pixels
    // to select output mode for the given display id.
    virtual bool GetResolutionForDisplayId(int64 display_id,
                                           int* width,
                                           int* height) const = 0;
  };

  // Interface for classes that implement software based mirroring.
  class SoftwareMirroringController {
   public:
    virtual ~SoftwareMirroringController() {}

    // Called when the hardware mirroring failed.
    virtual void SetSoftwareMirroring(bool enabled) = 0;
  };

  // Interface for classes that perform actions on behalf of OutputController.
  class Delegate {
   public:
    virtual ~Delegate() {}

    // Initializes the XRandR extension, saving the base event ID to
    // |event_base|.
    virtual void InitXRandRExtension(int* event_base) = 0;

    // Tells XRandR to update its configuration in response to |event|, an
    // RRScreenChangeNotify event.
    virtual void UpdateXRandRConfiguration(const base::NativeEvent& event) = 0;

    // Grabs the X server and refreshes XRandR-related resources.  While
    // the server is grabbed, other clients are blocked.  Must be balanced
    // by a call to UngrabServer().
    virtual void GrabServer() = 0;

    // Ungrabs the server and frees XRandR-related resources.
    virtual void UngrabServer() = 0;

    // Flushes all pending requests and waits for replies.
    virtual void SyncWithServer() = 0;

    // Sets the window's background color to |color_argb|.
    virtual void SetBackgroundColor(uint32 color_argb) = 0;

    // Enables DPMS and forces it to the "on" state.
    virtual void ForceDPMSOn() = 0;

    // Returns information about the current outputs. This method may block for
    // 60 milliseconds or more. The returned outputs are not fully initialized;
    // the rest of the work happens in
    // OutputConfigurator::UpdateCachedOutputs().
    virtual std::vector<OutputSnapshot> GetOutputs() = 0;

    // Adds |mode| to |output|.
    virtual void AddOutputMode(RROutput output, RRMode mode) = 0;

    // Calls XRRSetCrtcConfig() with the given options but some of our default
    // output count and rotation arguments. Returns true on success.
    virtual bool ConfigureCrtc(RRCrtc crtc,
                               RRMode mode,
                               RROutput output,
                               int x,
                               int y) = 0;

    // Called to set the frame buffer (underlying XRR "screen") size.  Has
    // a side-effect of disabling all CRTCs.
    virtual void CreateFrameBuffer(
        int width,
        int height,
        const std::vector<OutputConfigurator::OutputSnapshot>& outputs) = 0;

    // Configures XInput's Coordinate Transformation Matrix property.
    // |touch_device_id| the ID of the touchscreen device to configure.
    // |ctm| contains the desired transformation parameters.  The offsets
    // in it should be normalized so that 1 corresponds to the X or Y axis
    // size for the corresponding offset.
    virtual void ConfigureCTM(int touch_device_id,
                              const CoordinateTransformation& ctm) = 0;

    // Sends a D-Bus message to the power manager telling it that the
    // machine is or is not projecting.
    virtual void SendProjectingStateToPowerManager(bool projecting) = 0;

    // Gets HDCP state of output.
    virtual bool GetHDCPState(RROutput id, HDCPState* state) = 0;

    // Sets HDCP state of output.
    virtual bool SetHDCPState(RROutput id, HDCPState state) = 0;
  };

  // Helper class used by tests.
  class TestApi {
   public:
    TestApi(OutputConfigurator* configurator, int xrandr_event_base)
        : configurator_(configurator),
          xrandr_event_base_(xrandr_event_base) {}
    ~TestApi() {}

    const std::vector<OutputSnapshot>& cached_outputs() const {
      return configurator_->cached_outputs_;
    }

    // Dispatches an RRScreenChangeNotify event to |configurator_|.
    void SendScreenChangeEvent();

    // Dispatches an RRNotify_OutputChange event to |configurator_|.
    void SendOutputChangeEvent(RROutput output,
                               RRCrtc crtc,
                               RRMode mode,
                               bool connected);

    // If |configure_timer_| is started, stops the timer, runs
    // ConfigureOutputs(), and returns true; returns false otherwise.
    bool TriggerConfigureTimeout();

   private:
    OutputConfigurator* configurator_;  // not owned

    int xrandr_event_base_;

    DISALLOW_COPY_AND_ASSIGN(TestApi);
  };

  // Flags that can be passed to SetDisplayPower().
  static const int kSetDisplayPowerNoFlags                     = 0;
  // Configure displays even if the passed-in state matches |power_state_|.
  static const int kSetDisplayPowerForceProbe                  = 1 << 0;
  // Do not change the state if multiple displays are connected or if the
  // only connected display is external.
  static const int kSetDisplayPowerOnlyIfSingleInternalDisplay = 1 << 1;

  // Gap between screens so cursor at bottom of active display doesn't
  // partially appear on top of inactive display. Higher numbers guard
  // against larger cursors, but also waste more memory.
  // For simplicity, this is hard-coded to avoid the complexity of always
  // determining the DPI of the screen and rationalizing which screen we
  // need to use for the DPI calculation.
  // See crbug.com/130188 for initial discussion.
  static const int kVerticalGap = 60;

  // Returns a pointer to the ModeInfo struct in |output| corresponding to
  // |mode|, or NULL if the struct isn't present.
  static const ModeInfo* GetModeInfo(const OutputSnapshot& output,
                                     RRMode mode);

  // Returns the mode within |output| that matches the given size with highest
  // refresh rate. Returns None if no matching output was found.
  static RRMode FindOutputModeMatchingSize(const OutputSnapshot& output,
                                           int width,
                                           int height);

  OutputConfigurator();
  virtual ~OutputConfigurator();

  OutputState output_state() const { return output_state_; }
  DisplayPowerState power_state() const { return power_state_; }

  void set_state_controller(StateController* controller) {
    state_controller_ = controller;
  }
  void set_mirroring_controller(SoftwareMirroringController* controller) {
    mirroring_controller_ = controller;
  }

  // Replaces |delegate_| with |delegate| and sets |configure_display_| to
  // true.  Should be called before Init().
  void SetDelegateForTesting(scoped_ptr<Delegate> delegate);

  // Sets the initial value of |power_state_|.  Must be called before Start().
  void SetInitialDisplayPower(DisplayPowerState power_state);

  // Initialization, must be called right after constructor.
  // |is_panel_fitting_enabled| indicates hardware panel fitting support.
  void Init(bool is_panel_fitting_enabled);

  // Does initial configuration of displays during startup.
  // If |background_color_argb| is non zero and there are multiple displays,
  // OutputConfigurator sets the background color of X's RootWindow to this
  // color.
  void Start(uint32 background_color_argb);

  // Stop handling display configuration events/requests.
  void Stop();

  // Called when powerd notifies us that some set of displays should be turned
  // on or off.  This requires enabling or disabling the CRTC associated with
  // the display(s) in question so that the low power state is engaged.
  // |flags| contains bitwise-or-ed kSetDisplayPower* values.
  bool SetDisplayPower(DisplayPowerState power_state, int flags);

  // Force switching the display mode to |new_state|. Returns false if
  // switching failed (possibly because |new_state| is invalid for the
  // current set of connected outputs).
  bool SetDisplayMode(OutputState new_state);

  // Called when an RRNotify event is received.  The implementation is
  // interested in the cases of RRNotify events which correspond to output
  // add/remove events.  Note that Output add/remove events are sent in response
  // to our own reconfiguration operations so spurious events are common.
  // Spurious events will have no effect.
  virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE;

  // Overridden from base::MessagePumpObserver:
  virtual base::EventStatus WillProcessEvent(
      const base::NativeEvent& event) OVERRIDE;
  virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE;

  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  // Sets all the displays into pre-suspend mode; usually this means
  // configure them for their resume state. This allows faster resume on
  // machines where display configuration is slow.
  void SuspendDisplays();

  // Reprobes displays to handle changes made while the system was
  // suspended.
  void ResumeDisplays();

  const std::map<int, float>& GetMirroredDisplayAreaRatioMap() {
    return mirrored_display_area_ratio_map_;
  }

  // Configure outputs with |kConfigureDelayMs| delay,
  // so that time-consuming ConfigureOutputs() won't be called multiple times.
  void ScheduleConfigureOutputs();

  // Registers a client for output protection and requests a client id. Returns
  // 0 if requesting failed.
  OutputProtectionClientId RegisterOutputProtectionClient();

  // Unregisters the client.
  void UnregisterOutputProtectionClient(OutputProtectionClientId client_id);

  // Queries link status and protection status.
  // |link_mask| is the type of connected output links, which is a bitmask of
  // OutputType values. |protection_mask| is the desired protection methods,
  // which is a bitmask of the OutputProtectionMethod values.
  // Returns true on success.
  bool QueryOutputProtectionStatus(
      OutputProtectionClientId client_id,
      int64 display_id,
      uint32_t* link_mask,
      uint32_t* protection_mask);

  // Requests the desired protection methods.
  // |protection_mask| is the desired protection methods, which is a bitmask
  // of the OutputProtectionMethod values.
  // Returns true when the protection request has been made.
  bool EnableOutputProtection(
      OutputProtectionClientId client_id,
      int64 display_id,
      uint32_t desired_protection_mask);

 private:
  // Mapping a display_id to a protection request bitmask.
  typedef std::map<int64, uint32_t> DisplayProtections;
  // Mapping a client to its protection request.
  typedef std::map<OutputProtectionClientId,
                   DisplayProtections> ProtectionRequests;

  // Updates |cached_outputs_| to contain currently-connected outputs. Calls
  // |delegate_->GetOutputs()| and then does additional work, like finding the
  // mirror mode and setting user-preferred modes. Note that the server must be
  // grabbed via |delegate_->GrabServer()| first.
  void UpdateCachedOutputs();

  // Helper method for UpdateCachedOutputs() that initializes the passed-in
  // outputs' |mirror_mode| fields by looking for a mode in |internal_output|
  // and |external_output| having the same resolution. Returns false if a shared
  // mode wasn't found or created.
  //
  // |try_panel_fitting| allows creating a panel-fitting mode for
  // |internal_output| instead of only searching for a matching mode (note that
  // it may lead to a crash if |internal_info| is not capable of panel fitting).
  //
  // |preserve_aspect| limits the search/creation only to the modes having the
  // native aspect ratio of |external_output|.
  bool FindMirrorMode(OutputSnapshot* internal_output,
                      OutputSnapshot* external_output,
                      bool try_panel_fitting,
                      bool preserve_aspect);

  // Configures outputs.
  void ConfigureOutputs();

  // Notifies observers about an attempted state change.
  void NotifyObservers(bool success, OutputState attempted_state);

  // Switches to the state specified in |output_state| and |power_state|.
  // If the hardware mirroring failed and |mirroring_controller_| is set,
  // it switches to |STATE_DUAL_EXTENDED| and calls |SetSoftwareMirroring()|
  // to enable software based mirroring.
  // On success, updates |output_state_|, |power_state_|, and |cached_outputs_|
  // and returns true.
  bool EnterStateOrFallBackToSoftwareMirroring(
      OutputState output_state,
      DisplayPowerState power_state);

  // Switches to the state specified in |output_state| and |power_state|.
  // On success, updates |output_state_|, |power_state_|, and
  // |cached_outputs_| and returns true.
  bool EnterState(OutputState output_state, DisplayPowerState power_state);

  // Returns the output state that should be used with |cached_outputs_| while
  // in |power_state|.
  OutputState ChooseOutputState(DisplayPowerState power_state) const;

  // Computes the relevant transformation for mirror mode.
  // |output| is the output on which mirror mode is being applied.
  // Returns the transformation or identity if computations fail.
  CoordinateTransformation GetMirrorModeCTM(
      const OutputConfigurator::OutputSnapshot& output);

  // Computes the relevant transformation for extended mode.
  // |output| is the output on which extended mode is being applied.
  // |width| and |height| are the width and height of the combined framebuffer.
  // Returns the transformation or identity if computations fail.
  CoordinateTransformation GetExtendedModeCTM(
      const OutputConfigurator::OutputSnapshot& output,
      int framebuffer_width,
      int frame_buffer_height);

  // Returns the ratio between mirrored mode area and native mode area:
  // (mirror_mode_width * mirrow_mode_height) / (native_width * native_height)
  float GetMirroredDisplayAreaRatio(
      const OutputConfigurator::OutputSnapshot& output);

  // Applies output protections according to requests.
  bool ApplyProtections(const DisplayProtections& requests);

  StateController* state_controller_;
  SoftwareMirroringController* mirroring_controller_;
  scoped_ptr<Delegate> delegate_;

  // Used to enable modes which rely on panel fitting.
  bool is_panel_fitting_enabled_;

  // Key of the map is the touch display's id, and the value of the map is the
  // touch display's area ratio in mirror mode defined as :
  // mirror_mode_area / native_mode_area.
  // This is used for scaling touch event's radius when the touch display is in
  // mirror mode :
  // new_touch_radius = sqrt(area_ratio) * old_touch_radius
  std::map<int, float> mirrored_display_area_ratio_map_;

  // This is detected by the constructor to determine whether or not we should
  // be enabled.  If we aren't running on ChromeOS, we can't assume that the
  // Xrandr X11 extension is supported.
  // If this flag is set to false, any attempts to change the output
  // configuration to immediately fail without changing the state.
  bool configure_display_;

  // The base of the event numbers used to represent XRandr events used in
  // decoding events regarding output add/remove.
  int xrandr_event_base_;

  // The current display state.
  OutputState output_state_;

  // The current power state.
  DisplayPowerState power_state_;

  // Most-recently-used output configuration. Note that the actual
  // configuration changes asynchronously.
  std::vector<OutputSnapshot> cached_outputs_;

  ObserverList<Observer> observers_;

  // The timer to delay configuring outputs. See also the comments in
  // Dispatch().
  scoped_ptr<base::OneShotTimer<OutputConfigurator> > configure_timer_;

  // Id for next output protection client.
  OutputProtectionClientId next_output_protection_client_id_;

  // Output protection requests of each client.
  ProtectionRequests client_protection_requests_;

  DISALLOW_COPY_AND_ASSIGN(OutputConfigurator);
};

typedef std::vector<OutputConfigurator::OutputSnapshot> OutputSnapshotList;

}  // namespace chromeos

#endif  // CHROMEOS_DISPLAY_OUTPUT_CONFIGURATOR_H_