/*
 * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

#ifndef SHARE_VM_JFR_WRITERS_JFRSTORAGEADAPTER_HPP
#define SHARE_VM_JFR_WRITERS_JFRSTORAGEADAPTER_HPP

#include "jfr/utilities/jfrAllocation.hpp"

class Thread;

//
// The adapters present writers with a uniform interface over storage.
//
// Adapter policy
//
// StorageType* storage();
// const u1* start() const;
// const u1* pos();
// const u1* end() const;
// void commit(u1* position);
// bool flush(size_t used, size_t requested);
// void release();
//

template <typename Flush>
class Adapter {
 public:
  typedef typename Flush::Type StorageType;
  Adapter(StorageType* storage, Thread* thread) : _storage(storage), _thread(thread) {}
  Adapter(Thread* thread) : _storage(NULL), _thread(thread) {}

  void set_storage(StorageType* storage) {
    _storage = storage;
  }

  StorageType* storage() {
    return _storage;
  }

  const u1* start() const {
    assert(_storage != NULL, "invariant");
    return _storage->start();
  }

  u1* pos() {
    assert(_storage != NULL, "invariant");
    return _storage->pos();
  }

  const u1* end() const {
    assert(_storage != NULL, "invariant");
    return _storage->end();
  }

  void commit(u1* position) {
    assert(_storage != NULL, "invariant");
    _storage->set_pos(position);
  }

  bool flush(size_t used, size_t requested) {
    assert(_thread != NULL, "invariant");
    Flush f(_storage, used, requested, _thread);
    _storage = f.result();
    return _storage != NULL;
  }

  void release() {
    if (_storage != NULL && _storage->lease()) {
      // This flush call will return the lease
      // of a temporary storage area.
      // Since the requested size is 0,
      // the flush implementation will accomodate
      // that 'size' request in the
      // original thread local storage,
      // by implication restoring the original
      // in the process of returning a lease.
      flush(0, 0);
    }
  }

 private:
  StorageType* _storage;
  Thread* _thread;
};

template <size_t DEFAULT_SIZE = K>
class MallocAdapter {
 private:
  u1* _start;
  u1* _pos;
  u1* _end;
  size_t _initial_size;
  bool _has_ownership;

  bool allocate(size_t size);
  void deallocate();

 public:
  typedef u1 StorageType;
  MallocAdapter(u1* storage, Thread* thread);
  MallocAdapter(u1* storage, size_t size);
  MallocAdapter(Thread* thread);
  ~MallocAdapter();

  StorageType* storage() { return _start; }
  const u1* start() const { return _start; }
  u1* pos() { return _pos; }
  void commit(u1* position) { _pos = position; }
  const u1* end() const { return _end; }
  void release() {}
  bool flush(size_t used, size_t requested);
};

template <size_t DEFAULT_SIZE>
MallocAdapter<DEFAULT_SIZE>::MallocAdapter(u1* storage, size_t size) :
  _start(storage),
  _pos(storage),
  _end(storage + size),
  _initial_size(size),
  _has_ownership(false) {
}

template <size_t DEFAULT_SIZE>
MallocAdapter<DEFAULT_SIZE> ::MallocAdapter(u1* storage, Thread* thread) :
  _start(storage),
  _pos(storage),
  _end(storage),
  _initial_size(0),
  _has_ownership(false) {
}

template <size_t DEFAULT_SIZE>
MallocAdapter<DEFAULT_SIZE>::MallocAdapter(Thread* thread) :
  _start(NULL),
  _pos(NULL),
  _end(NULL),
  _initial_size(DEFAULT_SIZE),
  _has_ownership(true) {
  allocate(DEFAULT_SIZE);
}

template <size_t DEFAULT_SIZE>
MallocAdapter<DEFAULT_SIZE>::~MallocAdapter() {
  if (_has_ownership) {
    deallocate();
  }
}

template <size_t DEFAULT_SIZE>
bool MallocAdapter<DEFAULT_SIZE>::allocate(size_t size) {
  if (NULL == _start) {
    _start = JfrCHeapObj::new_array<u1>(size);
    if (_start) {
      _pos = _start;
      _end = _start + size;
      _initial_size = size;
    }
  }
  return _start != NULL;
}

template <size_t DEFAULT_SIZE>
void MallocAdapter<DEFAULT_SIZE>::deallocate() {
  if (_start != NULL) {
    JfrCHeapObj::free(_start, (size_t)(_end - _start));
  }
}

template <size_t DEFAULT_SIZE>
bool MallocAdapter<DEFAULT_SIZE>::flush(size_t used, size_t requested) {
  if (!_has_ownership) {
    // can't just realloc a storage that we don't own
    return false;
  }
  assert(_start != NULL, "invariant");
  assert(used <= (size_t)(_end - _pos), "invariant");
  assert(_pos + used <= _end, "invariant");
  const size_t previous_storage_size = _end - _start;
  const size_t new_storage_size = used + requested + (previous_storage_size * 2);
  u1* const new_storage = JfrCHeapObj::new_array<u1>(new_storage_size);
  if (!new_storage) {
    return false;
  }
  const size_t previous_pos_offset = _pos - _start;
  // migrate in-flight data
  memcpy(new_storage, _start, previous_pos_offset + used);
  JfrCHeapObj::free(_start, previous_storage_size);
  _start = new_storage;
  _pos = _start + previous_pos_offset;
  _end = _start + new_storage_size;
  return true;
}

class NoOwnershipAdapter {
 private:
  u1* _start;
  u1* _pos;
  u1* _end;
  size_t _size;

 public:
  typedef u1 StorageType;
  NoOwnershipAdapter(u1* storage, size_t size) : _start(storage), _pos(storage), _end(storage + size), _size(size) {}
  NoOwnershipAdapter(u1* storage, Thread* thread) : _start(storage), _pos(storage), _end(storage), _size(0) {
    ShouldNotCallThis();
  }
  NoOwnershipAdapter(Thread* thread) : _start(NULL), _pos(NULL), _end(NULL), _size(0) {
    ShouldNotCallThis();
  }
  StorageType* storage() { return _start; }
  const u1* start() const { return _start; }
  u1* pos() { return _pos; }
  void commit(u1* position) { _pos = position; }
  const u1* end() const { return _end; }
  void release() {}
  bool flush(size_t used, size_t requested) {
    // don't flush/expand a buffer that is not our own
    return false;
  }
};

#endif // SHARE_VM_JFR_WRITERS_JFRSTORAGEADAPTER_HPP
