/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef HIDUTIL_TRISTATE_H_
#define HIDUTIL_TRISTATE_H_

#include <cassert>
#include <iostream>

namespace HidUtil {
template<typename T>
class TriState {
public:
    // constructor
    TriState() : mIsSet(false) { }
    TriState(const TriState<T> &other) : mIsSet(other.mIsSet), mValue(other.mValue) { }
    explicit TriState(const T &value) : mIsSet(true), mValue(value) { }

    void clear() {
        mValue = T();
        mIsSet = false;
    }
    bool isSet() const {
        return mIsSet;
    }

    const T get(const T &defaultValue) const {
        return isSet() ? mValue : defaultValue;
    }

    // operator overloading
    explicit operator T () const {
        assert(mIsSet);
        return mValue;
    }

    TriState<T>& operator=(const TriState<T> &other) {
        mIsSet = other.mIsSet;
        mValue = other.mValue;
        return *this;
    }

    TriState<T>& operator=(const T& value) {
        mIsSet = true;
        mValue = value;
        return *this;
    }

    TriState<T>& operator++()  {
        if (mIsSet) {
            mValue++;
        }
        return *this;
    }

    TriState<T> operator++(int) {
        TriState<T> tmp(*this);
        operator++();
        return tmp;
    }

    TriState<T>& operator--()  {
        if (mIsSet) {
            mValue--;
        }
        return *this;
    }

    TriState<T> operator--(int) {
        TriState<T> tmp(*this);
        operator--();
        return tmp;
    }

#define UNARY_OP(op) \
    TriState<T> operator op() { \
        TriState<T> tmp(*this); \
        if (mIsSet) { \
            tmp.mValue = op tmp.mValue; \
        } \
        return tmp; \
    }

    UNARY_OP(!);
    UNARY_OP(-);
    UNARY_OP(~);
#undef UNARY_OP

#define COMPOUND_ASSIGN_OP(op) \
    TriState<T>& operator op (const TriState<T>& rhs) { \
        if (mIsSet && rhs.mIsSet) { \
            mValue op rhs.mValue; \
        } else { \
            mIsSet = false; \
        } \
        return *this; \
    } \
    TriState<T>& operator op(const T& rhs) { \
        if (mIsSet) { \
            mValue op rhs; \
        } \
        return *this; \
    }

    COMPOUND_ASSIGN_OP(+=);
    COMPOUND_ASSIGN_OP(-=);
    COMPOUND_ASSIGN_OP(*=);
    COMPOUND_ASSIGN_OP(/=);
    COMPOUND_ASSIGN_OP(%=);
    COMPOUND_ASSIGN_OP(&=);
    COMPOUND_ASSIGN_OP(|=);
    COMPOUND_ASSIGN_OP(^=);
#undef COMPOUND_ASSIGN_OP

    TriState<T>& operator >>=(int i) {
        if (mIsSet) {
            mValue >>= i;
        }
        return *this; \
    }

    TriState<T>& operator <<=(int i) {
        if (mIsSet) {
            mValue <<= i;
        }
        return *this; \
    }

    TriState<T> operator <<(int i) { \
        TriState<T> tmp(*this);
        operator<<(i);
        return tmp;
    }

    TriState<T> operator >>(int i) { \
        TriState<T> tmp(*this);
        operator>>(i);
        return tmp;
    }

#define BINARY_OP(op, compound_op) \
    friend TriState<T> operator op(TriState<T> lhs, const TriState<T>& rhs) { \
        lhs compound_op rhs; \
    return lhs; \
    }\
        friend TriState<T> operator op(TriState<T> lhs, const T& rhs) { \
    lhs compound_op rhs; \
        return lhs; \
    }\
    friend TriState<T> operator op(const T &lhs, const TriState<T>& rhs) { \
        TriState<T> tmp(lhs); \
        return tmp op rhs; \
    }

    BINARY_OP(+, +=);
    BINARY_OP(-, -=);
    BINARY_OP(*, *=);
    BINARY_OP(/, /=);
    BINARY_OP(%, %=);
    BINARY_OP(&, &=);
    BINARY_OP(|, |=);
    BINARY_OP(^, ^=);
#undef BINARY_OP

#define RELATION_OP(op) \
    friend TriState<bool> operator op(const TriState<T>& lhs, const TriState<T>& rhs) { \
        if (lhs.mIsSet && rhs.mIsSet) { \
            return TriState<bool>(lhs.mValue op rhs.mValue); \
        } else { \
        return TriState<bool>(); \
        } \
    } \
    friend TriState<bool> operator op(const TriState<T>& lhs, const T& rhs) { \
        if (lhs.mIsSet) { \
            return TriState<bool>(lhs.mValue op rhs); \
        } else { \
            return TriState<bool>(); \
        } \
    } \
    friend TriState<bool> operator op(const T& lhs, const TriState<T>& rhs) { \
        if (rhs.mIsSet) { \
            return TriState<bool>(lhs op rhs.mValue); \
        } else { \
            return TriState<bool>(); \
        } \
    }

    RELATION_OP(==);
    RELATION_OP(!=);
    RELATION_OP(>=);
    RELATION_OP(<=);
    RELATION_OP(>);
    RELATION_OP(<);
#undef RELATION_OP

    friend TriState<bool> operator &&(TriState<T>& lhs, const TriState<T>& rhs) {
        if (lhs.mIsSet && rhs.mIsSet) {
            return TriState<bool>(lhs.mValue && rhs.mValue);
        } else {
            return TriState<bool>();
        }
    }

    friend TriState<bool> operator ||(TriState<T>& lhs, const TriState<T>& rhs) {
        if (lhs.mIsSet && rhs.mIsSet) {
            return TriState<bool>(lhs.mValue || rhs.mValue);
        } else {
            return TriState<bool>();
        }
    }

    friend std::ostream& operator <<(std::ostream &os, const TriState<T> &v) {
        if (v.mIsSet) {
            os << v.mValue;
        } else {
            os << "[not set]";
        }
        return os;
    }

    friend std::istream& operator >>(std::istream &is, const TriState<T> &v) {
        T a;
        is >> a;
        v = TriState<T>(a);
        return is;
    }
private:
    bool mIsSet;
    T mValue;
};

// commonly used ones
typedef TriState<unsigned> tri_uint;
typedef TriState<int> tri_int;

typedef TriState<uint32_t> tri_uint32_t;
typedef TriState<int32_t> tri_int32_t;
typedef TriState<uint8_t> tri_uint8_t;
typedef TriState<uint16_t> tri_uint16_t;
}

#endif // HIDUTIL_TRISTATE_H_