import os
from pytube import YouTube
import discord
import ffmpeg
from discord.ext import commands

class misc(commands.Cog):
    def __init__(self,bot):
        self.bot = bot

    @commands.command(name='play')
    async def play(self,ctx,url):
        guild= ctx.message.guild

        song_check = os.path.isfile(f'{guild}.mp3')
        try:
            if song_check:
                os.remove(f'{guild}.mp3')
        except  PermissionError:
            await ctx.send("Wait for current audio to end")
            return
        yt = YouTube(url)
        stream = yt.streams.filter(only_audio=True).first()
        stream.download()
        for file in os.listdir('./'):
            if file.endswith(".mp4"):
                os.rename(file, f'{guild}.mp3')
        channel = ctx.message.author.voice.channel
        vc = await  channel.connect()
        print('Joined')
        server = ctx.message.guild
        voice_client = server.voice_client
        player = vc.play(discord.FFmpegPCMAudio(f'{guild}.mp3'))
        print('Playing')

    @commands.command(name='clean')
    @commands.has_permissions(administrator=True)
    async def purge(self,ctx,limit:int = 50):
        if ctx.author.guild_permissions.administrator:
            await ctx.channel.purge(limit=limit)
            await ctx.send(f'Cleared by {ctx.author.mention}')
            await  ctx.message.delete()
        else:
            await ctx.send("Admin power only!")

    @commands.command(name='help')
    async def help(self,ctx):
        embed=discord.Embed(title="Dergbot Help", description="Information on how to use me either @ me or use 'db.'", color=0x6830cf)
        embed.add_field(name="Reddit", value="Top", inline=True)
        embed.add_field(name="Nsfw", value="e621 ,r34 example\n ' db.e621 dragon,-female or db.r34 dragon,-female'", inline=False)
        embed.add_field(name="Fun stuff", value=f"hug example \n'db.hug @user'", inline=False)
        embed.set_thumbnail(url='https://d.furaffinity.net/art/raventhedragon/1564470492/1564470492.raventhedragon_4_-_kaqcfbq.gif')
        embed.set_footer(text="For more Information contact @Raβen Ebonscaly#0432")
        await ctx.message.channel.send(embed=embed)

def setup(bot):
    bot.add_cog(misc(bot))


from infomap import Infomap
from pysam import AlignmentFile, AlignedSegment
from typing import Dict, List, Tuple, TextIO
import time
import glob
from file_io import *
from parsers import *
from random import shuffle, seed

class NbContig():
    # contig is the basic unit of this network
    # use this class as the basis
    # it might help reduce the overhead
    # and allows modifying or adding info to the contig
    __slots__ = ['id', 'length', 'num_id']
    def __init__(self, id:str, length=NotImplemented, num_id=NotImplemented):
        """
        :param id: NODE_1
        :param length: contig length
        :param num_id: numeric id for informap
        """
        self.id = id
        self.length = length
        self.num_id = num_id

# format_ctg_idf = lambda x: "_".join(x.strip().split(" ")[0].split("_")[:2])
format_ctg_idf = lambda x: x.strip().split(" ")[0]

# def make_infomap_nodes(ctg_lookup: Dict[str, NbContig]):
#     "multilayer network builder using Infomap API"
#     im_network = Infomap("-i ")
#     for i_nbc in ctg_lookup.items():
#         im_network.add_node(i_nbc)
    # with open(contig_fa, 'r') as fr:
    #     for iline in fr
#     add nodes from the contig list
#     input: contig.fasta
#     output: im_network

def make_complete_graph(nodes: List[NbContig], im_network, layer_idx) -> None:
    if len(nodes) > 1:
        link_count = 0
        for i in range(len(nodes)):
            for j in range(i+1, len(nodes)):
                inode = nodes[i]
                jnode = nodes[j]
                im_network.add_multilayer_intra_link(layer_idx, inode.num_id,
                                                     jnode.num_id, weight=1)
                link_count += 1
        print("[:::Message:::] made complete graph with {} edges".format(link_count))

def make_infomap_binned(args, im_network: Infomap, layer_idx,
                        ctg_lookup: Dict[str, NbContig]) -> None:

    def _subbin_handler(subbin: List[NbContig]) -> NbContig:
        """
        decide which contig is the center (median) that connects to all the rest of contigs
        which contig is the representative (biggest) that serves as the node connected to the other subbins.
        this handler makes star
        :return:
        """
        if len(subbin) == 1:
            return subbin[0]

        ctg_lens = [i.length for i in subbin]
        ctg_lens.sort()
        ctg_median = ctg_lens[len(ctg_lens) // 2 - 1]
        ctg_max = ctg_lens[-1]

        center_ctg = None
        rep_ctg = None
        circle_ctg = []

        for i in subbin:
            ilen = i.length
            if ilen == ctg_median:
                center_ctg = i
            elif ilen == ctg_max:
                rep_ctg = i
                circle_ctg.append(rep_ctg)
            else:
                circle_ctg.append(i)

        for i_ctg in circle_ctg:
            im_network.add_multilayer_intra_link(layer_idx, i_ctg.num_id, center_ctg.num_id, weight=1)

        return rep_ctg

    def make_network_bin_fa(bin_fa: str, s: int) -> int:
        """

        :param bin_fa: fasta file represent one bin
        :param s: number of sub-bins
        :return:
        """
        # step 1: decide the bin-dividing plan
        # step 2: fill in the edges

        # step 1
        bin_headers = set(parse_contig_fa(bin_fa).keys())
        bin_ctg = [ctg_lookup[i] for i in bin_headers if i in ctg_lookup]

        if len(bin_ctg) <= s:
            print("[:::Message:::] make complete graph for {}".format(bin_fa))
            make_complete_graph(nodes=bin_ctg, im_network=im_network, layer_idx=layer_idx)

        else:
            print("[:::Message:::] make density-reduced graph for {}".format(bin_fa))
            # seed(12345)
            seed(args.random_seed)
            shuffle(bin_ctg)

            N = len(bin_ctg)
            Lmin = N//s
            Lmax = Lmin + 1
            all_L = [Lmax] * (N%s)
            all_L.extend([Lmin]*(s - N%s))

            # step 2
            l0 = 0
            reps = []
            for l1 in all_L:
                i_subbin = bin_ctg[l0:l0+l1]
                irep = _subbin_handler(i_subbin)
                reps.append(irep)
                l0 += l1

            make_complete_graph(reps, im_network=im_network,
                                layer_idx=layer_idx)
        return len(bin_ctg)

    for i_bin_dir in args.bin_dir:
        bin_fa_files = glob.glob(i_bin_dir + "*." + args.bin_suffix)
        print("[=== Binning layer ===] Parsing binning result directory \"{}\".".format(i_bin_dir))
        if not len(bin_fa_files):
            print("[::Warning::] No contigs detected in \"{}\". Directory skipped".format(i_bin_dir))
            continue

        time_begin = time.time()

        n_binned_ctg = 0
        for i_fa in bin_fa_files:
            n_binned_ctg += make_network_bin_fa(bin_fa=i_fa, s=args.s)

        # update_dict_val(im_edges_content, i_edges)
        time_finish = time.time()
        print("[=== Binning layer ===] Built network for {} contigs in provided binning.".format(n_binned_ctg))
        print("[=== Binning layer ===] Processing binning result took {:.1f} secs".format(time_finish - time_begin))

def make_infomap_pairing(args, im_network: Infomap, layer_idx:int, ctg_lookup:Dict[str, NbContig]):
    def _update_dict_val(k: Tuple[int, int], v: int, ctg_links: Dict[Tuple[int, int], int]) -> None:
        if k in ctg_links:
            ctg_links[k] += v
        else:
            ctg_links[k] = v

    def process_sam() -> Dict[Tuple[int, int], int]:
        sam_handle = AlignmentFile(args.aln_file, 'rb')
        ctg_links = {}
        waiting_read_dict = {}
        for i_read in sam_handle.fetch():
            if not i_read.is_paired or i_read.is_secondary or i_read.is_supplementary:
                # if read.is_secondary or read.is_supplementary:
                continue

            i_rid = i_read.query_name
            if i_rid not in waiting_read_dict:
                if i_read.is_read1:
                    waiting_read_dict[i_rid] = (i_read, None)
                elif i_read.is_read2:
                    waiting_read_dict[i_rid] = (None, i_read)

            else:
                _imate = waiting_read_dict.pop(i_rid)
                i_mate = None
                if i_read.is_read1 and _imate[1]:
                    i_mate = _imate[1]
                elif i_read.is_read2 and _imate[0]:
                    i_mate = _imate[0]

                i_ctg_id = i_read.reference_name
                i_mate_ctg_id = i_mate.reference_name
                if i_ctg_id in ctg_lookup and \
                        i_mate_ctg_id in ctg_lookup and \
                        i_ctg_id != i_mate_ctg_id:
                    i_nbctg = ctg_lookup[i_ctg_id]
                    i_mate_nbctg = ctg_lookup[i_mate_ctg_id]
                    ij_weight = (i_read.query_alignment_length +
                                 i_mate.query_alignment_length)/(i_nbctg.length + i_mate_nbctg.length)
                    # no need to sort the keys because the mate always shows up before the read
                    # thus always i_nbctg.num_id > i_mate_nbctg.num_id
                    _update_dict_val((i_nbctg.num_id, i_mate_nbctg.num_id), ij_weight, ctg_links)
        sam_handle.close()
        return ctg_links

    time_begin = time.time()
    print("[:::Message:::] Parsing alignment...")

    links = process_sam()
    n_edges = 0
    for (i, j), w in links.items():
        if w > 0.001:
            im_network.add_multilayer_intra_link(layer_idx, i, j, w)
            n_edges += 1
    time_finish = time.time()

    print("[=== Read pairing layer ===] Made {} edges based on read pairing.".format(n_edges))
    print("[=== Read pairing layer ===] Processing read pairing took {:.1f} secs".format(
        time_finish - time_begin))

def make_infomap_linked(args, im_network: Infomap, layer_idx:int, ctg_lookup) -> None:
    """
    does 3 steps:
    1. get read-to-barcode mapping
    2. get {barcode: (ctg_num_id, aligned_bases, ctg_length)}
    3. establish ctg-to-ctg edges.
    The edge weight is based on aligned_bases to total_bases
    The edge weight has to be > 0.001 (for now)
    """

    def _get_reads2bars(seq_file: str):
        seqfile = SeqFile(seq_file)
        seq_idf = seqfile.idf
        reads2bars = {}

        _header_spliter = lambda x: x[1:].split(" ")
        for iline in seqfile.file_content:
            if iline.startswith(seq_idf):
                iline_split = _header_spliter(iline)
                reads2bars[iline_split[0]] = iline_split[-1]
        return reads2bars

    def _update_ctg_dict(dict_:Dict[str, int], elem_key: str, elem_val: int)-> None:
        """when iter over each contig, a dict {bar: weight} is made.
        this call updates the dict"""
        if elem_key in dict_:
            dict_[elem_key] += elem_val
        else:
            dict_[elem_key] = elem_val

    def _update_bars2ctgs(b2c: Dict[str, List[Tuple[int, int, int]]], ctg_dict: Dict[str, int], ctg_id: str) -> None:
        """b2c: {bar: (ctg_num_id, mapped_bases, ctg_len)}
        ctg_dict: {bar: n_mapped_bases}"""
        ctg = ctg_lookup[ctg_id]
        ctg_num_id = ctg.num_id
        ctg_len = ctg.length
        for k, v in ctg_dict.items():
            if k in b2c:
                b2c[k].append((ctg_num_id, v, ctg_len))
            else:
                b2c[k] = [(ctg_num_id, v, ctg_len)]

    def get_bars2ctgs():
        b2c = {}  # bars2contigs, {barcode: [contigs]}
        reads2bars = {}
        if args.se_reads:
            reads2bars = _get_reads2bars(args.se_reads)
        if args.pe_reads:
            reads2bars.update(_get_reads2bars(args.pe_reads[0]))
        print("[:::Message:::] Processed {} reads with unique ids.".format(len(reads2bars)))

        sam_handle = AlignmentFile(args.aln_file, 'rb')
        last_ctg = None
        i_ctg_dict = {}

        for i_read in sam_handle.fetch():
            if i_read.is_unmapped or i_read.is_secondary or i_read.is_supplementary:
                continue

            i_ctg = i_read.reference_name
            if i_ctg not in ctg_lookup:
                continue

            if not last_ctg:
                last_ctg = i_ctg
                i_aln_len = i_read.query_alignment_length
                i_bar = reads2bars[i_read.query_name]
                i_ctg_dict[i_bar] = i_aln_len
            elif i_ctg != last_ctg:
                # conclude the last contig: update the b2c
                _update_bars2ctgs(b2c, i_ctg_dict, last_ctg)
                # set up the new contig
                i_ctg_dict = {}
                last_ctg = i_ctg
                i_aln_len = i_read.query_alignment_length
                i_bar = reads2bars[i_read.query_name]
                i_ctg_dict[i_bar] = i_aln_len
            else:
                i_aln_len = i_read.query_alignment_length
                i_bar = reads2bars[i_read.query_name]
                _update_ctg_dict(i_ctg_dict, i_bar, i_aln_len)

        sam_handle.close()

        return b2c

    def _make_ctg_link_single_bar(ctg_links: Dict[Tuple[int, int], float],
                                  ictgs: List[Tuple[int, int, int]]) -> None:
        nctgs = len(ictgs)
        for i in range(nctgs):
            for j in range(i, nctgs):
                ik = ictgs[i][0]
                jk = ictgs[j][0]
                k = None
                if ik < jk:
                    k = (ik, jk)
                else:
                    k = (jk, ik)

                v = (ictgs[i][1]+ ictgs[j][1])/(ictgs[i][2]+ ictgs[j][2])
                if k in ctg_links:
                    ctg_links[k] += v
                else:
                    ctg_links[k] = v

    def generate_ctg2ctg_edges(bars2ctgs_dict:Dict[str, List[Tuple[int, int, int]]],
                               min_bar_size: int) -> Dict[Tuple[int, int], float]:
        ctg_links = {}
        test_nbars = 0
        for ibar, ictgs in bars2ctgs_dict.items():
            if len(ictgs) >= min_bar_size:
                _make_ctg_link_single_bar(ctg_links, ictgs)
                test_nbars += 1

        print("[:::Dev Message:::] no. legit bars: {}".format(test_nbars))
        return ctg_links

    print("[=== Linked read layer ===] Parsing linked reads...")
    time_begin = time.time()

    bars2ctgs = get_bars2ctgs()
    print("[:::Message:::] processed reads with {} barcodes".format(len(bars2ctgs)))
    time_bars2ctgs = time.time()
    print("[:::Dev Message:::]bars2ctgs: {:.1f} secs".format(time_bars2ctgs - time_begin))
    # bars2ctgs elements:
    # BX:Z:A61B28C05D34: [(1, 0.00021912252873939136), (6, 0.0005723737009561289), (2428, 0.1162340178225494)]
    links = generate_ctg2ctg_edges(bars2ctgs, args.min_bar_size)
    nedges = 0
    for k, v in links.items():
        if v > 0.001:
            im_network.add_multilayer_intra_link(layer_idx, k[0], k[1], v)
            nedges += 1

    time_finish = time.time()
    print("[=== Linked read layer ===] made {} edges based on read barcodes".format(nedges))
    print("[=== Linked read layer ===] Processing read linkage took {:.1f} secs".format(time_finish - time_begin))

def make_infomap_assembly(args, im_network: Infomap,
                          layer_idx: int, all_contigs: Set[str],
                          ctg_lookup: Dict[str, NbContig]):
    # get paths: {contig_num_id: [(path_left_end, path_right_end)]}
    #  Dict[int, List[Tuple]]
    time_begin = time.time()
    # obuffer = []

    if args.assembler == "spades":
        path_segs = parse_spades_contig_paths(all_contigs, ctg_lookup, args.ctg_paths)
        contigs = list(path_segs.keys())
        contigs.sort()

        counter = 0
        for i in range(len(contigs)):
            for j in range(i+1, len(contigs)):
                ic = contigs[i]
                jc = contigs[j]
                isegs = path_segs[ic]
                jsegs = path_segs[jc]
                if isegs.intersection(jsegs):
                    im_network.add_multilayer_intra_link(layer_idx, ic, jc)
                    # obuffer.append("{0} {1} {2}".format(layer_idx, ic, jc))
                    counter +=1
        print("[=== Assembly layer ===] made {} links with {} contigs".format(counter, len(contigs)))
    time_end = time.time()

    print("[=== Assembly layer ===] Processing assembly took {:.1f} secs".format(time_end - time_begin))
    # return "\n".join(obuffer)

def generate_ctg_lookup(contig_fa_lookup: Dict[str, str], min_ctg_len: int):
    # alternative func for generate ctg lookup
    # function: generate ctg lookup dict
    #           and initialize im by initializing the network and adding nodes
    # change: node_id is no longer a str, but an int
    """
    :param contig_fa_lookup: {fasta_header: seq_len}
    :param min_ctg_len:
    :return: contig_fa_lookup.keys(): all contig names as a set
    :return: ctg_lookup: Dict[str, NbContig]
            contigs that passed the min contig length check, formatted for use in infomap
    :return: unbinned: [fasta record as string]
    :return: im_network is no longer generated here.
    """
    im_network = Infomap("-s 1")
    ctg_lookup = {}
    unbinned_short = []
    i = 1

    for iheader, iseq in contig_fa_lookup.items():
        ilen = len(iseq)

        if ilen < min_ctg_len:
            unbinned_short.append(iheader)
        else:
            formatted_id = format_ctg_idf(iheader)
            cur_nbcontig = NbContig("", ilen, i)  # changed node_id to int
            ctg_lookup[iheader] = cur_nbcontig  # changed the key to the fasta header
            im_network.set_name(i, formatted_id)
            # im_network.add_node(node_id=i,
            #                     name=formatted_id)

        i += 1
    print("[=== Parsing contigs ===] {} contigs with minimum length {} for binning".format(len(ctg_lookup), min_ctg_len))

    return ctg_lookup, im_network, unbinned_short


from math import cos, sin, radians
from django import template
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _

register = template.Library()

class RecurseTreeNode(template.Node):
    def __init__(self, template_nodes, tree_var):
        self.template_nodes = template_nodes
        self.tree_var = tree_var

    def _render_node(self, context, node):
        bits = []
        context.push()
        if not node.is_leaf:
            for child in node.childrens:
                context['node'] = child
                bits.append(self._render_node(context, child))
            context['children'] = mark_safe(u''.join(bits))
        context['node'] = node
        rendered = self.template_nodes.render(context)
        context.pop()
        return rendered

    def render(self, context):
        tree = self.tree_var.resolve(context)
        return self._render_node(context, tree)

@register.tag
def recursetree(parser, token):
    """
    Iterates over the nodes in the tree, and renders the contained block for each node.
    This tag will recursively render children into the template variable {{ children }}.
    Only one database query is required (children are cached for the whole tree)

    Usage:

                {% recursetree nodes %}

                        {{ node.name }}
                        {% if not node.is_leaf_node %}

                                {{ children }}

                        {% endif %}

                {% endrecursetree %}

    """
    bits = token.contents.split()
    if len(bits) != 2:
        raise template.TemplateSyntaxError(_('%s tag requires a queryset') % bits[0])

    tree_var = template.Variable(bits[1])

    template_nodes = parser.parse(('endrecursetree',))
    parser.delete_first_token()

    return RecurseTreeNode(template_nodes, tree_var)

class RecurseMenuNode(template.Node):
    def __init__(self, template_nodes, tree_var, distance):
        self.template_nodes = template_nodes
        self.tree_var = tree_var
        self.start_min_angle = 0
        self.start_max_angle = 360
        self.distance = distance

    def _render_node(self, context, parent, node, min_angle, max_angle, originex, originey, distance):
        bits = []
        total_leafs = node.leafs
        if node.level > 0:
            angle_position = ((max_angle - min_angle) / 2) + min_angle
            node.angle = angle_position
            radian_position = radians(angle_position)
            node.x = int(cos(radian_position) * (node.level * distance) + originex)
            node.y = int(sin(radian_position) * (node.level * distance) + originey)
            node.fromx = parent.x
            node.fromy = parent.y
#            print u"%s angle:%d (%d %d), level:%d, cos:%f, sin:%f, x:%d, y:%d" % (node.name, angle_position, min_angle, max_angle, node.level, cos(radian_position), sin(radian_position), node.x, node.y)
        else:
            node.x = originex
            node.y = originey
            node.fromx = 0
            node.fromy = 0
            node.margin = (node.max_level * distance) + 40
            node.size = (node.margin * 2)
#            print u"%s angle:aucun (%d %d), level:%d, x:%d, y:%d" % (node.name, min_angle, max_angle, node.level, node.x, node.y)

        current_angle = min_angle
        context.push()
        if not node.is_leaf:
            for child in node.childrens:
                context['node'] = child
                if (child.leafs > 0 ):
                    angle = (max_angle - min_angle) * child.leafs / total_leafs
                else:
                    angle = (max_angle - min_angle) / total_leafs
                next_angle = current_angle + angle
                bits.append(self._render_node(context, node, child, current_angle, next_angle, originex, originey, distance))
                current_angle = next_angle
            context['children'] = mark_safe(u''.join(bits))
        context['node'] = node
        rendered = self.template_nodes.render(context)
        context.pop()
        return rendered

    def render(self, context):
        tree = self.tree_var.resolve(context)
        distance = self.distance.resolve(context)
        max_level = tree.max_level
        originex = (max_level * distance) + (distance / 2)
        originey = (max_level * distance) + (distance / 2)
#        print u"X:%d - Y:%d" % (originex, originey)
        return self._render_node(context, None, tree, self.start_min_angle, self.start_max_angle, originex, originey, distance)

@register.tag
def recursemenu(parser, token):
    bits = token.contents.split()
    if len(bits) != 3:
        raise template.TemplateSyntaxError(_('%s tag requires 3 arguments') % bits[0])

    tree = template.Variable(bits[1])
    distance = template.Variable(bits[2])

    template_nodes = parser.parse(('endrecursemenu',))
    parser.delete_first_token()

    return RecurseMenuNode(template_nodes, tree, distance)

class LevelNode(template.Node):
    def __init__(self, tree_var, distance):
        self.tree_var = tree_var
        self.distance = distance
        self.levels = []
        self.current_level = -1

    def _list_levels(self, node, originex, originey, distance):
        if (node.level > self.current_level):
            self.current_level = node.level
            radius = (node.level * distance) + (distance / 2)
            level = {'num': node.level, 'radius': radius, 'x': originex, 'y': originey}
            self.levels.append(level)
        if not node.is_leaf:
            for child in node.childrens:
                self._list_levels(child, originex, originey, distance)

    def render(self, context):
        tree = self.tree_var.resolve(context)
        distance = self.distance.resolve(context)
        max_level = tree.max_level
        originex = (max_level * distance) + (distance / 2)
        originey = (max_level * distance) + (distance / 2)

        self._list_levels(tree, originex, originey, distance)
        context['levels'] = self.levels
        return ""

@register.tag
def list_levels(parser, token):
    bits = token.contents.split()
    if len(bits) != 3:
        raise template.TemplateSyntaxError(_('%s tag requires 3 arguments') % bits[0])

    tree = template.Variable(bits[1])
    distance = template.Variable(bits[2])
    return LevelNode(tree, distance)


from __future__ import annotations
import dataclasses
from typing import Sequence, TYPE_CHECKING, Callable
import bisect
import yakut

if TYPE_CHECKING:
    import pycyphal.application

@dataclasses.dataclass
class Result:
    names_per_node: dict[int, list[str] | None] = dataclasses.field(default_factory=dict)
    errors: list[str] = dataclasses.field(default_factory=list)
    warnings: list[str] = dataclasses.field(default_factory=list)

async def list_names(
    local_node: "pycyphal.application.Node",
    progress: Callable[[str], None],
    node_ids: Sequence[int],
    *,
    optional_service: bool,
    timeout: float,
) -> Result:
    res = Result()
    for nid, names in (await _impl_list_names(local_node, progress, node_ids, timeout=timeout)).items():
        _logger.debug("Names @%r: %r", nid, names)
        if isinstance(names, _NoService):
            res.names_per_node[nid] = None
            if optional_service:
                res.warnings.append(f"Register list service is not accessible at node {nid}, ignoring as requested")
            else:
                res.errors.append(f"Register list service is not accessible at node {nid}")
        else:
            lst = res.names_per_node.setdefault(nid, [])
            assert isinstance(lst, list)
            for idx, n in enumerate(names):
                if isinstance(n, _Timeout):
                    res.errors.append(f"Request #{idx} to node {nid} has timed out, data incomplete")
                else:
                    bisect.insort(lst, n)
    return res

class _NoService:
    pass

class _Timeout:
    pass

async def _impl_list_names(
    local_node: "pycyphal.application.Node",
    progress: Callable[[str], None],
    node_ids: Sequence[int],
    *,
    timeout: float,
) -> dict[int, list[str | _Timeout] | _NoService]:
    from uavcan.register import List_1

    out: dict[int, list[str | _Timeout] | _NoService] = {}
    for nid in node_ids:
        cln = local_node.make_client(List_1, nid)
        try:
            cln.response_timeout = timeout
            name_list: list[str | _Timeout] | _NoService = []
            for idx in range(2**16):
                progress(f"{nid: 5}: {idx: 5}")
                resp = await cln(List_1.Request(index=idx))
                assert isinstance(name_list, list)
                if resp is None:
                    if 0 == idx:  # First request timed out, assume service not supported or node is offline
                        name_list = _NoService()
                    else:  # Non-first request has timed out, assume network error
                        name_list.append(_Timeout())
                    break
                assert isinstance(resp, List_1.Response)
                name = resp.name.name.tobytes().decode(errors="replace")
                if not name:
                    break
                name_list.append(name)
        finally:
            cln.close()
        _logger.debug("Register names fetched from node %r: %r", nid, name_list)
        out[nid] = name_list
    return out

_logger = yakut.get_logger(__name__)


# coding: utf-8
"""
 FLAG DEFINITIONS (Consider moving elsewhere?)
 0: Good
 1: Pixel not read out
 2: Beammap failed to place pixel
 3: Succeeded in x, failed in y
 4: Succeeded in y, failed in x
 5: Multiple locations found for pixel
 6: Beammap placed the pixel in the wrong feedline
"""
import numpy as np
import scipy.optimize as opt

design_feedline_path = r"mec_feedline.txt"
design_feedline = np.loadtxt(design_feedline_path)

def residuals(parameters, feedlineobject, model):
    """
    Based on a model for our data fit to a polynomial find the residuals between the measured data and the model.
    This is formatted to be used in the non-linear least squares regression code block
    """
    p = np.poly1d(parameters)
    ydata, modeldata = flattendata(feedlineobject,model)
    err = ydata - p(modeldata)
    return err

def initialparamguesser(feedlineobject, model, order):
    """
    Because we are working in a space where we do not know the ideal number of parameters to fit, create a function
    where we can specify the order of the polynomial we want to describe our model with, which will then let us find out
    what order polynomial allows us to minimize our residuals by nonlinear least squares regression (maximimizing
    likelihood)
    NOTE: This works in conjunction with the feedlinefitter function based on the order it is given, which can be specific
    or run through a large number of orders to see which fits best
    """
    ydata, modeldata = flattendata(feedlineobject, model)
    params = np.polyfit(modeldata, ydata, order, full=True)[0]
    return params

def flattendata(feedlineobject, model):
    """
    Create a function which flattens the data to conveniently work in frequency space, ensuring that the model data and
    measured data match each other, which is to say that at a given index in the 1D array, the ydata array shows the frequency
    that was measured at a given position, will at the same index, the modeldata array gives the design frequency at the same
    coordinate.
    """
    ydata = feedlineobject.normfreqs.flatten()
    modeldata = model.flatten()
    modeldata = modeldata[~np.isnan(ydata)]
    ydata = ydata[~np.isnan(ydata)]
    return ydata, modeldata

def feedlinefitter(feedlineobj, modelfeedline, order=None):
    """
    This returns the nonlinear least squares object from the Scipy optimize package, and we use two of the return
    values: .x is the array of coefficients for the best fit and .fun is the actual array of residuals. More often we
    use the .fun method so we can see how well we were able to fit our data
    """
    if order == None:
        chisquarevals = np.zeros(30)
        for i in range(len(chisquarevals)):
            pguess = initialparamguesser(feedlineobj, modelfeedline, i)
            lssqsol = opt.least_squares(residuals, pguess, args=(feedlineobj, modelfeedline))
            chisquarevals[i] = np.sum((lssqsol.fun)**2)**(1/2)
        minchisquare = np.min(chisquarevals)
        bestfitorder = np.where(chisquarevals == minchisquare)[0]

        bestguess = initialparamguesser(feedlineobj, modelfeedline, bestfitorder)
        leastsquaressolution = opt.least_squares(residuals, bestguess, args=(feedlineobj, modelfeedline))
        return leastsquaressolution, bestfitorder
    else :
        bestguess = initialparamguesser(feedlineobj, modelfeedline, order)
        leastsquaressolution = opt.least_squares(residuals, bestguess, args=(feedlineobj, modelfeedline))
        return leastsquaressolution, order

def leastsquaremethod(feedlineobj, modelfeedline, order = None):
    """
    Given a feedline, design feedline, and whatever order you wish your least squares to be (None gives the best fit
    below order 30) and returns the measured frequencies, the frequencies fit by the least squares regression, the model
    frequencies, and the residuals
    """
    results, best_order = feedlinefitter(feedlineobj, modelfeedline,order)
    fitted_coeffs = np.poly1d(results.x) # Consider if this is necessary
    realdata, modeldata = flattendata(feedlineobj, design_feedline)
    fitteddata = fitted_coeffs(modeldata)
    residualvalues = results.fun
    return realdata, fitteddata, modeldata, residualvalues


"""infer.py

Main inference script.

Usage:
  infer.py [--gpu=<id>] [--mode=<mode>]
  infer.py (-h | --help)
  infer.py --version

Options:
  -h --help      Show this string.
  --version      Show version.
  --gpu=<id>     Comma separated GPU list. [default: 0]
  --mode=<mode>  Inference mode- use either 'seg' or 'class'.
"""

from docopt import docopt
import argparse
import glob
import math
import time
import os
import sys
from collections import deque
from keras.utils import HDF5Matrix

import cv2
import numpy as np

from tensorpack.predict import OfflinePredictor, PredictConfig
from tensorpack.tfutils.sessinit import get_model_loader

from config import Config
from misc.utils import rm_n_mkdir, cropping_center
from model.utils.model_utils import crop_op

import json
import operator

from sklearn.metrics import roc_auc_score

def get_best_chkpts(path, metric_name, comparator=">"):
    """
    Return the best checkpoint according to some criteria.
    Note that it will only return valid path, so any checkpoint that has been
    removed wont be returned (i.e moving to next one that satisfies the criteria
    such as second best etc.)

    Args:
        path: directory contains all checkpoints, including the "stats.json" file
    """
    stat_file = path + "/stats.json"
    ops = {
        ">": operator.gt,
        "<": operator.lt,
    }

    op_func = ops[comparator]
    with open(stat_file) as f:
        info = json.load(f)

    if comparator == ">":
        best_value = -float("inf")
    else:
        best_value = +float("inf")

    best_chkpt = None
    for epoch_stat in info:
        epoch_value = epoch_stat[metric_name]
        if op_func(epoch_value, best_value):
            chkpt_path = "%s/model-%d.index" % (path, epoch_stat["global_step"])
            if os.path.isfile(chkpt_path):
                selected_stat = epoch_stat
                best_value = epoch_value
                best_chkpt = chkpt_path
    return best_chkpt, selected_stat

class InferClass(Config):
    def __gen_prediction(self, x, predictor):
        """
        Using 'predictor' to generate the prediction of image 'x'

        Args:
            x : input image to be segmented. It will be split into patches
                to run the prediction upon before being assembled back
            tissue: tissue mask created via otsu -> only process tissue regions
                    if it is provided
        """

        prob = predictor(x)[0]
        pred = np.argmax(prob, -1)
        pred = np.squeeze(pred)
        prob = np.squeeze(prob[..., 1])

        return prob, pred

    ####
    def run(self):
        if self.inf_auto_find_chkpt:
            print(
                '-----Auto Selecting Checkpoint Basing On "%s" Through "%s" Comparison'
                % (self.inf_auto_metric, self.inf_auto_comparator)
            )
            model_path, stat = get_best_chkpts(
                self.save_dir, self.inf_auto_metric, self.inf_auto_comparator
            )
            print("Selecting: %s" % model_path)
            print("Having Following Statistics:")
            for key, value in stat.items():
                print("\t%s: %s" % (key, value))
        else:
            model_path = self.inf_model_path
        model_constructor = self.get_model()
        pred_config = PredictConfig(
            model=model_constructor(),
            session_init=get_model_loader(model_path),
            input_names=self.eval_inf_input_tensor_names,
            output_names=self.eval_inf_output_tensor_names,
            create_graph=False)
        predictor = OfflinePredictor(pred_config)

        ####
        save_dir = self.inf_output_dir
        predict_list = [["case", "prediction"]]

        file_load_img = HDF5Matrix(
            self.inf_data_list[0] + "camelyonpatch_level_2_split_test_x.h5", "x"
        )
        file_load_lab = HDF5Matrix(
            self.inf_data_list[0] + "camelyonpatch_level_2_split_test_y.h5", "y"
        )

        true_list = []
        prob_list = []
        pred_list = []

        num_ims = file_load_img.shape[0]
        last_step = math.floor(num_ims / self.inf_batch_size)
        last_step = self.inf_batch_size * last_step
        last_batch = num_ims - last_step
        count = 0
        for start_batch in range(0, last_step + 1, self.inf_batch_size):
            sys.stdout.write("\rProcessed (%d/%d)" % (start_batch, num_ims))
            sys.stdout.flush()
            if start_batch != last_step:
                img = file_load_img[start_batch : start_batch + self.inf_batch_size]
                img = img.astype("uint8")
                lab = np.squeeze(
                    file_load_lab[start_batch : start_batch + self.inf_batch_size]
                )
            else:
                img = file_load_img[start_batch : start_batch + last_batch]
                img = img.astype("uint8")
                lab = np.squeeze(file_load_lab[start_batch : start_batch + last_batch])

            prob, pred = self.__gen_prediction(img, predictor)

            for j in range(prob.shape[0]):
                predict_list.append([str(count), str(prob[j])])
                count += 1

            prob_list.extend(prob)
            pred_list.extend(pred)
            true_list.extend(lab)

        prob_list = np.array(prob_list)
        pred_list = np.array(pred_list)
        true_list = np.array(true_list)
        accuracy = (pred_list == true_list).sum() / np.size(true_list)
        error = (pred_list != true_list).sum() / np.size(true_list)

        print("Accurcy (%): ", 100 * accuracy)
        print("Error (%): ", 100 * error)
        if self.model_mode == "class_pcam":
            auc = roc_auc_score(true_list, prob_list)
            print("AUC: ", auc)

        # Save predictions to csv
        rm_n_mkdir(save_dir)
        for result in predict_list:
            predict_file = open("%s/predict.csv" % save_dir, "a")
            predict_file.write(result[0])
            predict_file.write(",")
            predict_file.write(result[1])
            predict_file.write("\n")
            predict_file.close()

class InferSeg(Config):
    def __gen_prediction(self, x, predictor):
        """
        Using 'predictor' to generate the prediction of image 'x'

        Args:
            x : input image to be segmented. It will be split into patches
                to run the prediction upon before being assembled back
            tissue: tissue mask created via otsu -> only process tissue regions
                    if it is provided
        """
        step_size = self.infer_output_shape
        msk_size = self.infer_output_shape
        win_size = self.infer_input_shape

        def get_last_steps(length, msk_size, step_size):
            nr_step = math.ceil((length - msk_size) / step_size)
            last_step = (nr_step + 1) * step_size
            return int(last_step), int(nr_step + 1)

        im_h = x.shape[0]
        im_w = x.shape[1]

        last_h, nr_step_h = get_last_steps(im_h, msk_size[0], step_size[0])
        last_w, nr_step_w = get_last_steps(im_w, msk_size[1], step_size[1])

        diff_h = win_size[0] - step_size[0]
        padt = diff_h // 2
        padb = last_h + win_size[0] - im_h

        diff_w = win_size[1] - step_size[1]
        padl = diff_w // 2
        padr = last_w + win_size[1] - im_w

        x = np.lib.pad(x, ((padt, padb), (padl, padr), (0, 0)), "reflect")

        #### TODO: optimize this
        sub_patches = []
        skipped_idx = []
        # generating subpatches from orginal
        idx = 0
        for row in range(0, last_h, step_size[0]):
            for col in range(0, last_w, step_size[1]):
                win = x[row : row + win_size[0], col : col + win_size[1]]
                sub_patches.append(win)
                idx += 1

        pred_map = deque()
        while len(sub_patches) > self.inf_batch_size:
            mini_batch = sub_patches[: self.inf_batch_size]
            sub_patches = sub_patches[self.inf_batch_size :]
            mini_output = predictor(mini_batch)[0]
            if win_size[0] > msk_size[0]:
                mini_output = cropping_center(mini_output, (diff_h, diff_w))
            mini_output = np.split(mini_output, self.inf_batch_size, axis=0)
            pred_map.extend(mini_output)
        if len(sub_patches) != 0:
            mini_output = predictor(sub_patches)[0]
            if win_size[0] > msk_size[0]:
                mini_output = cropping_center(mini_output, (diff_h, diff_w))
            mini_output = np.split(mini_output, len(sub_patches), axis=0)
            pred_map.extend(mini_output)

        #### Assemble back into full image
        output_patch_shape = np.squeeze(pred_map[0]).shape

        ch = 1 if len(output_patch_shape) == 2 else output_patch_shape[-1]

        #### Assemble back into full image
        pred_map = np.squeeze(np.array(pred_map))
        pred_map = np.reshape(pred_map, (nr_step_h, nr_step_w) + pred_map.shape[1:])
        pred_map = (
            np.transpose(pred_map, [0, 2, 1, 3, 4])
            if ch != 1
            else np.transpose(pred_map, [0, 2, 1, 3])
        )
        pred_map = np.reshape(
            pred_map,
            (
                pred_map.shape[0] * pred_map.shape[1],
                pred_map.shape[2] * pred_map.shape[3],
                ch,
            ),
        )
        # just crop back to original size
        pred_map = np.squeeze(pred_map[:im_h, :im_w])

        return pred_map

    ####
    def run(self):

        if self.inf_auto_find_chkpt:
            print(
                '-----Auto Selecting Checkpoint Basing On "%s" Through "%s" Comparison'
                % (self.inf_auto_metric, self.inf_auto_comparator)
            )
            model_path, stat = get_best_chkpts(
                self.save_dir, self.inf_auto_metric, self.inf_auto_comparator
            )
            print("Selecting: %s" % model_path)
            print("Having Following Statistics:")
            for key, value in stat.items():
                print("\t%s: %s" % (key, value))
        else:
            model_path = self.inf_model_path

        model_constructor = self.get_model()
        pred_config = PredictConfig(
            model=model_constructor(),
            session_init=get_model_loader(model_path),
            input_names=self.eval_inf_input_tensor_names,
            output_names=self.eval_inf_output_tensor_names,
            create_graph=False,
        )
        predictor = OfflinePredictor(pred_config)

        for data_dir in self.inf_data_list:
            save_dir = self.inf_output_dir + "/raw/"
            file_list = glob.glob("%s/*%s" % (data_dir, self.inf_imgs_ext))
            file_list.sort()  # ensure same order

            rm_n_mkdir(save_dir)
            for filename in file_list:
                start = time.time()
                filename = os.path.basename(filename)
                basename = filename.split(".")[0]
                print(data_dir, basename, end=" ", flush=True)

                ##
                img = cv2.imread(data_dir + filename)
                img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

                pred_map = self.__gen_prediction(img, predictor)

                np.save("%s/%s.npy" % (save_dir, basename), [pred_map])
                end = time.time()
                diff = str(round(end - start, 2))
                print("FINISH. TIME: %s" % diff)

####
if __name__ == "__main__":
    args = docopt(__doc__)
    print(args)

    if args["--gpu"]:
        os.environ["CUDA_VISIBLE_DEVICES"] = args["--gpu"]
        nr_gpus = len(args["--gpu"].split(","))

    if args["--mode"] is None:
        raise Exception('Mode cannot be empty. Use either "class" or "seg".')

    if args["--mode"] == "class":
        infer = InferClass()
    elif args["--mode"] == "seg":
        infer = InferSeg()
    else:
        raise Exception('Mode not recognised. Use either "class" or "seg".')

    infer.run()


from odoo import api, fields, models, _
from datetime import datetime

REPLACEMENT_OF_KEY = [('id', 'template_id')]
DATE_CONVERSION = ['date_created', 'date_edited']
UNWANTED_DATA = ['_links', 'created_by', 'edited_by', 'thumbnail']

class MailChimpTemplates(models.Model):
    _name = "mailchimp.templates"
    _description = "Templates"

    name = fields.Char("Name", required=True, help="The name of the template.")
    template_id = fields.Char("Template ID", copy=False)
    type = fields.Selection([('user', 'User'), ('gallery', 'Gallery'), ('base', 'Base')], default='user', copy=False,
                            help="The type of template (user, base, or gallery).")
    drag_and_drop = fields.Boolean("Drag and Drop", help="Whether the template uses the drag and drop editor.")
    responsive = fields.Boolean("Responsive", help="Whether the template contains media queries to make it responsive.")
    category = fields.Char("Template Category", help="If available, the category the template is listed in.")
    date_created = fields.Datetime("Created On")
    date_edited = fields.Datetime("Edited On")
    active = fields.Boolean("Active", default=True)
    folder_id = fields.Char("Folder ID", help="The id of the folder the template is currently in.")
    share_url = fields.Char("Share URL", help="The URL used for template sharing")
    account_id = fields.Many2one("mailchimp.accounts", string="Account", required=True, ondelete='cascade')
    # body_html = fields.Html(string='Body', sanitize_attributes=False)
    is_exported = fields.Boolean('Is Exported', help='This flag using identified template exported odoo to mailchimp')
    body_arch = fields.Html(string='Body', translate=False)
    body_html = fields.Html(string='Body converted to be send by mail', sanitize_attributes=False)

    def create_or_update_template(self, values_dict, account=False):
        template_id = values_dict.get('id')
        existing_list = self.search([('template_id', '=', template_id)])
        for item in UNWANTED_DATA:
            values_dict.pop(item)
        for old_key, new_key in REPLACEMENT_OF_KEY:
            values_dict[new_key] = values_dict.pop(old_key)
        for item in DATE_CONVERSION:
            if values_dict.get(item, False) == '':
                values_dict[item] = False
            if values_dict.get(item, False):
                values_dict[item] = account.covert_date(values_dict.get(item))
        values_dict.update({'account_id': account.id})
        if not existing_list:
            existing_list = self.create(values_dict)
        else:
            existing_list.write(values_dict)
        return True

    def import_templates(self, account=False):
        if not account:
            raise Warning("MailChimp Account not defined to import templates")
        count = 1000
        offset = 0
        template_list = []
        while True:
            prepared_vals = {'count': count, 'offset': offset}
            response = account._send_request('templates', {}, params=prepared_vals)
            if len(response.get('templates')) == 0:
                break
            if isinstance(response.get('templates'), dict):
                template_list += [response.get('templates')]
            template_list += response.get('templates')
            offset = offset + 1000
        for template_dict in template_list:
            self.create_or_update_template(template_dict, account=account)
        return True

    def export_update_templates_mailchimp(self, account=False):
        for record in self:
            body_html = self.env['mail.render.mixin']._replace_local_links(record.body_html)
            prepared_vals = {'name': record.name, 'html': body_html, 'type': record.type,
                             'drag_and_drop': record.drag_and_drop, 'responsive': record.responsive}
            if record.template_id:
                # return True
                response = record.account_id._send_request('templates/%s' % (record.template_id), prepared_vals,
                                                           method='PATCH')
            else:
                response = record.account_id._send_request('templates', prepared_vals, method='POST')
            # account.covert_date(values_dict.get(item))
            date_created = response.get('date_created') and datetime.strptime(response.get('date_created'),
                                                                              "%Y-%m-%dT%H:%M:%S+00:00") or False
            date_edited = response.get('date_edited') and datetime.strptime(response.get('date_edited'),
                                                                            "%Y-%m-%dT%H:%M:%S+00:00") or False
            record.write({
                'template_id': response.get('id'),
                'type': response.get('type'),
                'drag_and_drop': response.get('drag_and_drop'),
                'responsive': response.get('responsive'),
                'category': response.get('category'),
                'date_created': date_created,
                'date_edited': date_edited,
                'active': response.get('active'),
                'share_url': response.get('share_url'),
                'is_exported': True
            })
        return True


from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
from .models import *
from vocational.models import VocationalStatus

# Register your models here.
@admin.register(Country)
class Country(admin.ModelAdmin):
    list_display = ('name', 'code', 'region')
    list_editable = ('code', 'region')

class AddressInLine(admin.StackedInline):
    model = Address
    can_delete = True
    extra = 0

@admin.register(School)
class School(admin.ModelAdmin):
    inlines = [AddressInLine,]
    list_display = ('name', 'abbreviation',)
    list_editable = ('abbreviation', )

class ProfileInline(admin.StackedInline):
    model = Profile
    can_delete = True
    extra = 0

# class InstructorInline(admin.StackedInline):
#     model = Instructor
#     can_delete = True
#     extra = 0
# class VocationalCoordinatorInline(admin.StackedInline):
#     model = VocationalCoordinator
#     can_delete = True
#     extra = 0
# class SchoolAdminInline(admin.StackedInline):
#     model = SchoolAdmin
#     can_delete = True
#     e

class StudentInline(admin.StackedInline):
    model=Student
    can_delete=True
    extra=0

class UserAdmin(AuthUserAdmin):
    model = User
    inlines = [ProfileInline, StudentInline,]
    list_display = ('username', 'first_name', 'last_name', 'id','group', 'is_active')
    list_editable = ('is_active',)

    #def School(self, obj):
    #    return obj.teacher.school

    def group(self,obj):
        groups = []
        for group in obj.groups.all():
            groups.append(group.name)
            groups.append(" ")
        return ''.join(groups)

admin.site.unregister(User)
admin.site.register(User, UserAdmin)

class VocationalStatusInline(admin.StackedInline):
    model=VocationalStatus
    can_delete=True
    extra=0

@admin.register(Student)
class Student(admin.ModelAdmin):
    model= Student
    inlines = [VocationalStatusInline,]
    list_display = ('user','graduation_year', 'birthday', 'gender', 'is_active', "vocational_level")
    list_editable = ('gender', 'graduation_year')

    def is_active(self, obj):
        return obj.user.is_active

    def vocational_level(self, obj):
        return obj.vocationalstatus.vocational_level

# @admin.register(Instructor)
# class Instructor(admin.ModelAdmin):
#     #inlines = [UserInline]
#     #list_display = ('name', 'school',)
#     #list_editable = ('school', )
#     #list_display_links = ('name',)
#     def get_queryset(self, request):
#         qs = super().get_queryset(request)
#         return qs.filter(user__is_active = True)
#
# @admin.register(SchoolAdmin)
# class SchoolAdmin(admin.ModelAdmin):
#     #inlines = [UserInline]
#     #list_display = ('name', 'school',)
#     #list_editable = ('school', )
#     #list_display_links = ('name',)
#     def get_queryset(self, request):
#         qs = super().get_queryset(request)
#         return qs.filter(user__is_active = True)
#
# @admin.register(VocationalCoordinator)
# class VocationalCoordinator(admin.ModelAdmin):
#     #inlines = [UserInline]
#     #list_display = ('name', 'school',)
#     #list_editable = ('school', )
#     #list_display_links = ('name',)
#     def get_queryset(self, request):
#         qs = super().get_queryset(request)
#         return qs.filter(user__is_active = True)


import requests #dependency

url = "https://discord.com/api/webhooks/898946596086087733/sAM6GsAHDfASjqzB39PONOTwZZnxid2dHC4CkIhfA-G8g5xN6fmsrvMJzzr3MaawEYaf" #webhook url, from here: https://i.imgur.com/f9XnAew.png

#for all params, see https://discordapp.com/developers/docs/resources/webhook#execute-webhook
data = {
    "content" : "This is a test.",
    "username" : "Webhook Test"
}

#leave this out if you dont want an embed
#for all params, see https://discordapp.com/developers/docs/resources/channel#embed-object
data["embeds"] = [
    {
        "description" : "embed test",
        "title" : "embed title",
        "type" : "video",
        "url" : "https://www.youtube.com/watch?v=R7vxXfy09EA",
        "height" : "100",
        "width" : "200"
    }
]

result = requests.post(url, json = data)

try:
    result.raise_for_status()
except requests.exceptions.HTTPError as err:
    print(err)
else:
    print("Payload delivered successfully, code {}.".format(result.status_code))

#result: https://i.imgur.com/DRqXQzA.png