/*
 * Copyright 2018 The Android Open Source Project
 *
 * 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.
 */

#pragma once

#include <base/cancelable_callback.h>
#include <base/functional/bind.h>

#include <iostream>
#include <memory>
#include <stack>
#include <vector>

#include "avrcp_internal.h"
#include "hardware/avrcp/avrcp.h"
#include "packet/avrcp/avrcp_browse_packet.h"
#include "packet/avrcp/avrcp_packet.h"
#include "packet/avrcp/capabilities_packet.h"
#include "packet/avrcp/change_path.h"
#include "packet/avrcp/get_current_player_application_setting_value.h"
#include "packet/avrcp/get_element_attributes_packet.h"
#include "packet/avrcp/get_folder_items.h"
#include "packet/avrcp/get_item_attributes.h"
#include "packet/avrcp/get_total_number_of_items.h"
#include "packet/avrcp/list_player_application_setting_attributes.h"
#include "packet/avrcp/list_player_application_setting_values.h"
#include "packet/avrcp/play_item.h"
#include "packet/avrcp/register_notification_packet.h"
#include "packet/avrcp/set_addressed_player.h"
#include "packet/avrcp/set_browsed_player.h"
#include "packet/avrcp/set_player_application_setting_value.h"
#include "packet/avrcp/vendor_packet.h"
#include "profile/avrcp/media_id_map.h"
#include "types/raw_address.h"

namespace bluetooth {
namespace avrcp {

/**
 * A class representing a connection with a remote AVRCP device. It holds all
 * the state and message handling for the device that it represents.
 */
// TODO (apanicke): Once we move over to having the individual message
// responders for Browse and Classic AVRCP Messages move the device around via a
// weak pointer.
class Device {
public:
  /**
   * Device is friends with Avrcp::ConnectionHandler so that ConnectionHandler
   * can deliver messages to individual devices.
   */
  friend class ConnectionHandler;

  Device(const RawAddress& bdaddr, bool avrcp13_compatibility,
         base::RepeatingCallback<void(uint8_t label, bool browse,
                                      std::unique_ptr<::bluetooth::PacketBuilder> message)>
                 send_msg_cb,
         uint16_t ctrl_mtu, uint16_t browse_mtu);

  Device(const Device&) = delete;
  Device& operator=(const Device&) = delete;

  virtual ~Device() = default;

  /**
   * Gets a weak pointer to this device that is invalidated when the device is
   * disconnected.
   */
  base::WeakPtr<Device> Get();

  const RawAddress& GetAddress() const { return address_; }

  /**
   * Disconnects the AVRCP connection that this device represents.
   */
  bool Disconnect();

  /**
   * Set the status of the BIP obex client
   */
  void SetBipClientStatus(bool connected);

  /**
   * Returns true if the current device has a BIP OBEX client.
   */
  bool HasBipClient() const;

  /**
   * Returns true if the current device is silenced.
   */
  bool IsInSilenceMode() const;

  /**
   * Returns true if the current device is active.
   */
  bool IsActive() const;

  /**
   * Register the interfaces that the device uses to get information. If the
   * Volume Interface is null, then absolute volume is disabled.
   * TODO (apanicke): Add these to the constructor/factory so that each device
   * is created valid and can't be accidentally interacted with when no
   * interfaces are registered.
   */
  void RegisterInterfaces(MediaInterface* interface, A2dpInterface* a2dp_interface,
                          VolumeInterface* volume_interface,
                          PlayerSettingsInterface* player_settings_interface);

  /**
   * Set the maximum size of a AVRCP Browsing Packet. This is done after the
   * connection of the Browsing channel.
   */
  void SetBrowseMtu(uint16_t browse_mtu);

  /**
   * Notify the device that metadata, play_status, and/or queue have updated
   * via a boolean. Each boolean represents whether its respective content has
   * updated.
   */
  virtual void SendMediaUpdate(bool metadata, bool play_status, bool queue);

  /**
   * Notify the device that the available_player, addressed_player, or UIDs
   * have updated via a boolean. Each boolean represents whether its respective
   * content has updated.
   */
  virtual void SendFolderUpdate(bool available_player, bool addressed_player, bool uids);

  // TODO (apanicke): Split the message handlers into two files. One
  // for handling Browse Messages and the other for handling all other
  // messages. This prevents the .cc file from getting bloated like it is
  // now. The Device class will then become a state holder for each message
  // and all the functions in these handler classes can be static since the
  // device will be passed in. The extensions of the Device class can contain
  // any interop handling for specific messages on specific devices.

  void MessageReceived(uint8_t label, std::shared_ptr<Packet> pkt);
  void BrowseMessageReceived(uint8_t label, std::shared_ptr<BrowsePacket> pkt);
  void VendorPacketHandler(uint8_t label, std::shared_ptr<VendorPacket> pkt);

  /********************
   * MESSAGE RESPONSES
   ********************/
  // CURRENT TRACK CHANGED
  virtual void HandleTrackUpdate();
  virtual void TrackChangedNotificationResponse(uint8_t label, bool interim,
                                                std::string curr_song_id,
                                                std::vector<SongInfo> song_list);

  // GET CAPABILITY
  virtual void HandleGetCapabilities(uint8_t label,
                                     const std::shared_ptr<GetCapabilitiesRequest>& pkt);

  // REGISTER NOTIFICATION
  virtual void HandleNotification(uint8_t label,
                                  const std::shared_ptr<RegisterNotificationRequest>& pkt);

  // PLAY STATUS CHANGED
  virtual void HandlePlayStatusUpdate();

  // NOW PLAYING LIST CHANGED
  virtual void HandleNowPlayingUpdate();
  virtual void HandleNowPlayingNotificationResponse(uint8_t label, bool interim,
                                                    std::string curr_song_id,
                                                    std::vector<SongInfo> song_list);

  // PLAY POSITION CHANGED
  virtual void HandlePlayPosUpdate();
  virtual void PlaybackPosNotificationResponse(uint8_t label, bool interim, PlayStatus status);

  // GET PLAY STATUS
  virtual void GetPlayStatusResponse(uint8_t label, PlayStatus status);
  virtual void PlaybackStatusNotificationResponse(uint8_t label, bool interim, PlayStatus status);

  // PLAYER APPLICATION SETTINGS CHANGED
  virtual void HandlePlayerSettingChanged(std::vector<PlayerAttribute> attributes,
                                          std::vector<uint8_t> values);
  virtual void PlayerSettingChangedNotificationResponse(uint8_t label, bool interim,
                                                        std::vector<PlayerAttribute> attributes,
                                                        std::vector<uint8_t> values);

  // GET ELEMENT ATTRIBUTE
  // TODO (apanicke): Add a Handler function for this so if a specific device
  // needs to implement an interop fix, you only need to overload the one
  // function.
  virtual void GetElementAttributesResponse(uint8_t label,
                                            std::shared_ptr<GetElementAttributesRequest> pkt,
                                            SongInfo info);

  // AVAILABLE PLAYER CHANGED
  virtual void HandleAvailablePlayerUpdate();

  // ADDRESSED PLAYER CHANGED
  virtual void HandleAddressedPlayerUpdate();
  virtual void RejectNotification();
  virtual void AddressedPlayerNotificationResponse(uint8_t label, bool interim,
                                                   uint16_t curr_player);

  // GET FOLDER ITEMS
  virtual void HandleGetFolderItems(uint8_t label, std::shared_ptr<GetFolderItemsRequest> request);
  virtual void GetMediaPlayerListResponse(uint8_t label, std::shared_ptr<GetFolderItemsRequest> pkt,
                                          uint16_t curr_player,
                                          std::vector<MediaPlayerInfo> players);
  virtual void GetVFSListResponse(uint8_t label, std::shared_ptr<GetFolderItemsRequest> pkt,
                                  std::vector<ListItem> items);
  virtual void GetNowPlayingListResponse(uint8_t label, std::shared_ptr<GetFolderItemsRequest> pkt,
                                         std::string curr_song_id, std::vector<SongInfo> song_list);

  // GET TOTAL NUMBER OF ITEMS
  virtual void HandleGetTotalNumberOfItems(uint8_t label,
                                           std::shared_ptr<GetTotalNumberOfItemsRequest> pkt);
  virtual void GetTotalNumberOfItemsMediaPlayersResponse(uint8_t label, uint16_t curr_player,
                                                         std::vector<MediaPlayerInfo> list);
  virtual void GetTotalNumberOfItemsVFSResponse(uint8_t label, std::vector<ListItem> items);
  virtual void GetTotalNumberOfItemsNowPlayingResponse(uint8_t label, std::string curr_song_id,
                                                       std::vector<SongInfo> song_list);

  // GET ITEM ATTRIBUTES
  virtual void HandleGetItemAttributes(uint8_t label,
                                       std::shared_ptr<GetItemAttributesRequest> request);
  virtual void GetItemAttributesNowPlayingResponse(uint8_t label,
                                                   std::shared_ptr<GetItemAttributesRequest> pkt,
                                                   std::string curr_media_id,
                                                   std::vector<SongInfo> song_list);
  virtual void GetItemAttributesVFSResponse(uint8_t label,
                                            std::shared_ptr<GetItemAttributesRequest> pkt,
                                            std::vector<ListItem> item_list);

  // SET BROWSED PLAYER
  virtual void HandleSetBrowsedPlayer(uint8_t label,
                                      std::shared_ptr<SetBrowsedPlayerRequest> request);
  virtual void SetBrowsedPlayerResponse(uint8_t label, std::shared_ptr<SetBrowsedPlayerRequest> pkt,
                                        bool success, std::string root_id, uint32_t num_items);

  // CHANGE PATH
  virtual void HandleChangePath(uint8_t label, std::shared_ptr<ChangePathRequest> request);
  virtual void ChangePathResponse(uint8_t label, std::shared_ptr<ChangePathRequest> request,
                                  std::vector<ListItem> list);

  // PLAY ITEM
  virtual void HandlePlayItem(uint8_t label, std::shared_ptr<PlayItemRequest> request);

  // SET ADDRESSED PLAYER
  virtual void HandleSetAddressedPlayer(uint8_t label,
                                        std::shared_ptr<SetAddressedPlayerRequest> request,
                                        uint16_t curr_player);

  // LIST PLAYER APPLICATION SETTING ATTRIBUTES
  virtual void ListPlayerApplicationSettingAttributesResponse(
          uint8_t label, std::vector<PlayerAttribute> attributes);

  // LIST PLAYER APPLICATION SETTING VALUES
  virtual void ListPlayerApplicationSettingValuesResponse(uint8_t label, PlayerAttribute setting,
                                                          std::vector<uint8_t> values);

  // GET CURRENT PLAYER APPLICATION SETTING VALUE
  virtual void GetPlayerApplicationSettingValueResponse(uint8_t label,
                                                        std::vector<PlayerAttribute> attributes,
                                                        std::vector<uint8_t> values);

  // SET PLAYER APPLICATION SETTING VALUE
  virtual void SetPlayerApplicationSettingValueResponse(uint8_t label, CommandPdu pdu,
                                                        bool success);

  /********************
   * MESSAGE REQUESTS
   ********************/
  // VOLUME CHANGED NOTIFICATION
  virtual void RegisterVolumeChanged();
  virtual void HandleVolumeChanged(uint8_t label,
                                   const std::shared_ptr<RegisterNotificationResponse>& pkt);

  // SET VOLUME
  virtual void SetVolume(int8_t volume);

  /**
   * This function is called by Avrcp::ConnectionHandler to signify that
   * the remote device was disconnected.
   *
   * TODO (apanicke): Prevent allowing responses to messages while the device is
   * disconnected by using a weak pointer handle to the device when we separate
   * out the message handling. Also separate the logic in the future when
   * disconnecting only browsing (Though this shouldn't matter as if we are
   * disconnecting browsing then we should be fully disconnecting the device).
   */
  void DeviceDisconnected();

  friend std::ostream& operator<<(std::ostream& out, const Device& c);

private:
  // This should always contain one item which represents the root id on the
  // current player.
  std::string CurrentFolder() const {
    if (current_path_.empty()) {
      return "";
    }
    return current_path_.top();
  }

  void send_message(uint8_t label, bool browse,
                    std::unique_ptr<::bluetooth::PacketBuilder> message) {
    active_labels_.erase(label);
    send_message_cb_.Run(label, browse, std::move(message));
  }

  // A2DP interface implementation
  void connect_a2dp_sink_delayed(uint8_t handle) const {
    a2dp_interface_->connect_audio_sink_delayed(handle, address_);
  }

  bool find_sink_service(tA2DP_FIND_CBACK p_cback) const {
    return a2dp_interface_->find_audio_sink_service(address_, p_cback) == A2DP_SUCCESS;
  }

  base::WeakPtrFactory<Device> weak_ptr_factory_;

  // TODO (apanicke): Initialize all the variables in the constructor.
  RawAddress address_;

  // Enables AVRCP 1.3 Compatibility mode. This disables any AVRCP 1.4+ features
  // such as browsing and playlists but has the highest chance of working.
  bool avrcp13_compatibility_ = false;
  base::RepeatingCallback<void(uint8_t label, bool browse,
                               std::unique_ptr<::bluetooth::PacketBuilder> message)>
          send_message_cb_;
  uint16_t ctrl_mtu_;
  uint16_t browse_mtu_;
  bool has_bip_client_;

  int curr_browsed_player_id_ = -1;
  int curr_addressed_player_id_ = -1;

  std::stack<std::string> current_path_;

  // Notification Trackers
  using Notification = std::pair<bool, uint8_t>;
  Notification track_changed_ = Notification(false, 0);
  Notification play_status_changed_ = Notification(false, 0);
  Notification play_pos_changed_ = Notification(false, 0);
  Notification player_setting_changed_ = Notification(false, 0);
  Notification now_playing_changed_ = Notification(false, 0);
  Notification addr_player_changed_ = Notification(false, 0);
  Notification avail_players_changed_ = Notification(false, 0);
  Notification uids_changed_ = Notification(false, 0);

  MediaIdMap vfs_ids_;
  MediaIdMap now_playing_ids_;

  uint32_t play_pos_interval_ = 0;

  SongInfo last_song_info_;
  PlayStatus last_play_status_;

  base::CancelableClosure play_pos_update_cb_;

  MediaInterface* media_interface_ = nullptr;
  A2dpInterface* a2dp_interface_ = nullptr;
  VolumeInterface* volume_interface_ = nullptr;
  PlayerSettingsInterface* player_settings_interface_ = nullptr;

  // Labels used for messages currently in flight.
  std::set<uint8_t> active_labels_;

  int8_t volume_ = -1;
};

}  // namespace avrcp
}  // namespace bluetooth
