#!/usr/bin/env python3

import os
import sys
import pathlib
import io
import re
import requests
import json
import yaml
import argparse
from dotenv import load_dotenv
from datetime import date

VERBOSE=False

# ---------------------------------------------------------------------------------------------------------------------

def verbose(msg):
  if VERBOSE:
    print(msg)

# ---------------------------------------------------------------------------------------------------------------------

def get_compatibility_code(conversionCode):
  """
  Load the CompatibilityCode and adapt it.
  """

  # copatibility code laden

  with open(selfpath+"./.clobber/CompatibilityCode.c", "r") as f:
    compatibilityCode=f.read()

  # gucken, welche keys es im compatibility code gibt

  keys = {}

  for line in compatibilityCode.split("\n"):
    match=re.match(r'^\s*\/\*\|([^\|]*)\|\*\/(.*)$',line)
    if match != None:
      key=match.group(1).strip()
      if key != '' and not key in keys:
        keys[key]=False

  # gucken, welche keys im conversion code vorkommen

  for key in keys:
    if re.search(r'[^\w]'+key+r'[^\w]',conversionCode) != None:
      keys[key]=True

  # zeilen rausfiltern, deren key nicht verwendet wird

  abridgedCompatibilityCode=""

  for line in compatibilityCode.split("\n"):
    match=re.match(r'^\s*\/\*\|([^\|]*)\|\*\/(.*)$',line)
    if match==None:
      abridgedCompatibilityCode = abridgedCompatibilityCode+line+"\n"
    else:
      key  = match.group(1).strip()
      code = match.group(2)
      if key=='' or keys[key]:
        abridgedCompatibilityCode = abridgedCompatibilityCode+code+"\n"

  # ueberfluessegen whitespace eliminieren

  abridgedCompatibilityCode=re.sub(r'\t','  ',abridgedCompatibilityCode)
  abridgedCompatibilityCode=re.sub(r' +\n','\n',abridgedCompatibilityCode)
  abridgedCompatibilityCode=re.sub(r'\n +\n','\n\n',abridgedCompatibilityCode)
  abridgedCompatibilityCode=re.sub(r'\n{3,}','\n\n',abridgedCompatibilityCode)

  # und damit ist der compatibility code entsprechend angepasst

  return abridgedCompatibilityCode

# ---------------------------------------------------------------------------------------------------------------------

def get_basefilename(path,id):
  """
  Get the name preceding the '.<ID>.c' suffix.
  """

  files=[entry for entry in os.scandir(path) if entry.is_file() and entry.name.endswith(f".{id}.c")]

  if len(files) < 1:
    raise Exception(f"no such file '{path}*.{id}.c'")

  if len(files) > 1:
    raise Exception(f"there are multiple files matching '{path}*.{id}.c'")

  name = re.sub(r'^(.+)\.'+id+'\.c$',r'\1',files[0].name)

  # print(f"fuse conversion '{path}{name}.{id}.c'")

  return name

# ---------------------------------------------------------------------------------------------------------------------

def write_markdown_file(info,target_file,force=False):

  fuse_name     = info['fuse']['name']
  fuse_author   = info['fuse']['author']
  shader_author = info['shader']['author']
  shader_id     = info['shader']['id']
  shader_name   = info['shader']['name']
  description   = info['shader'].get('description','')

  if os.path.isfile(target_file) and not force:
    print("file '"+target_file+"' already exists - use force option to overwrite")
    return

  dlbtn = f"[![Download Installer](https://img.shields.io/static/v1?label=Download&message={fuse_name}-Installer.lua&color=blue)]({fuse_name}-Installer.lua \"Installer\")"
  with open(target_file, 'w') as f:
    f.write( "" \
      f"# Here insert a description of the shader! And maybe a gif\n\n" \
      f"# Description of the Shader in Shadertoy: \n{description}\n\n" \
      f"[![Thumbnail]({fuse_name}_screenshoot.png)]({fuse_name}.fuse)\n\n"
      )

    verbose("wrote file '"+target_file+"'")

# ---------------------------------------------------------------------------------------------------------------------

def write_sfi_file(info,target_file,force=False):

  fuse_name     = info['fuse']['name']
  fuse_author   = info['fuse']['author']
  shader_author = info['shader']['author']
  shader_id     = info['shader']['id']
  shader_name   = info['shader']['name']
  description   = info['shader'].get('description','')
  creationdate  = date.today().strftime("%Y-%m-%d")

  if os.path.isfile(target_file) and not force:
    print("file '"+target_file+"' already exists - use force option to overwrite")
    return

  with open(target_file, 'w') as f:
    f.write( "" \
      f"info = {{\n\n   -- https://www.shadertoy.com/view/{shader_id}\n\n   Shadertoy = {{\n      Name = \"{shader_name}\",\n      Author = \"{shader_author}\",\n      ID = \"{shader_id}\",\n   }},\n\n" \
      f"   Fuse = {{\n      Author = \"{fuse_author}\",\n      Date = \"{creationdate}\",\n   }},\n\n" \
      f"   Compatibility = {{\n      macOS_Metal = false,\n      macOS_OpenCL = false,\n      Windows_CUDA = false,\n      Windows_OpenCL = false,\n   }},\n}}"
      )

    verbose("wrote file '"+target_file+"'")

# ---------------------------------------------------------------------------------------------------------------------

def patch_kernel(conversionCode,kernelName,has_param,part=""):

  conversionKernelName=kernelName+"Fuse"+part

  verbose("Patch Kernel '"+conversionKernelName+"'")

  m = re.search(r'__KERNEL__\s+void\s+'+conversionKernelName+r'\s*\(([^\)]*)\)\s*\{',conversionCode)

  if not m:
    if part=="":
      raise Exception("no image kernel found!")
    return conversionCode

  kernel_parameters=m.group(1)

  fragColor_name = None # float4
  fragCoord_name = None # float2

  # ja, okay, jetzt wird's echt bescheuert ...

  i=-1 # iteration to allow fragColor and Coord only as the first two parameters

  kernel_parameters=kernel_parameters.strip()
  verbose("kernel parameters='"+kernel_parameters+"'")

  for p in kernel_parameters.split(','):

    i+=1

    t = None
    n = None
    m = re.search(r'^\s*([A-Za-z_][A-Za-z0-9_]*)\s+([A-Za-z_][A-Za-z_0-9]*)\s*$', p) #match "type name"
    if m:
        t = m.group(1)
        n = m.group(2)
    else:
      m = re.search(r'^\s*([A-Za-z_][A-Za-z0-9_]*)\s*\*\s*([A-Za-z_][A-Za-z_0-9]*)\s*$', p) # match "type * name" (yes, type*name matches, so what)
      if m:
        t = m.group(1)+'*'
        n = m.group(2)
      else:
        m= re.search(r'^\s*([A-Za-z_][A-Za-z0-9_]*)\s+([A-Za-z_][A-Za-z_0-9]*)\s*\[\s*\]\s*$', p) # match "type name [ ]"
        if m:
          t = m.group(1)+'*'
          n = m.group(2)

    if not m:
      raise Exception("parameter='"+p+"' has bad format")

    verbose("parameter"+str(i)+": type='"+t+"', name='"+n+"'")
    if has_param.get(n,None)==None:
      if i==0 and t=='float4' and fragColor_name==None:
        fragColor_name=n # first and only the first parameter can be the color (yes, stupid idea to call that color iDate)
      elif i<2 and t=='float2' and fragCoord_name==None:
        fragCoord_name=n # first or second parameter can be the coord
      else:
        raise Exception("unknown parameter '"+t+" "+n+"'")
    else:
      if t != has_param[n]['Type']:
        raise Exception("bad type '"+t+"' for '"+n+"' ('"+has_param[n]['Type']+"' expected)")

      if has_param[n]['Present']:
        print("multiple occurences of '"+t+" "+n+"'")
      else:
        has_param[n]['Present']=True

  # even 'gaps' in the channels (unused channels) must be counted
  # so if we have iChannel3 and only iChannel3, then the number
  # of channels is 4 (iChannel0 to iChannel3).

  NUM_INPUT_CHANNELS=0
  kernel_parameters = "__CONSTANTREF__ Params*  params, "
  for i in range(16):
    if has_param["iChannel"+str(i)]['Present']:
      kernel_parameters += " __TEXTURE2D__ iChannel"+str(i)+", "
      NUM_INPUT_CHANNELS=i+1

  kernel_parameters += "__TEXTURE2D_WRITE__ destinationTexture"

  if NUM_INPUT_CHANNELS==0:
    has_param['iChannelResolution']['Code'] = '/* no iChannels!?! */\n'
    has_param['iChannelTime']['Code']       = '/* no iChannels!?! */\n'
  else:

    # iChannelResolution[]
    # iChannelTime[]
    has_param['iChannelResolution']['Code']='  float2 iChannelResolution['+ str(NUM_INPUT_CHANNELS) +'];\n'
    has_param['iChannelTime']['Code']='  float iChannelTime['+ str(NUM_INPUT_CHANNELS) +'];\n'
    for i in range(NUM_INPUT_CHANNELS):
      if has_param["iChannel"+str(i)]['Present']:
        has_param['iChannelResolution']['Code'] += "  iChannelResolution["+str(i)+"] = to_float2(params->iChannelResolution["+str(i)+"][0], params->iChannelResolution["+str(i)+"][1]);\n"
        has_param['iChannelTime']['Code']       += "  iChannelTime["+str(i)+"] = params->iChannelTime["+str(i)+"];\n"
      else:
        has_param['iChannelResolution']['Code'] += "  iChannelResolution["+str(i)+"] = to_float2(0.0f,0.0f);\n"
        has_param['iChannelTime']['Code']       += "  iChannelTime["+str(i)+"] = 0.0f;\n"

  variable_declarations = ""

  for n,p in has_param.items():
    if p['Present'] and p['Code']!='':
      variable_declarations += p['Code']

  if fragColor_name != None:
    variable_declarations+="  float4 "+fragColor_name+"   = to_float4_s(0.0f);\n"

  if fragCoord_name != None:
    variable_declarations+="  float2 "+fragCoord_name+"   = to_float2(fusion_x,fusion_y);\n"

  m=r'__KERNEL__\s+void\s+'+conversionKernelName+r'\s*\([^\)]*\)\s*\{'

  r=  "__KERNEL__ void "+conversionKernelName+"("+kernel_parameters+")\n" \
      "{\n" \
      "   DEFINE_KERNEL_ITERATORS_XY(fusion_x, fusion_y);\n\n" \
      "   if (fusion_x >= params->width || fusion_y >= params->height)\n" \
      "     return;\n\n"+variable_declarations+"\n\n<<CODEFROMCONTROLS>>  // --------\n\n"

  conversionCode  = re.sub( m,r,conversionCode)

  conversionCode  = re.sub(
      r'(\s+)SetFragmentShaderComputedColor\s*\(\s*',
      r'\1_tex2DVec4Write(destinationTexture, fusion_x, fusion_y, ',
      conversionCode)

  return conversionCode

# ---------------------------------------------------------------------------------------------------------------------

def check_controls(conversionCode):

  controls = re.findall(r'\s+CONNECT_([A-Z]+)(\d+)\s*\(\s*([A-Za-z_][\w]*)\s*,\s*([^\)]+)\)\s*;',conversionCode)

  fullCodeCreate=''
  fullCodeProcess=''
  fullCodeKernel=''
  fullCodeParams=''

  for ctrl in controls:

    ctrlType    = ctrl[0]
    ctrlIdx     = ctrl[1]
    ctrlVar     = ctrl[2]
    ctrlParams  = ctrl[3] # ctrl[3].split(",")

    # print(f"create {ctrlType} for {ctrlVar} with parameters '{ctrlParams}'")

    if ctrlType == 'SLIDER' or ctrlType == 'SCREW':

      m = re.match(r'^\s*(-?\d+\.\d+)f\s*,\s*(-?\d+\.\d+)f\s*,\s*(-?\d+\.\d+)f\s*$',ctrlParams)

      if m == None:
        raise Exception (f"failed to match '{ctrlParams}' as {ctrlType} parameters")

      min, max, default = m.group(1), m.group(2), m.group(3)

      codeParams  = f"float  {ctrlVar};"
      codeKernel  = f"float  {ctrlVar} = params->{ctrlVar};"
      codeProcess = f"params.{ctrlVar} = In{ctrlVar}Slider:GetValue(req).Value"
      if ctrlType == 'SLIDER':
          codeCreate  = f"""
              In{ctrlVar}Slider = self:AddInput("{ctrlVar}", "{ctrlVar}", {{
                  LINKID_DataType    = "Number",
                  INPID_InputControl = "SliderControl",
                  INP_MinScale       = {min},
                  INP_MaxScale       = {max},
                  INP_Default        = {default},
              }})"""
      else:
          codeCreate  = f"""
              In{ctrlVar}Slider = self:AddInput("{ctrlVar}", "{ctrlVar}", {{
                  LINKID_DataType    = "Number",
                  INPID_InputControl = "ScrewControl",
                  INP_MinScale       = {min},
                  INP_MaxScale       = {max},
                  INP_Default        = {default},
              }})"""

    elif ctrlType == 'INTSLIDER':

      m = re.match(r'^\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*$',ctrlParams)

      if m == None:
        raise Exception (f"failed to match '{ctrlParams}' as {ctrlType} parameters")

      min, max, default = m.group(1), m.group(2), m.group(3)

      codeParams  = f"int    {ctrlVar};"
      codeKernel  = f"int    {ctrlVar} = params->{ctrlVar};"
      codeProcess = f"params.{ctrlVar} = In{ctrlVar}Slider:GetValue(req).Value"
      codeCreate  = f"""
          In{ctrlVar}Slider = self:AddInput("{ctrlVar}", "{ctrlVar}", {{
              LINKID_DataType    = "Number",
              INPID_InputControl = "SliderControl",
              INP_MinScale       = {min},
              INP_MaxScale       = {max},
              INP_Default        = {default},
              INP_Integer        = true,
          }})"""

    elif ctrlType == 'POINT':

      m = re.match(r'^\s*(-?\d+\.\d+)f\s*,\s*(-?\d+\.\d+)f\s*$',ctrlParams)

      if m == None:
        raise Exception (f"failed to match '{ctrlParams}' as {ctrlType} parameters")

      x, y = m.group(1), m.group(2)

      codeParams  = f"float  {ctrlVar}[2];"
      codeKernel  = f"float2 {ctrlVar} = to_float2(params->{ctrlVar}[0], params->{ctrlVar}[1]);"
      codeProcess = f"params.{ctrlVar} = {{In{ctrlVar}Point:GetValue(req).X,In{ctrlVar}Point:GetValue(req).Y}}"
      codeCreate  = f"""
          In{ctrlVar}Point = self:AddInput("{ctrlVar}", "{ctrlVar}", {{
              LINKID_DataType    = "Point",
              INPID_InputControl = "OffsetControl",
              INPID_PreviewControl  = "CrosshairControl",
              INP_DefaultX          = {x},
              INP_DefaultY          = {y},
          }})"""

    elif ctrlType == 'CHECKBOX':

      m = re.match(r'^\s*(1|true|0|false)\s*$',ctrlParams)

      if m == None:
        raise Exception (f"failed to match '{ctrlParams}' as {ctrlType} parameters")

      default = m.group(1)
      if default=="1" or default=="true":
        default=1
      else:
        default=0

      codeParams  = f"bool   {ctrlVar};"
      codeKernel  = f"bool   {ctrlVar} = params->{ctrlVar};"
      codeProcess = f"params.{ctrlVar} = In{ctrlVar}Checkbox:GetValue(req).Value"
      codeCreate  = f"""
          In{ctrlVar}Checkbox = self:AddInput("{ctrlVar}", "{ctrlVar}", {{
            LINKID_DataType     = "Number",
            INPID_InputControl  = "CheckboxControl",
            INP_Integer         = true,
            INP_Default         = {default},
          }})"""

    elif ctrlType == 'COLOR':

      m = re.match(r'^\s*(-?\d+\.\d+)f\s*,\s*(-?\d+\.\d+)f\s*,\s*(-?\d+\.\d+)f\s*,\s*(-?\d+\.\d+)f\s*$',ctrlParams)

      if m == None:
        raise Exception (f"failed to match '{ctrlParams}' as {ctrlType} parameters")

      r, g, b, a = m.group(1), m.group(2), m.group(3), m.group(4)

      codeParams  = f"float  {ctrlVar}[4];"
      codeKernel  = f"float4 {ctrlVar} = to_float4(params->{ctrlVar}[0], params->{ctrlVar}[1], params->{ctrlVar}[2], params->{ctrlVar}[3]);"
      codeProcess = f"params.{ctrlVar} = {{\n    In{ctrlVar}ColorR:GetValue(req).Value,\n    In{ctrlVar}ColorG:GetValue(req).Value,\n    In{ctrlVar}ColorB:GetValue(req).Value,In{ctrlVar}ColorA:GetValue(req).Value\n  }}"
      codeCreate  = f"""
          self:BeginControlNest("{ctrlVar}", "{ctrlVar}", true, {{}})

            ctrl_grp_cnt = (ctrl_grp_cnt==nil) and 1 or (ctrl_grp_cnt+1)

            attrs = {{
              ICS_Name = "{ctrlVar}",
              LINKID_DataType = "Number",
              INPID_InputControl = "ColorControl",
              INP_MinScale = 0.0,
              INP_MaxScale = 1.0,
              IC_ControlGroup = ctrl_grp_cnt,
            }}

            In{ctrlVar}ColorR = self:AddInput("Red",   "{ctrlVar}Red",   {{ INP_Default  = {r}, IC_ControlID = 0, attrs}})
            In{ctrlVar}ColorG = self:AddInput("Green", "{ctrlVar}Green", {{ INP_Default  = {g}, IC_ControlID = 1, attrs}})
            In{ctrlVar}ColorB = self:AddInput("Blue",  "{ctrlVar}Blue",  {{ INP_Default  = {b}, IC_ControlID = 2, attrs}})
            In{ctrlVar}ColorA = self:AddInput("Alpha", "{ctrlVar}Alpha", {{ INP_Default  = {a}, IC_ControlID = 3, attrs}})

          self:EndControlNest()"""

    elif ctrlType == 'BUTTON':
      #print(f"create {ctrlType} for {ctrlVar} with parameters '{ctrlParams}'")

      m=ctrlParams.split(",")

      if m == None:
        raise Exception (f"failed to match '{ctrlParams}' as {ctrlType} parameters")

      #print("m",m, m[0])

      if (m[0] == "0"):
        _type = ""
      if (m[0] == "1"):
        _type = f""" MBTNC_Type         = "Toggle", """

      buttons = ""
      m.pop(0)
      for i in m:
        buttons += f""" {{ MBTNC_AddButton  = "{i}", }}, """

      codeParams  = f"float  {ctrlVar};"
      codeKernel  = f"float  {ctrlVar} = params->{ctrlVar};"
      codeProcess = f"params.{ctrlVar} = In{ctrlVar}Button:GetValue(req).Value"
      codeCreate  = f"""
          In{ctrlVar}Button = self:AddInput("{ctrlVar}", "{ctrlVar}", {{
              LINKID_DataType    = "Number",
              INPID_InputControl = "MultiButtonControl",
              MBTNC_ForceButtons = true,
        {_type}
              MBTNC_ShowName     = false,
        {buttons}
		      MBTNC_StretchToFit = true,
              IC_NoLabel         = true,
		      INP_Default        = 0,
              IC_Visible         = true,
          }})"""

    else:
      raise Exception(f"unknow resp. not yet implemented contol {ctrlType} for {ctrlVar}")

    codeCreate  = re.sub(r'\n        ',  '\n', codeCreate)
    codeCreate  = re.sub(r'^\s+',        '  ', codeCreate)
    codeParams  = "  "+codeParams
    codeKernel  = "  "+codeKernel
    codeProcess = "  "+codeProcess

    fullCodeCreate  += codeCreate  + "\n\n"
    fullCodeParams  += codeParams  + "\n"
    fullCodeKernel  += codeKernel  + "\n"
    fullCodeProcess += codeProcess + "\n"

  return fullCodeCreate, "\n"+fullCodeProcess, fullCodeKernel+"\n",  fullCodeParams

# ---------------------------------------------------------------------------------------------------------------------

def fuse_it(id,force=False):

  source_path=selfpath+'./Conversions/'+folder+'/'
  target_path=selfpath+'./Converted/'

  conversion_file = get_basefilename(source_path,id)

  verbose(f"process '{conversion_file}'")

  # read .yaml file

  info=None
  with open(f"{source_path}{conversion_file}.{id}.yaml", "r") as f:
    info=yaml.safe_load(f)

  fuse_name     = info['fuse']['name']  # FuReg name ... this is what's displayed in the tool selection menu
  fuse_kernel   = conversion_file       # name of the fragment shader kernel function
  fuse_id       = info['fuse'].get('id',conversion_file) # FuReg ID (must follow identifier syntax)
  fuse_file     = info['fuse'].get('file',fuse_id)  # filename to save the fuse as

  if info['shader']['id'] != id:
    raise Exception("inconsistency in your source and yaml file")

  # write .md file

  write_markdown_file(info, target_file = target_path+fuse_file+".md", force = force )

  # write .sfi file

  write_sfi_file(info, target_file = target_path+fuse_file+".sfi", force = force )

  # read conversion code

  with open(f"{source_path}{conversion_file}.{id}.c", "r") as f:
    conversionCode=f.read()

  conversionCode = re.sub(r'\t','  ',conversionCode)
  conversionCode = re.sub(r' +\n','\n',conversionCode)

  # create compatibility code for this conversion

  compatibilityCode = get_compatibility_code(conversionCode)

  # load the data from yaml file

  with open(selfpath+".clobber/fuse-parameters.yaml", "r") as f:
    src=f.read()

  has_param=yaml.load(src, Loader=yaml.FullLoader)

  # iChannel<0..15> with 15 as the maximum number of input channels supported for the time being

  for i in range(16):
    has_param['iChannel'+str(i)]={ 'Type':'sampler2D', 'Code': '' }

  # do some code indention

  for k,v in has_param.items():
    for f in ['Code','Proc','Create','Param']:
      if v.get(f,None)==None:
        v[f]=''
      elif v[f]!='':
        v[f]='  '+v[f].replace('\n','\n  ')+'\n'

  # patch all kernel signatures

  channelsSeen    = {}
  channelsSeenNum = 0

  for i in range(16):
    channelsSeen['iChannel'+str(i)]=False

  for part in ['__Buffer_A','__Buffer_B','__Buffer_C','__Buffer_D','']:

    for k,v in has_param.items():
      v['Present'] = False

    conversionCode = patch_kernel(conversionCode,fuse_kernel,has_param,part)

    for i in range(16):
      channel='iChannel'+str(i)
      if has_param[channel]['Present']:
        channelsSeen[channel]=True
        if i+1>channelsSeenNum:
          channelsSeenNum=i+1

  ichan_create    = ''
  ichan_process1  = ''
  ichan_process2  = ''
  ichan_process2_time  = ''
  ichan_process2_res   = ''
  ichan_process3  = ''
  j=0 # how to handle gaps when it comes to LINK_Main?!?
  for i in range(channelsSeenNum):
    channel='iChannel'+str(i)
    if channelsSeen[channel]:
      j+=1
      ichan_create   += f'  InChannel{i} = self:AddInput( "iChannel{i}",  "iChannel{i}",  {{ LINKID_DataType = "Image", LINK_Main = {j}, INP_Required = false  }})\n'
      ichan_process1 += f'  iChannel{i} = InChannel{i}:GetValue(req)\n\n  if iChannel{i}==nil then\n    iChannel{i} = Image(imgattrs)\n    iChannel{i}:Fill(black)\n  end\n\n'
      ichan_process2_time += f'  params.iChannelTime[{i}] = 0\n'
      ichan_process2_res  += f'  params.iChannelResolution[{i}][0] = iChannel{i}.DataWindow:Width()    -- or maybe: iChannel{i}.Width\n  params.iChannelResolution[{i}][1] = iChannel{i}.DataWindow:Height()   -- or maybe: iChannel{i}.Height\n'
      ichan_process3 += f'  node:AddInput("iChannel{i}",iChannel{i}) -- TODO: add a better channel name\n'
    else:
      ichan_process2_time += f'  params.iChannelTime[{i}] = 0\n'
      ichan_process2_res  += f'  params.iChannelResolution[{i}][0] = 0\n  params.iChannelResolution[{i}][1] = 0\n'

  # if channelsSeenNum==0:
  #   ichan_process2 = f'  params.iChannelTime[0] = 0\n  params.iChannelResolution[0][0] = 0\n  params.iChannelResolution[0][1] = 0\n'
  # else:
  #   ichan_process2 = ichan_process2_time + "\n" + ichan_process2_res

  fuse_param    = ''
  fuse_create   = ''
  fuse_process  = ''

  for n,p in has_param.items():
    if p['Present'] and p['Code']!='':
      fuse_param   +=  p['Param']
      fuse_create  +=  p['Create']
      fuse_process +=  p['Proc']

  with open(selfpath+".clobber/fuse-template.lua", "r") as f:
    fuse=f.read()

  # now we need to equip the fuse with all the controls

  create, process, kernel, params = check_controls(conversionCode)

  # remove all controls from source

  conversionCode = re.sub(r' +CONNECT_[A-Z]+\d+\s*\(\s*[A-Za-z_][\w]*\s*,\s*[^\)]+\)\s*; *\n', '', conversionCode)

  fuse_param   += params
  fuse_create  += create
  fuse_process += process

  #print(create)
  #print(params)
  #print(kernel)
  #print(process)

  conversionCode = re.sub(r'<<CODEFROMCONTROLS>>',kernel,conversionCode)

  # load some author information from yaml file

  fuse_author_url   = ''
  fuse_author_logo  = ''

  with open(selfpath+".clobber/fuse-authors.yaml", "r") as f:
    tmp=f.read()

  tmp=yaml.load(tmp, Loader=yaml.FullLoader)

  for author in tmp:
    if author['Name']==info['fuse']['author']:
      fuse_author_url   = author['URL']
      fuse_author_logo  = author['Logo']

  fuse=re.sub(r'<<<SHADER_ID>>>',info['shader']['id'],fuse)
  fuse=re.sub(r'<<<SHADER_AUTHOR>>>',info['shader']['author'],fuse)
  fuse=re.sub(r'<<<SHADER_NAME>>>',info['shader']['name'],fuse)
  fuse=re.sub(r'<<<FUSE_NAME>>>',info['fuse']['name'],fuse)
  fuse=re.sub(r'<<<FUSE_AUTHOR>>>',info['fuse']['author'],fuse)
  fuse=re.sub(r'<<<FUSE_CREATION_DATE>>>',date.today().strftime("%B %d, %Y"),fuse)
  fuse=re.sub(r'<<<FUSE_AUTHOR_URL>>>',fuse_author_url,fuse)
  fuse=re.sub(r'<<<FUSE_AUTHOR_LOGO>>>',fuse_author_logo,fuse)

  fuse=re.sub(r'<<<CODE_COMPATIBILITY>>>',compatibilityCode,fuse)
  fuse=re.sub(r'<<<CODE_KERNEL>>>',conversionCode,fuse)
  fuse=re.sub(r'<<<CODE_PARAMETERS>>>',"\n"+fuse_param,fuse)
  fuse=re.sub(r'<<<CODE_CREATE>>>',fuse_create,fuse)
  fuse=re.sub(r'<<<CODE_PROCESS>>> *\n',fuse_process,fuse)

  fuse=re.sub(r'<<<ICHANNELS_CREATE>>>',ichan_create,fuse)
  fuse=re.sub(r'<<<ICHANNELS_PROCESS1>>> *\n',ichan_process1,fuse)
  fuse=re.sub(r'<<<ICHANNELS_PROCESS2>>> *\n',ichan_process2,fuse)
  fuse=re.sub(r'<<<ICHANNELS_PROCESS3>>> *\n',ichan_process3,fuse)

  # nur zum mal gucken wo wir stehen:
  with open(target_path+fuse_file+".fuse", 'w') as f:
    f.write(fuse)

print("\n#################### Fuse Script ###################")
#print("Aufruf :",sys.argv[0]) #ok

if (sys.argv[0] != "fuse.py" and sys.argv[0] != "./fuse.py" ):
 selfpath = os.path.dirname(sys.argv[0])+"\\"

 print("#SELFPATH:",selfpath)
 print(sys.argv, len(sys.argv))
 #print("##Argv2##",id,param,txt)

 print("Folder: ",folder)

 CONVERSIONS_PATH = selfpath+"\Conversions\\"+folder+"\\"

 #VERBOSE   = verbose
 ID = id
 FORCE = pforce
else:
 selfpath = ""
 folder   = ""

 parser = argparse.ArgumentParser(description='Encapsulate conversion in a Fuse.')
 parser.add_argument('-f','--force',action='store_true',help='overwrite fuse and/or markdown file even if it already exists')
 parser.add_argument('-v','--verbose',action='store_true',help="tell what's happening")
 parser.add_argument('-i','--id', help="shadertoy id as it can be found in the conversion's filename right before the'.c' suffix", required=True)
 args = parser.parse_args()

 VERBOSE=args.verbose
 FORCE  =args.force
 ID     =args.id

load_dotenv(selfpath+".env")

#fuse_it(args.id,force=args.force)
fuse_it(ID,force=FORCE)

# try:
#   fuse_it(args.id)
# except Exception as e:
#   print("outch: "+str(e))


#!/usr/bin/python3
# Before start do it
# module add openmpi/4.0.0-icc slurm/15.08.1 java/jdk1.8.0
# HowTo sbatch -p test -n 5 --ntasks-per-node 1 --output=test.out run python start.py

import os
import subprocess
import time
import sys

def FileCheck(fn):
    try:
        f = open(fn, "r")
        f.close()
        return 1
    except IOError:
        return 0

if (len(sys.argv) != 5):
    print('Ti glupiy? Vvedi 4 parametra')
    print('"spark.sql.shuffle.partitions=?"')
    print('"--total-executor-cores", "?"')
    print('"--executor-cores", "?"')
    print('"--executor-memory", "?GB"')
    sys.exit(-1)

rank = os.environ["SLURM_PROCID"]
jobid = os.environ["SLURM_JOB_ID"]
n = int(os.environ["SLURM_JOB_NUM_NODES"])

if rank == "0":
    # Starting master
    pathToAddress = subprocess.check_output(["./spark/sbin/start-master.sh"]).split(" ")[4][:-1]
    time.sleep(10)

    # Writing master's address to file "jobid + /master_started"
    subprocess.check_output(["mkdir", jobid])
    file = open(pathToAddress, "r")
    s = file.read()
    file.close()
    masterAddress = s.split("\n")[14].split(" ")[8]

    file = open(jobid + "/master_started", "w")
    file.write(masterAddress)
    file.close()

    print("Master started! " + masterAddress + " " + pathToAddress)

    # Wait until all slaves started
    cnt = 1
    while cnt != n:
        cnt += FileCheck(jobid + "/slave_started_" + str(cnt))
        time.sleep(2)

    # Your spark submits
    # output = subprocess.check_output(["./spark/bin/spark-submit",
    #                                   "--class", "SparkPi",
    #                                   "--master", masterAddress,
    #                                   "--num-executors", "4",
    #                                   "--executor-cores", "6",
    #                                   "--executor-memory", "14GB",
    #                                   "sparkpi.jar", "24"])
    output = subprocess.check_output(["./spark/bin/spark-submit",
                                      "--conf", "spark.sql.shuffle.partitions=" + str(sys.argv[1]),
                                      "--class", "com.wrapper.BoruvkaAlgorithm",
                                      "--master", masterAddress,
                                      "--total-executor-cores", str(sys.argv[2]),
                                      "--executor-cores", str(sys.argv[3]),
                                      "--executor-memory", str(sys.argv[4]),
                                      "boruvka_2.11-0.1-SNAPSHOT.jar"])
    print(output)

    # Create finish file to signal slaves to stop
    subprocess.check_output(["touch", jobid + "/finish"])

    # Wait until all slaves stoped
    cnt = 1
    while cnt != n:
        cnt += FileCheck(jobid + "/slave_stoped_" + str(cnt))
        time.sleep(2)

    # Stoping master
    subprocess.check_output(["./spark/sbin/stop-master.sh"])
    time.sleep(10)
    subprocess.check_output(["touch", jobid + "/master_stoped"])
else:
    # Wait until master started and read master's address
    while(not FileCheck(jobid + "/master_started")):
        time.sleep(2)
    file = open(jobid + "/master_started", "r")
    masterAddress = file.read()
    file.close()

    # Starting slave
    subprocess.check_output(["./spark/sbin/start-slave.sh", masterAddress])
    time.sleep(10)

    # Create file to signal master that slave has started
    print("Slave started!")
    subprocess.check_output(["touch", jobid + "/slave_started_" + rank])

    # Wait for finish file
    while(not FileCheck(jobid + "/finish")):
        time.sleep(10)

    # Stoping slave and signal master
    subprocess.check_output(["./spark/sbin/stop-slave.sh"])
    time.sleep(10)
    subprocess.check_output(["touch", jobid + "/slave_stoped_" + rank])


"""
Zahlenratespiel mit variablem Schwierigkeitsgrad
"""
import random

maxInt: int = 100
minInt: int = 1
maxTries: int = 0
difficulty: str = input("Schwierigkeitsgrade (einfach, normal, schwer): ")
hints: bool = False
baseGuess: int = 0
closestNumber: int = 0
global tipp
tipp: int = 0

userInput = 0
tries = 0

global toBeGuessed
toBeGuessed: int = random.randint(1, 100)

def hintshelper(param_hints):
    global closestNumber
    if param_hints:
        closestNumber = round(toBeGuessed / baseGuess) * baseGuess
        print("Die nächstgelegene Zahl ist {}".format(closestNumber))
        if tipp > closestNumber:
            print("Die eingegebene Zahl ist größer als die nächstgelegene Zahl")
        elif tipp < closestNumber:
            print("Die eingegebene Zahl ist kleiner als die nächstgelegene Zahl")
        if tipp == closestNumber :
            print("Die eingegebene Zahl ist gleich der nächstgelegenen Zahl")

if difficulty == "einfach":
    maxTries = 100
    baseGuess = 5
    hints = True
elif difficulty == "normal":
    maxTries = 25
    baseGuess = 10
    hints = True
elif difficulty == "schwer":
    maxTries = 10
else:
    print("undefinierter Schwierigkeitsgrad")
    exit()

while toBeGuessed != userInput:
    userInput = input("Bitte gebe die erratene Zahl ein: ")
    tipp = int(userInput)
    tries += 1
    # debugging only
    # print("Ratezahl (nur fürs Debugging):", toBeGuessed)
    hintshelper(hints)
    if difficulty == "schwer":
        if toBeGuessed < 50:
            print("Die gesuchte Zahl ist kleiner als 50")
        if toBeGuessed > 50:
            print("Die gesuchte Zahl ist größer als 50")
        if tipp < toBeGuessed:
            print("Die gesuchte Zahl ist größer als die eingegebene Zahl")
        if tipp > toBeGuessed:
            print("Die gesuchte Zahl ist kleiner als die eingegebene Zahl")
    if tipp == toBeGuessed:
        print("Gewonnen!")
        print("Du hast die Zahl {} erraten".format(toBeGuessed))
        print("Notwendige Versuche: {}".format(tries))
        print("Glückwunsch!")
        break
    elif tries > maxTries:
        print("Sorry, du hast die Zahl nicht erraten können")
        break
    else:
        print("Das schaffst du schon!")
print("Spiel beendet")


import argparse
import datetime
from enum import Enum
from pathlib import Path
from typing import Iterable, Dict
import pandas as pd
import requests

BEGINNING_OF_NBA_STAT_COLLECTION = 1996

HEADERS = {
    'Host': 'stats.nba.com',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0',
    'Accept': 'application/json, text/plain, */*',
    'Accept-Language': 'en-US,en;q=0.5',
    'Accept-Encoding': 'gzip, deflate, br',
    'x-nba-stats-origin': 'stats',
    'x-nba-stats-token': 'true',
    'Connection': 'keep-alive',
    'Referer': 'https://stats.nba.com/',
    'Pragma': 'no-cache',
    'Cache-Control': 'no-cache',
}

class TargetNBAObject(str, Enum):
    Players = 'players'
    Teams = 'teams'

TARGET_NBA_OBJECT_URL_MAP = {
    TargetNBAObject.Players: lambda year:
    f"https://stats.nba.com/stats/leaguedashplayerstats?College=&Conference=&Country=&DateFrom=&DateTo=&Division=&DraftPick=&DraftYear=&GameScope=&GameSegment=&Height=&LastNGames=0&LeagueID=00&Location=&MeasureType=Base&Month=0&OpponentTeamID=0&Outcome=&PORound=0&PaceAdjust=N&PerMode=Totals&Period=0&PlayerExperience=&PlayerPosition=&PlusMinus=N&Rank=N&Season={year}-{str(year + 1)[-2:]}&SeasonSegment=&SeasonType=Regular%20Season&ShotClockRange=&StarterBench=&TeamID=0&VsConference=&VsDivision=&Weight=",
    TargetNBAObject.Teams: lambda year:
    f"https://stats.nba.com/stats/leaguedashteamstats?Conference=&DateFrom=&DateTo=&Division=&GameScope=&GameSegment=&Height=&LastNGames=0&LeagueID=00&Location=&MeasureType=Base&Month=0&OpponentTeamID=0&Outcome=&PORound=0&PaceAdjust=N&PerMode=Totals&Period=0&PlayerExperience=&PlayerPosition=&PlusMinus=N&Rank=N&Season={year}-{str(year + 1)[-2:]}&SeasonSegment=&SeasonType=Regular%20Season&ShotClockRange=&StarterBench=&TeamID=0&TwoWay=0&VsConference=&VsDivision="
}

def valid_nba_year(year: str) -> int:
    year_as_int = int(year)
    assert year_as_int >= BEGINNING_OF_NBA_STAT_COLLECTION
    assert year_as_int <= datetime.date.today().year
    return year_as_int

def get_stats_from_nba_website(years: Iterable[int], target_nba_object: TargetNBAObject) -> Dict[int, pd.DataFrame]:
    years_stats_dfs_dict = {}
    for year in years:
        year_data = requests.get(TARGET_NBA_OBJECT_URL_MAP[target_nba_object](year),
                                 headers=HEADERS
                                 ).json()
        rows = year_data['resultSets'][0]['rowSet']
        headers = year_data['resultSets'][0]['headers']
        year_df = pd.DataFrame(rows, columns=headers)
        years_stats_dfs_dict[year] = year_df

    return years_stats_dfs_dict

def combine_years_stats(years_stats_dfs_dict: Dict[int, pd.DataFrame]) -> pd.DataFrame:
    seasons_stats_dict = {}
    for year, year_df in years_stats_dfs_dict.items():
        year_df['SEASON'] = year
        seasons_stats_dict[year] = year_df

    all_seasons_df = pd.concat(seasons_stats_dict.values())
    return all_seasons_df

def get_and_format_nba_stats(years: Iterable[int], target_nba_object: TargetNBAObject, output_file_path: Path):
    years_stats_dfs_dict: Dict[int, pd.DataFrame] = get_stats_from_nba_website(years, target_nba_object)
    combined_stats: pd.DataFrame = combine_years_stats(years_stats_dfs_dict)
    combined_stats.to_csv(output_file_path, index=False)

def main():
    parser = argparse.ArgumentParser(
        prog='obtain_nba_data',
        description='',
    )

    parser.add_argument('beginning_year', type=valid_nba_year)
    parser.add_argument('end_year', type=valid_nba_year)
    parser.add_argument('target_nba_object', choices=[target_object.value for target_object in TargetNBAObject])
    parser.add_argument('output_file_path', type=Path)

    args = parser.parse_args()

    years_range = range(args.beginning_year, args.end_year + 1)
    target_nba_object = args.target_nba_object
    output_file_path = args.output_file_path

    get_and_format_nba_stats(years_range,
                             target_nba_object,
                             output_file_path)
    print("Success!")

if __name__ == '__main__':
    main()


from tests.fixtures import setup
from tests.fixtures import webdriver

def test_process_instance_search_view(setup, webdriver):
    """
        List process-instances at process-instance search view.
    """

    # get process instances
    webdriver.path_get('/process-instance')

    # make actual
    actual = list()
    for tr in webdriver.find_elements('xpath', '//tr'):
        item = [e.text for e in tr.find_elements('xpath', './th | ./td')]
        actual.append(item)

    # define expected
    expected = [
        ['Key', 'Process version', 'BPMN process id', 'Start date', 'State', 'Process definition key',
         'Tenant id', ''],
        ['2251799813703087', '1', 'template-test', '2022-02-02\n12:12 +0000', 'ACTIVE',
         '2251799813703085', '<default>', 'Delete'],
        ['2251799813703081', '2', 'template-test-2', '-', 'ACTIVE',
         '2251799813703082', '<default>', 'Delete']
    ]

    # assert
    assert actual == expected

def test_view_single_process_instance_and_its_tasks_and_variables(setup, webdriver):
    key = '2251799813703087'

    # --- mock camunda api
    httpserver = setup[1]

    data = {
        'key': key,
        'processVersion': '1',
        'bpmnProcessId': 'template-test',
        'startDate': '2022-02-02\n12:12 +0000',
        'state': 'ACTIVE',
        'processDefinitionKey': '2251799813703085',
        'tenantId': '<default>',
    }

    httpserver.expect_request(
        f'/v1/process-instances/{key}').respond_with_json(data)

    httpserver.expect_request(
        '/v1/variables/search', method="POST", json={'processInstanceKey': key}).respond_with_json(
        {
            "items": [{
                'key': '1',
                'processInstanceKey': '2',
                'scopeKey': '3',
                'name': '4',
                'value': '5',
                'truncated': '6',
                'tenantId': '7',
            }]})
    # ---

    webdriver.path_get(f'/process-instance/{key}')

    actual = list()
    for table in webdriver.find_elements('xpath', "//table"):
        inner_actual = list()
        for tr in table.find_elements('xpath', './tbody/tr'):
            item = [e.text for e in tr.find_elements('xpath', './th | ./td')]
            inner_actual.append(item)
        actual.append(inner_actual)

    expected = [[['Key', '2251799813703087'], ['Process version', '1'], ['BPMN Process id', 'template-test'], ['Start date', '2022-02-02 12:12 +0000'], ['State', 'ACTIVE'], ['Process definition key', '2251799813703085'], ['Tenant id', '']], [['Id', 'Name', 'Creation data', 'Completion data', 'Assignee', 'Task state', 'Process definition key', 'Process instance key', 'Form', ''], ['6755399441084844', "Decide what's for dinner", '2022-02-02T12:12:12.000+0000',
                                                                                '2022-02-02T12:12:12.000+0000', 'Lisa', 'COMPLETED', '2251799813703085', '6755399441084839', 'Goto form', ''], ['6755399441084841', "Decide what's for dinner", '2022-02-02T12:12:12.000+0000', 'None', 'Unassigned\nAssign', 'CREATED', '2251799813703011', '6755399441084822', 'Goto form', 'Complete']], [['Key', 'Process instance key', 'Scope key', 'Name', 'Value', 'Truncated', 'Tenant id'], ['1', '2', '3', '4', '5', '6', '7']]]

    assert actual == expected

def test_process_instance_delete_at_search_view(setup, webdriver):
    """
        Make sure delete process-instances at process-instance search view actual sends
        the delete request to rest.
    """

    webdriver.path_get('/process-instance')

    # --- mock camunda api process-instances/search
    httpserver = setup[1]
    keys = ['key', 'processVersion', 'bpmnProcessId', 'startDate', 'state', 'processDefinitionKey',
            'tenantId', ]

    pi_id = '2251799813703081'  # the process id we want to delete

    items_values = [
        [
            '2251799813703087', '1', 'template-test', "2022-02-02T12:12:12.000+0000", 'ACTIVE',
            '2251799813703085',  '<default>',
        ],
        [
            pi_id, '2', 'template-test-2', None, 'ACTIVE',
            '2251799813703082',  '<default>',
        ]
    ]
    items = [{k: v for k, v in zip(keys, values)} for values in items_values]
    httpserver.expect_request(
        '/v1/process-instances/search').respond_with_json({'items': items})
    # ---

    # --- mock camunda api delete process-instance (for a specific process-instance)
    delete_request_handler = httpserver.expect_oneshot_request(
        f'/v1/process-instances/{pi_id}', method='DELETE')
    delete_request_handler.respond_with_json({})
    # ---

    # get process instances and delete second row button
    tr = find_table_row(webdriver, 'Key', pi_id)

    for delete_button in tr.find_elements('xpath', "./td/form/button[text()='Delete']"):
        delete_button.click()

    assert delete_request_handler not in httpserver.oneshot_handlers

def find_table_row(webdriver, column, value):
    for table in webdriver.find_elements('xpath', '//table'):
        for e, th in enumerate(table.find_elements('xpath', './tbody/tr[1]/th'), 1):
            if th.text == column:
                for tr in table.find_elements(
                        'xpath', f"./tbody/tr/td[{e}]/a[text()='{value}']/parent::td/parent::tr"):
                    return tr


import subprocess
import argparse
import sys
import os
from os import path
import pandas as pd
import tempfile
from atomicwrites import atomic_write
import logging

_ROOT = path.abspath(path.join(os.getcwd(), "."))

logger = logging.getLogger('GMSC-mapper')

def parse_args(args):
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
                                     description='GMSC-mapper')
    subparsers = parser.add_subparsers(title='GMSC-mapper subcommands',
                                       dest='cmd',
                                       metavar='')

    cmd_download_db = subparsers.add_parser('downloaddb',
                                          help='Download target database')

    cmd_download_db.add_argument('--dbdir',
                               required=False,
                               help='Path to the database files.',
                               dest='dbdir',
                               default = path.join(_ROOT, 'db'))
    cmd_download_db.add_argument('--all', action="store_true", dest='all',
                                help='Download all database')
    cmd_download_db.add_argument('-f', action="store_true", dest='force',
                                help='Force download even if the files exist')

    cmd_create_db = subparsers.add_parser('createdb',
                                          help='Create target database')
    cmd_create_db.add_argument('-i',
                               required=True,
                               help='Path to the GMSC FASTA file.',
                               dest='target_faa',
                               default = None)
    cmd_create_db.add_argument('-o', '--output',
                               required=False,
                               help='Path to database output directory.',
                               dest='output',
                               default = path.join(_ROOT, 'db'))
    cmd_create_db.add_argument('-m', '--mode',
                               required=True,
                               help='Alignment tool (Diamond / MMseqs2)',
                               dest='mode',
                               default = None)
    cmd_create_db.add_argument('--quiet',
                                action='store_true',
                                dest='quiet',
                                help='Disable alignment console output')

    parser.add_argument('-i', '--input',
                        required=False,
                        help='Path to the input genome contig sequence FASTA file.',
                        dest='genome_fasta',
                        default = None)

    parser.add_argument('--nt-genes', '--nt_genes',
                        required=False,
                        help='Path to the input nucleotide gene sequence FASTA file.',
                        dest='nt_input',
                        default=None)

    parser.add_argument('--aa-genes', '--aa_genes',
                        required=False,
                        help='Path to the input amino acid sequence FASTA file.',
                        dest='aa_input',
                        default=None)

    parser.add_argument('-o', '--output',
                        required=False,
                        help='Output directory (will be created if non-existent)',
                        dest='output',
                        default=path.join(_ROOT, 'output'))

    parser.add_argument('--tool', '--tool',
                        required=False,
                        choices=['diamond', 'mmseqs'],
                        help='Sequence alignment tool (Diamond / MMseqs).',
                        dest='tool',
                        default='diamond')

    parser.add_argument('-s', '--sensitivity',
                        required=False,
                        help='Sensitivity.',
                        dest='sensitivity')

    parser.add_argument('--id', '--id',
                        required=False,
                        help='Minimum identity to report an alignment (range 0.0-1.0).',
                        dest='identity',
                        default=0.0)

    parser.add_argument('--cov', '--cov',
                        required=False,
                        help='Minimum coverage to report an alignment (range 0.0-1.0).',
                        dest='coverage',
                        default=0.9)

    parser.add_argument('-e', '--evalue',
                        required=False,
                        help='Maximum e-value to report alignments.',
                        dest='evalue',
                        default=0.00001)

    parser.add_argument('-t', '--threads',
                        required=False,
                        help='Number of CPU threads.',
                        dest='threads',
                        default=1)

    parser.add_argument('--filter','--filter',action='store_true', help='Use this to filter <100 aa or <303 nt input sequences.')

    parser.add_argument('--no-habitat','--no-habitat',action='store_true', dest='nohabitat', help='Use this if no need to annotate habitat')

    parser.add_argument('--no-taxonomy', '--no-taxonomy',action='store_true', dest='notaxonomy', help='Use this if no need to annotate taxonomy')

    parser.add_argument('--no-quality', '--no-quality',action='store_true', dest='noquality', help='Use this if no need to annotate quality')

    parser.add_argument('--no-domain', '--no-domain',action='store_true', dest='nodomain', help='Use this if no need to annotate quality')

    parser.add_argument('--quiet','--quiet',action='store_true', help='Disable alignment console output')

    parser.add_argument('--dbdir', '--dbdir',
                        required=False,
                        help='Path to the GMSC database directory.',
                        dest='dbdir',
                        default = path.join(_ROOT, 'db'))

    return parser.parse_args(args[1:])

def check_install():
    from shutil import which
    import sys

    has_mmseqs = False
    has_diamond = False
    dependencies = ['diamond', 'mmseqs']
    logger.debug("Looking for dependencies...")

    for dep in dependencies:
        p = which(dep)
        if p:
            if dep == 'diamond':
                has_diamond = True
            if dep == 'mmseqs':
                has_mmseqs = True

    if not has_diamond and not has_mmseqs:
        logger.warning('GMSC-mapper Warning: Neither diamond nor mmseqs appear to be available! It will download diamond and mmseqs.\n')
        subprocess.check_call(['wget','https://mmseqs.com/latest/mmseqs-linux-avx2.tar.gz',
                                '-P','./bin'])
        subprocess.check_call(['tar','xvfz',
                                './bin/mmseqs-linux-avx2.tar.gz',
                                '-C','./bin'])
        subprocess.check_call(['wget','http://github.com/bbuchfink/diamond/releases/download/v2.1.8/diamond-linux64.tar.gz',
                                '-P','./bin'])
        subprocess.check_call(['tar','xvfz',
                                './bin/diamond-linux64.tar.gz',
                                '-C','./bin'])
    else:
        logger.info('Dependencies installation is OK\n')
    return has_diamond,has_mmseqs

def validate_args(args,has_diamond,has_mmseqs):
    def expect_file(f):
        if not os.path.exists(f):
            sys.stderr.write(f"GMSC-mapper Error: Expected file '{f}' does not exist\n")
            sys.exit(1)

    def expect_database(f):
        if not os.path.exists(f):
            sys.stderr.write(f"GMSC-mapper Error: Expected file '{f}' does not exist. Please use --dbdir to assign your database directory.\n")
            sys.exit(1)

    if not args.genome_fasta and not args.aa_input and not args.nt_input:
        pass
    elif args.genome_fasta and not args.aa_input and not args.nt_input:
        expect_file(args.genome_fasta)
    elif args.aa_input and not args.genome_fasta and not args.nt_input:
        expect_file(args.aa_input)
    elif args.nt_input and not args.genome_fasta and not args.aa_input:
        expect_file(args.nt_input)
    else:
        sys.stderr.write("GMSC-mapper Error: --input or --aa-genes or --nt_genes shouldn't be assigned at the same time\n")
        sys.exit(1)

    if args.tool == "diamond" and not has_diamond:
        logger.warning("GMSC-mapper Warning:mmseqs is not available.It will download diamond.\n")
        subprocess.check_call(['wget','http://github.com/bbuchfink/diamond/releases/download/v2.1.8/diamond-linux64.tar.gz',
                                '-P','./bin'])
        subprocess.check_call(['tar','xvfz',
                                './bin/diamond-linux64.tar.gz',
                                '-C','./bin'])
    if args.tool == "mmseqs" and not has_mmseqs:
        logger.warning("GMSC-mapper Warning:mmseqs is not available.It will download mmseqs.\n")
        subprocess.check_call(['wget','https://mmseqs.com/latest/mmseqs-linux-avx2.tar.gz',
                                '-P','./bin'])
        subprocess.check_call(['tar','xvfz',
                                './bin/mmseqs-linux-avx2.tar.gz',
                                '-C','./bin'])

    if args.tool == "diamond":
        expect_database(path.join(args.dbdir, "diamond_targetdb.dmnd"))
    if args.tool == "mmseqs":
        expect_database(path.join(args.dbdir, "mmseqs_targetdb"))

    if not args.nohabitat:
        expect_database(path.join(args.dbdir, "GMSC10.90AA.habitat.index.tsv"))
        expect_database(path.join(args.dbdir, "GMSC10.90AA.habitat.npy"))

    if not args.notaxonomy:
        expect_database(path.join(args.dbdir, "GMSC10.90AA.taxonomy.index.tsv"))
        expect_database(path.join(args.dbdir, "GMSC10.90AA.taxonomy.npy"))

    if not args.noquality:
        expect_database(path.join(args.dbdir, "GMSC10.90AA.high_quality.tsv.xz"))

    if not args.nodomain:
        expect_database(path.join(args.dbdir, "GMSC10.90AA.cdd.tsv.xz"))

def download_db(args):
    from gmsc_mapper.utils import ask

    if args.force or not os.path.exists(os.path.join(args.dbdir,'GMSC10.90AA.faa.gz')):
        if args.all or ask("Download 90AA fasta file (~11G)?") == 'y':
            logger.info('Start downloading 90AA fasta file...')
            subprocess.check_call(['wget','https://gmsc-api.big-data-biology.org/files/GMSC10.90AA.faa.gz',
                                '-P',args.dbdir])
            logger.info('90AA fasta file has been downloaded successfully.')
        else:
            print('Skip downloading 90AA fasta file.')
    else:
        print('90AA fasta file already exists. Skip downloading 90AA fasta file. Use -f to force download')

    if args.force or not os.path.exists(os.path.join(args.dbdir,'GMSC10.90AA.habitat.npy')) or not os.path.exists(os.path.join(args.dbdir,'GMSC10.90AA.habitat.index.tsv')):
        if args.all or ask("Download habitat index file (~2.3G)?") == 'y':
            logger.info('Start downloading habitat index file...')
            subprocess.check_call(['wget','https://gmsc-api.big-data-biology.org/files/GMSC10.90AA.habitat.npy',
                                '-P',args.dbdir])
            subprocess.check_call(['wget','https://gmsc-api.big-data-biology.org/files/GMSC10.90AA.habitat.index.tsv',
                                '-P',args.dbdir])
            logger.info('Habitat index file has been downloaded successfully.')
        else:
            print('Skip downloading habitat index file.')
    else:
        print('Habitat index file already exists. Skip downloading habitat index file. Use -f to force download')

    if args.force or not os.path.exists(os.path.join(args.dbdir,'GMSC10.90AA.taxonomy.npy')) or not os.path.exists(os.path.join(args.dbdir,'GMSC10.90AA.taxonomy.index.tsv')):
        if args.all or ask("Download taxonomy index file (~2.3G)?") == 'y':
            logger.info('Start downloading taxonomy index file...')
            subprocess.check_call(['wget','https://gmsc-api.big-data-biology.org/files/GMSC10.90AA.taxonomy.npy',
                                '-P',args.dbdir])
            subprocess.check_call(['wget','https://gmsc-api.big-data-biology.org/files/GMSC10.90AA.taxonomy.index.tsv',
                                '-P',args.dbdir])
            logger.info('Taxonomy index file has been downloaded successfully.')
        else:
            print('Skip downloading taxonomy index file.')
    else:
        print('Taxonomy index file already exists. Skip downloading taxonomy index file. Use -f to force download')

    if args.force or not os.path.exists(os.path.join(args.dbdir,'GMSC10.90AA.high_quality.tsv.xz')):
        if args.all or ask("Download quality index file (2.6M)?") == 'y':
            logger.info('Start downloading quality index file...')
            subprocess.check_call(['wget','https://gmsc-api.big-data-biology.org/files/GMSC10.90AA.high_quality.tsv.xz',
                                '-P',args.dbdir])
            logger.info('Quality index file has been downloaded successfully.')
        else:
            print('Skip downloading quality index file.')
    else:
        print('Quality index file already exists. Skip downloading quality index file. Use -f to force download')

    if args.force or not os.path.exists(os.path.join(args.dbdir,'GMSC10.90AA.cdd.tsv.xz')):
        if args.all or ask("Download conserved domain index file (88M)?") == 'y':
            logger.info('Start downloading conserved domain index file...')
            subprocess.check_call(['wget','https://gmsc-api.big-data-biology.org/files/GMSC10.90AA.cdd.tsv.xz',
                                '-P',args.dbdir])
            logger.info('Conserved domain index file has been downloaded successfully.')
        else:
            print('Skip downloading conserved domain index file.')
    else:
        print('Conserved domain index file already exists. Skip downloading conserved domain index file. Use -f to force download')

def create_db(args):
    from shutil import which

    if not os.path.exists(args.output):
        os.makedirs(args.output)

    diamond_out_db = path.join(args.output,"diamond_targetdb")
    mmseqs_out_db = path.join(args.output,"mmseqs_targetdb")

    if which('diamond'):
        diamond = 'diamond'
    else:
        diamond = './bin/diamond'

    if which('mmseqs'):
        mmseqs = 'mmseqs'
    else:
        mmseqs = './bin/mmseqs/bin/mmseqs'

    if args.quiet:
        diamond_cmd = [diamond,'makedb',
                        '--in',args.target_faa,
                        '-d',diamond_out_db,
                        '--quiet']
        mmseqs_cmd = [mmseqs,'createdb',
                        args.target_faa,
                        mmseqs_out_db,
                        '-v','0']
    else:
        diamond_cmd = [diamond,'makedb',
                    '--in',args.target_faa,
                    '-d',diamond_out_db]
        mmseqs_cmd = [mmseqs,'createdb',
                    args.target_faa,
                    mmseqs_out_db]

    if args.mode == "diamond":
        logger.info('Start creating Diamond database...')
        subprocess.check_call(diamond_cmd)
        logger.info('Diamond database has been created successfully.')

    if args.mode == "mmseqs":
        logger.debug('Start creating MMseqs database...')
        subprocess.check_call(mmseqs_cmd)
        logger.info('MMseqs database has been created successfully.')

def predict(args,tmpdir):
    from gmsc_mapper.predict import predict_genes,filter_smorfs

    logger.debug('Starting smORF prediction...')

    predicted_smorf = path.join(tmpdir,"predicted.smorf.faa")
    filtered_smorf = path.join(args.output,"predicted.filterd.smorf.faa")

    predict_genes(args.genome_fasta,predicted_smorf)
    if not path.getsize(predicted_smorf):
        logger.info("GMSC-mapper Info:No smORFs have been predicted.Please check your input file.\n")
        return ("",False)
    else:
        filter_smorfs(predicted_smorf, filtered_smorf)
        if not path.getsize(filtered_smorf):
            logger.info("GMSC-mapper Info:No smORFs remained after filtering by length(<100aa).\n")
            return ("",False)
        else:
            logger.info('smORF prediction complete')
            return (filtered_smorf,True)

def translate_gene(args,tmpdir):
    from gmsc_mapper.translate import translate_gene
    logger.debug('Starting gene translation...')
    translated_file = translate_gene(args.nt_input,tmpdir)
    logger.info('Gene translation complete')
    return translated_file

def check_length(queryfile):
    from gmsc_mapper.fasta import fasta_iter
    logger.debug('Start length check...')
    if all(len(seq) < 303
                for _, seq in fasta_iter(queryfile)):
        logger.warning('GMSC-mapper Warning: Input sequences are all greater than 300bps.\n'
                       'Please check if your input consists of contigs, which should use -i not --nt-genes or --aa-genes as input. '
                       'However, we will regard your input sequences as nucleotide genes and continue to process.\n')

    logger.info('Length checking has done.\n')

def filter_length(queryfile,tmpdir,N):
    from gmsc_mapper.filter_length import filter_length
    logger.debug('Starting length filter...')
    filtered_file = filter_length(queryfile,tmpdir,N)
    logger.info('Length filter complete')
    return filtered_file

def mapdb_diamond(args,queryfile):
    from shutil import which

    logger.debug('Starting smORF mapping...')

    resultfile = path.join(args.output,"alignment.out.smorfs.tsv")
    outfmt = '6,qseqid,sseqid,full_qseq,full_sseq,qlen,slen,length,qstart,qend,sstart,send,bitscore,pident,evalue,qcovhsp,scovhsp'

    if which('diamond'):
        diamond = 'diamond'
    else:
        diamond = './bin/diamond'

    database = path.join(args.dbdir, "diamond_targetdb.dmnd")

    if args.sensitivity != '--default':
        diamond_cmd = [diamond,'blastp',
                    '-q',queryfile,
                    '-d',database,
                    '-o',resultfile,
                    args.sensitivity,
                    '-e',str(args.evalue),
                    '--id',str(float(args.identity)*100),
                    '--query-cover',str(float(args.coverage)*100),
                    '--subject-cover',str(float(args.coverage)*100),
                    '-p',str(args.threads),
                    '--outfmt'] + outfmt.split(',')
    else:
        diamond_cmd = [diamond,'blastp',
                    '-q',queryfile,
                    '-d',database,
                    '-o',resultfile,
                    '-e',str(args.evalue),
                    '--id',str(float(args.identity)*100),                                                                                '--query-cover',str(float(args.coverage)*100),
                    '--subject-cover',str(float(args.coverage)*100),                                                                        '-p',str(args.threads),                                                                                '--outfmt'] + outfmt.split(',')
    if args.quiet:
        diamond_cmd.append('--quiet')

    subprocess.check_call(diamond_cmd)

    logger.info('smORF mapping complete')
    return resultfile

def mapdb_mmseqs(args, queryfile, tmpdir):
    from shutil import which

    logger.info('Start smORF mapping...')

    querydb = path.join(tmpdir,"query.db")
    resultdb = path.join(tmpdir,"result.db")
    tmp = path.join(tmpdir,"tmp","")
    resultfile = path.join(args.output,"alignment.out.smorfs.tsv")
    outfmt = 'query,target,qseq,tseq,qlen,tlen,alnlen,qstart,qend,tstart,tend,bits,pident,evalue,qcov,tcov'

    if which('mmseqs'):
        mmseqs = 'mmseqs'
    else:
        mmseqs = './bin/mmseqs/bin/mmseqs'

    database = path.join(args.dbdir, "mmseqs_targetdb")

    mmseqs_cmd_db = [mmseqs, 'createdb', queryfile, querydb]
    mmseqs_cmd_search = [mmseqs,'search',
                        querydb,
                        database,
                        resultdb,
                        tmp,
                        '-s',str(args.sensitivity),
                        '-e',str(args.evalue),
                        '--min-seq-id',str(args.identity),
                        '-c',str(args.coverage),
                        '--threads',str(args.threads)]
    mmseqs_cmd_out = [mmseqs,'convertalis',
                    querydb,
                    database,
                    resultdb,
                    resultfile,
                    '--format-output', outfmt]
    for mmseqs_cmd in [mmseqs_cmd_db,mmseqs_cmd_search,mmseqs_cmd_out]:
        if args.quiet:
            mmseqs_cmd.extend(['-v', '0'])
        subprocess.check_call(mmseqs_cmd)

    logger.info('smORF mapping complete')
    return resultfile

def generate_fasta(output,queryfile,resultfile):
    import pandas as pd
    from gmsc_mapper.fasta import fasta_iter

    try:
        result = pd.read_csv(resultfile, sep='\t',header=None)
    except:
        print('GMSC-mapper info: There is no alignment results between your input sequences and GMSC.\n')
        logger.info('GMSC-mapper info: There is no alignment results between your input sequences and GMSC.\n')
        return ("",False)
    else:
        logger.debug('Start smORF fasta file generation...')
        fastafile = path.join(output,"mapped.smorfs.faa")

        smorf_id = set(result.iloc[:, 0].tolist())

        with open(fastafile,"wt") as f:
            for ID,seq in fasta_iter(queryfile):
                if ID in smorf_id:
                    f.write(f'>{ID}\n{seq}\n')
        logger.debug('smORF fasta file generation complete')
        return (fastafile,True)

def habitat(args,resultfile):
    from gmsc_mapper.map_habitat import smorf_habitat
    logger.debug('Starting habitat annotation...')

    habitatindex = path.join(args.dbdir, "GMSC10.90AA.habitat.index.tsv")
    habitat = path.join(args.dbdir, "GMSC10.90AA.habitat.npy")
    r_habitat = smorf_habitat(habitatindex,args.output,habitat,resultfile)

    logger.info('habitat annotation has done.')
    return r_habitat

def taxonomy(args,resultfile,tmpdirname):
    from gmsc_mapper.map_taxonomy import deep_lca,taxa_summary
    logger.debug('Start taxonomy annotation...')

    taxonomyindex = path.join(args.dbdir, "GMSC10.90AA.taxonomy.index.tsv")
    taxonomy = path.join(args.dbdir, "GMSC10.90AA.taxonomy.npy")
    deep_lca(taxonomyindex,taxonomy,args.output,resultfile,tmpdirname)
    r_summary = taxa_summary(args.output)

    logger.info('Taxonomy annotation complete.')
    return r_summary

def quality(args,resultfile):
    from gmsc_mapper.map_quality import smorf_quality
    logger.debug('Start quality annotation...')

    quality = path.join(args.dbdir, "GMSC10.90AA.high_quality.tsv.xz")
    number,percentage = smorf_quality(args.output,quality,resultfile)

    logger.info('Quality annotation completed.')
    return number,percentage

def domain(args,resultfile):
    from gmsc_mapper.map_domain import smorf_domain
    logger.debug('Start domain annotation...')

    domain = path.join(args.dbdir, "GMSC10.90AA.cdd.tsv.xz")
    number = smorf_domain(domain,args.output,resultfile)
    logger.info('Domain annotation completed.')
    return number

def predicted_smorf_count(file_name):
    return sum(1 for _ in open(file_name, 'rt'))

def main(args=None):
    if args is None:
        args = sys.argv
    args = parse_args(args)
    if not args.cmd and not args.genome_fasta and not args.aa_input and not args.nt_input:
        sys.stderr.write("GMSC-mapper Error: Please see gmsc-mapper -h. Choose a subcommand or input file.\n")
        sys.exit(1)
    has_diamond,has_mmseqs = check_install()

    if args.cmd == 'createdb':
        create_db(args)

    if args.cmd == 'downloaddb':
        download_db(args)

    if not args.cmd:
        validate_args(args,has_diamond,has_mmseqs)

        if args.tool == 'diamond':
            args.sensitivity = {
                None: '--sensitive',
                '1': '--fast',
                '2': '--default',
                '3': '--mid-sensitive',
                '4': '--sensitive',
                '5': '--more-sensitive',
                '6': '--very-sensitive',
                '7': '--ultra-sensitive',
            }.get(args.sensitivity, args.sensitivity)

        if args.tool == 'mmseqs':
            if args.sensitivity is None:
                args.sensitivity = 5.7

        os.makedirs(args.output, exist_ok=True)

        with tempfile.TemporaryDirectory() as tmpdir:
            try:
                summary = []
                summary.append(f'# Total number')
                if args.genome_fasta:
                    (queryfile,ifpredict) = predict(args,tmpdir)
                    if ifpredict:
                        smorf_number = int(predicted_smorf_count(queryfile)/2)
                        summary.append(f'{smorf_number} smORFs are predicted in total.')
                    else:
                        summary.append(f'No smORFs are predicted.')
                if args.nt_input:
                    if args.filter:
                        args.nt_input = filter_length(args.nt_input,tmpdir,303)
                    else:
                        check_length(args.nt_input)
                    queryfile = translate_gene(args,tmpdir)
                if args.aa_input:
                    if args.filter:
                        args.aa_input = filter_length(args.aa_input,tmpdir,100)
                    queryfile = args.aa_input

                if (args.genome_fasta and ifpredict) or args.nt_input or args.aa_input:
                    if args.tool == 'diamond':
                        resultfile = mapdb_diamond(args,queryfile)
                    if args.tool == 'mmseqs':
                        resultfile = mapdb_mmseqs(args,queryfile,tmpdir)

                    (fastafile,ifsuccess) = generate_fasta(args.output,queryfile,resultfile)
                    if ifsuccess:
                        smorf_number = int(predicted_smorf_count(fastafile)/2)
                        summary.append(f'{smorf_number} smORFs are aligned against GMSC in total.\n')

                        if not args.noquality:
                            summary.append(f'# Quality')
                            number,percentage = quality(args,resultfile)
                            summary.append(f'{number} ({percentage:.2%}) aligned smORFs are high quality.\n')

                        if not args.nohabitat:
                            summary.append(f'# Habitat')
                            single_number,single_percentage,multi_number,multi_percentage = habitat(args,resultfile)
                            summary.append(f'{single_number} ({single_percentage:.2%}) aligned smORFs are single-habitat.')
                            summary.append(f'{multi_number} ({multi_percentage:.2%}) aligned smORFs are multi-habitat.\n')

                        if not args.notaxonomy:
                            summary.append(f'# Taxonomy')
                            annotated_number,rank_number,rank_percentage = taxonomy(args,resultfile,tmpdir)
                            summary.append(f'{annotated_number} ({1 - rank_percentage["no rank"]:.2%}) aligned smORFs have taxonomy annotation.')
                            for rank in ['kingdom','phylum','class','order','family','genus','species']:
                                summary.append(f'{rank_number[rank]} ({rank_percentage[rank]:.2%}) aligned smORFs are annotated at level of {rank}.')

                        if not args.nodomain:
                            summary.append(f'\n# Conserved domain')
                            number = domain(args,resultfile)
                            summary.append(f'{number} aligned smORFs are annotated with CDD database.\n')
                    else:
                        summary.append(f'None of sequences are aligned against GMSC.\n')

                with atomic_write(f'{args.output}/summary.txt', overwrite=True) as ofile:
                    for s in summary:
                        print(s)
                        ofile.write(f'{s}\n')

            except Exception as e:
                sys.stderr.write('GMGC-mapper Error: ')
                sys.stderr.write(str(e))
                sys.stderr.write('\n')
                sys.exit(1)

if __name__ == '__main__':
    main(sys.argv)


import rrdtool

def createBase(comunidad):
    ret = rrdtool.create(comunidad + ".rrd",
                         "--start",'N',
                         "--step",'40',
                         "DS:inoctets:COUNTER:600:U:U",
                         "DS:outoctets:COUNTER:600:U:U",
                         "RRA:AVERAGE:0.2:1:10",
                         "RRA:AVERAGE:0.2:1:10")

    if ret:
        print (rrdtool.error())


from django.shortcuts import render, redirect, get_object_or_404
from banks.models import Bank
from django.contrib import messages
from .models import Report
from django.urls import reverse
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.http import JsonResponse
import json
from django.db.models import Q
from company.models import Company
from django.core.files.storage import FileSystemStorage
from reports.models import Comment
import datetime

from django.http import JsonResponse, HttpResponse

from django.template.loader import render_to_string
from weasyprint import HTML
import tempfile
from django.contrib.sites.shortcuts import get_current_site

@login_required(login_url='/accounts/login')
def search_reports(request):
    data = request.body.decode('utf-8')
    search_val = json.loads(data).get('data')

    if request.user.role in ['BOSS', 'ACCOUNTANT']:
        reports = Report.objects.filter(upi__icontains=search_val) | Report.objects.filter(
            location__startswith=search_val) | Report.objects.filter(
            owner__icontains=search_val) | Report.objects.filter(
            purpose__icontains=search_val) | Report.objects.filter(
            client__icontains=search_val) | Report.objects.filter(
            delivery_date__icontains=search_val) | Report.objects.filter(
            contact__icontains=search_val) | Report.objects.filter(
            created_by__email__icontains=search_val)
        data = list(reports.values())
        return JsonResponse(data, safe=False)
    if(request.user.role == 'TECHNICIAN'):
        reports = Report.objects.filter(upi__icontains=search_val, created_by=request.user) | Report.objects.filter(
            location__startswith=search_val, created_by=request.user) | Report.objects.filter(
            owner__icontains=search_val, created_by=request.user) | Report.objects.filter(
            purpose__icontains=search_val, created_by=request.user) | Report.objects.filter(
            client__icontains=search_val, created_by=request.user) | Report.objects.filter(
            delivery_date__icontains=search_val, created_by=request.user) | Report.objects.filter(
            contact__icontains=search_val, created_by=request.user) | Report.objects.filter(
            created_by__email__icontains=search_val, created_by=request.user)
        data = list(reports.values())
        return JsonResponse(data, safe=False)

@login_required(login_url='/accounts/login')
def index(request):
    if request.user.role == 'BOSS' or request.user.role == 'ACCOUNTANT':
        reports = Report.objects.all().order_by('approved')
        paginator = Paginator(reports, 7)  # Show 7 items per page.
        page_number = request.GET.get('page')
        page_obj = paginator.get_page(page_number)
        context = {
            'reports': reports,
            'page_obj': page_obj
        }
        return render(request, 'reports/all_reports.html', context)
    reports = Report.objects.filter(created_by=request.user)
    paginator = Paginator(reports, 7)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    comments = Comment.objects.all()
    context = {
        'my_reports': reports,
        'page_obj': page_obj,
        'comments': comments
    }

    return render(request, 'reports/index.html', context)

@login_required(login_url='/accounts/login')
def add_report(request):
    banks = Bank.objects.all()

    context = {
        'banks': banks}
    if request.method == 'GET':
        return render(request, 'reports/add_report.html', context)
    if request.method == 'POST':
        banks = Bank.objects.all()
        has_error = False
        context = {
            'values': request.POST,
            'banks': banks
        }
        owner = request.POST['owner']
        contact = request.POST['contact']
        location = request.POST['location']
        upi = request.POST['upi']
        inspection_date = request.POST['inspection_date']
        delivery_date = request.POST['delivery_date']
        amount = request.POST['amount'] if request.POST['amount'] else 0
        purpose = request.POST['purpose']
        bank = request.POST['bank']
        report_file = request.FILES and request.FILES['report_file']
        if not report_file:
            messages.error(request,  'Please choose a report file document')
            has_error = True
        if not owner:
            messages.error(request,  'Asset Owner fullname is required')
            has_error = True
        if not purpose:
            messages.error(request,  'Purpose is required')
            has_error = True
        if contact and len(contact) != 10:
            has_error = True
            messages.error(
                request,  'The Contact phone should be  10 characters')

        if not upi:
            has_error = True
            messages.error(request,  'UPI is required')
        if not inspection_date:
            has_error = True
            messages.error(request,  'Inspection Date is required')
        if not delivery_date:
            has_error = True
            messages.error(request,  'Delivery Date  is required')
        if not bank:
            has_error = True
            messages.error(request,  'Client Bank is required')

        if not has_error:
            num_results = Report.objects.filter(
                upi=request.POST['upi'].upper()).count()
            if num_results > 0:
                has_error = True
                messages.error(request,  'Report with this UPI Exists')
                return render(request, 'reports/add_report.html', context)
        if not has_error:
            rep = Report.objects.create(created_by=request.user,
                                        owner=owner,
                                        location=location,
                                        amount=amount,
                                        client=bank,
                                        purpose=purpose,
                                        report_file=report_file,
                                        contact=contact,
                                        upi=upi.upper(),
                                        inspection_date=inspection_date,
                                        delivery_date=delivery_date,
                                        report_payed_for=True,
                                        reason_for_not_paying='N/A')

            if rep:
                messages.success(request, 'Report Submitted Successfully')
                return redirect('reports')

        return render(request, 'reports/add_report.html', context)

@login_required(login_url='/accounts/login')
def report(request, id):
    report = Report.objects.get(id=id)
    comments = Comment.objects.filter(report_id=id)
    return render(request, 'reports/report.html', {'report': report, 'comments': comments})

@login_required(login_url='/accounts/login')
def report_reciept(request, id):
    report = Report.objects.get(id=id)
    company = Company.objects.first()
    return render(request, 'reports/reciept.html', {'report': report, 'company': company})

@login_required(login_url='/accounts/login')
def report_edit(request, id):
    if request.method == 'GET':
        banks = Bank.objects.all()
        report = Report.objects.get(id=id)
        context = {
            'values': report,
            'banks': banks}
        return render(request, 'reports/edit-report.html', context)
    if request.method == 'POST':
        report = Report.objects.get(id=id)
        has_error = False
        context = {
            'values': request.POST,
            'banks': Bank.objects.all()
        }
        owner = request.POST['owner']
        contact = request.POST['contact']
        location = request.POST['location']
        upi = request.POST['upi']
        inspection_date = request.POST['inspection_date']
        delivery_date = request.POST['delivery_date']
        amount = request.POST['amount']
        purpose = request.POST['purpose']
        bank = request.POST['bank']
        report_file = request.FILES and request.FILES['report_file']
        if not owner:
            messages.error(request,  'Owner fullname is required')
            has_error = True
        if not purpose:
            messages.error(request,  'Purpose is required')
            has_error = True
        if contact and len(contact) is not 10:
            has_error = True
            messages.error(
                request,  'The Contact phone should be  10 characters')

        if not upi:
            has_error = True
            messages.error(request,  'UPI is required')
        if not inspection_date:
            has_error = True
            messages.error(request,  'Inspection Date is required')
        if not delivery_date:
            has_error = True
            messages.error(request,  'Delivery Date  is required')
        if not amount:
            amount = 0
        if not bank:
            has_error = True
            messages.error(request,  'Client Bank is required')
        if not has_error:
            report = Report.objects.get(id=id)

            if report_file:
                report.report_file = report_file
            report.location = location
            report. amount = amount
            report.client = bank
            report.purpose = purpose
            report.owner = owner
            report.contact = contact
            report.upi = upi
            report.inspection_date = inspection_date
            report.delivery_date = delivery_date
            report.report_payed_for = True
            report.reason_for_not_paying = 'N/A'
            report.save()
            messages.success(request, 'Report Updated Successfully')
            return redirect('report', report.pk)
        else:
            return redirect('report-edit', report.pk)

        return render(request, 'reports/edit-report.html', {'values': report})

@login_required(login_url='/accounts/login')
def report_approve(request, id):
    report = Report.objects.get(id=id)
    if report.approved == True:
        report.approved = False
        report.approved_at = datetime.datetime.now()
        report.approval_date = None
    else:
        report.approved = True
        report.approved_at = datetime.datetime.now()
        report.approval_date = datetime.date.today()
    report.save()
    messages.success(request, 'Report status updated Successfully')
    return redirect('report', id)

@login_required(login_url='/accounts/login')
def report_verified(request, id):
    report = Report.objects.get(id=id)
    if report.paid == True:
        report.paid = False
        report.verified_at = None
    else:
        report.paid = True
        report.verified_at = datetime.datetime.now()

    report.save()
    messages.success(
        request, 'Payment verification status updated Successfully')
    return redirect('reports')

@login_required(login_url='/accounts/login')
def add_report_comments(request, id):
    if request.method == 'GET':
        return render(request, 'reports/add_comment.html', {'id': id})
    if request.method == 'POST':
        has_error = False
        context = {
            'values': request.POST,
        }
        comment = request.POST['message']

    if not comment:
        has_error = True
        messages.error(request,  'Please add your comment')
    if has_error:
        return render(request, 'reports/add_comment.html', {'id': id})
    rep = Comment.objects.create(created_by=request.user,
                                 message=comment,
                                 report=Report.objects.get(id=id))

    if rep:
        messages.success(request, 'Comment added  Successfully')
        return redirect('report', id)

@login_required(login_url='/accounts/login')
def report_stats(request):
    all_reports = Report.objects.filter(approved=True)
    today = datetime.datetime.today().date()
    today2 = datetime.date.today()
    week_ago = today - datetime.timedelta(days=7)
    month_ago = today - datetime.timedelta(days=30)
    year_ago = today - datetime.timedelta(days=366)
    todays_amount = 0
    todays_count = 0
    this_week_amount = 0
    this_week_count = 0
    this_month_amount = 0
    this_month_count = 0
    this_year_amount = 0
    this_year_count = 0

    todays_reports = Report.objects.filter(
        approved=True,  approval_date=datetime.date.today())

    for td in todays_reports:
        todays_amount += td.amount
        todays_count += 1

    start_date = datetime.datetime.today(
    ) - datetime.timedelta(days=datetime.datetime.today().weekday() % 7)

    this_week_reports = Report.objects.filter(
        approved=True, approval_date__gte=start_date.date())

    for td in this_week_reports:
        this_week_amount += td.amount
        this_week_count += 1

    months_first_day = datetime.datetime.today().date().replace(day=1)
    this_month_reports = Report.objects.filter(
        approved=True, approval_date__gte=months_first_day)

    for td in this_month_reports:
        this_month_amount += td.amount
        this_month_count += 1

    start_date = starting_day_of_current_year = datetime.datetime.now(
    ).date().replace(month=1, day=1)
    this_year_reports = Report.objects.filter(
        approved=True, approval_date__gte=start_date)

    for one in this_year_reports:
        this_year_amount += one.amount
        this_year_count += 1

    context = {
        'today': {
            'amount': todays_amount,
            "count": todays_count,

        },
        'this_week': {
            'amount': this_week_amount,
            "count": this_week_count,

        },
        'this_month': {
            'amount': this_month_amount,
            "count": this_month_count,

        },
        'this_year': {
            'amount': this_year_amount,
            "count": this_year_count,

        },

        'today_reports': 'today_reports',
        'this_week_reports': 'this_week_reports',
        'this_year_reports': 'this_year_reports',
        'this_months_reports': 'this_months_reports'

    }

    return render(request, 'reports/stats.html', context)

def report_stats_rest(request):
    all_reports = Report.objects.filter(approved=True)
    today_month, today_year = datetime.datetime.today(
    ).month, datetime.datetime.today().year
    months_data = {}
    week_days_data = {}

    def get_amount_for_month(month):
        month_amount = 0
        for one in all_reports:
            month_, year = one.approved_at.month, one.approved_at.year
            if month == month_ and year == today_year:
                month_amount += one.amount
        return month_amount

    for x in range(1, 13):
        today_month, today_year = x, datetime.datetime.today().year
        for one in all_reports:
            months_data[x] = get_amount_for_month(x)

    def get_amount_for_day(x, today_day, month, today_year):
        day_amount = 0
        for one in all_reports:
            day_, date_,  month_, year_ = one.approved_at.isoweekday(
            ), one.approved_at.date().day, one.approved_at.month, one.approved_at.year
            if x == day_ and month == month_ and year_ == today_year:
                if not day_ > today_day:
                    day_amount += one.amount
        return day_amount

    for x in range(1, 8):
        today_day, today_month, today_year = datetime.datetime.today(
        ).isoweekday(), datetime.datetime.today(
        ).month, datetime.datetime.today().year
        for one in all_reports:
            week_days_data[x] = get_amount_for_day(
                x, today_day, today_month, today_year)

    data = {"months": months_data, "days": week_days_data}
    return JsonResponse({'data': data}, safe=False)

    return JsonResponse({'data': data}, safe=False)

def get_income_query_set(period):
    today = datetime.date.today()
    if period == 'today_reports':
        reports = Report.objects.filter(approval_date=today)
        return reports
    elif period == 'this_months_reports':
        months_first_day = datetime.datetime.today().date().replace(day=1)
        return Report.objects.filter(approved=True, approval_date__gte=months_first_day)

    elif period == 'this_week_reports':
        start_date = datetime.datetime.today(
        ) - datetime.timedelta(days=datetime.datetime.today().weekday() % 7)

        reports = Report.objects.filter(
            approved=True, approval_date__gte=start_date.date())
        return reports

    elif period == 'this_year_reports':
        start_date = starting_day_of_current_year = datetime.datetime.now(
        ).date().replace(month=1, day=1)
        reports = Report.objects.filter(
            approved=True, approval_date__gte=start_date)
        return reports

def generate_pdf(queryset, request, period, total, title):
    """Generate pdf."""

    # Rendered
    html_string = render_to_string(
        'reports/pdf-output.html', {'reports': queryset,
                                    'period': period,
                                    'total': total,
                                    'title': title,
                                    'current_site': get_current_site(request).domain})
    html = HTML(string=html_string)
    result = html.write_pdf()

    # Creating http response
    response = HttpResponse(content_type='application/pdf;')
    response['Content-Disposition'] = 'inline; filename=list_people.pdf'
    response['Content-Transfer-Encoding'] = 'binary'
    with tempfile.NamedTemporaryFile(delete=True) as output:
        output.write(result)
        output.flush()
        output = open(output.name, 'rb')
        response.write(output.read())

    return response

def report_export(request, period, total):

    options = {'today_reports': ' REPORTS  TODAY',
               'this_week_reports': 'REPORTS THIS WEEK',
               'this_year_reports': 'REPORTS THIS YEAR',
               'this_months_reports': 'REPORTS THIS MONTH'}

    rows = get_income_query_set(period=period)
    return generate_pdf(rows, request, period, total, options[period])


import tkinter as tk
import tkinter.ttk as ttk
from tkinter.messagebox import *

class ListboxSearcher(ttk.Frame):
    def __init__(self, master, **kw):
        super().__init__(master, **kw)
        self._fm_content = ttk.Frame(self)
        self._fm_content.pack()
        self._et_content = ttk.Entry(self._fm_content, width=16)
        self._et_content.pack(side=tk.LEFT, fill=tk.X)
        self._et_content.bind('<Key-Return>', self._on_search)
        self._et_content.bind('<Key-Next>', self._on_next)
        self._et_content.bind('<Key-Prior>', self._on_prior)
        self._bt_search = tk.Button(self._fm_content, text='🔍', relief='flat', overrelief='groove')
        self._bt_search.config(command=self._on_search)
        self._bt_search.pack(side=tk.LEFT)
        self._bt_next = tk.Button(self._fm_content, text='↓', relief='flat', overrelief='groove')
        self._bt_next.config(command=self._on_next)
        self._bt_next.pack(side=tk.LEFT)
        self._bt_prior = tk.Button(self._fm_content, text='↑', relief='flat', overrelief='groove')
        self._bt_prior.config(command=self._on_prior)
        self._bt_prior.pack(side=tk.LEFT)
        self._func_search = None
        self._func_prior = None
        self._func_next = None
        self._res_hint = tk.StringVar()
        self._lb_hint = ttk.Label(self, textvariable=self._res_hint)
        self._lb_hint.pack()
        self._li_listbox = []
        self._res = []
        self._index = -1
        self._func_hide_hint = ''

    def get_entry(self):
        return self._et_content

    def set_size(self, value):
        self._et_content.config(width=value)

    def set_search_func(self, func=None):
        self._func_search = func

    def set_prior_func(self, func=None):
        self._func_prior = func

    def set_next_func(self, func=None):
        self._func_next = func

    def _on_search(self, event=None):
        if self._func_search is None:
            self.search()
        else:
            self._func_search()

    def _on_prior(self, event=None):
        if self._func_prior is None:
            self.previous()
        else:
            self._func_prior()

    def _on_next(self, event=None):
        if self._func_next is None:
            self.next()
        else:
            self._func_next()

    def set_listbox(self, li):
        if li != self._li_listbox:
            self._li_listbox = li
        self.search()

    def search(self):
        content = self._et_content.get()
        self._res.clear()
        self._index = -1
        if content == '':
            self._res_hint.set('')
            return
        for idx in range(len(self._li_listbox)):
            lsb = self._li_listbox[idx]
            lsb.select_clear('0', 'end')
            for i in range(lsb.size()):
                if content in lsb.get(i):
                    self._res.append((idx, i))
        if len(self._res) == 0:
            self._lb_hint.config(foreground='red')
            self._res_hint.set('没有找到任何内容')
            if self._func_hide_hint != '':
                self.after_cancel(self._func_hide_hint)
            self._func_hide_hint = self.after(
                2500,
                lambda: self._res_hint.set('') if '/' not in self._res_hint.get() else None
            )
        else:
            self._lb_hint.config(foreground='black')
            self._res_hint.set(f'?/{len(self._res)}')

    def next(self):
        if len(self._res) == 0:
            self.bell()
            return False
        if self._index >= len(self._res) - 1:
            showerror('错误', '最后一项', parent=self.winfo_toplevel())
            return False
        self._index += 1
        self._res_hint.set(f'{self._index + 1}/{len(self._res)}')
        id_lsb, i = self.get()
        for lsb in self._li_listbox:
            lsb.select_clear('0', 'end')
        self._li_listbox[id_lsb].select_set(i)
        self._li_listbox[id_lsb].see(i)
        return True

    def previous(self):
        if len(self._res) == 0:
            self.bell()
            return False
        if self._index <= 0:
            showerror('错误', '第一项', parent=self.winfo_toplevel())
            return False
        self._index -= 1
        self._res_hint.set(f'{self._index + 1}/{len(self._res)}')
        id_lsb, i = self.get()
        for lsb in self._li_listbox:
            lsb.select_clear('0', 'end')
        self._li_listbox[id_lsb].select_set(i)
        self._li_listbox[id_lsb].see(i)
        return True

    def get(self):
        return self._res[self._index]


# coding:utf-8
import json
import thread
import time
import requests
import os
from unittest import TestCase
from flask import Flask, request

app = Flask(__name__)
#__test_case = TestCase()
#http://0.0.0.0:9022/api/v1/daemons
__base_url = '/api/v1/'
__agent_nic1 = 'ens33'
__agent_nic2 = 'ens32'
__daemon = {}
__sync_cnts = 0
__remote_ip1 = '147.147.147.147'
__remote_ip2 = '146.146.146.146'
__fp = open('err.log', 'a+')

def resp_with_error(sc, code, message):
    rep = {
        'code': code,
        'message': message
    }
    rep_js = json.dumps(rep)
    return rep_js, sc

def add_agent(req, id, key, filter):
    global __daemon, __remote_ip1, __remote_ip2, __agent_nic1, __agent_nic2

    req['agent'] = {
        "id": id,
        "agentType": "PA",
        "cpuLimit": 0.1, #10%cpu
        "memLimit": 40, #40M
        "packetAgentStrategies": [{
            "serviceTag": 1,
            "startup": "",
            'networkInterface': {
                "name" : __agent_nic1
            },
            "bpf": filter,
            "packetChannelType": "GRE",
            "address": __remote_ip1
        },
        {
            "serviceTag": 1,
            "startup": "",
            'networkInterface': {
                "name" : __agent_nic2
            },
            "bpf": filter,
            "packetChannelType": "VXLAN",
            "address": __remote_ip2
        }]
    }

@app.route(__base_url + 'daemons', methods=['POST'])
def post_daemons():
    global __daemon, __test_case, __fp

    req_data = request.get_data()
    req = json.loads(req_data.decode("utf-8"))
    if req['syncMode'] != 'pull':
        __fp.write("Err: mode is not pull in REG\n")
        return resp_with_error(404, 404, "syncMode not pull")

    if not req.has_key('networkInterfaces'):
        __fp.write("Err: no networkInterfaces in REG\n")
        return resp_with_error(404, 404, "no networkInterfaces in REG")

    rep_js = json.dumps(req)
    return rep_js, 200

@app.route(__base_url + 'daemons/sync', methods=['POST'])
def post_daemons_sync():
    global __daemon, __sync_cnts, __test_case, __fp

    req_data = request.get_data()
    req = json.loads(req_data.decode("utf-8"))

    hasNic1 = False
    hasNic2 = False
    for interface in req['networkInterfaces']:
        if interface['name'] == __agent_nic1:
            hasNic1 = True
        elif interface['name'] == __agent_nic2:
            hasNic2 = True
    if hasNic1 == False or hasNic2 == False:
        __fp.write("Err: no nic1 or nic 2 in sync\n")
        return resp_with_error(404, 404, "no nic1 or nic2 in REG")

    if __sync_cnts == 0:
        add_agent(req, 0, 29, 'host 10.1.1.10') #82pkts

    pro=os.system("ps -ef|grep pktminerg|grep -v grep")
    if pro == 0x100:
        __fp.write("Err: no pktminerg running\n")
        return resp_with_error(404, 404, "no pktminerg running")

    __daemon = req
    __sync_cnts = __sync_cnts + 1

    rep_js = json.dumps(req)
    if __sync_cnts == 1:
        __sync_cnts
        return rep_js, 200
    else:
        return rep_js, 304

if __name__ == '__main__':
    app.run(debug=True, host='127.0.0.1', port=9090)


import itertools
n, w = map(int, input().split())
a = list(map(int, input().split()))

s = set()
for i in range(len(a)):
  if a[i] <= w:
    s.add(a[i])

l = list(itertools.combinations(a,2))
for i in range(len(l)):
  if sum(l[i]) <= w:
    s.add(sum(l[i]))

l2 = list(itertools.combinations(a,3))
for i in range(len(l2)):
  if sum(l2[i]) <= w:

    s.add(sum(l2[i]))

print(len(s))


from argparse import ArgumentParser
import logging
import config
from requests import get
from os import path, makedirs
from bs4 import BeautifulSoup
from datetime import datetime

def download_file(url, outputfolder, logger):
    """
    helper file to download any file to specified folder
    :param url: url of the file
    :param outputfolder: folder to save the file to
    :param logger:
    :return:
    """
    logger.info('download actual dump')
    filename = url.split('/')[-1]
    local_filename = path.join(outputfolder, filename)

    r = get(url, stream=True)
    download_size = int(r.headers['Content-Length'])

    # check if file is already downloaded
    if path.isfile(local_filename):
        local_size = path.getsize(local_filename)
        if local_size == download_size:
            logger.info('skip: ' + filename)
            return local_filename

    logger.info('{} ({:,d} Bytes)'.format(url, download_size))
    with open(local_filename, 'wb') as f:
        size_to_log = download_size / 20  # 5% steps
        downloaded = 0
        last_time = datetime.now()
        for chunk in r.iter_content(chunk_size=1024):
            if chunk:  # filter out keep-alive new chunks
                downloaded += 1024
                if downloaded % (size_to_log) < 1024:
                    actual_time = datetime.now()
                    delta = actual_time - last_time
                    logger.info(
                        '{done:>5,d} MB ({done_percent:>6.{digits}f}%, {speed:>10.{digits}f} KB/s)'
                        .format(
                            done=downloaded / (1048576),
                            done_percent=100 * float(downloaded) / download_size,
                            speed=(size_to_log / (delta.seconds + delta.microseconds / 1E6)) / 1024,
                            digits=2,
                        )
                    )
                    last_time = actual_time
                f.write(chunk)
                f.flush()
    logger.info('finished downloading')
    return local_filename

def download_wd_dump(outputfolder, logger):
    """
    downloads the latest wikidata json dump
    :param outputfolder:
    :param logger:
    :return:
    """
    if not path.exists(outputfolder):
        logger.info('create outputfolder')
        makedirs(outputfolder)

    resp = get(config.WIKIDATA_DUMPS_URL.format(''))
    soup = BeautifulSoup(resp.text)
    all_dumps = set()
    for a in soup.find_all('a'):
        href = a.attrs['href'].strip()
        if not href == '../':
            all_dumps.add(href)

    latest_dump = sorted(all_dumps)[-1]
    download_file(config.WIKIDATA_DUMPS_URL.format(latest_dump), outputfolder, logger)

if __name__ == '__main__':
    parser = ArgumentParser()
    parser.add_argument("-f", "--folder", default='dumps',
                        help="folder where the wikidata dumps are getting downloaded to")
    args = parser.parse_args()

    # set up logging to file
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
                        datefmt='%m-%d %H:%M',
                        filename='wd_download.log',
                        filemode='a')

    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s', "%H:%M:%S")
    console.setFormatter(formatter)
    logging.getLogger('').addHandler(console)

    download_wd_dump(args.folder, logging.getLogger('download'))


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def getMoonPhase(jd, southernHemisphere=False):
	"""
		returns a float number that: 0 <= phase < 2
		0.0 = no moon
		1.0 = full moon
	"""
	from scal3.cal_types import hijri
	_, _, d = hijri.jd_to(jd)
	if d >= 28:
		phase = 0.0
	else:
		phase = d / 14.0
	# 0 <= phase < 2
	if southernHemisphere:
		phase = (2.0 - phase) % 2.0
	return phase