/*  *********************************************************************
 *  ASTRIS - Remote JTAG probe
 *
 *  Astris.framework - C++ API
 *
 *  File: NewAPI_Maybe.hpp
 *
 ***********************************************************************
 *
 *  Copyright (C) 2022- Apple Inc. All rights reserved.
 *
 *  This document is the property of Apple Inc.
 *  It is considered confidential and proprietary.
 *
 *  This document may not be reproduced or transmitted in any form,
 *  in whole or in part, without the express written permission of
 *  Apple Inc.
 ********************************************************************* */

#ifndef NewAPI_Memory_h
#define NewAPI_Memory_h

#include "astris_api.h"

namespace AstrisAPI {

template<typename T>
class BusAccess
{
public:
    using T_ptr_t = typename T::ptr_t;

    BusAccess(const T_ptr_t &target, const Maybe<uint32_t> &ap_select):
        m_target(target),
        m_ap_select(0),
        m_select_error(ASTRIS_ERR_OK)
    {
        if (ap_select) {
            m_ap_select = ap_select.ok();
        } else {
            m_select_error = ap_select.error();
        }
    }

    BusAccess(const T_ptr_t &target, uint32_t ap_select):
        m_target(target),
        m_ap_select(ap_select),
        m_select_error(ASTRIS_ERR_OK)
    {
    }

    template <typename V, size_t N>
    Maybe<size_t> read(AXmemaddr_t address, V (&value)[N], bool fifo=false) { return readBuffer(address, value, N, fifo); }

    template <typename V>
    Maybe<size_t> read(AXmemaddr_t address, V &value) { return readBuffer(address, &value); }

    template <typename V, size_t N>
    Maybe<size_t> write(AXmemaddr_t address, const V (&value)[N], bool fifo=false) { return writeBuffer(address, value, N, fifo); }

    template <typename V>
    Maybe<size_t> write(AXmemaddr_t address, const V &value) { return writeBuffer(address, &value); }


    template <typename V>
    Maybe<size_t> readBuffer(AXmemaddr_t address, V *buffer, size_t count=1, bool fifo=false)
    {
        err_t res = validateInstance();
        if (res == ASTRIS_ERR_OK) {
            // This is non-alingning read, so it operates on count of values
            return m_target->readValuesWithAP(m_ap_select, address, sizeof(V), reinterpret_cast<uint8_t*>(buffer), count, fifo);
        }
        return Maybe<size_t>(withError, res);
    }

    template <typename V>
    Maybe<size_t> writeBuffer(AXmemaddr_t address, const V *buffer, size_t count=1, bool fifo=false)
    {
        err_t res = validateInstance();
        if (res == ASTRIS_ERR_OK) {
            return m_target->writeValuesWithAP(m_ap_select, address, sizeof(V), reinterpret_cast<const uint8_t*>(buffer), count, fifo);
        }
        return Maybe<size_t>(withError, res);
    }

private:

    err_t validateInstance()
    {
        return !m_target ? ASTRIS_ERR_INVAL : m_select_error;
    }

    T_ptr_t m_target;
    uint32_t m_ap_select;
    err_t m_select_error;
};

template<typename T>
class MemoryAccess
{
public:
    using T_ptr_t = typename T::ptr_t;

    MemoryAccess(const T_ptr_t &target):
        m_target(target)
    {
    }

    template <typename V, size_t N>
    Maybe<size_t> read(AXmemaddr_t address, V (&value)[N]) { return readBuffer(address, value, N); }

    template <typename V>
    Maybe<size_t> read(AXmemaddr_t address, V &value) { return readBuffer(address, &value); }

    template <typename V, size_t N>
    Maybe<size_t> write(AXmemaddr_t address, const V (&value)[N], bool iflush=false) { return writeBuffer(address, iflush, value, N); }

    template <typename V>
    Maybe<size_t> write(AXmemaddr_t address, const V &value, bool iflush=false) { return writeBuffer(address, iflush, &value); }


    template <typename V>
    Maybe<size_t> readBuffer(AXmemaddr_t address, V *buffer, size_t count=1)
    {
        err_t res = validateInstance();
        if (res == ASTRIS_ERR_OK) {
            return m_target->readMemory(address, reinterpret_cast<uint8_t*>(buffer), sizeof(V) * count);
        }
        return Maybe<size_t>(withError, res);
    }

    template <typename V>
    Maybe<size_t> writeBuffer(AXmemaddr_t address, bool iflush, const V *buffer, size_t count=1)
    {
        err_t res = validateInstance();
        if (res == ASTRIS_ERR_OK) {
            return m_target->writeMemory(address, reinterpret_cast<const uint8_t*>(buffer), sizeof(V) * count, iflush);
        }
        return Maybe<size_t>(withError, res);
    }

private:

    err_t validateInstance()
    {
        return !m_target ? ASTRIS_ERR_INVAL : ASTRIS_ERR_OK;
    }

    T_ptr_t m_target;
    uint32_t m_ap_select;
    err_t m_select_error;
};

}   // namespace AstrisAPI


#endif /* NewAPI_Memory_h */
