from scipy import sparse
import numpy as np

import pytest
import mock


class MockGroup(dict):
    file = mock.Mock(["mode"])
    attrs = mock.MagicMock(dict)

    def __getitem__(self, path):
        current_item = self
        if set(path) == {"/"}:
            return self
        for item in path.split("/"):
            current_item = dict.__getitem__(current_item, item)
        return current_item

    def __setitem__(self, path, value):
        if isinstance(value, dict):
            value = MockGroup()
        # else:
        #     value = mock.Mock(h5py.Dataset)
        current_item = self
        previous_item = None
        for item in path.split("/"):
            previous_item = current_item
            try:
                current_item = dict.__getitem__(current_item, item)
            except KeyError:
                dict.__setitem__(current_item, item, MockGroup())
                current_item = dict.__getitem__(current_item, item)
        dict.__setitem__(previous_item, item, value)

    def __delitem__(self, path):
        current_item = self
        previous_item = None
        for item in path.split("/"):
            previous_item = current_item
            current_item = dict.__getitem__(current_item, item)
        dict.__delitem__(previous_item, item)


class MockCooler(MockGroup):
    @classmethod
    def make_random(cls, chrom_offsets, binsize, density):
        chrom_nbins = np.diff(chrom_offsets)
        assert chrom_offsets[0] == 0 and np.all(np.diff(chrom_offsets) >= 0)
        n_chroms = len(chrom_offsets) - 1
        n_bins = chrom_offsets[-1]
        chroms = {
            "name": np.array(
                ["chr" + str(i) for i in range(1, n_chroms + 1)], dtype="S"
            ),
            "length": np.array(
                [chrom_nbins[i] * binsize for i in range(n_chroms)], dtype=np.int32
            ),
        }
        bins = {
            "chrom": np.concatenate([[i] * chrom_nbins[i] for i in range(n_chroms)]),
            "start": np.concatenate(
                [
                    np.arange(0, chrom_nbins[i] * binsize, binsize)
                    for i in range(n_chroms)
                ]
            ),
            "end": np.concatenate(
                [
                    np.arange(binsize, chrom_nbins[i] * (binsize + 1), binsize)
                    for i in range(n_chroms)
                ]
            ),
        }
        r = sparse.random(n_bins, n_bins, density=density, random_state=1)
        r = sparse.triu(r, k=1).tocsr()
        pixels = {"bin1_id": r.tocoo().row, "bin2_id": r.indices, "count": r.data}
        indexes = {
            "chrom_offset": np.array(chrom_offsets, dtype=np.int32),
            "bin1_offset": r.indptr,
        }
        return cls(chroms, bins, pixels, indexes, binsize)

    def __init__(self, chroms, bins, pixels, indexes, binsize):
        self.file = self
        self.file.mode = "r"
        self.file.filename = "mock.cool"
        self.name = "/"
        self.attrs = {
            "bin-size": binsize,
            "bin-type": "fixed",
            "symmetric-storage-mode": "upper",
            "nchroms": len(chroms),
            "nbins": len(bins["chrom"]),
            "nnz": len(pixels["bin1_id"]),
            "metadata": "{}",
        }
        super(MockCooler, self).__init__(
            {"chroms": chroms, "bins": bins, "pixels": pixels, "indexes": indexes}
        )


@pytest.fixture(params=[([0, 10, 20], 100, 1)])
def mock_cooler(request):
    chrom_offsets, binsize, density = request.param
    return MockCooler.make_random(chrom_offsets, binsize, density)