/*  *********************************************************************
 *  ASTRIS - Remote JTAG probe
 *
 *  Astris.framework - C++ API
 *
 *  File: NewAPI_Maybe.hpp
 *
 ***********************************************************************
 *
 *  Copyright (C) 2021- 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_Maybe_h
#define NewAPI_Maybe_h

#include <stdexcept>
#include <utility>

namespace AstrisAPI {

using err_t = int;          // Astris error type for returning and throwing

struct ErrorTag
{
    explicit ErrorTag() = default;
};

// Using attribute explicitly instead of ASTRIS_HIDE to avoid pulling the entire
// Astris API in here. It needs to be hidden because it can be defined in multiple
// compile sites and and should not be pulled from the framework. It is just a
// tag after all.
inline constexpr ErrorTag __attribute__((visibility("hidden"))) withError{};

template<typename T, typename E=err_t>
class Maybe
{
public:

    Maybe(const T &value):
        m_outcome(SUCCESS),
        m_value(value)
    {}

    Maybe(const ErrorTag&, E err):
        m_outcome(FAILED),
        m_value(T()),
        m_err(err)
    {}

    Maybe(const ErrorTag&, E err, const T &value):
        m_outcome(FAILED_WITH_RESULT),
        m_value(value),
        m_err(err)
    {}

    T& ok()
    {
        if (m_outcome != SUCCESS) {
            throw ::std::logic_error("Failed Maybe instance does not have a success value");
        }
        return m_value;
    }

    const T& ok() const
    {
        if (m_outcome != SUCCESS) {
            throw ::std::logic_error("Failed Maybe instance does not have a success value");
        }
        return m_value;
    }

    template <typename U>
    const T& okOr(U &&def_value) const
    {
        if (m_outcome == SUCCESS) {
            return ok();
        } else {
            return static_cast<const T&>(::std::forward<const U>(def_value));
        }
    }

    operator bool() const { return m_outcome == SUCCESS; }
    T& operator*() { return ok(); }

    E error() const
    {
        if (m_outcome == SUCCESS) {
            throw ::std::logic_error("Suceeded Maybe instance does not have an error");
        }
        return m_err;
    }

    T failed() const
    {
        if (m_outcome != FAILED_WITH_RESULT) {
            const char *message = m_outcome == SUCCESS ?
                "Suceeded Maybe instance does not have a failed value" :
                "Failed Maybe instance does not have a failed value";
            throw ::std::logic_error(message);
        }
        return m_value;
    }

    bool hasResult() const { return m_outcome != FAILED; }

private:
    using outcome_e = enum { SUCCESS = 1, FAILED, FAILED_WITH_RESULT };

    outcome_e m_outcome;
    T m_value;
    E m_err;
};

}   // namespace AstrisAPI

#endif /* NewAPI_Maybe_h */
