

import fnmatch
import socket
import time

from contracts import check_isinstance

from .dcache import DistributedRepo, ProcessFailure, interpret_message, Envelope
from .dcache_wire import MsgPing, create_pong_message, \
    MsgRequest, create_dismiss_message, create_propose_message, create_signature_message
from .ipfs_utils import IPFSInterface
from .irc2 import MessagingInterface
from .utils import DoAtInterval, get_sha256_base58


class DistributedRepoSwarm(DistributedRepo):

    def __init__(self):
        DistributedRepo.__init__(self)
        self.init_bucket('networks', allowed=[])
        self.init_bucket('peer', allowed=[])
        self.init_bucket('trusted', allowed=['trusted', 'admin'])
        self.init_bucket('admin', allowed=['admin'])

        self.init_bucket('files', allowed=[])
        self.init_bucket('verified', allowed=[])  #
        self.init_bucket('safe', allowed=[])  #

        self.init_bucket('summaries', allowed=[])  #

        self.init_bucket('pri', allowed=[])  #


MINUTES = 60
ALL_BRAINS = '/dswarm/*/brain'
DELTA_MAKE_SUMMARY = 5 * MINUTES
DAYS = 60 * 60 * 24


def brain(mi, cache_dir):
    check_isinstance(mi, MessagingInterface)

    ipfsi = IPFSInterface(mi.ipfs_path)

    dc = DistributedRepoSwarm()
    dc.use_cache_dir(cache_dir)

    t = time.time()

    proposed = set(dc.query('files'))
    verified = set(dc.query('verified'))
    for p in proposed:
        if not p in verified:
            mi.send_to_verifier(p)

    print('From cache: %s files' % len(dc.query('files')))
    print('From cache: %s verified' % len(dc.query('verified')))

    def on_new_files(d):
        data = d['data']
        #print('New file: %s' % data)
        mi.send_to_verifier(data)

    def on_new_peer(d):
        data = d['data']
        print('New peer: %s' % data)
        if data != identity:
            mi.send_to_connector(data)

    def on_new_verifed(d):
        data = d['data']
        #print('New verified: %s' % data)
        mi.send_to_pinner(data)

    def on_new_summary(d):
        bucket = d['bucket_name']
        data = d['data']
        if bucket[-1] == 'summaries':
            #print('New summary: %s' % d)
            which = bucket[1]
            if which != identity:
                print('Look up from %s %s' % (which, data))
                mi.send_to_readsummaries(data)
        else:
            pass
            #print('Ignoring new in bucket %s' % str(bucket))

    dc.add_hook_proposed('peer', on_new_peer)
    dc.add_hook_proposed('files', on_new_files)
    dc.add_hook_proposed('verified', on_new_verifed)
    dc.add_hook_proposed_all(on_new_summary)

    interval_make_summary = DoAtInterval(DELTA_MAKE_SUMMARY)
    DELTA_PEER = 10 * 60
    interval_advertise_peer = DoAtInterval(DELTA_PEER * 0.5)
    interval_addresses = DoAtInterval(10 * 60)
    interval_rebroadcast = DoAtInterval(10)

    key = mi.key
    identity = key.hash

    try:
        while True:
            if interval_addresses.its_time():
                addresses = ipfsi.get_addresses()
                for address in addresses:
                    if (address.startswith('/ip4/127.0.0.1/') or
                        address.startswith('/ip6/')):
                        continue
                    t = time.time()
                    msg = create_propose_message(('pri', identity, 'addresses'),
                                                 address, validity=[t, t + 300])
                    mi.send_to_brain('', msg, sign=True)

            if interval_make_summary.its_time():
                periodic_make_summary(dc, mi, identity, ipfsi)

            if interval_advertise_peer.its_time():
                t = time.time()
                msg = create_propose_message('peer', identity, validity=[t, t + DELTA_PEER])
                mi.send_to_brain('', msg, sign=True)

                import platform
                DELTA_INFO = 5 * DAYS
                properties = {}
                properties['hostname'] = socket.gethostname()
                properties['processor'] = platform.processor()
                properties['machine'] = platform.machine()
                properties['node'] = platform.node()
                properties['python_version'] = platform.python_version()
                properties['system'] = platform.system()

                for k, v in properties.items():
                    bucket = ('pri', identity, 'info', k)
                    msg = create_propose_message(bucket, v, validity=[t, t + DELTA_INFO])
                    mi.send_to_brain('', msg, sign=True)

            envelopes = mi.get_many_for_me(timeout=2)

            for envelope in envelopes:

                try:
                    m = interpret_message(envelope.contents)
                except:
                    pass
                else:
                    if isinstance(m, MsgPing):
                        reply_ping(mi, dc, envelope)

                    elif isinstance(m, MsgRequest):
                        reply_msg_request(mi, dc, envelope, m)

                    else:
                        try:
                            dc.process(envelope.contents, envelope.maddr_from)
                        except ProcessFailure as e:
                            em = 'Cannot interpret message: '
                            em += '\n' + str(e)
                            print(em)

            if interval_rebroadcast.its_time():
                periodic_rebroadcast(mi, dc)

            dc.cleanup()
            dc.sync()

    finally:
        print('Clean up cache')
        dc.close()


last_summary = None


def periodic_make_summary(dc, mi, identity, ipfsi):
    global last_summary
    summary_humans = dc.summary()
    summary_humans = summary_humans.replace(identity, 'QmSELF')
    summary_messages = dc.summary_messages()

    summary = ipfsi.get_tree_builder()
    summary.add_file_content('humans.txt', summary_humans)
    summary.add_file_content('machines.txt', '\n'.join(summary_messages))
    Q = summary.get_hash()

    bucket = ('pri', identity, 'summaries')
    t = time.time()
    if last_summary is not None:
        msg = create_dismiss_message(bucket, last_summary, validity=[t, None])
        mi.broadcast_to_all_brains(msg, sign=True)

    validity = [t, t + DELTA_MAKE_SUMMARY * 2]
    msg = create_propose_message(bucket, Q, validity)
    mi.broadcast_to_all_brains(msg, sign=True)

    last_summary = Q
    print('summary: %s' % last_summary)


def periodic_rebroadcast(mi, dc):
    messages = []

    for t, _ in dc.root.items():
        out_channels = mi._get_broadcasting_channels()
        these = dc.rebroadcast(t, out_channels)

        messages.extend(these)

    for envelope in messages:
        # find signatures
        hashed = get_sha256_base58(envelope.contents)
        if hashed in dc.signatures:
            for signer, signature in dc.signatures[hashed].items():
                msg = create_signature_message(hashed, signer, signature)
                e_sign = Envelope(envelope.maddr_from, envelope.maddr_to, msg)
                mi.dispatch(e_sign)
        mi.dispatch(envelope)


def reply_msg_request(mi, dc, envelope, m):
    messages = []
    pattern = m.bucket
    for bucket_name, _bucket in dc.root.items():
        c = "/" + "/".join(bucket_name)
        print('looking for %s' % str(bucket_name))
        if not fnmatch.fnmatch(c, pattern):
            print('does not match')
            continue
        out = dc._summary_message_for_bucket(bucket_name, only_positive=True)
#        print(bucket.summary())

        messages.extend(out)
    messages = [_[1] for _ in messages]
    messages = list(set(messages))

    print('Sending %d messages' % len(messages))
    for msg in messages:
        e = Envelope('', envelope.maddr_from, msg)
        mi.dispatch(e)

    pong = create_pong_message()
    e = Envelope('', envelope.maddr_from, pong)
    mi.dispatch(e)


def reply_ping(mi, _, envelope):
    pong = create_pong_message()
    e = Envelope('', envelope.maddr_from, pong)
    mi.dispatch(e)
