//===-- Either.h -----------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef liblldb_Either_h_
#define liblldb_Either_h_

#include "llvm/ADT/Optional.h"

#include <functional>

namespace lldb_utility {
    template <typename T1, typename T2>
    class Either
    {
    private:
        enum class Selected
        {
            One, Two
        };
        
        Selected m_selected;
        union
        {
            T1 m_t1;
            T2 m_t2;
        };
        
    public:
        Either (const T1& t1)
        {
            m_t1 = t1;
            m_selected = Selected::One;
        }
        
        Either (const T2& t2)
        {
            m_t2 = t2;
            m_selected = Selected::Two;
        }
        
        Either (const Either<T1,T2>& rhs)
        {
            switch (rhs.m_selected)
            {
                case Selected::One:
                    m_t1 = rhs.GetAs<T1>().getValue();
                    m_selected = Selected::One;
                    break;
                case Selected::Two:
                    m_t2 = rhs.GetAs<T2>().getValue();
                    m_selected = Selected::Two;
                    break;
            }
        }
        
        template <class X, typename std::enable_if<std::is_same<T1,X>::value>::type * = nullptr>
        llvm::Optional<T1>
        GetAs() const
        {
            switch (m_selected)
            {
                case Selected::One:
                    return m_t1;
                default:
                    return llvm::Optional<T1>();
            }
        }
        
        template <class X, typename std::enable_if<std::is_same<T2,X>::value>::type * = nullptr>
        llvm::Optional<T2>
        GetAs() const
        {
            switch (m_selected)
            {
                case Selected::Two:
                    return m_t2;
                default:
                    return llvm::Optional<T2>();
            }
        }
        
        template <class ResultType>
        ResultType
        Apply (std::function<ResultType(T1)> if_T1,
               std::function<ResultType(T2)> if_T2) const
        {
            switch (m_selected)
            {
                case Selected::One:
                    return if_T1(m_t1);
                case Selected::Two:
                    return if_T2(m_t2);
            }
        }
        
        bool
        operator == (const Either<T1,T2>& rhs)
        {
            return (GetAs<T1>() == rhs.GetAs<T1>()) && (GetAs<T2>() == rhs.GetAs<T2>());
        }
        
        explicit
        operator bool ()
        {
            switch (m_selected)
            {
                case Selected::One:
                    return (bool)m_t1;
                case Selected::Two:
                    return (bool)m_t2;
            }
        }
        
        Either<T1,T2>&
        operator = (const Either<T1,T2>& rhs)
        {
            switch (rhs.m_selected)
            {
                case Selected::One:
                    m_t1 = rhs.GetAs<T1>().getValue();
                    m_selected = Selected::One;
                    break;
                case Selected::Two:
                    m_t2 = rhs.GetAs<T2>().getValue();
                    m_selected = Selected::Two;
                    break;
            }
            return *this;
        }
        
        ~Either ()
        {
            switch (m_selected)
            {
                case Selected::One:
                    m_t1.T1::~T1();
                    break;
                case Selected::Two:
                    m_t2.T2::~T2();
                    break;
            }
        }
    };
    
} // namespace lldb_utility

#endif // #ifndef liblldb_Either_h_

