/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "StreamFilterChild.h"
#include "StreamFilter.h"

#include "mozilla/Assertions.h"
#include "mozilla/UniquePtr.h"

namespace mozilla {
namespace extensions {

using mozilla::dom::StreamFilterStatus;
using mozilla::ipc::IPCResult;

/*****************************************************************************
 * Initialization and cleanup
 *****************************************************************************/

void StreamFilterChild::Cleanup() {
  switch (mState) {
    case State::Closing:
    case State::Closed:
    case State::Error:
    case State::Disconnecting:
    case State::Disconnected:
      break;

    default:
      ErrorResult rv;
      Disconnect(rv);
      break;
  }
}

/*****************************************************************************
 * State change methods
 *****************************************************************************/

void StreamFilterChild::Suspend(ErrorResult& aRv) {
  switch (mState) {
    case State::TransferringData:
      mState = State::Suspending;
      mNextState = State::Suspended;

      SendSuspend();
      break;

    case State::Suspending:
      switch (mNextState) {
        case State::Suspended:
        case State::Resuming:
          mNextState = State::Suspended;
          break;

        default:
          aRv.Throw(NS_ERROR_FAILURE);
          return;
      }
      break;

    case State::Resuming:
      switch (mNextState) {
        case State::TransferringData:
        case State::Suspending:
          mNextState = State::Suspending;
          break;

        default:
          aRv.Throw(NS_ERROR_FAILURE);
          return;
      }
      break;

    case State::Suspended:
      break;

    default:
      aRv.Throw(NS_ERROR_FAILURE);
      break;
  }
}

void StreamFilterChild::Resume(ErrorResult& aRv) {
  switch (mState) {
    case State::Suspended:
      mState = State::Resuming;
      mNextState = State::TransferringData;

      SendResume();
      break;

    case State::Suspending:
      switch (mNextState) {
        case State::Suspended:
        case State::Resuming:
          mNextState = State::Resuming;
          break;

        default:
          aRv.Throw(NS_ERROR_FAILURE);
          return;
      }
      break;

    case State::Resuming:
    case State::TransferringData:
      break;

    default:
      aRv.Throw(NS_ERROR_FAILURE);
      return;
  }

  FlushBufferedData();
}

void StreamFilterChild::Disconnect(ErrorResult& aRv) {
  switch (mState) {
    case State::Suspended:
    case State::TransferringData:
    case State::FinishedTransferringData:
      mState = State::Disconnecting;
      mNextState = State::Disconnected;

      WriteBufferedData();
      SendDisconnect();
      break;

    case State::Suspending:
    case State::Resuming:
      switch (mNextState) {
        case State::Suspended:
        case State::Resuming:
        case State::Disconnecting:
          mNextState = State::Disconnecting;
          break;

        default:
          aRv.Throw(NS_ERROR_FAILURE);
          return;
      }
      break;

    case State::Disconnecting:
    case State::Disconnected:
      break;

    default:
      aRv.Throw(NS_ERROR_FAILURE);
      return;
  }
}

void StreamFilterChild::Close(ErrorResult& aRv) {
  switch (mState) {
    case State::Suspended:
    case State::TransferringData:
    case State::FinishedTransferringData:
      mState = State::Closing;
      mNextState = State::Closed;

      SendClose();
      break;

    case State::Suspending:
    case State::Resuming:
      mNextState = State::Closing;
      break;

    case State::Closing:
      MOZ_DIAGNOSTIC_ASSERT(mNextState == State::Closed);
      break;

    case State::Closed:
      break;

    default:
      aRv.Throw(NS_ERROR_FAILURE);
      return;
  }

  mBufferedData.clear();
}

/*****************************************************************************
 * Internal state management
 *****************************************************************************/

void StreamFilterChild::SetNextState() {
  mState = mNextState;

  switch (mNextState) {
    case State::Suspending:
      mNextState = State::Suspended;
      SendSuspend();
      break;

    case State::Resuming:
      mNextState = State::TransferringData;
      SendResume();
      break;

    case State::Closing:
      mNextState = State::Closed;
      SendClose();
      break;

    case State::Disconnecting:
      mNextState = State::Disconnected;
      SendDisconnect();
      break;

    case State::FinishedTransferringData:
      if (mStreamFilter) {
        mStreamFilter->FireEvent(u"stop"_ns);
        // We don't need access to the stream filter after this point, so break
        // our reference cycle, so that it can be collected if we're the last
        // reference.
        mStreamFilter = nullptr;
      }
      break;

    case State::TransferringData:
      FlushBufferedData();
      break;

    case State::Closed:
    case State::Disconnected:
    case State::Error:
      mStreamFilter = nullptr;
      break;

    default:
      break;
  }
}

void StreamFilterChild::MaybeStopRequest() {
  if (!mReceivedOnStop || !mBufferedData.isEmpty()) {
    return;
  }

  if (mStreamFilter) {
    (void)mStreamFilter->CheckAlive();
  }

  switch (mState) {
    case State::Suspending:
    case State::Resuming:
      mNextState = State::FinishedTransferringData;
      return;

    case State::Disconnecting:
    case State::Closing:
    case State::Closed:
      break;

    default:
      mState = State::FinishedTransferringData;
      if (mStreamFilter) {
        mStreamFilter->FireEvent(u"stop"_ns);
        // We don't need access to the stream filter after this point, so break
        // our reference cycle, so that it can be collected if we're the last
        // reference.
        mStreamFilter = nullptr;
      }
      break;
  }
}

/*****************************************************************************
 * State change acknowledgment callbacks
 *****************************************************************************/

void StreamFilterChild::RecvInitialized(bool aSuccess) {
  MOZ_ASSERT(mState == State::Uninitialized);

  if (aSuccess) {
    mState = State::Initialized;
  } else {
    mState = State::Error;
    if (mStreamFilter) {
      mStreamFilter->FireErrorEvent(u"Invalid request ID"_ns);
      mStreamFilter = nullptr;
    }
  }
}

IPCResult StreamFilterChild::RecvError(const nsCString& aError) {
  mState = State::Error;
  if (mStreamFilter) {
    mStreamFilter->FireErrorEvent(NS_ConvertUTF8toUTF16(aError));
    mStreamFilter = nullptr;
  }
  SendDestroy();
  return IPC_OK();
}

IPCResult StreamFilterChild::RecvClosed() {
  MOZ_DIAGNOSTIC_ASSERT(mState == State::Closing);

  SetNextState();
  return IPC_OK();
}

IPCResult StreamFilterChild::RecvSuspended() {
  MOZ_DIAGNOSTIC_ASSERT(mState == State::Suspending);

  SetNextState();
  return IPC_OK();
}

IPCResult StreamFilterChild::RecvResumed() {
  MOZ_DIAGNOSTIC_ASSERT(mState == State::Resuming);

  SetNextState();
  return IPC_OK();
}

IPCResult StreamFilterChild::RecvFlushData() {
  MOZ_DIAGNOSTIC_ASSERT(mState == State::Disconnecting);

  SendFlushedData();
  SetNextState();
  return IPC_OK();
}

/*****************************************************************************
 * Other binding methods
 *****************************************************************************/

void StreamFilterChild::Write(Data&& aData, ErrorResult& aRv) {
  switch (mState) {
    case State::Suspending:
    case State::Resuming:
      switch (mNextState) {
        case State::Suspended:
        case State::TransferringData:
          break;

        default:
          aRv.Throw(NS_ERROR_FAILURE);
          return;
      }
      break;

    case State::Suspended:
    case State::TransferringData:
    case State::FinishedTransferringData:
      break;

    default:
      aRv.Throw(NS_ERROR_FAILURE);
      return;
  }

  SendWrite(std::move(aData));
}

StreamFilterStatus StreamFilterChild::Status() const {
  switch (mState) {
    case State::Uninitialized:
    case State::Initialized:
      return StreamFilterStatus::Uninitialized;

    case State::TransferringData:
      return StreamFilterStatus::Transferringdata;

    case State::Suspended:
      return StreamFilterStatus::Suspended;

    case State::FinishedTransferringData:
      return StreamFilterStatus::Finishedtransferringdata;

    case State::Resuming:
    case State::Suspending:
      switch (mNextState) {
        case State::TransferringData:
        case State::Resuming:
          return StreamFilterStatus::Transferringdata;

        case State::Suspended:
        case State::Suspending:
          return StreamFilterStatus::Suspended;

        case State::Closing:
          return StreamFilterStatus::Closed;

        case State::Disconnecting:
          return StreamFilterStatus::Disconnected;

        default:
          MOZ_ASSERT_UNREACHABLE("Unexpected next state");
          return StreamFilterStatus::Suspended;
      }
      break;

    case State::Closing:
    case State::Closed:
      return StreamFilterStatus::Closed;

    case State::Disconnecting:
    case State::Disconnected:
      return StreamFilterStatus::Disconnected;

    case State::Error:
      return StreamFilterStatus::Failed;
  };

  MOZ_ASSERT_UNREACHABLE("Not reached");
  return StreamFilterStatus::Failed;
}

/*****************************************************************************
 * Request state notifications
 *****************************************************************************/

IPCResult StreamFilterChild::RecvStartRequest() {
  MOZ_ASSERT(mState == State::Initialized);

  mState = State::TransferringData;

  if (mStreamFilter) {
    mStreamFilter->FireEvent(u"start"_ns);
    (void)mStreamFilter->CheckAlive();
  }
  return IPC_OK();
}

IPCResult StreamFilterChild::RecvStopRequest(const nsresult& aStatus) {
  mReceivedOnStop = true;
  MaybeStopRequest();
  return IPC_OK();
}

/*****************************************************************************
 * Incoming request data handling
 *****************************************************************************/

void StreamFilterChild::EmitData(const Data& aData) {
  MOZ_ASSERT(CanFlushData());
  if (mStreamFilter) {
    mStreamFilter->FireDataEvent(aData);
  }

  MaybeStopRequest();
}

void StreamFilterChild::FlushBufferedData() {
  while (!mBufferedData.isEmpty() && CanFlushData()) {
    UniquePtr<BufferedData> data(mBufferedData.popFirst());

    EmitData(data->mData);
  }
}

void StreamFilterChild::WriteBufferedData() {
  while (!mBufferedData.isEmpty()) {
    UniquePtr<BufferedData> data(mBufferedData.popFirst());

    SendWrite(data->mData);
  }
}

IPCResult StreamFilterChild::RecvData(Data&& aData) {
  MOZ_ASSERT(!mReceivedOnStop);

  if (mStreamFilter) {
    (void)mStreamFilter->CheckAlive();
  }

  switch (mState) {
    case State::TransferringData:
    case State::Resuming:
      EmitData(aData);
      break;

    case State::FinishedTransferringData:
      MOZ_ASSERT_UNREACHABLE("Received data in unexpected state");
      EmitData(aData);
      break;

    case State::Suspending:
    case State::Suspended:
      BufferData(std::move(aData));
      break;

    case State::Disconnecting:
      SendWrite(std::move(aData));
      break;

    case State::Closing:
      break;

    default:
      MOZ_ASSERT_UNREACHABLE("Received data in unexpected state");
      return IPC_FAIL_NO_REASON(this);
  }

  return IPC_OK();
}

/*****************************************************************************
 * Glue
 *****************************************************************************/

void StreamFilterChild::ActorDestroy(ActorDestroyReason aWhy) {
  mStreamFilter = nullptr;
}

}  // namespace extensions
}  // namespace mozilla
