/*
 * libjingle
 * Copyright 2011, Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "talk/app/webrtc/mediastreamproxy.h"
#include "talk/base/refcount.h"
#include "talk/base/scoped_ref_ptr.h"

namespace {

enum {
  MSG_SET_TRACKLIST_IMPLEMENTATION = 1,
  MSG_REGISTER_OBSERVER,
  MSG_UNREGISTER_OBSERVER,
  MSG_LABEL,
  MSG_ADD_AUDIO_TRACK,
  MSG_ADD_VIDEO_TRACK,
  MSG_READY_STATE,
  MSG_COUNT,
  MSG_AT
};

typedef talk_base::TypedMessageData<std::string*> LabelMessageData;
typedef talk_base::TypedMessageData<size_t> SizeTMessageData;
typedef talk_base::TypedMessageData<webrtc::ObserverInterface*>
    ObserverMessageData;
typedef talk_base::TypedMessageData<webrtc::MediaStreamInterface::ReadyState>
    ReadyStateMessageData;

template<typename T>
class MediaStreamTrackMessageData : public talk_base::MessageData {
 public:
  explicit MediaStreamTrackMessageData(T* track)
      : track_(track),
        result_(false) {
  }

  talk_base::scoped_refptr<T> track_;
  bool result_;
};

typedef MediaStreamTrackMessageData<webrtc::AudioTrackInterface>
    AudioTrackMsgData;
typedef MediaStreamTrackMessageData<webrtc::VideoTrackInterface>
    VideoTrackMsgData;

template <class TrackType>
class MediaStreamTrackAtMessageData : public talk_base::MessageData {
 public:
  explicit MediaStreamTrackAtMessageData(size_t index)
      : index_(index) {
  }

  size_t index_;
  talk_base::scoped_refptr<TrackType> track_;
};

class MediaStreamTrackListsMessageData : public talk_base::MessageData {
 public:
  talk_base::scoped_refptr<webrtc::AudioTracks> audio_tracks_;
  talk_base::scoped_refptr<webrtc::VideoTracks> video_tracks_;
};

}  // namespace anonymous

namespace webrtc {

talk_base::scoped_refptr<MediaStreamProxy> MediaStreamProxy::Create(
    const std::string& label,
    talk_base::Thread* signaling_thread) {
  ASSERT(signaling_thread != NULL);
  talk_base::RefCountedObject<MediaStreamProxy>* stream =
      new talk_base::RefCountedObject<MediaStreamProxy>(
          label, signaling_thread,
          reinterpret_cast<LocalMediaStreamInterface*>(NULL));
  return stream;
}

talk_base::scoped_refptr<MediaStreamProxy> MediaStreamProxy::Create(
    const std::string& label,
    talk_base::Thread* signaling_thread,
    LocalMediaStreamInterface* media_stream_impl) {
  ASSERT(signaling_thread != NULL);
  ASSERT(media_stream_impl != NULL);
  talk_base::RefCountedObject<MediaStreamProxy>* stream =
      new talk_base::RefCountedObject<MediaStreamProxy>(label, signaling_thread,
                                                media_stream_impl);
  return stream;
}

MediaStreamProxy::MediaStreamProxy(const std::string& label,
                                   talk_base::Thread* signaling_thread,
                                   LocalMediaStreamInterface* media_stream_impl)
    : signaling_thread_(signaling_thread),
      media_stream_impl_(media_stream_impl),
      audio_tracks_(new talk_base::RefCountedObject<
                        MediaStreamTrackListProxy<AudioTrackInterface> >(
                              signaling_thread_)),
      video_tracks_(new talk_base::RefCountedObject<
                        MediaStreamTrackListProxy<VideoTrackInterface> >(
                            signaling_thread_)) {
  if (media_stream_impl_ == NULL) {
    media_stream_impl_ = MediaStream::Create(label);
  }

  MediaStreamTrackListsMessageData tracklists;
  Send(MSG_SET_TRACKLIST_IMPLEMENTATION, &tracklists);
  audio_tracks_->SetImplementation(tracklists.audio_tracks_);
  video_tracks_->SetImplementation(tracklists.video_tracks_);
}

std::string MediaStreamProxy::label() const {
  if (!signaling_thread_->IsCurrent()) {
    std::string label;
    LabelMessageData msg(&label);
    Send(MSG_LABEL, &msg);
    return label;
  }
  return media_stream_impl_->label();
}

MediaStreamInterface::ReadyState MediaStreamProxy::ready_state() const {
  if (!signaling_thread_->IsCurrent()) {
    ReadyStateMessageData msg(MediaStreamInterface::kInitializing);
    Send(MSG_READY_STATE, &msg);
    return msg.data();
  }
  return media_stream_impl_->ready_state();
}

void MediaStreamProxy::set_ready_state(
    MediaStreamInterface::ReadyState new_state) {
  if (!signaling_thread_->IsCurrent()) {
    // State should only be allowed to be changed from the signaling thread.
    ASSERT(!"Not Allowed!");
    return;
  }
  media_stream_impl_->set_ready_state(new_state);
}

bool MediaStreamProxy::AddTrack(AudioTrackInterface* track) {
  if (!signaling_thread_->IsCurrent()) {
    AudioTrackMsgData msg(track);
    Send(MSG_ADD_AUDIO_TRACK, &msg);
    return msg.result_;
  }
  return media_stream_impl_->AddTrack(track);
}

bool MediaStreamProxy::AddTrack(VideoTrackInterface* track) {
  if (!signaling_thread_->IsCurrent()) {
    VideoTrackMsgData msg(track);
    Send(MSG_ADD_VIDEO_TRACK, &msg);
    return msg.result_;
  }
  return media_stream_impl_->AddTrack(track);
}

void MediaStreamProxy::RegisterObserver(ObserverInterface* observer) {
  if (!signaling_thread_->IsCurrent()) {
    ObserverMessageData msg(observer);
    Send(MSG_REGISTER_OBSERVER, &msg);
    return;
  }
  media_stream_impl_->RegisterObserver(observer);
}

void MediaStreamProxy::UnregisterObserver(ObserverInterface* observer) {
  if (!signaling_thread_->IsCurrent()) {
    ObserverMessageData msg(observer);
    Send(MSG_UNREGISTER_OBSERVER, &msg);
    return;
  }
  media_stream_impl_->UnregisterObserver(observer);
}

void MediaStreamProxy::Send(uint32 id, talk_base::MessageData* data) const {
  signaling_thread_->Send(const_cast<MediaStreamProxy*>(this), id,
                          data);
}

// Implement MessageHandler
void MediaStreamProxy::OnMessage(talk_base::Message* msg) {
  talk_base::MessageData* data = msg->pdata;
  switch (msg->message_id) {
    case MSG_SET_TRACKLIST_IMPLEMENTATION: {
      MediaStreamTrackListsMessageData* lists =
          static_cast<MediaStreamTrackListsMessageData*>(data);
      lists->audio_tracks_ = media_stream_impl_->audio_tracks();
      lists->video_tracks_ = media_stream_impl_->video_tracks();
      break;
    }
    case MSG_REGISTER_OBSERVER: {
      ObserverMessageData* observer = static_cast<ObserverMessageData*>(data);
      media_stream_impl_->RegisterObserver(observer->data());
      break;
    }
    case MSG_UNREGISTER_OBSERVER: {
      ObserverMessageData* observer = static_cast<ObserverMessageData*>(data);
      media_stream_impl_->UnregisterObserver(observer->data());
      break;
    }
    case MSG_LABEL: {
      LabelMessageData * label = static_cast<LabelMessageData*>(data);
      *(label->data()) = media_stream_impl_->label();
      break;
    }
    case MSG_ADD_AUDIO_TRACK: {
      AudioTrackMsgData * track =
          static_cast<AudioTrackMsgData *>(data);
      track->result_ = media_stream_impl_->AddTrack(track->track_.get());
      break;
    }
    case MSG_ADD_VIDEO_TRACK: {
      VideoTrackMsgData * track =
          static_cast<VideoTrackMsgData *>(data);
      track->result_ = media_stream_impl_->AddTrack(track->track_.get());
      break;
    }
    case MSG_READY_STATE: {
      ReadyStateMessageData* state = static_cast<ReadyStateMessageData*>(data);
      state->data() = media_stream_impl_->ready_state();
      break;
    }
    default:
      ASSERT(!"Not Implemented!");
      break;
  }
}

template <class T>
MediaStreamProxy::MediaStreamTrackListProxy<T>::MediaStreamTrackListProxy(
    talk_base::Thread* signaling_thread)
    : signaling_thread_(signaling_thread) {
}

template <class T>
void MediaStreamProxy::MediaStreamTrackListProxy<T>::SetImplementation(
    MediaStreamTrackListInterface<T>* track_list) {
  track_list_ = track_list;
}

template <class T>
size_t MediaStreamProxy::MediaStreamTrackListProxy<T>::count() const {
  if (!signaling_thread_->IsCurrent()) {
    SizeTMessageData msg(0u);
    Send(MSG_COUNT, &msg);
    return msg.data();
  }
  return track_list_->count();
}

template <class T>
T* MediaStreamProxy::MediaStreamTrackListProxy<T>::at(
    size_t index) {
  if (!signaling_thread_->IsCurrent()) {
    MediaStreamTrackAtMessageData<T> msg(index);
    Send(MSG_AT, &msg);
    return msg.track_;
  }
  return track_list_->at(index);
}

template <class T>
void MediaStreamProxy::MediaStreamTrackListProxy<T>::Send(
    uint32 id, talk_base::MessageData* data) const {
  signaling_thread_->Send(
      const_cast<MediaStreamProxy::MediaStreamTrackListProxy<T>*>(
          this), id, data);
}

// Implement MessageHandler
template <class T>
void MediaStreamProxy::MediaStreamTrackListProxy<T>::OnMessage(
    talk_base::Message* msg) {
  talk_base::MessageData* data = msg->pdata;
  switch (msg->message_id) {
    case MSG_COUNT: {
      SizeTMessageData* count = static_cast<SizeTMessageData*>(data);
      count->data() = track_list_->count();
      break;
    }
    case MSG_AT: {
      MediaStreamTrackAtMessageData<T>* track =
          static_cast<MediaStreamTrackAtMessageData<T>*>(data);
      track->track_ = track_list_->at(track->index_);
      break;
    }
    default:
      ASSERT(!"Not Implemented!");
      break;
  }
}

}  // namespace webrtc
