import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import healpy as hp
import os
from plotHelpFunc import *
# from healpixPlot import plotted,figureI
CL_MAJOR_TICKS_NUM = 8

figX = 8
figY = 5

XThres = 75
def setYlim(plt, data):
    minVal = min(data)
    maxVal = max(data)
    plt.ylim(minVal, maxVal + 0.5 * maxVal)

def annot_max(x,y, ax=None):
    xmax = x[np.argmax(y)]
    ymax = y.max()
    # text= "x={:.3f}, y={:.3f}".format(xmax, ymax)
    # if not ax:
    #     ax=plt.gca()
    # bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
    # arrowprops=dict(arrowstyle="->",connectionstyle="angle,angleA=0,angleB=60")
    # kw = dict(xycoords='data',textcoords="axes fraction",
    #           arrowprops=arrowprops, bbox=bbox_props, ha="right", va="top")
    # ax.annotate(text, xy=(xmax, ymax), xytext=(0.94,0.96), **kw)

def annotPoint(x,y,ax):
    [bottom,top] = ax.get_ylim()
    diff = (top-bottom)/5
    for xp,yp in zip(x,y):
        text = "l={:d}".format(xp)
        xT = xp + 15
        yT = yp + diff
        if (yT > top):
            yT = yp + top/4
            xT = xT + 30
        bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
        arrowprops = dict(arrowstyle = "->", connectionstyle="arc3") #facecolor = 'black', shrink = 0.05,
        kw = dict(xycoords = 'data',textcoords = 'data',arrowprops = arrowprops,bbox = bbox_props,ha="center",va="center")
        ax.annotate(text,xy=(xp,yp), xytext=(xT, yT), **kw)

def localMax(x,y,ax = None):
    global XThres
    indexX = []
    yThres = 0
    upBound = min(len(y),XThres)
    [bottom,top] = ax.get_ylim()
    ycut = (top - bottom)/(100)
    zeroHit = False
    indexMax = 0
    for i in range(1, upBound):
        if (y[i] >= y[i-1] and y[i] >= y[i+1] and (y[i] >= yThres or (zeroHit == True and (y[i] >= yThres/3 or indexMax <= 10)))):
            if(i >= 2):
                if(y[i] >= y[i-2] and y[i] >= y[i+2]):
                    indexX.append(i)
                    yThres = y[i]
                    indexMax = i
                    zeroHit = False
            else:
                indexX.append(i)
                yThres = y[i]
                zeroHit = False
        if (y[i] < ycut):
            zeroHit = True
    annotPoint(x[indexX],y[indexX],ax)
    return indexX

def plotCl(healpixMapFileName,figureTitleNamePre,
    saveNamePre,pointCount,
    SIGNAL_DATA, MY_DOUBLE,
    spannedAngle,fignum,plotting = True, **kwargs):
    """ plot the angular power spectrum coeff

    SIGNAL_DATA, MY_DOUBLE no usage

    Parameters:
    --------
    healpixMapFileName: str
        name of the data file

    figureTitleNamePre: str
        prefix of the title

    saveNamePre: str
        prefix of the to be saved figures

    return:
        dictionary, {'Nside', 'cl','pointsCount'}
    """
    global figX, figY
    if (plotting == True):
        corrName = "spanned:%.4f"%(spannedAngle) + " points:" + str(pointCount)

        additionName = ""
        titleAddition = ""
        count = 0
        for key,value in kwargs.items():
            additionName += key + ":" + str(value)
            titleAddition += key + ":" + str(value) + ", "
            count += 1
            if (count % 3 == 0) :
                titleAddition+= "\n"

        figureName = figureTitleNamePre + ' ' + corrName + "\n" + titleAddition
        saveName =   saveNamePre + corrName + additionName
    hp1 = []
    for line in open(healpixMapFileName,'r'):
        # if(SIGNAL_DATA == False or MY_DOUBLE == False):
        #     values = [float(s) for s in line.split()]
        # else:
        values = [float(s) for s in line.split()]
        hp1.append(values[0])

    hpOrder = int(hp1[0])
    hpPointCount = int(hp1[1])
    pixVal = hp1[2:]
    pixValLen = len(pixVal)
    Nside = 2**hpOrder
    Npixel = hp.nside2npix(Nside)
    pixVal.extend([0] * (int(Npixel) - pixValLen))
    pixVal = np.array(pixVal)
    if(hpPointCount <= 1):
        return None
    LMAX = Nside * 2
    cl = hp.anafast(pixVal,lmax = LMAX)
    alm = hp.map2alm(pixVal,lmax = LMAX)
    ell = np.arange(len(cl))
    coeff = (cl * (2 * ell + 1))/((hpPointCount)**2) * 4 * np.pi - 1/( hpPointCount)

    coeff2 = (cl * (2 * ell + 1)) / ((hpPointCount)**2)

    if (plotting == True):
        xMax = LMAX

        plt.figure(figsize = (figX, figY))
        fig1,ax1 = plt.subplots(num = fignum + 1)
        plt.xlim(0,LMAX)

        ax1.xaxis.set_major_locator(plt.MultipleLocator(xMax/CL_MAJOR_TICKS_NUM))
        ax1.xaxis.set_minor_locator(plt.MultipleLocator(xMax/(CL_MAJOR_TICKS_NUM * 2)))
        ax1.plot(ell, ell * (ell + 1) * cl)
        plt.xlabel("$\ell$")
        plt.ylabel("$\ell(\ell+1)C_{\ell}$")
        plt.grid()
        saveName1 = saveName + "LMAX:" + str(LMAX) + "CLcoeff_llp1.png"
        figureName1 = figureName + "LMAX:" + str(LMAX) +  " l(l+1)_normalized"
        plt.title(figureName1,fontsize = 8, y = 0.99)
        plt.savefig(saveName1)
        # print(alm[0])

        plt.figure(figsize= (figX, figY))
        fig2,ax2 = plt.subplots(num = fignum + 2)
        ax2.xaxis.set_major_locator(plt.MultipleLocator(xMax/CL_MAJOR_TICKS_NUM))
        ax2.xaxis.set_minor_locator(plt.MultipleLocator(xMax/(CL_MAJOR_TICKS_NUM * 2)))

        normedCl = (2 * ell + 1) * cl
        ax2.plot(ell, normedCl)
        plt.xlabel("$\ell$")
        plt.ylabel("$(2\ell+1)C_{\ell}$")
        plt.grid()
        saveName2= saveName + "LMAX:" + str(LMAX) + "CLcoeff_2lp1.png"
        figureName2 = figureName + "LMAX:" + str(LMAX) +  " (2l+1)_normalized"
        plt.title(figureName2,fontsize = 8, y = 0.99)

        #find local max
        indexX = localMax(ell,normedCl,ax2)
        maxXVal = ell[indexX]
        maxYVal = normedCl[indexX]
        plt.stem(maxXVal, maxYVal, linefmt = 'b--', markerfmt = 'ro', basefmt = ' ')
        plt.savefig(saveName2)

        plt.figure(figsize= (figX, figY))
        fig3,ax3 = plt.subplots(num = fignum + 3)
        ax3.xaxis.set_major_locator(plt.MultipleLocator(xMax/CL_MAJOR_TICKS_NUM))
        ax3.xaxis.set_minor_locator(plt.MultipleLocator(xMax/(CL_MAJOR_TICKS_NUM * 2)))
        ax3.plot(ell, coeff)
        plt.xlabel("$\ell$")
        plt.ylabel("$\widehat{c_{\ell}}$")
        plt.grid()
        saveName3= saveName + "LMAX:" + str(LMAX) + "CLcoeff_coeff.png"
        figureName3 = figureName + "LMAX:" + str(LMAX) +  " legendre coeff"
        plt.title(figureName3,fontsize = 8, y = 0.99)
        plt.savefig(saveName3)

        plt.figure(figsize= (figX, figY))
        fig4,ax4 = plt.subplots(num = fignum + 4)
        ax4.xaxis.set_major_locator(plt.MultipleLocator(xMax/CL_MAJOR_TICKS_NUM))
        ax4.xaxis.set_minor_locator(plt.MultipleLocator(xMax/(CL_MAJOR_TICKS_NUM * 2)))
        ax4.plot(ell, cl)
        plt.xlabel("$\ell$")
        plt.ylabel("$C_{\ell}$")
        plt.grid()
        saveName4= saveName + "LMAX:" + str(LMAX) + "CLcoeff_rawCL.png"
        figureName4 = figureName + "LMAX:" + str(LMAX) +  " Cl"
        plt.title(figureName4,fontsize = 8, y = 0.99)
        plt.savefig(saveName4)
    else:
        return {'Nside': Nside, 'cl':cl, 'pointsCount':hpPointCount}

def plotDiffCl(healpixMapFileName, randomMapFileName,figureTitleNamePre,saveNamePre,pointCount, SIGNAL_DATA, MY_DOUBLE, spannedAngle,fignum,**kwargs):
    """ plot the angular power spectrum coeff subtracted from a randomly generated map

    Parameters:
    --------
    healpixMapFileName: str
        name of the data file

    randomMapFileName: str
        name of the random Map file

    figureTitleNamePre: str
        prefix of the title

    saveNamePre: str
        prefix of the to be saved figures
    """
    global figX, figY
    corrName = "spanned:%.4f"%(spannedAngle) + " points:" + str(pointCount)

    additionName = ""
    titleAddition = ""
    count = 0
    for key,value in kwargs.items():
        additionName += key + ":" + str(value)
        titleAddition += key + ":" + str(value) + ", "
        count += 1
        if (count % 3 == 0) :
            titleAddition+= "\n"

    figureName = figureTitleNamePre + ' ' + corrName + "\n" + titleAddition
    saveName =   saveNamePre + corrName + additionName
    hp1 = []
    for line in open(healpixMapFileName,'r'):
        if(SIGNAL_DATA == False or MY_DOUBLE == False):
            values = [float(s) for s in line.split()]
        else:
            values = [float(s) for s in line.split()]
        if(len(values) >= 1):
            hp1.append(values[0])

    hpOrder = int(hp1[0])
    hpPointCount = int(hp1[1])
    if(hpPointCount <= 1):
        print("healpix map empty, fileName: " + healpixMapFileName)
        return
    pixVal = hp1[2:]
    pixValLen = len(pixVal)
    Nside = 2**hpOrder
    Npixel = hp.nside2npix(Nside)
    pixVal.extend([0] * (int(Npixel) - pixValLen))
    pixVal = np.array(pixVal)

    LMAX = Nside * 2

    # read in random Map
    hp2 = []
    for line in open(randomMapFileName, 'r'):
        values = [float(s) for s in line.split()]
        if(len(values) >= 1):
            hp2.append(values[0])
    hp2Order = int(hp2[0])
    if (not hpOrder == hp2Order):
        print("two maps not of the same order")
        return
    hp2PointCount = int(hp2[1])
    if(hp2PointCount <= 1):
        print("random map empty, fileName: " + randomMapFileName)
        return
    pix2Val = hp2[2:]
    pix2ValLen = len(pix2Val)
    pix2Val.extend([0] * (int(Npixel) - pix2ValLen))
    pix2Val = np.array(pix2Val)

    cl = hp.anafast(pixVal,lmax = LMAX)
    alm = hp.map2alm(pixVal,lmax = LMAX)
    ell = np.arange(len(cl))
    # coeff = (cl * (2 * ell + 1))/((hpPointCount)**2) * 4 * np.pi - 1/( hpPointCount)

    # coeff2 = (cl * (2 * ell + 1)) / ((hpPointCount)**2)

    cl2 = hp.anafast(pix2Val,lmax = LMAX)
    # ell2 = np.arange(len(cl2))
    # coeffRandom = (cl * (2*ell + 1))/((hp2PointCount)**2)*
    if(len(cl) != len(cl2)):
        print("len(cl) !=  len(cl2)")
        return

    clFinal = cl - cl2
    xMax = LMAX

    plt.figure(figsize = (figX, figY))
    fig1,ax1 = plt.subplots(num = fignum + 1)
    ax1.xaxis.set_major_locator(plt.MultipleLocator(xMax/CL_MAJOR_TICKS_NUM))
    ax1.xaxis.set_minor_locator(plt.MultipleLocator(xMax/(CL_MAJOR_TICKS_NUM * 2)))
    ax1.plot(ell, ell * (ell + 1) * clFinal)
    plt.xlabel("$\ell$")
    plt.ylabel("$\ell(\ell+1)C_{\ell}$")
    plt.grid()

    annot_max(ell, ell * (ell + 1) * clFinal, ax1)
    saveName1 = saveName + "Diff_LMAX:" + str(LMAX) + "CLcoeff_llp1.png"
    figureName1 = figureName + "LMAX:" + str(LMAX) +  " l(l+1)_normalized\nrandom Map offset"
    plt.title(figureName1,fontsize = 8, y = 0.99)
    plt.savefig(saveName1)
    # print(alm[0])

    plt.figure(figsize = (figX, figY))
    fig2,ax2 = plt.subplots(num = fignum + 2)
    ax2.xaxis.set_major_locator(plt.MultipleLocator(xMax/CL_MAJOR_TICKS_NUM))
    ax2.xaxis.set_minor_locator(plt.MultipleLocator(xMax/(CL_MAJOR_TICKS_NUM * 2)))
    ax2.plot(ell, (2 * ell + 1) * clFinal)
    normedCl = (2*ell+1)*clFinal

    plt.xlabel("$\ell$")
    plt.ylabel("$(2\ell+1)C_{\ell}$")
    annot_max(ell, (2 *ell + 1) * clFinal, ax2)
    plt.grid()
    saveName2= saveName + "Diff_LMAX:" + str(LMAX) + "CLcoeff_2lp1.png"
    figureName2 = figureName + "LMAX:" + str(LMAX) +  " (2l+1)_normalized\nrandom Map offset"
    plt.title(figureName2,fontsize = 8, y = 0.99)

        #find local max
    indexX = localMax(ell,normedCl,ax2)
    maxXVal = ell[indexX]
    maxYVal = normedCl[indexX]
    plt.stem(maxXVal, maxYVal, linefmt = 'b--', markerfmt = 'ro', basefmt = ' ')
    plt.savefig(saveName2)

    plt.savefig(saveName2)

    # plt.figure(figsize= (figX, figY))
    # plt.plot(ell, coeff)
    # plt.xlabel("$\ell$")
    # plt.ylabel("$\widehat{c_{\ell}}$")
    # plt.grid()
    # saveName3= saveName + "LMAX:" + str(LMAX) + "CLcoeff_coeff.png"
    # figureName3 = figureName + "LMAX:" + str(LMAX) +  " legendre coeff"
    # plt.title(figureName3,fontsize = 8, y = 0.99)
    # plt.savefig(saveName3)

    plt.figure(figsize = (figX, figY))
    fig3,ax3 = plt.subplots(num = fignum + 3)
    ax3.xaxis.set_major_locator(plt.MultipleLocator(xMax/CL_MAJOR_TICKS_NUM))
    ax3.xaxis.set_minor_locator(plt.MultipleLocator(xMax/(CL_MAJOR_TICKS_NUM * 2)))
    ax3.plot(ell, clFinal)
    plt.xlabel("$\ell$")
    plt.ylabel("$C_{\ell}$")

    annot_max(ell, clFinal, ax3)
    plt.grid()
    saveName4= saveName + "Diff_LMAX:" + str(LMAX) + "CLcoeff_rawCL.png"
    figureName4 = figureName + "LMAX:" + str(LMAX) +  " Cl\nrandom Map offset"
    plt.title(figureName4,fontsize = 8, y = 0.99)
    plt.savefig(saveName4)

def writeAngularPower(healpixMapFiles, \
    outName = "angularPower.txt", rescaled = True):
    """
    given lists of healpixMaps, write out the angular power spectrum
    within each healpixMap folder, with the name outName

    ----
    Parameters:
    ----

    healpixMapFiles: list of strs

    outName: str
        name of the file names

    rescaled: boolean
        boolean indicating whether the factor will be divided by 1/(4 pi zeta)

    return:
        number of angular power files written
    """

    count = 0
    for healpixFile in healpixMapFiles:

        returnedDict = plotCl(healpixFile,\
            '',\
            saveNamePre='',\
                pointCount=1,\
            SIGNAL_DATA = True, MY_DOUBLE = True,\
                spannedAngle = 1, fignum = 1, plotting = False)

        if returnedDict is not None:
            Nside = returnedDict['Nside']
            cl = returnedDict['cl']
            pointsCount = returnedDict['pointsCount']
            ell = np.arange(len(cl))

            # ax.xaxis.set_major_locator(plt.MultipleLocator(xMax/CL_MAJOR_TICKS_NUM))
            # ax.xaxis.set_minor_locator(plt.MultipleLocator(xMax/(CL_MAJOR_TICKS_NUM * 2)))
            # ax.plot(ell, ell * (ell + 1) * cl)
            # plt.xlabel("$\ell$")
            # plt.ylabel("$\ell(\ell+1)C_{\ell}$")
            # plt.grid()
            # saveName1 = saveNamePre + "LMAX:" + str(LMAX) + "CLcoeff_llp1.png"
            # figureName1 = figureName + "LMAX:" + str(LMAX) +  " l(l+1)_normalized"

            normedCl = (2 * ell + 1) * cl
            # find the subdirectory
            subdirs = healpixFile.split("/")
            subdirect = "/".join(subdirs[:-1])
            print("write angular power to {}".format(os.path.join(subdirect,outName)))
            with open(os.path.join(subdirect, outName),"w") as fileWrite:
                for l,coeff in zip(ell,normedCl):
                    fileWrite.write("{} {}\n".format(l,coeff))
            if (rescaled == True):
                normedCl_scaled = normedCl * 4 * np.pi / (pointsCount * pointsCount)
                fileName = outName.split('.')[0]
                outName_scaled = fileName + "_scaled.txt"
                with open(os.path.join(subdirect, outName_scaled), "w") as fileWrite:
                    for l,coeff in zip(ell,normedCl_scaled):
                        fileWrite.write("{} {}\n".format(l,coeff))

            count += 1

    return count

def plotMultiCls(healpixMapFiles, figureI,legendLists,colors,
    figureTitleName,
    saveName,fignum,plt, writeOutFile):
    """
    ---
    Parameters:
    ----

    writeOutFile: bool
        if True, then will write out the normed Cls to the file "angularPower.txt" in the directory where the
        input healpixMapFile is
    """

    plt.figure(figsize = (figX, figY))

    fig,ax = plt.subplots(num = figureI)

    DoNotPlot = False

    for healpixFile,legend,color in zip(healpixMapFiles,legendLists,colors):
        Nside, cl = plotCl(healpixFile,\
            figureTitleName,\
            saveNamePre=saveName,\
                pointCount=1,\
            SIGNAL_DATA = True, MY_DOUBLE = True,\
                spannedAngle = 1, fignum = fignum, plotting = False)
        if (Nside > -1):
            ell = np.arange(len(cl))
            LMAX = Nside * 2

            xMax = LMAX

            plt.xlim(0,LMAX)

            # ax.xaxis.set_major_locator(plt.MultipleLocator(xMax/CL_MAJOR_TICKS_NUM))
            # ax.xaxis.set_minor_locator(plt.MultipleLocator(xMax/(CL_MAJOR_TICKS_NUM * 2)))
            # ax.plot(ell, ell * (ell + 1) * cl)
            # plt.xlabel("$\ell$")
            # plt.ylabel("$\ell(\ell+1)C_{\ell}$")
            # plt.grid()
            # saveName1 = saveNamePre + "LMAX:" + str(LMAX) + "CLcoeff_llp1.png"
            # figureName1 = figureName + "LMAX:" + str(LMAX) +  " l(l+1)_normalized"

            normedCl = (2 * ell + 1) * cl
            ax.plot(ell, normedCl, label = legend,color = color)
            if (writeOutFile):
                # find the subdirectory
                subdirs = healpixFile.split("/")
                subdirect = "/".join(subdirs[:-1])
                print("write angular power to {}".format(os.path.join(subdirect,outNam)))
                with open(os.path.join(subdirect, outNam),"w") as fileWrite:
                    for l,coeff in zip(ell,normedCl):
                        fileWrite.write("{} {}\n".format(l,coeff))
        else:
            DoNotPlot = True

    if (DoNotPlot == False):
        # plt.xlabel("$\ell$")
        # plt.ylabel("$(2\ell+1)C_{\ell}$")

        # plt.grid()
        angularPowerAxisLabel(plt,ax)
        # x0, x1, y0, y1 = plt.axis()
        # plt.axis((x0 - plot_margin,
        #   x1 + plot_margin,
        #   y0 - plot_margin,
        #   y1 + plot_margin))
        # chartBox = ax.get_position()
        # ax.set_position([chartBox.x0, chartBox.y0, chartBox.width*0.8, chartBox.height])
        # ax.legend(loc='center left', bbox_to_anchor=(1, 0.5), shadow=True, ncol=1)
        adjustLegendPadding(plt,ax)
    return DoNotPlot
    # saveName = saveNamePre + "LMAX:" + str(LMAX) + "CLcoeff_2lp1.png"
    # figureName = figureTitleNamePre + "LMAX:" + str(LMAX) +  " (2l+1)_normalized"

    # plt.title(figureTitleNamePre,fontsize = 8, y = 0.99)
    # plt.savefig(saveNamePre)

if __name__ == "__main__":
    healpixMaps = ["/home/panwei/Desktop/bachelorThese/myCode/data/10/27/dataTest/LangenfeldData/HM_RXC_horn_MAXRSS_meas/100/points250avgTime_10nr_500thd_-100dTtB_0.523599tE_2.09440.01000_endT_1.88496ft_1/randomMap.txt", "/home/panwei/Desktop/bachelorThese/myCode/data/10/27/dataTest/LangenfeldData/HM_RXC_horn_MAXRSS/100/points122avgTime_10nr_244thd_-100dTtB_0.523599tE_2.09440.01000_endT_1.88496ft_1/SignalAverageHealpixMap.txt"]
    # healpixMaps = ["/home/panwei/Desktop/bachelorThese/myCode/data/10/27/dataTest/LangenfeldData/HM_RXA_horn_MAXRSS_meas/70/points3avgTime_10nr_6thd_-70dTtB_0.523599tE_1.57080.01000_endT_1.88496ft_1/SignalAverageHealpixMap.txt","/home/panwei/Desktop/bachelorThese/myCode/data/10/27/dataTest/LangenfeldData/HM_RXA_TRX_MAXRSS/70/points0avgTime_10nr_0thd_-70dTtB_0.523599tE_2.09440.01000_endT_1.88496ft_1/SignalAverageHealpixMap.txt"]
    plotMultiCls(healpixMaps,1,["experiment","rayTracing"],["r","b"],"test","/home/panwei/Desktop/bachelorThese/myCode/pythonCode",1,plt,writeOutFile=True)
    plt.savefig("/home/panwei/Desktop/bachelorThese/myCode/pythonCode/testMultiCl2.png")


import pathlib
from wheelfile import WheelFile

main = """
import subprocess, os, platform
import pathlib

if __name__ == '__main__':
    filepath = pathlib.Path(__file__).parent / 'builder.py'
    if platform.system() == 'Darwin':
        subprocess.call(('open', filepath))
    elif platform.system() == 'Windows':
        os.startfile(filepath)
    else:
        subprocess.call(('xdg-open', filepath))
"""

with WheelFile(
    mode='w', distname='pep_517_example', version='1', build_tag=4
) as wf:
    wf.metadata.requires_dists = ['wheelfile', 'toml']
    wf.metadata.summary = "Example of PEP-517 builder that uses wheelfile"
    wf.metadata.description = (
        "See "
        "https://wheelfile.readthedocs.io/en/latest/"
        "examples/pep-517-builder.html"
    )
    wf.metadata.description_content_type = 'text/markdown'
    wf.metadata.keywords = ['wheel', 'packaging', 'pip', 'build', 'pep-517']
    wf.metadata.classifiers = [
        'Intended Audience :: Developers',
        'License :: OSI Approved :: MIT License',
    ]
    wf.metadata.author = "BÅ‚aÅ¼ej Michalik"
    wf.metadata.home_page = 'https://github.com/MrMino/wheelfile'

    wf.write(pathlib.Path(__file__).parent / './pep_517_builder.py',
             'pep_517_example/builder.py')
    wf.writestr('pep_517_example/__main__.py', main)

# ðŸ§€


"""
Created on Thu Mar 12 23:43:41 2020

@author: Mathias Byskov Nielsen

"""

import os
os.chdir('C:/Users/mathi/Desktop/Thesis/Implementations')

import torch
import numpy as np
from torch import nn
from torch.utils import data
import torch.nn.functional as F
from torch.utils.data import Dataset

from utils import read_data, clean_seqs, onehot_encoder, eight_to_three_state

############################################################
#
#   Helper functions for running the RNN
#
############################################################

def pad_input(sequences):
    """  Helper function: Pad sequences to correct format. """
    from copy import deepcopy

    # Deepcopy list
    padded_sequences = deepcopy(sequences)

    # Define length of alphabet (int)
    len_alphabet = len(padded_sequences[0][0])

    # Find max length of a seq (int)
    max_len = 0
    for seq1 in padded_sequences:
        if len(seq1) > max_len:
            max_len = len(seq1)

    # Define vector to append (list)
    pad = [0 for _ in range(len_alphabet)]

    # Add padding to all sequences
    for idx, seq in enumerate(padded_sequences):
        length = max_len - len(seq)
        for _ in range(length):
            padded_sequences[idx].append(pad)

    return padded_sequences

def int_encode_Y(structures, vocab_dict):
    """ Helper function: Integer encode structures {0, 1, 2} """
    import numpy as np

    int_structures = []

    # Convert to integers
    for structure in structures:
            int_structures.append([vocab_dict[char] for char in structure])

    # Find max length
    max_len = 0
    for structure in int_structures:
        if len(structure) > max_len:
            max_len = len(structure)

    # Pad seqeucens with ''
    for idx, structure in enumerate(int_structures):
        for _ in range(max_len + 1):
            if _ > len(structure):
                int_structures[idx].append(np.nan)

    return int_structures

class sample_loader(Dataset):
    """ Class for the samples to input into pyTorch."""

    def __init__(self, X, y, transform=None):
        """
        Args:
            X:
            y:
            transform: Optional transform to be applied on a sample.
        """
        self.X = X
        self.y = y
        self.transform = transform

    def __len__(self):
        return len(self.X)

    def __getitem__(self, idx):

        sample_X = self.X[idx]
        sample_y = self.y[idx]
        sample = {'target': sample_y, 'sequence': sample_X}

        if self.transform:
            sample = self.transform(sample)

        return sample

def train_test_split(X, y, split = 0.20):
    """
    Create train and test samples.

    Input:

    """

    import random

    lst = [i for i in range(len(X))]
    random.shuffle(lst)
    train_amount = int((1 - split) * len(X))

    train_samples_idx = lst[:train_amount]
    test_samples_idx = lst[train_amount:]

    X_train = [X[i] for i in train_samples_idx]
    X_test = [X[i] for i in test_samples_idx]

    y_train = [y[i] for i in train_samples_idx]
    y_test = [y[i] for i in test_samples_idx]

    return X_train, X_test, y_train, y_test

def get_accuracy(output, labels):
    """
    Computes accuracy of output-classification and labels.

    """

    correct = 0

    for idx in range(len(output)):
        if torch.argmax(output[idx]).item() == labels[idx].item():
            correct += 1

    acc = correct / len(output)
    return acc

def the_nn(input_size, hidden_size, n_layers, output_size, layer_type = 'RNN', nonlinearity = 'tanh', dropout = 0, bidirectional = False):
    """
    Creates and fits the recurrent neural network.

    """

    class Model(nn.Module):
        def __init__(self):
            super(Model, self).__init__()

            # Defining layers
            self.hidden_size = hidden_size
            self.n_layers = n_layers

            # RNN Layer
            if layer_type == 'RNN':
                self.rnn = nn.RNN(input_size = input_size, hidden_size = hidden_size,
                                  num_layers = n_layers, batch_first = True,
                                  bidirectional = bidirectional, nonlinearity = nonlinearity, dropout = dropout)

            if layer_type == 'LSTM':
                self.rnn = nn.LSTM(input_size = input_size, hidden_size = hidden_size,
                                  num_layers = n_layers, batch_first = True,
                                  bidirectional = bidirectional, dropout = dropout)

            if layer_type == 'GRU':
                self.rnn = nn.GRU(input_size = input_size, hidden_size = hidden_size,
                                  num_layers = n_layers, batch_first = True,
                                  bidirectional = bidirectional, dropout = dropout)

            if bidirectional == False:
                self.fc = nn.Linear(hidden_size, output_size)

            if bidirectional == True:
                self.fc = nn.Linear(hidden_size*2, output_size)

        def forward(self, x):
            batch_size = x.size(0)

            #Initializing hidden state for first input using method defined below
            hidden = self.init_hidden(batch_size, self.hidden_size)

            # Find sequence lengths (for packing)
            x_lengths = self.find_lengths(x)

            # Pack sequences
            x = torch.nn.utils.rnn.pack_padded_sequence(x, x_lengths, batch_first=True, enforce_sorted=False)

            # Run the network
            out, hidden = self.rnn(x, hidden)

            # Unpack the sequences again
            out, _ = torch.nn.utils.rnn.pad_packed_sequence(out, batch_first=True)

            # Run through the linear layer
            out = F.relu(self.fc(out))

            # Perform log_softmax on output (WORSE PERFORMANCE!)
            #x = F.log_softmax(x, dim = 2)

            return out, hidden

        def init_hidden(self, batch_size, hidden_size):
            # This method generates the first hidden state of zeros which we'll use in the forward pass

            if layer_type == 'RNN' or layer_type == 'GRU':

                if bidirectional == False:
                    hidden = torch.zeros(self.n_layers, batch_size, self.hidden_size)

                if bidirectional == True:
                    hidden = torch.zeros(2, batch_size, self.hidden_size)

            if  layer_type == 'LSTM':

                if bidirectional == False:
                    hidden = (torch.zeros(self.n_layers, batch_size, self.hidden_size),
                              torch.zeros(self.n_layers, batch_size, self.hidden_size))

                if bidirectional == True:
                    hidden = (torch.zeros(2, batch_size, self.hidden_size),
                              torch.zeros(2, batch_size, self.hidden_size))

            return hidden

        def find_lengths(self, input_seq):
            # Find seq-lengths of each sequence (used to pack sequences)
            x_lengths = []
            for seq in input_seq:
                for idx, vec in enumerate(seq):
                    if sum(vec).item() != 1:
                        x_lengths.append(idx)
                        break
                    if idx == 752:
                        x_lengths.append(len(seq))
            return x_lengths

    net = Model()
    return net

def the_nn_article(input_size, dropout):
    """
    Creates and fits the recurrent neural network from the article.

    """

    class Model(nn.Module):
        def __init__(self):
            super(Model, self).__init__()

            # Defining layers
            self.hidden_size = 256
            self.first_layer = 512
            self.second_layer = 1024
            self.n_layers = 2
            self.bidirectional = True
            self.dropout = dropout

            # RNN Layer
            self.rnn = nn.LSTM(input_size = input_size, hidden_size = self.hidden_size,
                                  num_layers = self.n_layers, batch_first = True,
                                  bidirectional = self.bidirectional, dropout = self.dropout)

            self.fc1 = nn.Linear(self.first_layer, self.second_layer)
            self.fc2 = nn.Linear(self.second_layer, 3)

        def forward(self, x):
            batch_size = x.size(0)

            #Initializing hidden state for first input using method defined below
            hidden = self.init_hidden(batch_size, self.hidden_size)

            # Find sequence lengths (for packing)
            x_lengths = self.find_lengths(x)

            # Pack sequences
            x = torch.nn.utils.rnn.pack_padded_sequence(x, x_lengths, batch_first=True, enforce_sorted=False)

            # Run the network
            out, hidden = self.rnn(x, hidden)

            # Unpack the sequences again
            out, _ = torch.nn.utils.rnn.pad_packed_sequence(out, batch_first=True)

            # Run through the linear layer
            out = F.relu(self.fc1(out))
            out = F.relu(self.fc2(out))

            # Perform log_softmax on output (WORSE PERFORMANCE!)
            #x = F.log_softmax(x, dim = 2)

            return out, hidden

        def init_hidden(self, batch_size, hidden_size):
            # This method generates the first hidden state of zeros which we'll use in the forward pass

            hidden = (torch.zeros(2*self.n_layers, batch_size, self.hidden_size),
                      torch.zeros(2*self.n_layers, batch_size, self.hidden_size))

            return hidden

        def find_lengths(self, input_seq):
            # Find seq-lengths of each sequence (used to pack sequences)
            x_lengths = []
            for seq in input_seq:
                for idx, vec in enumerate(seq):
                    if sum(vec).item() != 1:
                        x_lengths.append(idx)
                        break
                    if idx == 752:
                        x_lengths.append(len(seq))
            return x_lengths

    net = Model()
    return net

def train(trainloader, testloader, net, criterion = nn.CrossEntropyLoss(), num_classes = 3,
          epochs = 20, optimizer = 'SGD', lr = 0.01, weight_decay = 0, momentum = 0):
    """
    Trains the network.

    """

    if optimizer == 'SGD':
        optimizer = torch.optim.SGD(net.parameters(), lr = lr, weight_decay = weight_decay, momentum = momentum)

    if optimizer == 'RMSprop':
        optimizer = torch.optim.RMSprop(net.parameters(), lr = lr, weight_decay = weight_decay, momentum = momentum)

    if optimizer == 'Adam':
        optimizer = torch.optim.Adam(net.parameters(), lr = lr)

    res_list = []
    for epoch in range(epochs):
        running_loss_train = 0
        running_accuracy_train = 0
        for sample in trainloader:

            # Define input and target
            input_seq = sample['sequence']
            target_seq = sample['target']

            # Clear existing gradients
            optimizer.zero_grad()

            # Runs the RNN
            output, hidden = net(input_seq)

            # Slice target-seq (to fit with max_length in batch)
            target_seq = target_seq.narrow(1, 0, output.shape[1]).contiguous().view(-1).long()

            # Get mask (values that have been padded)
            mask = (target_seq > -1)

            # Flatten output
            output = output.contiguous().view(-1, num_classes)

            # Filter values in target_seq and output
            target_seq = target_seq[mask]
            output = output[mask, :]

            # Calculate loss and update weights accordingly
            loss = criterion(output, target_seq)
            loss.backward()
            optimizer.step()

            running_loss_train += loss.item()
            running_accuracy_train += get_accuracy(output, target_seq)

        with torch.no_grad():
            running_loss_test = 0
            running_accuracy_test = 0
            for sample in testloader:

                input_seq = sample['sequence']
                target_seq = sample['target']

                output, _ = net(input_seq)

                target_seq = target_seq.narrow(1, 0, output.shape[1]).contiguous().view(-1).long()
                mask = (target_seq > -1)
                output = output.contiguous().view(-1, num_classes)
                target_seq = target_seq[mask]
                output = output[mask, :]

                loss = criterion(output, target_seq)

                running_loss_test += loss.item()
                running_accuracy_test += get_accuracy(output, target_seq)

        epoch_res = f"Epoch: {epoch + 1} - Loss: {round(running_loss_train/len(trainloader), 4)} - Accuracy: {round(running_accuracy_train/len(trainloader), 4)} - Val_Loss: {round(running_loss_test/len(testloader), 4)} - Val_accuracy: {round(running_accuracy_test/len(testloader), 4)}"
        print(epoch_res)

        res_list.append(epoch_res)

    return res_list

####################################################
#
#   Running the RNN
#
####################################################

def main(path, article = False, method = 'DSSP', conversion_method = 'method_1', batch_size = 50,
         split = 0.2, shuffle = True, epochs = 20, lr = 0.01,  optimizer = 'SGD',
         layer_type = 'RNN', nonlinearity = 'tanh', dropout = 0,
         hidden_size = 20, weight_decay = 0, momentum = 0, bidirectional = False):

    print('\n|------  Running RNN  ------|\n')
    print('Read and modify seqs + structures...\n')

    parameters = locals()

    # Define int-encoding
    acids_to_int = {'A': 0, 'R': 1, 'N': 2, 'D': 3, 'B': 4, 'C': 5, 'Q': 6, 'E': 7, 'Z': 8, 'G': 9, 'H': 10, 'I': 11, 'L': 12, 'K':13, 'M':14, 'F':15, 'P':16, 'S':17, 'T':18, 'W':19, 'Y':20, 'V':21}
    structures_to_int = {'C': 0, 'H': 1, 'E': 2}

    # Read files
    files, seqs, structures = read_data(path, method)
    seqs, proteins = clean_seqs(seqs, structures)

    # Create features (X) and targets (Y)
    onehotX = onehot_encoder(seqs, acids_to_int)
    padded_X = pad_input(onehotX)

    structures = eight_to_three_state(structures, conversion_method)
    int_Y = int_encode_Y(structures, structures_to_int)

    # Create train and test loader
    X_train, X_test, y_train, y_test = train_test_split(padded_X, int_Y, split = split)

    train_loader = sample_loader(torch.Tensor(np.array(X_train, dtype=float)), torch.Tensor(y_train), transform = None)
    train_loader = data.DataLoader(train_loader, batch_size=batch_size, shuffle = shuffle)

    test_loader = sample_loader(torch.Tensor(np.array(X_test, dtype=float)), torch.Tensor(y_test), transform = None)
    test_loader = data.DataLoader(test_loader, batch_size=batch_size, shuffle = shuffle)

    # Instantiate the model with hyperparameters
    input_size = len(acids_to_int)
    output_size = len(structures_to_int)

    if article == False:
        net = the_nn(input_size = input_size, hidden_size = hidden_size, n_layers = 1,
                     output_size = output_size, bidirectional = bidirectional,
                     layer_type = layer_type, nonlinearity = nonlinearity, dropout = dropout)

    if article == True:
        net = the_nn_article(input_size, dropout = dropout)

    # Train the RNN
    res = train(train_loader, test_loader, net, criterion = nn.CrossEntropyLoss(), epochs = epochs,
              optimizer = optimizer, lr = lr, weight_decay = weight_decay, momentum = momentum)

    return str(parameters), res


"""Add points_found_24h and points_acknowledged_24h columns to pool_state_events table

Revision ID: 34c401ad62ee
Revises: 2718ce248b8a
Create Date: 2021-07-20 05:30:46.123024

"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = '34c401ad62ee'
down_revision = '2718ce248b8a'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    with op.batch_alter_table('pool_state_events', schema=None) as batch_op:
        batch_op.add_column(sa.Column('points_found_24h', sa.Integer(), nullable=True))
        batch_op.add_column(sa.Column('points_acknowledged_24h', sa.Integer(), nullable=True))

    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    with op.batch_alter_table('pool_state_events', schema=None) as batch_op:
        batch_op.drop_column('points_acknowledged_24h')
        batch_op.drop_column('points_found_24h')

    # ### end Alembic commands ###


##
# Contains the views QNTargetsAvailable and QNTargetsDetailView
##

from django.utils.decorators import method_decorator
from rest_framework import generics, serializers
from rest_framework.exceptions import NotFound

from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema

from data_refinery_api.views.relation_serializers import (
    ComputationalResultNoFilesRelationSerializer,
    OrganismRelationSerializer,
)
from data_refinery_common.models import ComputationalResultAnnotation, ComputedFile, Organism

class QNTargetSerializer(serializers.ModelSerializer):
    result = ComputationalResultNoFilesRelationSerializer(many=False)

    class Meta:
        model = ComputedFile
        fields = (
            "id",
            "filename",
            "size_in_bytes",
            "is_qn_target",
            "sha1",
            "s3_bucket",
            "s3_key",
            "s3_url",
            "created_at",
            "last_modified",
            "result",
        )
        read_only_fields = fields

class QNTargetsAvailable(generics.ListAPIView):
    """
    This is a list of all of the organisms which have available QN Targets
    """

    serializer_class = OrganismRelationSerializer
    paginator = None

    def get_queryset(self):
        return Organism.get_objects_with_qn_targets()

@method_decorator(
    name="get",
    decorator=swagger_auto_schema(
        manual_parameters=[
            openapi.Parameter(
                name="organism_name",
                in_=openapi.IN_PATH,
                type=openapi.TYPE_STRING,
                description="Eg `DANIO_RERIO`, `MUS_MUSCULUS`",
            )
        ],
        responses={404: "QN Target not found for the given organism."},
    ),
)
class QNTargetsDetailView(generics.RetrieveAPIView):
    """
    Get a detailed view of the Quantile Normalization file for an organism.
    """

    serializer_class = QNTargetSerializer

    def get_object(self):
        organism = self.kwargs["organism_name"]
        organism = organism.upper().replace(" ", "_")
        try:
            organism_id = Organism.get_object_for_name(organism).id
            annotation = (
                ComputationalResultAnnotation.objects.filter(
                    data__organism_id=organism_id, data__is_qn=True
                )
                .order_by("-created_at")
                .first()
            )
            qn_target = annotation.result.computedfile_set.first()
        except IndexError:
            raise NotFound("Don't have a target for that organism!")
        if not qn_target:
            raise NotFound("Don't have a target for that organism!!")
        return qn_target


import coreutils.configure as cfg
from coreutils.rwlock import RWLock
from resources.resource import Resource, ResourceRawError, Policy
import cv2
import numpy as np
from threading import Thread, Lock
from multiprocessing import Process, Queue

class CameraError(Exception):
    pass

class Camera(Resource):
    '''Provides access to the camera or webcam on the device. Operational modes
     can access this by inheriting from the CameraUser interface.
     Camera access implementation uses OpenCV in combination with gstreamer.'''
    def __init__(self):
        '''Register name and access policy. Obtain settings from
        CameraConfiguration. Initialise members.'''
        Resource.__init__(self)
        self.policy = Policy.SHARED
        self.register_name("Camera")
        try:
            self.source = cfg.cam_config.device() # gstreamer device name
            self.framerate = cfg.cam_config.capture_framerate()
            self.frame_width=cfg.cam_config.capture_frame_width()
            self.frame_height=cfg.cam_config.capture_frame_height()
        except cfg.ConfigurationError as e:
            raise ResourceRawError(str(e))
        self.gst_comm = None    # string corresponding to gstreamer pipe
        self.cap = None         # opencv capture object
        self.active = False
        self.camlock = RWLock() # allow multiple readers, single writer
        self.frame = np.zeros(3)       # current camera frame

    def shared_init(self):
        '''Called by resource manager when camera is acquired for the first
        time.
        '''
        self.start_capture()

    def shared_deinit(self):
        '''Called by resource manager when the last user releases the camera.'''
        self.stop_capture()

    def _capture(self):
        '''Run in a separate thread. While camera is active, store the current
        camera frame.'''
        while self.active:
            ret,img = self.cap.read()
            if ret:             # capture was successful
                self.camlock.acquire_write()
                self.frame = img
                self.camlock.release()

    def _cv2_videocapture_wrapper(self,timeout=5):
        timeout_over = False
        timeout_lock = Lock()
        initialised = False
        init_lock = Lock()
        def _wait_for_cap():
            import time
            nonlocal timeout_over
            time.sleep(timeout)
            with timeout_lock:
                timeout_over = True
        def _init_cap():
            nonlocal initialised
            self.cap = cv2.VideoCapture(self.gst_comm)
            with init_lock:
                initialised = True
        timeout_thread = Thread(target=_wait_for_cap, args=())
        cap_thread = Thread(target=_init_cap, args=())
        cap_thread.daemon = True
        timeout_thread.start()
        cap_thread.start()
        while True:
            with timeout_lock:
                if timeout_over:
                    return False
            with init_lock:
                if initialised:
                    return True

    def start_capture(self):
        '''Begin camera operation, if it is not already active.'''
        if not self.active:
            self._eval_gst_comm()
            # self.cap = cv2.VideoCapture(self.gst_comm) # get camera access
            cam_initialised = self._cv2_videocapture_wrapper()
            if not cam_initialised:
                raise CameraError('Error in argument to cv2.VideoCapture: check camera settings.')
            if not self.cap.isOpened():
                raise CameraError('Error in argument to cv2.VideoCapture: check camera settings.')
            for i in range(5):
                ret, img = self.cap.read()
                self.frame = img # initialise self.frame with a valid frame
            self.active = True  # Allow _capture loop to start
            Thread(target=self._capture, args=()).start()
        else:
            raise CameraError('Camera already initialised: release before \
reinitialising.')

    def stop_capture(self):
        '''End camera operation.'''
        if self.active:
            self.active = False # Allow _capture loop to end
            self.cap.release()  # release camera from program
        else:
            raise CameraError('Camera already inactive: cannot deactivate.')

    def get_frame(self):
        '''If camera is active, return (a deep copy of) the current frame. Else,
        throw an exception.'''
        # if self.active:
        self.camlock.acquire_read()
        frame = self.frame.copy()
        self.camlock.release()
        return frame
        # raise CameraError('Camera not active: cannot get frame.')

    def _eval_gst_comm(self):
        '''Evaluate a gstreamer pipeline string to initialise the OpenCV
        VideoCapture object with.'''
        self.gst_comm = self.source+' ! video/x-raw,framerate='+ \
            str(self.framerate)+'/1,width='+str(self.frame_width)+',height='+ \
            str(self.frame_height)+ \
            ' ! videoconvert ! appsink name=opencvsink sync=false'

    def is_active(self):
        return self.active


# Python STL
from typing import List, Tuple
# 3rd Party
import numpy as np

def multilayer_default_boxes(feature_shapes, ratios, scales, quadrangles=False):
    # Coordinates of default boxes
    default_boxes = []
    for (feature_shape, ratio_set, scale_set) in zip(feature_shapes, ratios, scales):
        coords = default_box_mesh(feature_shape,ratio_set,scale_set)
        coords = np.reshape(coords, [
                                    feature_shape[0]*feature_shape[1]*len(ratio_set),
                                    8 if quadrangles else 4
        ])
        default_boxes.append(coords)
    default_boxes = np.concatenate(default_boxes, axis=0)
    return default_boxes

def default_box_mesh(feature_shape: Tuple[int,int], ratios: List[float], scales: List[float]) -> np.ndarray:
    """
    Coordinates are vertex-based: (ymin,xmin, ymax, xmax)

    :returns: Bounding box array [anchor, coords]
    """
    assert len(ratios) == len(scales), f"{len(ratios)} vs. {len(scales)}"
    n_anchors = len(ratios)

    feature_shape = np.squeeze(np.array(feature_shape))
    if np.ndim(feature_shape) == 0: feature_shape = [1,1]

    ratios = np.squeeze(np.array(ratios))
    scales = np.squeeze(np.array(scales))
    Y_min,X_min = np.mgrid[ 0:feature_shape[0], 0:feature_shape[1] ]

    Y_min =  Y_min / feature_shape[0] # For whatever reason, /= caused errors
    X_min =  X_min / feature_shape[1]
    H = scales / np.sqrt(ratios)
    W = scales * np.sqrt(ratios)

    X_min = np.repeat(np.expand_dims(X_min,-1), n_anchors, axis=-1)
    Y_min = np.repeat(np.expand_dims(Y_min,-1), n_anchors, axis=-1)

    coordinates = np.stack([
                            Y_min,
                            X_min,
                            np.ones([*Y_min.shape]) * H,
                            np.ones([*Y_min.shape]) * W
    ],axis = -1)

    coordinates[...,2] += coordinates[...,0]
    coordinates[...,3] += coordinates[...,1]

    return coordinates

__all__ = ["multilayer_default_boxes, default_box_mesh"]


import io

class HtmlTag:
    def __init__(self, name: str, attributes: dict, style: dict, closer_subtag: bool = True):
        # TAG NAME
        self.__name = name.upper()
        # Does tag have close part like ?
        self.closer_subtag = closer_subtag
        # HTML Tag attributes
        self.attributes = attributes
        # HTML TAG CSS style
        self.style = style
        # Body between subtag
        self.__content = io.StringIO()

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, new_name:str):
        self.__name = new_name
        print("name successfully changed")

    @property
    def content(self):
        return self.__content.getvalue()

    # @content.setter
    # def content(self, new_str:str):
    #     self.__content.write(new_str)

    #Setting HTML Tag attribute
    def set_attribute(self, attribute_name:str, attribute_value:str):
        self.attributes[attribute_name] = attribute_value

    #Set a new CSS style
    def set_style(self, style_name:str, style_value:str):
        self.style[style_name] = style_value

    #Add content into the text
    def add_content(self, content:str):
        self.__content.write(content)
        print("new content has successfully added")

    #Clear content
    def clear_content(self):
        self.__content = io.StringIO()

    #SHOW TAG as is
    def show(self):
        tag_io = io.StringIO()
        opening = r'<'
        close_opening = r'</'
        closing = r">"
        tag_io.write(f'{opening}{self.name}')

#  CONTENTCONTENTCONTENT

        if self.attributes:
            tag_io.write(' ' + ' '.join([f'{attr_key}="{self.attributes[attr_key]}"' for attr_key in self.attributes]))

        if self.style:
            tag_io.write(' style="' + '; '.join([f'{style_key}: {self.style[style_key]}' for style_key in self.style]))

        tag_io.write(closing)

        if self.closer_subtag:
            tag_io.write(self.content)
            tag_io.write(f'{close_opening}{self.name}{closing}')

        return tag_io.getvalue()

class Style(HtmlTag):
    def __init__(self, attributes:dict, style: dict):
        super().__init__(name="style",attributes=attributes, style=style, closer_subtag=True )

a = HtmlTag("img", {"attr1":"attr1_value", "attr2":"attr2_value"}, {"style1":"style1_value", "style2":"style2_value"})
a.add_content("Testing Html_tag")
print(a.show())
print("--------------STYLE")
s = Style({"attr1":"attr1_value", "attr2":"attr2_value"}, {"style1":"style1_value", "style2":"style2_value"})
print(s.show())
s2 = Style({"attr1":"attr1_value", "attr2":"attr2_value"}, {"style1":"style1_value", "style2":"style2_value"})


#encoding: utf-8
import numpy as np
import numpy.ma as ma
import itertools
from scipy import ndimage
from skimage.measure import label
import re

def regex_match(necessity, string_to_match):

    """"Gathering all regex matches used throughout the code to make it applicable to other datasets by modifying.
    Therefore, if we have to train on a new dataset where sequence description is in a new format, only this
    function has to be editted and all regex matches used to make the dataset mapping will be up to date. Generally
    regex are extensivelly used to navigate through map files and identify if something is a directory, etc. In short,
    it is used to find what is what.

    ..note:: Downside is that this script will have to be imported to other scripts under module directory.
             This makes it impossible to import any script from modules directory to *global_functions* script,
             otherwise mutual import will happen and an exception will be raised.

    :param string necessity: can be *patient image*, *patient image*, *axial T2*, *axial DWI*, *windows or unix directory*
    It indicates what we are looking for, which means that we are questioning if a string is a directory or an image etc.
    :param string_to_match:

    :returns: Bool, True if the string is what we are looking for, false otherwise.
    """
    expressions = {'patient image':re.compile(r'^PC-[0-9]{10}.*?.nii$|^aa[a,b]0*[0-9]{2}.*?.nii$|^01\.00[0-9]{2}$'),
                   'patient directory': re.compile(r'^PC-[0-9]{10}$|^01.[0-9]{4}$|^aa[a,b]0*[0-9]{2}$'),
                   'axial T2':re.compile(r'(ax.*?t2|t2.*?ax)(?!.*pelvis.*$)', re.IGNORECASE),
                   'axial DWI': re.compile(r'diff.*?adc|adc.*?diff|^dADC|apparent diff', re.IGNORECASE),
                   'windows or unix directory': re.compile(r'^[A-Z]:.*?|^/.*?')
                   }
    if re.match(expressions[necessity], string_to_match) is not None:
        return True
    else:
        return False

def pretty(data, string, fun_range, *indeces):

    value_dic = {}
    for i in range(fun_range-1,-1,-1):
        the_value = data[i]
        for idx in indeces:
            the_value = the_value[idx]
        value_dic[string + str(i+1)] = the_value
    return value_dic

def merge_dictionairies(*dics):

    x = {}
    for dic in dics:
        x.update(dic)
    return x

def custom_pixelwise_features(img, ws, function, *extra_args):
    """Returns features for the windows within the image calculated by function.

    It is a common issue that many features have to be calculated for each pixel within the image,
    with respect to a neighborhood of pixels, referred to as window. Window has a size *ws x ws*.
    *pixelwise_features* calls iteratively a function that calculates all these features.
    Function *function* can have in this version arbitrarilly many arguments given as
    the input specified by *extra_args*. *pixelwise_features* calls then this function for every
    window within the image. This means, that , for example, two directly subsequent windows only differ in one value.

    The important thing about *pixelwise_features* is that it takes in account boundary conditions.
    For a pixel that lies close to the boundary and a window of size *ws x ws* would yield an index error,
    is truncated to a window just fitting in the image. For example, the window of a pixel at *img[1,0]*
    for *ws = 9*, would be *img[0:5, 0:4]*.

    It is used for sliding window estimation of C2 and C5 features. Otherwise, *pixelwise_features* which calls an implementation
    from *ndimage* library, which is way more efficient is used.

    :param np.array img: image for which the features are calculated
    :param int ws: window size
    :param function.object function: function that calculates the features. It only takes one input, namely window (e.g. img[0:5, 0:4]
    :param any *extra_args: Any further set of parameters that function *function* could take as input

    :returns: dictionary where keys are features names and entries are 2d arrays with feature values over the image.
    """

    dummy_img = np.ones(img.shape)
    feat_characteristics = function(dummy_img)
    features_num = len(feat_characteristics)
    feat_names = feat_characteristics.keys()
    if (ws%2 == 0):
        # raise ValueError("radiomics.C1 : window size has to be odd numbers")
        pass
    shape = list(img.shape)
    shape.append(features_num)
    x_start_shifted = np.array([a - int(ws/2) for a in range(shape[0])])
    x_start = [a[a >= 0] for a in x_start_shifted]
    x_stop_shifted = np.array([a + int(ws / 2) for a in range(shape[0])])
    x_stop = [a[a < len(img)] for a in x_stop_shifted]
    y_start_shifted = np.array([a - int(ws/2) for a in range(shape[1])]) # supposing square support region
    y_start = [a[a >= 0] for a in y_start_shifted]
    y_stop_shifted = np.array([a + int(ws / 2) for a in range(shape[1])])
    y_stop = [a[a < len(img[0])] for a in y_stop_shifted]

    for i in range(len(x_start)):
        if x_start[i].size == 0:
            x_start[i] = 0
        else:
            x_start[i] = int(x_start[i])
    for i in range(len(x_stop)):
        if x_stop[i].size == 0:
            x_stop[i] = int(shape[0])
        else:
            x_stop[i] = int(x_stop[i])
    for i in range(len(y_start)):
        if y_start[i].size == 0:
            y_start[i] = 0
        else:
            y_start[i] = int(y_start[i])
    for i in range(len(y_stop)):
        if y_stop[i].size == 0:
            y_stop[i] = int(shape[1])
        else:
            y_stop[i] = int(y_stop[i])
    features = np.zeros(shape)
    for i in range(shape[0]):
        for j in range(shape[1]):
            features[i][j] = function(img[x_start[i]:x_stop[i], y_start[j]:y_stop[j]], *extra_args)
    features = np.swapaxes(features, 1, 2)
    features = np.swapaxes(features, 0, 1)
    dict_to_return = {a_key:an_array for a_key, an_array in itertools.izip(feat_names, features)}
    return dict_to_return

def pixelwise_features(img, ws, function, *extra_args):

    """Sliding window feature estimation by using *ndimage.filters.generic_filter*, which is much
    faster than the custom implementation.

    :param np.array img: image for which the features are calculated
    :param int ws: window size
    :param function.object function: function that calculates the features. It only takes one input, namely window (e.g. img[0:5, 0:4]
    :param any *extra_args: Any further set of parameters that function *function* could take as input

    :returns: 2d numpy array with feature values over the image.
    """

    feat_shape = img.shape
    features = np.zeros(feat_shape)
    ndimage.filters.generic_filter(img, function, size=ws, output=features, extra_arguments=extra_args)
    return features

def single_values_to_img(dictionary, img_shape):
    """"For ROI based features, replicate img.shape times the single value over the entire ROI"""
    extended_dic = {}
    for item in dictionary:
        if type(dictionary[item]) is list or type(dictionary[item]) is np.ndarray:
            i=0
            for value in dictionary[item]:
                level = np.tile(value, img_shape)
                hist_bin_name = item + '_%d'%i
                extended_dic[hist_bin_name] = level
                i += 1
        else:
            level = np.tile(dictionary[item], img_shape)
            extended_dic[item] = level
    return extended_dic

def set_in_range(img, indices, values):

    """Crop an array to the boundaries specified by indices and fill it with specified values. New image shape
    and values must have the same shape.

    :param 2d numpy array img:
    :param numpy array indices:
    :param 2d numpy array values:

    :returns: 2d numpy array with new shape and values
    """
    rows_0 = np.min(indices[1])
    rows_1 = np.max(indices[1])
    columns_0 = np.min(indices[0])
    columns_1 = np.max(indices[0])
    img[columns_0:columns_1, rows_0:rows_1] = values
    return img

def trim_array_to_ROI(img, return_support_region = False):

    """Crop an image to the boundaries specified by the circumscribed rectangle of the region defined by a mask.

    :param 2d numpy masked array img: mask is the region of interest.
    :param bool return_support_region: If set to true, also the boudaries of the circumscribed rectangle are returned.
    Default is False.

    :returns: 2d numpy array, cropped image

    :returns: If *return_support_region* is set, also a list with coordinates of the ROI circumscribed rectangle is returned.
    """
    #img.mask = np.logical_not(img.mask)
    edges_y = []
    for row in img:
        edges = ma.notmasked_edges(row)
        if edges is not None:
            edges_y.append(edges)
    edges_x = []
    for col in np.swapaxes(img, 0, 1):
        edges = ma.notmasked_edges(col)
        if edges is not None:
            edges_x.append(edges)
    if edges_x == [] and edges_y == []:
        return None
    edges_x = np.swapaxes(edges_x, 0, 1)
    edges_y = np.swapaxes(edges_y, 0, 1)
    min_x = np.min(edges_x[0])
    max_x = np.max(edges_x[1])
    min_y = np.min(edges_y[0])
    max_y = np.max(edges_y[1])
    # do not return stripes
    if max_x - min_x <= 1:
        if min_x == 0:
            max_x += 1
        elif max_x == edges_x[1, -1]:
            min_x -= 1
        else:
            max_x += 1
    if max_y - min_y <= 1:
        if min_y == 0:
            max_x += 1
        elif max_y == edges_y[1, -1]:
            min_y -= 1
        else:
            max_y += 1
    new_data = img.data[min_x : max_x+1, min_y : max_y+1]
    support_region = [(min_x, max_x+1), (min_y, max_y+1)]
    if return_support_region:
        return new_data, support_region
    return new_data

def healthy_rois(img, roi_inds, ROIs, a_function):
    """For ROI based calculation of C2 and C5 features. Apply the function defined as input to regions
    defined by the dual of circumscribed rectangles around regions of interest defined at ROIs.
    Healthy ROIs are defined by the intersections of lines defining the boundaries of circumscribed rectangles
    around regions defined by ROIs, thus they are compact and recangularly shaped.

    :param 2d numpy array img:
    :param list roi_inds: indices of lines defining the boundaries of circumscribed rectangles
    around regions defined by ROIs
    :param 2d numpy array ROIs:
    :param function object a_function: function to apply on healthy regions

    :returns: features dictionary, where keys are features' names and entries are 2d numpy arrays with the feature values over each pixel.

    :returns: A list with coordinates of the healthy ROI circumscribed rectangle is also returned.
    """
    features = []
    indices = []
    roi_inds.append([(0, img.shape[0]), (0, img.shape[1])])
    row_inds = np.sort(np.unique(np.ravel(np.array([x[0] for x in roi_inds]))))
    col_inds = np.sort(np.unique(np.ravel(np.array([x[1] for x in roi_inds]))))
    for i in range(len(row_inds)-1):
        for j in range(len(col_inds)-1):
            if not np.any(ROIs[row_inds[i]:row_inds[i+1], col_inds[j]:col_inds[j+1]]): # is healthy
                min_x = row_inds[i]
                max_x = row_inds[i+1]
                min_y = col_inds[j]
                max_y = col_inds[j+1]
                if max_x - min_x == 1:
                    if min_x <= 0:
                        max_x += 2
                        min_x = 0
                    elif max_x >= img.shape[0]:
                        min_x -= 2
                        max_x = img.shape[0]
                    else:
                        max_x += 1
                if max_y - min_y == 1:
                    if min_y <= 0:
                        max_y += 2
                        min_y = 0
                    elif max_y >= img.shape[-1]:
                        min_y -= 2
                        max_y = img.shape[-1]
                    else:
                        max_y += 1
                indices.append([(min_x, max_x), (min_y, max_y)])
                sub_image = img[min_x:max_x, min_y:max_y]
                features.append(a_function(sub_image))
    roi_inds.pop(-1) # the instance gets affected globaly!!
    return features, indices

def ROI_based_calclulations(img, ROIs, the_function):

    """For ROI based calculation of C2 and C5 features. Apply the function defined as input to regions
    defined by the circumscribed rectangles around regions of interest defined at ROIs. ROIs is a numpy array
    representing the mask where 0 refers to healthy tissue and 1 to lesions. There can multiple lesions of
    arbitrary shape. Also a 4-connected 1-pixel binary errosion
    and dilation is applied on ROIs to compensate for registration errors of the ROIs masks, that usually are
    the outputs of a registation and salt and pepper noise is frequently observed.
    Lesions with a total area less than 10 pixels are disregarded as they are probably a result of registration errors that
    are not compensated during erosion-dilation.

    :param 2d numpy array img:
    :param 2d numpy array ROIs:
    :param function object the_function:

    :returns: features dictionary, where keys are features' names and entries are 2d numpy arrays with the feature values over each pixel.
    """
    roi_features = []
    roi_indices = []
    corrected_ROIs = np.zeros(ROIs.shape)
    # 0 is the healthy, handled later
    #CHANGE
    non_compact_ROIs, components = label(ROIs, neighbors=4, return_num=True)
    if components > 1:  # 0 is healthy, 1 is one compact region. then usually it's noise if it's more
        remove_registration_errors = np.logical_not(ROIs)
        remove_registration_errors = ndimage.binary_erosion(remove_registration_errors, iterations=1)
        remove_registration_errors = ndimage.binary_dilation(remove_registration_errors, iterations=1)
        ROIs = np.logical_not(remove_registration_errors)
        non_compact_ROIs, components = label(ROIs, neighbors=4, return_num=True)
    ROIs = np.logical_or(corrected_ROIs, np.logical_not(ROIs))  # this roi masks out True values.
    for subroi in range(1, components + 1):  # 0 is the rest, the non-roi
        #CHANGE
        this_subroi = np.where(non_compact_ROIs == subroi, False, True)
        if np.sum(np.logical_not(this_subroi)) > 10:
            roi_img = ma.masked_array(img, this_subroi)
            roi_img, their_indices = trim_array_to_ROI(roi_img, return_support_region=True)
            roi_features.append(the_function(roi_img))
            roi_indices.append(their_indices)
        else:
            ROIs = np.where(non_compact_ROIs == subroi, True, ROIs) # change noise to healthy to give features
    hr, hi = healthy_rois(img, roi_indices, np.logical_not(ROIs), the_function) #needs True to consider as sick
    roi_features.extend(hr)
    roi_indices.extend(hi)
    # reconstruct
    features = {}
    for feature_name in list(roi_features[0]):
        features[feature_name] = np.zeros(img.shape)
    for this_roi_feature_dic, this_roi_support in itertools.izip(roi_features, roi_indices):
        # assuming compact rois
        for feature_name in this_roi_feature_dic:
            features[feature_name] = set_in_range(features[feature_name], this_roi_support,
                                                  this_roi_feature_dic[feature_name])

    return features

# from scipy.misc import toimage
# toimage(this_roi).show()


#!/usr/bin/env python
# -*- coding: utf-8 -*-
from cid import CID_WORD, CID_ANSWER
from po import Po
from feed_render import zsite_id_list_by_follow
from feed import PAGE_LIMIT
from feed_po import feed_po_iter, FeedPoMerge, mc_feed_po_dict, mc_feed_user_dict
from zsite import Zsite
from zkit.mc_func import mc_func_get_list, mc_func_get_dict
from txt import txt_get

@mc_feed_user_dict('{user_id}')
def user_dict(user_id):
    user = Zsite.mc_get(user_id)
    unit, title = user.career
    d = dict(id=user_id, name=user.name, unit=unit, title=title)
    ico_url = user._ico96
    if ico_url:
        d['ico_url'] = ico_url
    return d

def question_dict(question_id):
    o = Po.mc_get(question_id)
    return dict(question_id=question_id, question_name=o.name, question_user=user_dict(o.user_id))

def feed_po_dict_by_db(id):
    o = Po.mc_get(id)
    cid = o.cid
    rid = o.rid

    d = dict(id=id, cid=cid)

    if cid == CID_WORD and rid or cid == CID_ANSWER:
        d.update(question_dict(rid))
    else:
        d['name'] = o.name

    return d

def feed_po_dict_list(id_list):
    return mc_func_get_list(mc_feed_po_dict, feed_po_dict_by_db, id_list)

def feed_user_dict_dict(id_list):
    id_list = set(id_list)
    return mc_func_get_dict(mc_feed_user_dict, user_dict, id_list)

def render_feed_api_by_zsite_id(zsite_id, limit=PAGE_LIMIT, begin_id=None):
    feed_merge = FeedPoMerge(zsite_id_list_by_follow(zsite_id))
    id_list = []
    zsite_id_list = []
    id = 0
    for i in feed_merge.merge_iter(limit, begin_id):
        id = i.id
        zsite_id = i.zsite_id
        id_list.append(id)
        zsite_id_list.append(zsite_id)
    user_dict_dict = feed_user_dict_dict(zsite_id_list)
    li = feed_po_dict_list(id_list)
    for i, zsite_id in zip(li, zsite_id_list):
        i['user'] = user_dict_dict[zsite_id]
        txt = txt_get(i['id'])
        if txt:
            i['txt'] = txt
    return li, id

def render_user_feed_api_by_zsite_id(zsite_id, limit=PAGE_LIMIT, begin_id=None):
    id_list = []
    id = 0
    count = 0
    for id in feed_po_iter(zsite_id, begin_id):
        id_list.append(id)
        count += 1
        if count >= limit:
            break
    li = feed_po_dict_list(id_list)
    for i in li:
        txt = txt_get(i['id'])
        if txt:
            i['txt'] = txt
    return li, id

if __name__ == '__main__':
    pass


# url: http://rosalind.info/problems/revc/

# Problem

# In DNA strings, symbols 'A' and 'T' are complements of each other, as
# are 'C' and 'G'.

# The reverse complement of a DNA string s is the string sc formed by
# reversing the symbols of s, then taking the complement of each symbol
# (e.g., the reverse complement of "GTCA" is "TGAC").

# Given: A DNA string s of length at most 1000 bp.

# Return: The reverse complement sc of s.

from common import rev_complement

if __name__ == "__main__":
    file1, file2 = "inputs/REVC_input.txt", "outputs/REVC_output.txt"
    with open(file1, "r") as f1, open(file2, "w") as f2:
        dna_seq = f1.read().strip()
        f2.write(rev_complement(dna_seq))


import math

from pyramid.exceptions import NotFound
from planevent.offers import models
from planevent.accounts.models import Account
from planevent.core.decorators import (
    route,
    seo_route,
    Rest,
    Template,
)
from planevent.offers import service
from planevent.core.views import View
from planevent.categories import service as categories_service

@route('seo_home')
class HomeView(View):
    def get(self) -> Template('seo/pages/index'):
        promoted_offers = service.get_promoted_offers(
            limit_per_category=10,
            categories_limit=None,
        )
        return {
            'promoted_offers': promoted_offers,
        }

@seo_route('seo_search')
class SearchView(View):
    LIMIT = 30

    def get(self, page: int=0) -> Template('seo/pages/search'):
        total_quantity = models.Offer.count()

        offers = models.Offer.query() \
            .order_by(models.Offer.promotion) \
            .limit(self.LIMIT) \
            .offset(self.LIMIT * page) \
            .all()

        return {
            'offers': offers,
            'current_page': page,
            'total_pages': math.ceil(total_quantity / self.LIMIT),
        }

@seo_route('seo_offer')
class OfferView(View):
    def get(self, id: Rest(int)) -> Template('seo/pages/offer'):
        offer = models.Offer.get(id)
        if offer is None:
            raise NotFound
        else:
            return {
                'offer': models.Offer.get(id)
            }


import sendgrid
import os
from sendgrid.helpers.mail import *

emailaddress = os.environ.get('EMAIL_DEFAULT')
sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY'))
from_email = Email(emailaddress)

def sendEmail(cont, dt, membername, toemailaddress):
    to_email = Email(toemailaddress)
    subject = membername + " Twit Alert " + "(" + dt + ")"
    content = Content("text/plain", cont)
    mail = Mail(from_email, subject, to_email, content)
    response = sg.client.mail.send.post(request_body=mail.get())
    print(response.status_code)
    print(response.body)
    print(response.headers)


import sys
from PyQt5.QtWidgets import QDialog, QApplication
from ui_showDate import *

class MyForm(QDialog):
     def __init__(self, parent = None):
          super().__init__()
          self.ui = Ui_Dialog()
          self.ui.setupUi(self)

          # Add signal and slot to pushButton
          self.ui.pushButton_ShowDate.clicked.connect(self.showDate)

     def showDate(self):
          self.ui.dateEdit.setDisplayFormat('MMM d yyyy')
          self.ui.dateEdit.setDate(self.ui.calendarWidget.selectedDate())

if __name__=="__main__":
    app = QApplication(sys.argv)
    w = MyForm()
    w.show()
    sys.exit(app.exec_())