/* Copyright (c) 2021, The Linux Foundataion. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * 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.
*     * Neither the name of The Linux Foundation nor the names of its
*       contributors may be used to endorse or promote products derived
*       from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* 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.
*
*/

#ifndef __GENERIC_PAYLOAD_H__
#define __GENERIC_PAYLOAD_H__

#include <errno.h>
#include <debug_handler.h>
#include <assert.h>
#include <functional>
#include <cstring>

// Do not define __CLASS__ for logging in shared utility header like this one

namespace sdm {

struct GenericPayload {
 public:
  GenericPayload():
    type_size(0), payload(nullptr), array_size(0) {}

  GenericPayload(const GenericPayload &in) {
    type_size = 0;
    payload = nullptr;
    array_size = 0;
    if (in.payload) {
      display::DebugHandler::Get()->Error("GenericPayload::%s:New GenericPayload will not copy"
               "payload data! Use CopyPayload on a new GenericPayload instance.", __FUNCTION__);
    }
    copy_constructed = true;
  }

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

  template<typename A> int CopyPayload(const GenericPayload &in) {
    if (sizeof(A) != in.type_size) {
      return -EINVAL;
    }
    A* p = reinterpret_cast<A *>(in.payload);
    type_size = sizeof(A);
    array_size = in.array_size;

    if (payload != nullptr) {
      release();
    }
    A* p2 = nullptr;
    if (array_size > 1) {
      p2 = new A[array_size];
    } else {
      p2 = new A();
    }
    if (p2 == nullptr) {
      return -ENOMEM;
    }
    *p2 = *p;
    payload = reinterpret_cast<uint8_t *>(p2);
    if (array_size > 1) {
      release = std::function<void(void)>([p2]() -> void {delete [] p2;});
    } else {
      release = std::function<void(void)>([p2]() -> void {delete p2;});
    }
    return 0;
  }

  template<typename A> int CreatePayload(A *&p) {
    if (payload) {
      p = nullptr;
      return -EALREADY;
    }

    p = new A();
    if (p == nullptr) {
      return -ENOMEM;
    }

    type_size = sizeof(A);
    array_size = 1;
    payload = reinterpret_cast<uint8_t *>(p);
    release = std::function<void(void)>([p]() -> void {delete p;});

    return 0;
  }

  template<typename A> int CreatePayload(A *&p, uint32_t sz) {
    if (payload) {
      p = nullptr;
      return -EALREADY;
    }

    if (!sz) {
      return -EINVAL;
    }

    p = new A[sz];
    if (p == nullptr) {
      return -ENOMEM;
    }

    type_size = sizeof(A);
    array_size = sz;
    payload = reinterpret_cast<uint8_t *>(p);
    release = std::function<void(void)>([p]() -> void {delete [] p;});

    return 0;
  }

  template<typename A> int GetPayload(A *&p, uint32_t *sz) const {
    if ((sz == nullptr) || (sizeof(A) != type_size)) {
      p = nullptr;
      return -EINVAL;
    }

    p = reinterpret_cast<A *>(payload);
    *sz = 0;
    if (p == nullptr && copy_constructed) {
      display::DebugHandler::Get()->Error("GenericPayload::%s:Payload was not properly"
                                          "copied via CopyPayload", __FUNCTION__);
      return -ENOMEM;
    } else if (p == nullptr) {
      display::DebugHandler::Get()->Error("GenericPayload::%s:Payload was not properly"
                                          "created via CreatePayload", __FUNCTION__);
      return -ENOMEM;
    }
    *sz = array_size;

    return 0;
  }

  void DeletePayload() {
    if (payload != nullptr) {
      release();
    }

    type_size = 0;
    payload = nullptr;
    array_size = 0;
  }

  ~GenericPayload() {
    DeletePayload();
  }

 private:
  uint32_t type_size;
  uint8_t *payload;
  uint32_t array_size;
  std::function<void(void)> release;
  bool copy_constructed = false;
};

}  // namespace sdm

#endif  // __GENERIC_PAYLOAD_H__

