import functools
import hashlib
import pathlib
from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Literal, Optional, Sequence

from ._utils import TrackType
from .api import track

__all__ = [
    "LocalTileset",
    "RemoteTileset",
    "remote",
    "bigwig",
    "multivec",
    "cooler",
    "hitile",
    "bed2ddb",
]

TileId = str
Tile = Dict[str, Any]
TilesetInfo = Dict[str, Any]

DataType = Literal["vector", "multivec", "matrix"]


@dataclass
class LocalTileset:
    tiles: Callable[[Sequence[TileId]], List[Tile]]
    info: Callable[[], TilesetInfo]
    uid: str
    datatype: Optional[DataType] = None
    name: Optional[str] = None


@dataclass
class RemoteTileset:
    uid: str
    server: str
    name: Optional[str] = None

    def track(self, type_: TrackType, **kwargs):
        t = track(
            type_=type_,
            server=self.server,
            tilesetUid=self.uid,
            **kwargs,
        )
        if self.name:
            t.opts(name=self.name, inplace=True)
        return t


def remote(uid: str, server: str = "https://higlass.io/api/v1", **kwargs):
    return RemoteTileset(uid, server, **kwargs)


def hash_absolute_filepath_as_default_uid(fn: Callable[[str, str], LocalTileset]):
    def wrapper(filepath: str, uid: Optional[str] = None):
        if uid is None:
            abspath = pathlib.Path(filepath).absolute()
            uid = hashlib.md5(str(abspath).encode()).hexdigest()
        return fn(filepath, uid)

    return wrapper


@hash_absolute_filepath_as_default_uid
def bigwig(filepath: str, uid: str):
    try:
        from clodius.tiles.bigwig import tiles, tileset_info
    except ImportError:
        raise ImportError(
            'You must have `clodius` installed to use "vector" data-server.'
        )

    return LocalTileset(
        datatype="vector",
        tiles=functools.partial(tiles, filepath),
        info=functools.partial(tileset_info, filepath),
        uid=uid,
    )


@hash_absolute_filepath_as_default_uid
def multivec(filepath: str, uid: str):
    try:
        from clodius.tiles.multivec import tiles, tileset_info
    except ImportError:
        raise ImportError(
            'You must have `clodius` installed to use "multivec" data-server.'
        )

    return LocalTileset(
        datatype="multivec",
        tiles=functools.partial(tiles, filepath),
        info=functools.partial(tileset_info, filepath),
        uid=uid,
    )


@hash_absolute_filepath_as_default_uid
def cooler(filepath: str, uid: str):
    try:
        from clodius.tiles.cooler import tiles, tileset_info
    except ImportError:
        raise ImportError(
            'You must have `clodius` installed to use "matrix" data-server.'
        )

    return LocalTileset(
        datatype="matrix",
        tiles=functools.partial(tiles, filepath),
        info=functools.partial(tileset_info, filepath),
        uid=uid,
    )


@hash_absolute_filepath_as_default_uid
def hitile(filepath: str, uid: str):
    try:
        from clodius.tiles.hitile import tiles, tileset_info
    except ImportError:
        raise ImportError(
            'You must have `clodius` installed to use "vector" data-server.'
        )

    return LocalTileset(
        datatype="vector",
        tiles=functools.partial(tiles, filepath),
        info=functools.partial(tileset_info, filepath),
        uid=uid,
    )


@hash_absolute_filepath_as_default_uid
def bed2ddb(filepath: str, uid: str):
    try:
        from clodius.tiles.bed2ddb import tiles, tileset_info
    except ImportError:
        raise ImportError(
            'You must have `clodius` installed to use "vector" data-server.'
        )

    return LocalTileset(
        datatype="2d-rectangle-domains",
        tiles=functools.partial(tiles, filepath),
        info=functools.partial(tileset_info, filepath),
        uid=uid,
    )
