/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "MiniShmParent.h"

#include "base/scoped_handle.h"

#include <sstream>

namespace mozilla {
namespace plugins {

// static
const unsigned int MiniShmParent::kDefaultMiniShmSectionSize = 0x1000;

MiniShmParent::MiniShmParent()
  : mSectionSize(0),
    mParentEvent(nullptr),
    mParentGuard(nullptr),
    mChildEvent(nullptr),
    mChildGuard(nullptr),
    mRegWait(nullptr),
    mFileMapping(nullptr),
    mView(nullptr),
    mIsConnected(false),
    mTimeout(INFINITE)
{
}

MiniShmParent::~MiniShmParent()
{
  CleanUp();
}

void
MiniShmParent::CleanUp()
{
  if (mRegWait) {
    ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE);
    mRegWait = nullptr;
  }
  if (mParentEvent) {
    ::CloseHandle(mParentEvent);
    mParentEvent = nullptr;
  }
  if (mParentGuard) {
    ::CloseHandle(mParentGuard);
    mParentGuard = nullptr;
  }
  if (mChildEvent) {
    ::CloseHandle(mChildEvent);
    mChildEvent = nullptr;
  }
  if (mChildGuard) {
    ::CloseHandle(mChildGuard);
    mChildGuard = nullptr;
  }
  if (mView) {
    ::UnmapViewOfFile(mView);
    mView = nullptr;
  }
  if (mFileMapping) {
    ::CloseHandle(mFileMapping);
    mFileMapping = nullptr;
  }
}

nsresult
MiniShmParent::Init(MiniShmObserver* aObserver, const DWORD aTimeout,
                    const unsigned int aSectionSize)
{
  if (!aObserver || !aSectionSize || (aSectionSize % 0x1000) || !aTimeout) {
    return NS_ERROR_ILLEGAL_VALUE;
  }
  if (mFileMapping) {
    return NS_ERROR_ALREADY_INITIALIZED;
  }
  SECURITY_ATTRIBUTES securityAttributes = {sizeof(securityAttributes),
                                            nullptr,
                                            TRUE};
  ScopedHandle parentEvent(::CreateEvent(&securityAttributes,
                                         FALSE,
                                         FALSE,
                                         nullptr));
  if (!parentEvent.IsValid()) {
    return NS_ERROR_FAILURE;
  }
  ScopedHandle parentGuard(::CreateEvent(&securityAttributes,
                                         FALSE,
                                         TRUE,
                                         nullptr));
  if (!parentGuard.IsValid()) {
    return NS_ERROR_FAILURE;
  }
  ScopedHandle childEvent(::CreateEvent(&securityAttributes,
                                        FALSE,
                                        FALSE,
                                        nullptr));
  if (!childEvent.IsValid()) {
    return NS_ERROR_FAILURE;
  }
  ScopedHandle childGuard(::CreateEvent(&securityAttributes,
                                        FALSE,
                                        TRUE,
                                        nullptr));
  if (!childGuard.IsValid()) {
    return NS_ERROR_FAILURE;
  }
  ScopedHandle mapping(::CreateFileMapping(INVALID_HANDLE_VALUE,
                                           &securityAttributes,
                                           PAGE_READWRITE,
                                           0,
                                           aSectionSize,
                                           nullptr));
  if (!mapping.IsValid()) {
    return NS_ERROR_FAILURE;
  }
  ScopedMappedFileView view(::MapViewOfFile(mapping,
                                            FILE_MAP_WRITE,
                                            0, 0, 0));
  if (!view.IsValid()) {
    return NS_ERROR_FAILURE;
  }
  nsresult rv = SetView(view, aSectionSize, false);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = SetGuard(childGuard, aTimeout);
  NS_ENSURE_SUCCESS(rv, rv);

  MiniShmInit* initStruct = nullptr;
  rv = GetWritePtrInternal(initStruct);
  NS_ENSURE_SUCCESS(rv, rv);
  initStruct->mParentEvent = parentEvent;
  initStruct->mParentGuard = parentGuard;
  initStruct->mChildEvent = childEvent;
  initStruct->mChildGuard = childGuard;

  if (!::RegisterWaitForSingleObject(&mRegWait,
                                     parentEvent,
                                     &SOnEvent,
                                     this,
                                     INFINITE,
                                     WT_EXECUTEDEFAULT)) {
    return NS_ERROR_FAILURE;
  }

  mParentEvent = parentEvent.Take();
  mParentGuard = parentGuard.Take();
  mChildEvent = childEvent.Take();
  mChildGuard = childGuard.Take();
  mFileMapping = mapping.Take();
  mView = view.Take();
  mSectionSize = aSectionSize;
  SetObserver(aObserver);
  mTimeout = aTimeout;
  return NS_OK;
}

nsresult
MiniShmParent::GetCookie(std::wstring& cookie)
{
  if (!mFileMapping) {
    return NS_ERROR_NOT_INITIALIZED;
  }
  std::wostringstream oss;
  oss << mFileMapping;
  if (!oss) {
    return NS_ERROR_FAILURE;
  }
  cookie = oss.str();
  return NS_OK;
}

nsresult
MiniShmParent::Send()
{
  if (!mChildEvent) {
    return NS_ERROR_NOT_INITIALIZED;
  }
  if (!::SetEvent(mChildEvent)) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

bool
MiniShmParent::IsConnected() const
{
  return mIsConnected;
}

void
MiniShmParent::OnEvent()
{
  if (mIsConnected) {
    MiniShmBase::OnEvent();
  } else {
    FinalizeConnection();
  }
  ::SetEvent(mParentGuard);
}

void
MiniShmParent::FinalizeConnection()
{
  const MiniShmInitComplete* initCompleteStruct = nullptr;
  nsresult rv = GetReadPtr(initCompleteStruct);
  mIsConnected = NS_SUCCEEDED(rv) && initCompleteStruct->mSucceeded;
  if (mIsConnected) {
    OnConnect();
  }
}

} // namespace plugins
} // namespace mozilla

