import Foundation

/// A `BitField` is a struct that provides storage for a set a flags,
/// where each flag represents a bit either being set or not set (clear).
///
/// `BitField` is closely related to Swift's `OptionSet`, but it specializes
/// on treating bits as bits, rather than as abstract flags.
public struct BitField<T: UnsignedInteger> {
    /// `naturalSize` represents the maximum number of bits that a `BitField` can hold, depending on the
    /// size of the type over which it is generic.
    ///
    /// A `BitField<UInt8>` has a `naturalSize` of `8`.
    ///
    /// A `BitField<UInt16>` has a `naturalSize` of `16`.
    ///
    /// A `BitField<UInt32>` has a `naturalSize` of `32`.
    ///
    /// A `BitField<UInt>` has a `naturalSize` of `32` or `64` depending on (hardware) platform.
    /// 
    /// A `BitField<UInt64>` has a `naturalSize` of `64`.
    ///
    public static var naturalSize: Int { MemoryLayout<T>.size * 8 }

    /// A BitField's `size` represents the number of bits in the BitField.
    /// Setting a size larger than the `naturalSize` is not allowed.
    ///
    /// If you try to set a size larger than the `naturalSize`, the `size` will be the `naturalSize`.
    public var size: Int = Self.naturalSize {
        didSet {
            if size > Self.naturalSize {
                size = Self.naturalSize
            }
        }
    }

    /// The `rawValue` is the numeric value that represents the sum of the values of the bits that are set.
    public var rawValue: T = 0

    /// The default initializer creates an empty `BitField` if no `rawValue` is provided.
    /// It also defaults the size of the` BitField` to its natural size.
    public init(rawValue: T = 0) {
        size = Self.naturalSize
        self.rawValue = rawValue
    }

    /// You can instantiate a `BitField` by optionally providing a raw value, and a size
    /// that is smaller than, or equal to, its natural size.
    /// The rawValue that is passed in must not be greater than the amount the bits can represent.
    public init?(rawValue: T = 0, size: Int) {
        guard size > 0, size <= Self.naturalSize,
              rawValue < Int(pow(Double(2), Double(size)))
        else { return nil }

        self.size = size
        self.rawValue = rawValue
    }

    /// You can instantiate a `BitField` by providing a variadic set of indexes, and, optionally, a size
    /// that is smaller than, or equal to, its natural size.
    /// An index represents a bit in the `BitField`, with index 0 representing the least significant bit.
    /// The initializer will fail if one or more invalid indexes are supplied.
    public init?(_ indexes: T..., size: Int = Self.naturalSize) {
        guard setInitialIndexes(indexes, size: size)
        else { return nil }
    }

    /// You can instantiate a `BitField` by providing an array of indexes, and, optionally, a size
    /// that is smaller than, or equal to, its natural size.
    /// An index represents a bit in the `BitField`, with index 0 representing the least significant bit.
    /// The initializer will fail if one or more invalid indexes are supplied.
    public init?(_ indexes: [T], size: Int = Self.naturalSize) {
        guard setInitialIndexes(indexes, size: size)
        else { return nil }
    }

    private mutating func setInitialIndexes(_ indexes: [T], size: Int) -> Bool {
        guard size > 0, size <= Self.naturalSize
        else { return false }

        self.size = size

        for index in indexes {
            guard index < size else { return false }
            rawValue |= (1 << index)
        }

        return true
    }

    /// Set one or more bits in the `BitField`.
    /// Invalid indexes are ignored.
    @discardableResult
    public mutating func set(_ indexes: T...) -> Self {
        for index in indexes {
            guard index < size else { continue }
            rawValue |= (1 << index)
        }

        return self
    }

    /// Clear one or more bits in the `BitField`.
    /// Invalid indexes are ignored.
    @discardableResult
    public mutating func clear(_ indexes: T...) -> Self {
        for index in indexes {
            guard index < size else { continue }
            rawValue = rawValue & ~(1 << index)
        }

        return self
    }

    /// Check whether one or more bits in the `BitField` are set.
    /// If all bits passed in are set this function returns `true`.
    /// If at least one of the checked bits is not set, or if any invalid bit indexes
    /// are provided, this function returns `false`.
    public func isSet(_ indexes: T...) -> Bool {
        for index in indexes {
            guard index < size else { return false }
            if (rawValue & (1 << index)) == 0 {
                return false
            }
        }
        return true
    }

    func debugDescription() -> String {
        (0 ..< size).reversed().reduce("0b") {
            $0 + (isSet(T($1)) ? "1" : "0")
        }
    }
}

extension BitField {
    /// Initialize a `BitField` from an array of `bool`s.
    public init(_ bools: [Bool]) {
        rawValue = 0
        for (index, bool) in bools.enumerated() where bool {
            set(T(index))
        }
    }

    /// Turn a `BitField` into an array of `bool`s.
    public func boolArrayOf(size: Int) -> [Bool] {
        let validSize = min(size, Self.naturalSize)
        var array = [Bool]()
        for bitIndex in 0 ..< validSize {
            array.append(isSet(T(bitIndex)))
        }

        return array
    }

    /// Create a copy of the `BitField` with the sequence of bits reversed.
    public func reversed(size: Int? = nil) -> BitField {
        BitField(boolArrayOf(size: size ?? Self.naturalSize).reversed())
    }
}

extension BitField: Comparable {
    public static func < (lhs: Self, rhs: Self) -> Bool {
        lhs.rawValue < rhs.rawValue
    }
}

extension BitField {
    public func contains(_ other: BitField) -> Bool {
        self & other == other
    }

    public static func & (lhs: Self, rhs: Self) -> Self {
        BitField(rawValue: lhs.rawValue & rhs.rawValue)
    }

    public static func | (lhs: Self, rhs: Self) -> Self {
        BitField(rawValue: lhs.rawValue | rhs.rawValue)
    }

    public static func ^ (lhs: Self, rhs: Self) -> Self {
        BitField(rawValue: lhs.rawValue ^ rhs.rawValue)
    }
}