from pymoku.parts import BuildStep
from pymoku.sdk import PythonZip, Bitstream, AbstractShell, SCM_VERSION, FIRMWARE_VERSION


class BaseManifest:
    def __init__(self, _id, *args, aliases=[], **kwargs):
        self._id = _id
        self.aliases = aliases
        self._bitstreams = self._resolve_bitstreams(*args)

    def _resolve_bitstreams(self, *args):
        res = set()
        for a in args:
            if isinstance(a, Bitstream):
                res.add(a)
            elif isinstance(a, BuildStep):
                res.update(self._resolve_bitstreams(*a.outputs()))
            elif hasattr(a, '__iter__'):
                res.update(self._resolve_bitstreams(*a))
        return res

    def bitstreams(self):
        return self._bitstreams

    def entry_for_item(self, item, arcname_for):
        return dict(
            _id=self._id,
            _type=self.TYPE,
            # aliases=self.aliases,  # TODO conflict with firmware
            sha256=item.sha(),
            version=SCM_VERSION,
            filename=arcname_for(item),
            size=item.path.stat().st_size,
        )

    def __call__(self, arcname_for):
        return [(self.entry_for_item(bs, arcname_for), bs) for bs in self.bitstreams()]


class Manifest(BaseManifest):
    TYPE = 'instrument'

    def entry_for_item(self, item, arcname_for):
        d = super().entry_for_item(item, arcname_for)
        d.update(
            hwver=item.platform.hwver,
            plat_sha=item.platform.sha,
            slot=item.slot,
            extra=item.manifest_extra,
        )
        return d


class PlatformManifest(BaseManifest):
    TYPE = 'platform'

    def entry_for_item(self, item, arcname_for):
        d = super().entry_for_item(item, arcname_for)
        d.update(
            hwver=item.platform.hwver,
            slots=item.platform.slots,
            fwcompat=FIRMWARE_VERSION,
            inputs=item.platform.slot_ains,
            outputs=item.platform.slot_aouts,
            slot_outputs=item.platform.slot_aouts,
            slot_cbufs=item.platform.slot_cbufs,
            loops=0  # COMPAT
        )
        return d


class PythonManifest(BaseManifest):
    TYPE = 'python'

    def _resolve_bitstreams(self, *args):
        res = set()
        for a in args:
            if isinstance(a, PythonZip):
                res.add(a)
            else:
                res.update(self._resolve_bitstreams(*a))
        return res


class NetworkManifest(BaseManifest):
    TYPE = 'network'

    def entry_for_item(self, item, arcname_for):
        d = super().entry_for_item(item, arcname_for)
        d.update(
            hwver=item.platform.hwver,
            plat_sha=item.platform.sha,
        )
        return d


class CheckpointManifest(BaseManifest):
    TYPE = 'checkpoint'

    def _resolve_bitstreams(self, *args):
        res = set()
        for a in args:
            if isinstance(a, AbstractShell):
                res.add(a)
            else:
                res.update(self._resolve_bitstreams(*a))
        return res

    def entry_for_item(self, item, arcname_for):
        d = super().entry_for_item(item, arcname_for)
        d.update(
            plat_sha=item.platform.sha,
            extra=dict(
                hwver=item.platform.hwver,
                part=item.platform.part,
                arch=item.platform.arch,
                cell_name=item.cell_name,
                pblock_name=item.pblock_name,
                slot=item.slot,
                # TODO try to keep platform abstraction?
                num_ain=item.platform.slot_ains,
                num_aout=item.platform.slot_aouts,
                num_cbufs=item.platform.slot_cbufs,
                # TODO
                # dsps. clbs, brams
            ),
        )
        return d
