<?php

declare(strict_types=1);

namespace Evulmastah\Bitset;

use Evulmastah\Bitset\Exception\BitsetException;

class Bitset implements BitsetInterface
{
    private $set = [];

    /**
     * @throws BitsetException
     */
    public static function fromString(string $string): BitsetInterface
    {
        if (!preg_match('/[0|1]$/', $string)) {
            throw BitsetException::invalidStringException($string);
        }

        if (strlen($string) > 8 * PHP_INT_SIZE - 1) {
            throw BitsetException::numberOutOfRange();
        }

        $self = new self();

        foreach (str_split($string) as $bit) {
            $self->set[] = $bit === '1';
        }

        $self->set = array_reverse($self->set);

        return $self;
    }

    public static function fromNumber(int $number): BitsetInterface
    {
        $self = new self();

        while ($number > 0) {
            $self->set[] = ($number & 1) === 1;
            $number >>= 1;
        }

        return $self;
    }

    public function toString(): string
    {
        return implode(
            array_map(function ($isSet) {
                return $isSet ? '1' : '0';
            }, array_reverse($this->set))
        );
    }

    public function toNumber(): int
    {
        $number = 0;
        foreach ($this->set as $index => $isSet) {
            if ($isSet) {
                $number += (1 << $index);
            }
        }

        return $number;
    }

    public function count(): int
    {
        return array_sum($this->set);
    }

    public function test(int $index): bool
    {
        return $this->set[$index] ?? false;
    }

    public function all(): bool
    {
        return count(array_filter($this->set)) === count($this->set);
    }

    public function any(): bool
    {
        return array_sum($this->set) > 0;
    }

    public function none(): bool
    {
        return array_sum($this->set) === 0;
    }

    public function set(int $index, bool $isSet): void
    {
        $bitsCount = count($this->set);
        if ($index > $bitsCount) {
            $this->set = array_merge(
                $this->set,
                array_fill($bitsCount, $index - $bitsCount, false)
            );
        }

        $this->set[$index] = $isSet;
    }

    public function reset(): void
    {
        $this->set = [];
    }

    public function flip(): void
    {
        $this->set = array_map(function ($isSet) {
            return !$isSet;
        }, $this->set);
    }
}