from __future__ import division

import os
import sys
import glob
import re
import fileinput
import shutil  # needed?
import subprocess
from optparse import OptionParser
from subprocess import Popen
"""
Installation guide for Linux
----------------------------

Install the following software:
 - Mono (mono-runtime)
 - Python3

Create a folder /home/user/bin
to create your own bin dir for executable files: (this step is not needed for all distros)
* Create the dir:
    mkdir ~/bin
* To add a directory to your PATH, add
    #my own executable files
    PATH=$PATH:$HOME/bin
  to your .bashrc file

Put the files awescript.py, srr.exe and srs.exe in ~/bin
http://pastebin.com/JMWKZqTt (version that always stores paths in the srr)

Create a file "awescript" with the following content:

#!/bin/sh
/usr/bin/python3.2 ~/bin/awescript.py "$1"

Do "whereis python" in the terminal to find out the path of you python installation if needed.
Do "chmod u+x awescript" to change the permissions.

In awescript.py change this:
    path_to_srs = "mono /usr/local/bin/srs.exe"
    path_to_srr = "mono /usr/local/bin/srr.exe"
to this:
    path_to_srs = "mono /home/user/bin/srs.exe"
    path_to_srr = "mono /home/user/bin/srr.exe"

Type "awescript --help" in any path in your terminal.

awescript -R . --srr-dir=~/srrs
"""

# version modified to work with pyReScene
# - works with Python 2.7
# - it has path support: it always creates a .srr file with the -p option:
#   paths will be stored.
# - works with mp4 and wmv releases
# - the nfo file will be stored at the top
# - will always overwrite existing .srr files

# better handling of srs-dir and srr-dir - i.e. remove meta=DIREECTORY and just os.path.normalize it

# allow rarfix to be srr'd, skip unrar

# allow multiple main_files and loop each (for sfv's with multiple rar sets)
#   will have to delete sfv files after unrar is done for all
# ------ currently works for subs sfv's with multiple rar sets... will screw up if non-scene sfv has 2+ sets that aren't subs

# add option to never unrar compressed subtyp files

# add option to unrar/delete subs

# add error logging to a file

# add sfv check option

# parse existing srs/srr files and see if need to make new ones or use/rename existing!
# srs -l
# - look for .avi .mkv .divx
# srr -l
# - look for .srs .nfo .sfv and main rar sets
# only delete srs in the filelist, bypassing DeleteFiles hardcoded

# need to make sure there's enough space to extract?

# replace unrar lb code with rar header detection code - avoid external programs at all times :)

# if sfv found and no rar - do a recursive check from that dir.

# if unrar runs and is okay, move subs/sample/etc to unrar_dir

# add support to ignore *.1.par2/sfv/rar files that par2 or alt.binz doesn't delete
# - could cause alt.binz to execute before subs/sample downloaded though

# doesn't support .r00+ if the filename before it has part\d (i.e. pfa-tfs.part2.r37)

# CJ| is gay

if(os.name == "nt"):
    path_to_srs = "pysrs"
    path_to_srr = "pysrr"
else:
    path_to_srs = "/usr/local/bin/srs"
    path_to_srr = "/usr/local/bin/srr"
path_to_unrar = "unrar"
unrar_options = "-o+ -p-"

# 7z, optional, if set use 7z to extract
path_to_7z = ""  # "7z"
options_7z = "-y -p1"

overwrite_existing_subs = 1
overwrite_existing_samples = 1

def get_files(path, cwdsolo, options):
    sfvList = []
    fileList = glob.glob("*.*")
    fileMainList = []
    blackList = []
    sets_in_main = 0

    for root, dirs, _files in os.walk(path):
        base = os.path.relpath(root) + os.sep
        if base == "." + os.sep:
            base = ""
        for d in dirs:
            path = base + d + os.sep
            # fix paths with [ or ] in name
            path = re.sub("([[\\]])", "[\\1]", path) + "*.*"
            if options.debug:
                print("glob.glob " + path)
            fileList += glob.glob(path)
    fileList.sort()

    # move sfv files to top of list
    for sfv_file in fileList:
        if re.search("\.sfv$", sfv_file, re.IGNORECASE):
            sfvList.append(sfv_file)
            fileList.remove(sfv_file)

    fileList = sfvList + fileList

    for lfile in fileList:
        add = True
        folder = ""
        main_file = ""
        main_files = []
        sfv = ""
        fset = []
        typ = ""
        subtyp = ""
        dest = ""
        sr_dir = ""  # dir to store path for srs or srr that was created for fileset

        (folder, filename) = os.path.split(lfile)
        if folder:
            folder += os.sep

        if not re.search("\.(avi|mkv|m4v|mp4|wmv|ts|divx|ogm|mpg|mpeg|part0?0?1\.rar|00[0-1]|vob|m2ts|sfv|srs|srr|nfo|jpg)$", filename, re.IGNORECASE):
            if (not (re.search("\.rar$", filename, re.IGNORECASE) and
                     not re.search("\.part\d{2,3}\.rar$", filename, re.IGNORECASE))):
                continue
            if re.search("\.part[2-9]\.rar$", filename, re.IGNORECASE):
                basename = filename.split(".rar", 1)[0]
                if not os.path.exists(folder + basename + ".r00"):
                    continue

        # SFV Detection
        if re.search("\.sfv$", filename, re.IGNORECASE):
            sfv = filename
            missingList = []
            for line in fileinput.input([folder + filename]):
            # for line in open(folder+filename, 'r').readlines(): #alternative way, reads entire sfv into memory
                line = line.strip()
                if not len(line) < 10 and not line.startswith(";"):
                    f = line[:-9]

                    if not os.path.exists(folder + str(f)):
                        skip = True
                        if re.search("\.(rar|[rstu0-9][0-9][0-9])$", f, re.IGNORECASE):
                            if not options.rename_wrong_case or not os.path.exists(folder + str(f).lower()):
                                missingList.append(f)
                            else:
                                try:
                                    os.rename(folder + str(f).lower(), folder + str(f))
                                    skip = False
                                except Exception:
                                    skip = True
                        if skip:
                            continue

                    if (re.search("\.part0?0?1\.rar$", f, re.IGNORECASE) or
                        (re.search("\.(rar|00[0-1])$", f, re.IGNORECASE) and
                         not re.search("\.part\d{1,3}\.rar$", f, re.IGNORECASE))):
                        if main_file:
                            blackList.append(f)
                        else:
                            main_file = f
                        main_files.append(f)
                    elif re.search("\.part[2-9]\.rar$", f, re.IGNORECASE):
                        if os.path.exists(folder + f.split(".rar", 1)[0] + ".r00"):
                            if main_file:
                                blackList.append(f)
                            else:
                                main_file = f
                            main_files.append(f)
                    if re.search("\.(rar|[rstu0-9][0-9][0-9])$", f, re.IGNORECASE):
                        fset.append(f)
            #
            # multi-rar sets subs fix
            #
            # if len(main_files) > 1:
            #    for i in range(len(main_files)-1):
            #        blackList.append(main_files[i])
            # fset.append(sfv)
            if len(missingList) > 0:
                cont = False
                print("Files missing from " + filename + ":\n " + str(missingList))  # str(missingList) +
                for miss in missingList:
                    if re.search("\.(avi|divx|mkv|m4v|mp4|wmv|ts|ogm|mpg|mpeg)$", miss, re.IGNORECASE):
                        print("SFV contains missing video file.  Skipping instead of quitting.")
                        cont = True
                        break
                    elif re.search("\.(nfo|par2)$", miss, re.IGNORECASE):
                        print("SFV contains MISC files.  Non-Scene SFV.")
                        cont = True
                        break
                    elif re.search("(sub.*|-s)\.rar", miss, re.IGNORECASE) and len(missingList) <= 2:
                        print("Subs files from SFV missing.")
                        cont = True
                        break
                if cont: continue
                if main_file:
                    blackList.append(f)
                return []

            if not main_file: continue  # sfv was probably corrupt

        if not main_file:
            main_file = filename

        # Look inside RAR and get types (i.e. AVI,MKV,SUBS)
        if re.search("\.(rar|00[0-1])$", main_file, re.IGNORECASE):
            if blackList.count(main_file):
                continue
            if os.path.exists(folder + main_file.split(".001", 1)[0] + ".000"):
                continue  # split files - not RARs

            for i in range(len(fileMainList)):
                if fileMainList[i][1].lower() == main_file.lower():
                    add = False
                    break  # RAR file added by SFV, skip
            if not add:
                continue

            if len(fset) == 0:  # NO SFV!
                print("no sfv for %s" % main_file)
                fset = glob.glob(wildc(folder, main_file))
                for i in range(len(fset)):
                    fset[i] = fset[i].rsplit(os.sep)[-1:][0]  # remove path, keeping only filename
                # print(fset)

            if len(fset) >= 2:
                # print(fset)
                fset.sort()  # Fixes when SFV set is out of order (i.e. RAR file at top in .r## set
                if options.check_rarsets and os.path.getsize(folder + fset[len(fset) - 1]) == os.path.getsize(folder + fset[len(fset) - 2]):
                    print("%s is the same size as %s.  Incomplete set." % (folder + fset[len(fset) - 1], folder + fset[len(fset) - 2]))
                    # continue
                    return []
                else:  # since last 2 aren't same size, we must have last rar to check for missing!
                    numRARs = 0
                    if re.search("\.part(\d{1,3})\.rar$", fset[len(fset) - 1], re.IGNORECASE):
                        numRARs = int(re.search("\.part(\d{1,3})\.rar$", fset[len(fset) - 1], re.IGNORECASE).groups()[0])
                    elif re.search("\.u(\d{2})$", fset[len(fset) - 1], re.IGNORECASE):
                        numRARs = int(re.search("\.u(\d{2})$", fset[len(fset) - 1], re.IGNORECASE).groups()[0])
                        numRARs += 302
                    elif re.search("\.t(\d{2})$", fset[len(fset) - 1], re.IGNORECASE):
                        numRARs = int(re.search("\.t(\d{2})$", fset[len(fset) - 1], re.IGNORECASE).groups()[0])
                        numRARs += 202
                    elif re.search("\.s(\d{2})$", fset[len(fset) - 1], re.IGNORECASE):
                        numRARs = int(re.search("\.s(\d{2})$", fset[len(fset) - 1], re.IGNORECASE).groups()[0])
                        numRARs += 102
                    elif re.search("\.r(\d{2})$", fset[len(fset) - 2], re.IGNORECASE):
                        numRARs = int(re.search("\.r(\d{2})$", fset[len(fset) - 2], re.IGNORECASE).groups()[0])
                        numRARs += 2
                    elif re.search("\.(\d{3})$", fset[len(fset) - 1], re.IGNORECASE):
                        numRARs = int(re.search("\.(\d{3})$", fset[len(fset) - 1], re.IGNORECASE).groups()[0])
                        if re.search("\.(rar|000)$", main_file, re.IGNORECASE):
                            numRARs += 1
                    # else: return []

                    if numRARs != 0:
                        if options.check_rarsets and len(fset) != numRARs:
                            print("Files missing from RAR set.  Quitting.\n")
                            print(fset)
                            print("\nNumber of RARs in SFV: %d" % len(fset))
                            print("Number of RARs needed for complete set: %d" % numRARs)
                            return []
                        # else: #check for RARs with wrong size - exit if 2+ have different size.
                        #    fileSize = os.path.getsize(folder+fset[0])
                        #    diffFiles = 0
                        #    for f in fset:
                        #        if os.path.getsize(folder+f) != fileSize:
                        #            diffFiles += 1
                        #        if diffFiles == 2:
                        #            print("2 or more files in the RAR set are a different size.  Set is bad.  Quitting.")
                        #            return []

            output = ""
            proc = subprocess.Popen("%s lb \"%s%s\"" % (path_to_unrar, folder, main_file), shell=True, stdout=subprocess.PIPE)
            std = str(proc.communicate()[0]).lstrip("b'").rstrip("'")
            if std:
                std = std.replace("\\n", "\n")
                std = std.replace("\\r", "")
                std = std.replace("\r", "")
                output = std.split("\n")
            elif len(output) == 0 and re.search("\.(avi|divx|mkv|m4v|mp4|ts|ogm|mpg|mpeg|)\.00[0-1]$", main_file, re.IGNORECASE):
                # if os.path.exists(folder + main_file.split(".001",1)[0] + ".000"):
                #    main_file = main_file.split(".001",1)[0] + ".000"
                print("%s is a joined file." % main_file)
                typ = "Video"
                subtyp = "Joined"
                # wild = glob.glob(wildc(folder,main_file)).sort()
                # continue
            else:
                print("%s could be corrupt?" % main_file)
                continue

            if re.search("extra", main_file, re.IGNORECASE):
                subtyp = "Extras"
                if not re.search("extra", cwdsolo, re.IGNORECASE):
                    dest = "Extras" + os.sep

            #
            # need to check if already in a folder and use that
            #
            for s in output:  # could be multiple files in the rar
                if not s:
                    continue  # for blanks at the end from splitting \r\n or \n
                if re.search("\.(avi|divx|mkv|m4v|mp4|wmv|ts|ogm|mka|dts|ac3|mpg|mpeg|mp3|ogg)$", s, re.IGNORECASE):
                    typ = "Video"
                    if not folder:
                        sets_in_main += 1  # i.e. not in CD[1-9], so may need to move
                elif re.search("\.(iso|img|nrg|bin|gcm|cdi|dvd|gi)$", s, re.IGNORECASE):
                    typ = "ISO"
                    subtyp = "Compressed"
                else:
                    # print("Unkown files in RAR %s or corrupt." % main_file)
                    continue
                break
            if not typ:
                for s in output:  # could be multiple files in the rar
                    if not s:
                        continue  # for blanks at the end from splitting \r\n or \n
                    if re.search("\.(srt|sub|idx|rar)$", s, re.IGNORECASE):
                        if re.search("vob.?sub", main_file, re.IGNORECASE):
                            typ = "VobSubs"
                            if not folder:
                                dest = "VobSubs" + os.sep
                        else:
                            typ = "Subs"
                            if not folder:
                                dest = "Subs" + os.sep
                        if subtyp == "Extras" and not re.search("extra", cwdsolo, re.IGNORECASE):
                            if not folder:
                                dest = "Extras" + os.sep + dest
                        break
                if not typ:
                    typ = "Other"

        # Check for Video files NOT in RAR files
        # i.e. samples or previously extracted video
        elif re.search("\.(avi|mkv|m4v|mp4|wmv|ts|vob|m2ts|mpg|mpeg)$", main_file, re.IGNORECASE):

            if re.search("extra", folder + main_file, re.IGNORECASE):
                subtyp = "Extras"
            # check if sample
            if re.search("sample", folder, re.IGNORECASE) or is_sample(folder + main_file, subtyp):
                if re.search("\.vob$", main_file, re.IGNORECASE):
                    typ = "VobSample"
                    if not folder:
                        dest = "Sample" + os.sep
                elif re.search("\.m2ts$", main_file, re.IGNORECASE):
                    typ = "m2tsSample"
                    if not folder:
                        dest = "Sample" + os.sep
                else:
                    typ = "Sample"
                    if not folder:
                        dest = "Sample" + os.sep
                if subtyp == "Extras" and not re.search("extra", cwdsolo, re.IGNORECASE):
                    if not folder:
                        dest = "Extras" + os.sep + dest
            else:
                # not a sample, add as extracted video
                typ = "Video"
                if subtyp == "Extras":
                    subtyp = "Extracted_Extras"
                else:
                    subtyp = "Extracted"
                if not folder:
                    sets_in_main += 1  # i.e. not in CD[1-9], so may need to move
                # continue

        elif re.search("\.nfo$", main_file, re.IGNORECASE):
            typ = "Other"
            subtyp = "NFO"
            if not folder:
                dest = options.nfos_dir + os.sep

        elif re.search(".*proof.*\.jpg$", main_file, re.IGNORECASE):
            typ = "Proof"
            subtyp = ""
            if not folder:
                dest = "Proof" + os.sep

        elif re.search("\.srs$", main_file, re.IGNORECASE):
            if re.search("extra", main_file, re.IGNORECASE):
                subtyp = "Extras_SRS"
                if not folder:
                    dest = "Extras" + os.sep + "Sample" + os.sep
            else:
                subtyp = "SRS"
                if not folder:
                    dest = "Sample" + os.sep
            typ = "Sample"

        elif re.search("\.srr$", main_file, re.IGNORECASE):
            typ = "Other"
            subtyp = "SRR"

        if len(fset) == 0:
            fset.append(main_file)
        # if dest and options.unrar_dir: dest = options.unrar_dir.rstrip(os.sep) + os.sep + dest

        fileMainList.append([folder, main_file, sfv, fset, typ, subtyp, dest, sr_dir])

    # Detect CD folders - skip if folder has TV tags
    if sets_in_main >= 2 and not re.search("([\._\s]s?\d{1,3}[\._\s]?[ex]\d{1,3}|s\d{1,3})", cwdsolo, re.IGNORECASE):  # 2 or more CDs possible
        fileMainList = get_cds(fileMainList)

    return fileMainList

def is_sample(video, subtyp):
    max_size = 50000000
    if re.search("\.(mkv|m4v|mp4|ts)$", video, re.IGNORECASE):
        max_size = 250000000

    if re.search("^.*?([\.\-_\w]?sa?mp).*?\.(?:avi|mkv|m4v|mp4|wmv|vob|m2ts|ts|mpg|mpeg)$", video, re.IGNORECASE):
        if os.path.getsize(video) < max_size: return True
    # TODO: add check to make sure not extras before doing this
    else:  # no s?mp in filename - reduce filesize limits manually
        if subtyp != "Extras" and re.search("\.(avi|mkv|m4v|mp4|wmv|vob|m2ts|ts|mpg|mpeg)$", video, re.IGNORECASE) and os.path.getsize(video) < max_size / 2:
            return True

    return False

def only_samples(files):
    sample = False
    for sfile in files:
        if sfile[4] == "Video":
            return False
        elif sfile[4] == "Sample":
            sample = True
    return sample

def get_cds(fileMainList):
    # folder, main_file, sfv, fset, typ, dest
    fileList = fileMainList

    fileListNew = []
    position = False
    for i in range(len(fileList)):
        if fileList[i][4] != "Video": continue
        fileListTemp = []
        file = fileList[i][1]
        posTemp = 0

        if re.search("(s\d{1,3}[\._]?e\d{1,3})", file, re.IGNORECASE):
            continue  # TV Test (file) - MAKE THIS FOR DIR
        for j in range(len(fileList)):
            if i == j: continue  # i <= j ?  since they've already been compared once?
            if fileList[j][4] != "Video":
                continue
            file2 = fileList[j][1]
            if re.search("(s\d{1,3}[\._]?e\d{1,3})", file2, re.IGNORECASE):
                continue  # TV Test (file2)
            if len(file) == len(file2):
                diff = 0
                for k in range(len(file)):
                    if file[k] != file2[k]:
                        diff += 1
                        if diff > 1:
                            break
                        if posTemp:
                            if k != posTemp:
                                # Can't compute differences for CDs.  Skipping.
                                continue
                        posTemp = k
                        # print(file)
                if diff == 1:
                    fileListTemp.append(j)
        if len(fileListTemp) > len(fileListNew):
            fileListNew = fileListTemp
            fileListNew.append(i)
            position = posTemp
    fileListNew.sort()

    if len(fileListNew) <= 1:
        return fileMainList
    print("CDs: Detected!")

    diff = 99

    for i in fileListNew:
        file = fileMainList[i][1]
        char = file[position]
        if re.match("^([1-9])$", char):
            cd = char
        elif re.match("^([A-Ia-i])$", char):
            if char == "a": cd = "1"
            elif char == "b": cd = "2"
            elif char == "c": cd = "3"
            elif char == "d": cd = "4"
            elif char == "e": cd = "5"
            elif char == "f": cd = "6"
            elif char == "g": cd = "7"
            elif char == "h": cd = "8"
            elif char == "i": cd = "9"
        else:
            print(file + " is NOT a cd, subs?")
            continue

        if char == "e" and len(fileListNew) <= 4:  # no CD4 so most likely Extras
            print(file + " is Extras")
            if re.search("\.(rar|00[0-1])$", file, re.IGNORECASE):
                fileMainList[i][5] = "Extras"
            else:
                fileMainList[i][5] = "Extracted_Extras"
            fileMainList[i][6] = "Extras" + os.sep
            continue

        # fix for releases with only parts 3/4 but pre as CD1/CD2 (separate release with 1/2)
        if diff == 99:
            if int(cd) == 1:
                diff = 0
            else:  # assume it's sorted and just didn't start at 1
                diff = int(cd) - 1
        cd = str(int(cd) - diff)
        print(file + " is CD" + cd)

        # get/make directory
        fileMainList[i][6] = "CD" + cd + os.sep
        if re.search("\.(rar|00[0-1])$", file, re.IGNORECASE):
            fileMainList[i][5] = "CD"
        else:
            fileMainList[i][5] = "Extracted_CD"

    return fileMainList

def wildc(folder, file):
    if not os.path.exists(folder + file):
        return False

    basename = False
    wildcard = False
    if re.search("\.(part01\.rar)$", file, re.IGNORECASE):
        ext = re.search("\.(part01\.rar)$", file, re.IGNORECASE).groups()[0]
        wildcard = ".[Pp][Aa][Rr][Tt][0-9][0-9].[Rr][Aa][Rr]"
    elif re.search("\.(part001\.rar)$", file, re.IGNORECASE):
        ext = re.search("\.(part001\.rar)$", file, re.IGNORECASE).groups()[0]
        wildcard = ".[Pp][Aa][Rr][Tt][0-9][0-9][0-9].[Rr][Aa][Rr]"
    elif re.search("\.(rar)$", file, re.IGNORECASE):
        if re.search("\.(part1\.rar)$", file, re.IGNORECASE):
            filename = folder + file.split(".rar", 1)[0] + ".r00".replace("[", "[[]")
            if os.path.exists(filename):
                ext = re.search("\.(part1\.rar)$", file, re.IGNORECASE).groups()[0]
                wildcard = ".[Pp][Aa][Rr][Tt]1.[Rr]??"
            else:
                ext = re.search("\.(part1\.rar)$", file, re.IGNORECASE).groups()[0]
                wildcard = ".[Pp][Aa][Rr][Tt][0-9].[Rr][Aa][Rr]"
        else:
            ext = re.search("\.(rar)$", file, re.IGNORECASE).groups()[0]
            wildcard = ".[Rr]??"
    elif re.search(".(00[0-1])$", file, re.IGNORECASE):
        ext = re.search("\.(00[0-1])$", file, re.IGNORECASE).groups()[0]
        wildcard = ".[0-9][0-9][0-9]"
    else: return False

    basename = file.split("." + ext, 1)[0].replace("[", "[[]")

    return folder + basename + wildcard

def move_files(files, options, cwdsolo):
    if options.move_subs:
        files = move(files, "Subs", "", True, options.debug)
        files = move(files, "VobSubs", "", True, options.debug)
        if options.move_extras:
            files = move(files, "Subs", "Extras", True, options.debug)
            files = move(files, "VobSubs", "Extras", True, options.debug)

    if options.move_samples and not (options.delete_samples and options.delete_srs_after_srr):
        if not only_samples(files):
            files = move(files, "Sample", "", True, options.debug)
            if not options.srs_dir:
                files = move(files, "Sample", "SRS", True, options.debug)
            if options.move_extras:
                files = move(files, "Sample", "Extras", True, options.debug)
                if not options.srs_dir:
                    files = move(files, "Sample", "Extras_SRS", True, options.debug)
        else:
            print("Only samples found, not moving.")

    if options.move_proof:
        files = move(files, "Proof", "", True, options.debug)

    if options.move_samples:
        files = move(files, "VobSample", "", True, options.debug)
        files = move(files, "m2tsSample", "", True, options.debug)
        if options.move_extras:
            files = move(files, "VobSample", "Extras", True, options.debug)
            files = move(files, "m2tsSample", "Extras", True, options.debug)

    if options.move_cds and not options.delete_rars:
        # print("Moving RAR sets to CD1-N folders.")
        files = move(files, "Video", "CD", True, options.debug)
        if options.move_cds_extracted:
            print("Moving previously extracted video files to CD1-N folders.")
            files = move(files, "Video", "Extracted_CD", True, options.debug)

    if options.move_extras:
        files = move(files, "Video", "Extras", True, options.debug)
        files = move(files, "Video", "Extracted_Extras", True, options.debug)
        files = move(files, "Other", "Extras", True, options.debug)

    if options.move_nfos:
        nfos_found = 0
        for i in range(len(files)):
            if files[i][4] == "Other" and files[i][5] == "NFO":
                nfos_found = nfos_found + 1
            if nfos_found >= 2:
                files = move(files, "Other", "NFO", True, options.debug)
                break

    return files

def move(files, typ, subtyp, overwrite, debug):
    for i in range(len(files)):
        if files[i][4] != typ or files[i][5] != subtyp or not files[i][6]:
            continue

        file = files[i]
        ok = True
        fset = file[3]
        dest = file[6]

        if not os.path.isdir(dest):
            if debug:
                print("mkdir %s" % dest)
            else:
                os.makedirs(dest)
            # directory = ""
            # for d in dest.rstrip(os.sep).split(os.sep):
            #    directory += d + os.sep
            #    if not os.path.isdir(directory):
            #        os.mkdir(directory)

        if file[2]:  # sfv
            fset.append(file[2])
        for f in fset:
            if not os.path.isfile(f):
                continue
            try:
                if os.path.isfile(dest + f):
                    if overwrite:
                        if debug:
                            print("remove %s%s to overwrite." % (dest, f))
                        else:
                            os.remove(dest + f)
                    else:
                        if os.path.isfile(dest + "copy.of." + f):
                            if debug:
                                print("remove %scopy.of.%s to make new copy." % (dest, f))
                            else:
                                os.remove(dest + "copy.of." + f)
                        if debug:
                            print("rename  %s%s to %scopy.of.%s" (dest, f, dest, f))
                        else:
                            os.rename(dest + f, dest + "copy.of." + f)
                if debug:
                    print("rename %s to %s%s" % (f, dest, f))
                else:
                    os.rename(f, dest + f)
            except OSError:
                ok = False

        if ok:
            file[0] = dest  # folder = dest
            file[6] = ""  # unrar_dir.rstrip(os.sep) + os.sep
            files[i] = file

    return files

# move full directories post-extraction if unrar_dir set?
def move_dir(file):
    return file

def srs_srr(files, options, cwdsolo):
    code = 0

    if options.create_srs:
        code, files = srs(files, options, cwdsolo)  # create srs

    if options.create_srr:  # if srs fails, so be it?
        code, files = srr(files, options, cwdsolo, False)  # False to not ignore Extras.  if fail, ignore Extras and try again

    if code == 0 and options.create_srr and (options.delete_srs_after_srr and options.include_srs_in_srr):
        if options.debug:
            print("remove *.srs")
        else:
            deleteFiles(["*.srs", "*[Ss][Aa][Mm][Pp][Ll][Ee]*/*.srs"], "Re-Sample (SRS)", None, False)  # delete *.srs

    return code, files

def srs(files, options, cwdsolo):
    global path_to_srs

    code = -1
    filesToAdd = []
    onlySamples = only_samples(files)

    for i in range(len(files)):
        if files[i][4] != "Sample" or files[i][5] == "SRS" or files[i][5] == "Extras_SRS": continue
        cmd = path_to_srs
        folder = files[i][0]
        file = files[i][1]
        srs_file = file[:-3] + "srs"

        if options.srs_namep:
            srs_file = cwdsolo + ".srs"
        try:
            cmd += " \"" + folder + file + "\""
            cmd += " -o \""
            if options.srs_dir:
                if os.path.exists(options.srs_dir):
                    folder = options.srs_dir.rstrip(os.sep) + os.sep
                else:  # create srs_dir
                    if os.makedirs(options.srs_dir):
                        folder = options.srs_dir.rstrip(os.sep) + os.sep
                        print("SRS directory %s created." % options.srs_dir)
                    else:
                        print("SRS directory %s could not be created.  SRS will default to release directory." % options.srs_dir)

            if os.path.isfile(folder + srs_file) and os.path.getsize(folder + srs_file) > 0:
                print("SRS file exists, skipping %s" % srs_file)
                code = 0
                if options.delete_samples or options.delete_samples_force:
                    if options.debug:
                        print("delete %s%s" % (folder, file))
                    deleteFiles([folder + file], "Video Sample", None, False)

                continue

            if not folder:
                folder = "." + os.sep

            cmd += folder + srs_file + "\""
            print(cmd)
            if options.debug:
                code = 0
            else:
                code = os.system(cmd)

            if code == 2:
                print("SRS: Sample file \"%s\" is bad." % file)
                if options.delete_samples_force and not onlySamples:
                    if options.debug:
                        print("delete %s%s" % (folder, file))
                    else:
                        deleteFiles([folder + file], "Video Sample", None, False)
                    code = 0
                else:
                    code = -1
            elif code == 0:
                files[i][7] = srs_file
                if files[i][5] == "Extras":
                    subtyp = "Extras_SRS"
                else:
                    subtyp = "SRS"
                filesToAdd.append([folder, srs_file, "", [srs_file], "Sample", subtyp, ""])
                if options.delete_samples and not onlySamples:
                    if options.debug:
                        print("delete %s%s" % (folder, file))
                    else:
                        deleteFiles([folder + file], "Video Sample", None, False)
            else:
                print("Error with SRS.")
                if options.delete_samples_force and not onlySamples:
                    if options.debug:
                        print("delete %s%s" % (folder, file))
                    else:
                        deleteFiles([folder + file], "Video Sample", None, False)
                    code = 0
                else:
                    code = -1
        except OSError:
            print("SRS: Error running SRS.")
            return -1, files

    files += filesToAdd

    return code, files

def srr(files, options, cwdsolo, ignore_extras):
    global path_to_srr

    cmd = path_to_srr
    code = -1
    srrNum = 0
    dest = ""
    srr_file = cwdsolo + ".srr"
    joined = False
    extras = False

    if options.srr_dir:
        if os.path.exists(options.srr_dir):
            dest = options.srr_dir
        else:  # create srr_dir
            if os.makedirs(options.srr_dir):
                dest = options.srr_dir
                print("SRR directory %s created." % options.srr_dir)
            else:
                print("SRR directory %s could not be created.  "
                      "SRR will default to release directory." % options.srr_dir)
    elif options.unrar_dir:
        if not os.path.exists(options.unrar_dir):
            if options.debug:
                print("mkdir %s" % options.unrar_dir)
                dest = options.unrar_dir
            elif os.makedirs(options.unrar_dir):
                dest = options.unrar_dir
        else:
            dest = options.unrar_dir

    for file in files:
        if file[4] != "Video" or "Extracted" in file[5] or file[5] == "Compressed": continue
        if file[5] == "Joined":
            joined = True
            continue
        if file[5] == "Extras":
            extras = True
        # if subtyp and file[5] != subtyp: continue
        folder = file[0]
        if file[2]: file = file[2]  # sfv
        elif file[1]: file = file[1]  # rar
        else: continue
        cmd += " \"" + folder + file + "\""
        srrNum += 1

    if joined and srrNum == 0:
        return 1, files
    elif srrNum == 0:
        return 1, files

    # -s include files below (at least *.nfo)
    cmd += " -s *.nfo"
    if options.include_srs_in_srr:
        for file in files:
            if file[4] == "Sample" and (file[5] == "SRS" or file[5] == "Extras_SRS"):
                cmd += " -s \"%s%s\"" % (file[0], file[1])

    if dest and os.path.isdir(dest):
        cmd += " -o \"%s%s%s\"" % (dest.rstrip(os.sep), os.sep, srr_file)
    else:
        cmd += " -o \"%s\"" % srr_file

    # we will always do paths when creating the srr file
    # always overwrite an existing .srr file (no question popup)
    cmd += " -p -y"

    try:
        if options.delete_srr:
            deleteFiles(["*.[Ss][Rr][Rr]"], "SRR", None, options.debug)
        print(cmd)
        if options.debug:
            code = 0
        else:
            code = os.system(cmd)
            print(code)
        if code == 3 or code == 768:  # 3 for windows, 768 for linux ; compressed RARs
            if extras == True and ignore_extras == False:
                print("SRR: RARs are compressed.  Re-Trying without Extras.")
                code, files = srr(files, options, cwdsolo, True)
            else:
                print("SRR: RARs are compressed.  Continuing like normal, ignoring srs deletion")
            return code, files
        elif code != 0:
            print("SRR: Re-Scene failed.  Skipping the rest.")
            return -1, files
        else:
            return 0, files
    except OSError:
        print("SRR: Error running SRR.")
        return -1, files

    return -1, files

def unrar(files, options, code):
    global path_to_unrar, unrar_options, path_to_7z, options_7z
    # options.unrar_dir, options.extract_to_main_dir, options.delete_rars, options.delete_sfv
    # [folder, main_file, sfv, fset, typ, subtyp, dest]

    if not options.extract_rars or code == -1:
        return code, files

    if path_to_7z == "":
        maincmd = "%s x %s" % (path_to_unrar, unrar_options)
    else:
        maincmd = "%s x %s" % (path_to_7z, options_7z)

    if(os.name == "nt"):
        joincmd = "copy /b"
    else:
        joincmd = "cat"

    for fileset in files:
        if (fileset[4] != "Video" and fileset[4] != "ISO") or "Extracted" in fileset[5]: continue  # typ

        folder = fileset[0]
        file = fileset[1]  # name.rar
        sfv = fileset[2]
        fset = fileset[3]
        if folder and (not options.extract_to_main_dir or fileset[5] == "Extras"):
            if options.unrar_dir:
                dest = options.unrar_dir.rstrip(os.sep) + os.sep + folder
            else:
                dest = folder
        else:
            if options.unrar_dir:
                dest = options.unrar_dir.rstrip(os.sep) + os.sep
            else:
                dest = "." + os.sep

        try:
            # input file
            if fileset[5] != "Joined":
                cmd = maincmd
                if path_to_7z != "":
                    cmd += " -o\"" + dest + "\""
                cmd += " \"" + folder + file + "\""
                if path_to_7z == "":
                    cmd += " \"" + dest + "\""
            else:
                cmd = joincmd + " "
                for i in range(len(fset)):
                    cmd += "\"%s\"" % fset[i]
                    if i + 1 != len(fset):
                        if(os.name == "nt"):
                            cmd += "+"
                        else:
                            cmd += " "
                if(os.name != "nt"):
                    cmd += " >"
                dest = dest + file[:-4]

                cmd += " \"" + dest + "\""

            print(cmd)
            if options.debug:
                code = 0
            else:
                code = os.system(cmd)

            if code != 0:
                print("UnRAR failed.  Skipping the rest.")
                return -1, files
            elif options.delete_rars:
                for f in fset:
                    try:
                        if options.debug:
                            print("remove %s%s" % (folder, f))
                        else:
                            os.remove(folder + f)
                    except OSError:
                        print("Error trying to remove %s%s" % (folder, f))
                print("UnRAR: %s RAR files that were processed have been removed." % folder)
            if options.delete_sfv and sfv:
                try:
                    if options.debug:
                        print("remove %s%s" % (folder, sfv))
                    else:
                        os.remove(folder + sfv)
                        print("UnRAR: %s%s removed." % (folder, sfv))
                except OSError:
                    print("UnRAR: Error trying to remove %s%s" % (folder, sfv))

        except OSError:
            print("Error trying to unrar.")
            return -1, files

        except KeyboardInterrupt:
            print("\nUnRAR was interrupted by user.  Exiting")
            sys.exit()

    return code, files

def cleanup(files, options, code):
    if options.delete_par2 and not (code != 0 and options.extract_rars):
        deleteFiles(["*.[Pp][Aa][Rr]2"], "PAR2", None, options.debug)
    deleteFiles(["*.[Nn][Zz][Bb]"], "NZB", None, options.debug)

    # need to delete empty folders
    deleteDirectories(["*[Cc][Dd][1-9]*/", "*[Ss][Aa][Mm][Pp][Ll][Ee]*/"], options.debug)

    return

def deleteFiles(wildcards, word, ignoreList, debug):
    fileList = []
    for wc in wildcards:
        fileList += glob.glob(wc)

    if len(fileList) == 0:
        print("No %s files to delete." % word)
        return 0

    for dfile in fileList:
        cont = True
        if ignoreList:
            for ignore in ignoreList:
                if dfile == ignore:
                    ignoreList.remove(ignore)
                    cont = False
                    break
        if cont:
            try:
                if debug:
                    print("remove %s" % dfile)
                else:
                    os.remove(dfile)
                    print("%s deleted." % dfile)
            except OSError:
                print("Error trying to remove %s" % dfile)
                return -1

    return 0

def deleteDirectories(wildcards, debug):
    dirs = []
    for wc in wildcards:
        dirs += glob.glob(wc)
    for directory in dirs:
        if not os.listdir(directory):
            try:
                if debug:
                    print("rmdir %s" % directory)
                else:
                    os.rmdir(directory)
                    print("%s was empty and removed." % directory)
            except OSError:
                print("%s was empty but could not be removed." % directory)

    return

def main(options, path):
    global origcwd, cwd

    # altbinz mode - fix for altbinz sending lowercase dirname
    if options.exit_if_par2_exists:
        for p in glob.glob(os.path.normpath(os.path.join(path, '..')) + os.sep + "*"):
            if path.lower() == p.lower() or path.lower() == p.lower()[2:]:
                os.chdir(p)
                break
    else:
        os.chdir(path)
    cwd = os.getcwd()
    cwdsolo = os.path.split(cwd)[-1]

    if options.exit_if_par2_exists:
        if len(glob.glob("*.par2")) > 0:
            print("PAR2 files exist.  Exiting...")
            sys.exit()

    if re.search("(subpack|sub.?fix|subs\.)", cwdsolo, re.IGNORECASE):
        print("SUBS directory detected.  Not processing.")
        return
    elif re.search("(sync.?fix)", cwdsolo, re.IGNORECASE):
        print("SYNCFiX directory detected.  Not processing.")
        return
    elif re.search("(sample.?fix)", cwdsolo, re.IGNORECASE):
        print("SAMPLEFiX directory detected.  Not processing.")
        return

    print("\nProcessing: %s\n" % cwd)

    files = get_files(cwd, cwdsolo, options)
    if len(files) == 0:
        # print("No files to process.")
        return

    # debug
    if options.debug:  # or options.verbose
        print("\nFiles:\n[folder, filename, sfv, file_list, type, subtype, destination_dir, rescene_dir]\n")
        for i in range(len(files)):
            print(str(i + 1) + ": " + str(files[i]) + "\n")
        print("")

    files = move_files(files, options, cwdsolo)

    code, files = srs_srr(files, options, cwdsolo)

    code, files = unrar(files, options, code)

    cleanup(files, options, code)

    return

if __name__ == '__main__':
    usage = "usage: %prog -<option 1> -<option N> <@listdirs...>"
    version = "%prog 0.7"
    parser = OptionParser(usage=usage, version=version)
    parser.add_option("--altbinz", dest="exit_if_par2_exists", action="store_true", default=False,
                      help="Alt.Binz mode: exit if par2 exists in directory.  Fixes multiple execution issues.")

    parser.add_option("-R", dest="recursive", action="store_true", default=False,
                      help="Recursively walk through directories looking for releases and processing each.")

    parser.add_option("--no-srr", dest="create_srr", action="store_false", default=True,
                      help="Do not create Re-Scene (SRR) files for RAR sets.")
    parser.add_option("--no-srs", dest="create_srs", action="store_false", default=True,
                      help="Do not create Re-Sample (SRS) files for AVI/MKV Samples.")
    parser.add_option("--separate-srs-srr", dest="include_srs_in_srr", action="store_false", default=True,
                      help="Do not include SRS files in SRR.  This implies --keep-srs.")
    parser.add_option("--keep-srs", dest="delete_srs_after_srr", action="store_false", default=True,
                      help="Do not delete SRS files that are stored in the SRR.")
    parser.add_option("--srs-namep", dest="srs_namep", action="store_true", default=False,
                      help="Rename SRS after release name instead of sample name.")
    parser.add_option("--srs-dir", dest="srs_dir", action="store", default=False,
                      metavar="DIR",
                      help="Directory to move SRS files to in case you keep them all in one directory.")
    parser.add_option("--srr-dir", dest="srr_dir", action="store", default=False,
                      metavar="DIR",
                      help="Directory to move SRR files to in case you keep them all in one directory.")

    parser.add_option("-f", "--filter", dest="filters", action="store", default=["xvid", "divx", "x264", "h264"],
                      help="List of filters for input directory.  If it doesn't match it will skip.  DOES NOT CURRENTLY WORK!")

    parser.add_option("--rename-wrong-case", dest="rename_wrong_case", action="store_true", default=False,
                      help="If a file is missing, it will check the sfv for a lowercase match, and rename to the SFV case if found")
    parser.add_option("--ignore-cds", dest="move_cds", action="store_false", default=True,
                      help="Do not move rar sets to CD1-N folders.")
    parser.add_option("--move-extracted-cds", dest="move_cds_extracted", action="store_true", default=False,
                      help="Move already extracted video files to CD1-N folders.")
    parser.add_option("--ignore-extras", dest="move_extras", action="store_false", default=True,
                      help="Do not move EXTRAS to Extras folder.")
    parser.add_option("--ignore-subs", dest="move_subs", action="store_false", default=True,
                      help="Do not move subs to Subs directory.")
    parser.add_option("--ignore-proof", dest="move_proof", action="store_false", default=True,
                      help="Do not move proof to Proof directory.")
    parser.add_option("-i", "--ignore-sample", dest="move_samples", action="store_false", default=True,
                      help="Do not move sample(s) to Sample directory.")
    parser.add_option("-d", "--delete-sample", dest="delete_samples", action="store_true", default=False,
                      help="Delete samples.  Recommended to keep SRS creation enabled (on by default).")
    parser.add_option("-D", "--delete-sample-force", dest="delete_samples_force", action="store_true", default=False,
                      help="Delete samples, even if SRS fails due to an undersized/bad file.")
    parser.add_option("--ignore-multiple-nfos", dest="move_nfos", action="store_false", default=True,
                      help="Move multiple NFO files (2 or more) to a directory set by --nfos-dir.  Recommended if you like clean TV season folders.")
    parser.add_option("--nfos-dir", dest="nfos_dir", action="store", default="NFOs",
                      metavar="DIR",
                      help="NFOs directory used when moving multiple NFO files to a directory.  Ignore using --ignore-multiple-nfos")

    parser.add_option("-e", "--extract", dest="extract_rars", action="store_true", default=False,
                      help="Extract RAR sets.  Specify directory with -u \"dir\" or -m for main directory.")
    parser.add_option("-u", "--unrar-dir", dest="unrar_dir", action="store",
                      help="Directory to UnRAR files.  Will unRAR in place if not specified.", metavar="\"DIRECTORY\"")
    parser.add_option("-m", "--extract-to-main-dir", dest="extract_to_main_dir", action="store_true", default=False,
                      help="UnRAR to input directory if RAR files are in separate folders within (i.e. CD1/CD2).")
    parser.add_option("-r", "--delete-rars", dest="delete_rars", action="store_true", default=False,
                      help="Delete RAR files after successful UnRAR.  You should probably enable SFV deletion with this.")
    parser.add_option("--ignore-rarset-checking", dest="check_rarsets", action="store_false", default=True,
                      help="Do not check RAR sets for completion (i.e. making sure .r00-.r48 exist if .rar and .r48 exist).  Use this if having issues and you know the rarset is good.")

    parser.add_option("-s", "--delete-sfv", dest="delete_sfv", action="store_true", default=False,
                      help="Delete SFV files when done.  Will not delete if UnRAR fails.")
    parser.add_option("-p", "--delete-par2", dest="delete_par2", action="store_true", default=False,
                      help="Delete PAR2 files when done.  Will not delete if UnRAR fails.")
    parser.add_option("-z", "--keep-nzb", dest="delete_nzb", action="store_false", default=True,
                      help="Do not delete NZB files.")

    parser.add_option("--delete-srr", dest="delete_srr", action="store_true", default=False,
                      help="Delete existing SRR files before SRR creation.  Use this carefully.")

    parser.add_option("--debug", dest="debug", action="store_true", default=False,
                      help="Only list what the program would do.  Will not move, unrar, etc.")

    (options, args) = parser.parse_args()

    if len(args) == 0:
        parser.print_help()
        sys.exit()

    if options.extract_to_main_dir and not options.extract_rars:
        options.extract_rars = True
    if options.unrar_dir:
        options.unrar_dir = os.path.abspath(options.unrar_dir)

    globals()["origcwd"] = os.getcwd()
    sys.path.append(globals()["origcwd"])

    for path in args:
        # file = open(r"E:\DOWNLOAD\xvid.txt", 'a')
        # file.write(path + '\n')
        # file.write(options.unrar_dir + '\n')
        # file.write('\n')
        # file.close()

        os.chdir(globals()["origcwd"])

        if not os.path.isdir(path):
            print("Argument %s is not a valid directory." % path)
            continue

        if not options.recursive:
            main(options, path)
        else:
            # recursive
            current = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
            for root, dirs, files in os.walk(path):
                found = False
                if current in root:
                    continue
                for mfile in files:
                    if re.search("\.(rar|00[0-1]|avi|mkv|m4v|mp4|wmv|ts|ogm|divx|mpg|mpeg)$", mfile):
                        found = True
                        break
                if not found:
                    for directory in dirs:
                        if re.match("^cd[1-9]$", directory, re.IGNORECASE):
                            found = True
                            break
                if found:
                    current = root
                    main(options, root)
                    os.chdir(origcwd)

            print("\n\n\nDone with %s directory.\n\n" % path)


from aiogram import executor
from pymongo.errors import DuplicateKeyError

import config
from manipulationWithName import returnWithoutSmiles
from simple import *
from config import chat_report, release, test
from mongoDB import usersNameExists, createColl, setDefaultNameUser, updateDefaultNameUser, deleteChatTrigger, \
    triggerChatExists
from user_triggersDB import deleteUserTrigger, triggerUserExists
from tools import textWithoutCommand

import chatTriggers
import userTriggers
import gamepoll.gamepoll
import gamepoll.addingList

@dp.message_handler(commands=['setme', 'setMe', 'set_me'], commands_prefix='!/.')
async def setMyDefaultName(message: types.Message):
    msg = message.from_user
    if message.reply_to_message and message.from_user.id == config.creator:
        msg = message.reply_to_message.from_user

    name = textWithoutCommand(message.text)
    if name is None:
        await message.answer('Используйте /setme [Ваше имя] где вместо [Ваше имя] ставите ваш '
                             'никнейм или имя. ', parse_mode='HTML')
        return

    if not usersNameExists(msg.id):
        setDefaultNameUser(user_id=msg.id, name=name,
                           telegram_name=msg.first_name,
                           username=msg.username)
    else:
        updateDefaultNameUser(user_id=msg.id, name=name)
    await message.answer(f'{name} установлен.')

@dp.message_handler(commands=['setname', 'set_name'], commands_prefix='!/.')
async def setMyDefaultNameByCoping(message: types.Message):
    if message.reply_to_message and message.from_user.id == config.creator:
        name = message.reply_to_message.from_user.first_name
        msg = message.reply_to_message.from_user
    else:
        name = message.from_user.first_name
        msg = message.from_user

    if not usersNameExists(msg.id):
        setDefaultNameUser(user_id=msg.id, name=name,
                           telegram_name=msg.first_name,
                           username=msg.username)
    else:
        updateDefaultNameUser(user_id=msg.id, name=name)

    await message.answer(f'{name} установлен.')

@dp.message_handler(commands=['check'], commands_prefix='!/.')
async def checkHowItWorks(message: types.Message):
    nickname = textWithoutCommand(message.text)
    if not nickname is None:
        ready_nick = returnWithoutSmiles(nickname)
        if ready_nick == 0:
            result = [i for i in nickname if i in normal_char or i in caps_normal_char]
            ready_nick = "".join(result).lstrip()
            if len(ready_nick) == 0:
                ready_nick = nickname
        await message.answer(f"🎻ʀᴇ| {ready_nick} 🌅")

@dp.message_handler(content_types=['new_chat_members'])
async def addNewChatToColl(msg: types.Message):
    if msg["new_chat_member"]["id"] == test or msg["new_chat_member"]["id"] == release:
        buttons = [
            types.InlineKeyboardButton(text="Канал бота", url="https://t.me/savonarola_chan"),
        ]
        keyboard = types.InlineKeyboardMarkup(row_width=1)
        keyboard.add(buttons[0])
        await msg.answer('Привет. Я бот для турнирных чатов. Документация еще не готова но осталось совсем немного. '
                         'Но уже есть телеграм канал где объясняются некоторые фишки бота.', reply_markup=keyboard)
        try:
            createColl(chat_id=msg.chat.id, name=msg.chat.title)

            m = await bot.send_message(chat_id=chat_report, text=f'NEW\ngroup: {msg.chat.title}, id: {msg.chat.id}')
            await bot.pin_chat_message(chat_id=chat_report, message_id=m['message_id'])
        except DuplicateKeyError:
            pass
    elif msg["new_chat_member"]["id"] == creator:
        await msg.answer(f'Привет, {msg.from_user.first_name}!⚡')

@dp.message_handler(commands=['del', 'delete', 'del_trigger'], is_admin=True, commands_prefix='!/.')
async def deleteTrigger(message: types.Message):
    try:
        trigger_name = message.text.split()[1]
        if not message.chat.type == 'private':
            deleteChatTrigger(chatId=message.chat.id, trigger_name=trigger_name)
            if not triggerChatExists(chatId=message.chat.id, trigger_name=trigger_name):
                await message.answer(f'Триггер {trigger_name} не существовал в списке.')
            else:
                await message.answer(f'Триггер {trigger_name} был удален из списка чата.')
    except IndexError:
        await message.answer('/del_trigger [имя_триггера]', parse_mode='HTML')

@dp.message_handler(commands=['del', 'delete', 'del_trigger'], commands_prefix='!/.')
async def deleteTrigger2(message: types.Message):
    try:
        trigger_name = message.text.split()[1]
        if message.chat.type == 'private':
            deleteUserTrigger(user_id=message.from_user.id, trigger_name=trigger_name)
            if not triggerUserExists(user_id=message.from_user.id, trigger_name=trigger_name):
                await message.answer(f'Триггер {trigger_name} не существовал в вашем списке.')
            else:
                await message.answer(f'Триггер {trigger_name} был удален из вашего списка.')
    except IndexError:
        await message.answer('/del_trigger [имя_триггера]', parse_mode='HTML')

@dp.message_handler(content_types=["text"])
async def PinMafiaList(message: types.Message):
    if message.chat.id == config.command_chat or message.chat.id == -1001829614132:
        if message.text.startswith('Запомни своих союзников'):
            await bot.pin_chat_message(chat_id=message.chat.id, message_id=message.message_id)
        elif message.text.startswith("Добро пожаловать во"):
            await bot.delete_message(chat_id=message.chat.id, message_id=message.message_id)
    elif message.chat.id == -1001591495273:
        if message.text.startswith('Запомни своих союзников'):
            await bot.pin_chat_message(chat_id=message.chat.id, message_id=message.message_id)

if __name__ == "__main__":
    executor.start_polling(dp, skip_updates=True)


from turtle import Turtle, Screen, colormode
import random

colormode(255)
def random_color():
    r=random.randint(0,255)
    g=random.randint(0,255)
    b=random.randint(0,255)
    return (r,g,b)

tim=Turtle()

tim.speed('fastest')
tim.shape('turtle')

# using right direction
# for _ in range(900):
#     tim.left(_)
#     tim.circle(100)

def draw_spirograph(size_of_graph):

    for _ in range(360//size_of_graph):
        tim.color(random_color())
        tim.circle(100)
        tim.setheading(tim.heading()+size_of_graph)

draw_spirograph(5)

screen=Screen()
screen.exitonclick()


import torch.nn as nn
import torch

class TD_LSTM(nn.Module):
	def __init__(self):
		super().__init__()
		self.rnn = nn.LSTM(10, 20, 3) # params: embedding, hidden_size, num_layers
	def forward(self):
		input = torch.randn(5, 3, 10)
		h0 = torch.randn(2, 3, 20)
		c0 = torch.randn(2, 3, 20)
		output, (hn, cn) = self.rnn(input, (h0, c0))
		print('output==hn', output[-1,:,:]==hn[-1])

model = TD_LSTM()
for i in model.parameters():
	print(i.size())

def _reset_params(model):
	for child in model.children():
		print('child-->', child, type(child))
		i = 0
		for p in child.parameters():
			i=i+1
			print('i-->',i)
			print('p-->', p)
	# 		if p.requires_grad:
	# 			if len(p.shape) > 1:
	# 				opt.initializer(p)
	# 			else:
	# 				stdv = 1. / math.sqrt(p.shape[0])
	# 				torch.nn.init.uniform_(p, a=-stdv, b=stdv)

# _reset_params(model)


from distutils.cmd import Command
from distutils.command.install import INSTALL_SCHEMES
import os
import re
from setuptools import setup
from distutils.command.build import build as distutils_build
from setuptools.command.install import install as setuptools_install
from setuptools.command.sdist import sdist as setuptools_sdist

import wirecloud.platform

class bcolors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'

    def disable(self):
        self.HEADER = ''
        self.OKBLUE = ''
        self.OKGREEN = ''
        self.WARNING = ''
        self.FAIL = ''
        self.ENDC = ''

class build(distutils_build):

    """Customized setuptools build command - compile po files before creating the distribution package."""

    sub_commands = [('compiletranslations', None)] + distutils_build.sub_commands

class sdist(setuptools_sdist):

    """Customized setuptools sdist command - compile po files before creating the distribution package."""

    sub_commands = [('compiletranslations', None)] + setuptools_sdist.sub_commands

class compiletranslations(Command):

    description = 'compile message catalogs to MO files via django compilemessages'
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):

        try:
            from django.core.management import call_command
        except:
            import pip
            pip.main(['install', 'Django>=1.6,<1.10'])

            from django.core.management import call_command

        oldwd = os.getcwd()
        wirecloud_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'wirecloud'))
        for subpath in os.listdir(wirecloud_path):
            current_path = os.path.join(wirecloud_path, subpath)
            if os.path.isdir(os.path.join(current_path, 'locale')):
                os.chdir(current_path)
                try:
                    call_command('compilemessages')
                except Exception as e:
                    print('Error compiling translations for module %(module)s: %(error)s' % {'module': subpath.replace('/', '.'), 'error': e})

        os.chdir(oldwd)

class install(setuptools_install):

    """Customized setuptools install command - prints info about the license of Wirecloud after installing it."""
    def run(self):
        setuptools_install.run(self)

        print('')
        print(bcolors.HEADER + 'License' + bcolors.ENDC)
        print('')
        print("Wirecloud is licensed under a AGPLv3+ license with a classpath-like exception \n" +
              "that allows widgets/operators and mashups to be licensed under any propietary or \n" +
              "open source license.")
        print('')
        license_file = os.path.join(self.install_purelib, 'wirecloud', 'LICENSE')
        print('A copy of the license has been installed at: ' + bcolors.WARNING + license_file + bcolors.ENDC)

for scheme in INSTALL_SCHEMES.values():
    scheme['data'] = scheme['purelib']

setup(
    name='wirecloud',
    version=wirecloud.platform.__version__,
    description='Widgets Container and Mashup edition tools for composing end-user centric mashup applications.',
    long_description='',
    author='CoNWeT Lab',
    author_email='wirecloud@conwet.com',
    url='http://github.com/Wirecloud/wirecloud',
    license='AGPLv3+ with classpath-like exception',
    packages=('wirecloud',),
    entry_points={
        'console_scripts': (
            'wirecloud-admin = wirecloud.commons.wirecloud_admin:execute_from_command_line',
        ),
    },
    include_package_data=True,
    install_requires=(
        'Django>=1.6,<1.10',
        'south>=1.0,<2.0',
        'lxml>=2.3',
        'django-appconf>=1.0.1,<2.0',
        'django_compressor>=1.4,<3.0',
        'rdflib>=3.2.0',
        'requests>=2.1.0',
        'selenium>=2.41',
        'pytz',
        'django_relatives',
        'user-agents',
        'regex',
        'markdown',
        'whoosh>=2.5.6',
        'pycrypto',
        'pyScss>=1.3.4,<2.0',
        'Pygments',
        'pillow',
    ),
    extras_require={
        ":python_version < '3.2'": ('futures>=2.1.3',),
    },
    tests_require=(
        'django-nose',
        'mock>=1.0,<2.0',
    ),
    setup_requires={
        'wheel>=0.24',
    },
    classifiers=(
        'Development Status :: 5 - Production/Stable',
        'Environment :: Web Environment',
        'Framework :: Django',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: JavaScript',
        'Topic :: Software Development :: Libraries :: Python Modules',
    ),
    cmdclass={
        'build': build,
        'install': install,
        'sdist': sdist,
        'compiletranslations': compiletranslations
    },
)


'''

    Name = Saket Ahlawat
    Branch = ECE
    SID = 21105105

    Question : 1. Take an array of numbers.
               2. Use it to construct a BST.
               3. Write separate functions to delete a node from BST and Array.
               4. Now compute the space complexity of both structures.
'''

# Function for deleting the element from the array

def deleteArr(arr,val):
    for i in range(len(arr)):
        if arr[i] == val:
            arr.pop(i)

# Creating the binary tree node

class BinaryTreeNode:
    def __init__(self,data):
        self.data=data
        self.left=None
        self.right=None

# Constructing the BST (Binary Search Tree)

class BST:
    def __init__(self):                 # constructor
        self.root=None

    def printTreeHelper(self,root):             # using helper function to use recursion
        if root==None:
            return
        print(root.data,end=":")
        if root.left!=None:
            print("L",root.left.data,end=",")
        if root.right!=None:
            print("R",root.right.data,end="")
        print()
        self.printTreeHelper(root.left)
        self.printTreeHelper(root.right)

    def printTree(self):                        # function for printing the BST
        self.printTreeHelper(self.root)

    def insertHelper(self,root,value):           # creating the helper function to use recursion
        if root == None:
            node = BinaryTreeNode(value)
            return node
        if root.data > value:
            root.left = self.insertHelper(root.left,value)
            return root
        else:
            root.right = self.insertHelper(root.right,value)
            return root

    def insert(self,value):          # function for inserting the data into BST
        self.root = self.insertHelper(self.root,value)

    def min(self,root):             # function for finding the minimum element in the BST
        if root == None:
            return 10000
        if root.left == None:
            return root.data
        return self.min(root.left)

    def deleteDataHelper(self,root,value):       # creating the helper function for using recursion
        if root == None:
            return None
        if root.data < value:
            newRightNode = self.deleteDataHelper(root.right,value)
            root.right = newRightNode
            return root
        if root.data > value:
            newLeftNode = self.deleteDataHelper(root.left,value)
            root.left = newLeftNode
            return root

        #root is leaf
        if root.left==None and root.right==None:
            return None

        # root has one child
        if root.left==None:
            return root.right
        if root.right==None:
            return root.left

        #root has 2 children
        replacement=self.min(root.right)
        root.data=replacement
        newRightNode=self.deleteDataHelper(root.right,replacement)
        root.right=newRightNode
        return root

    def deleteData(self,data):                  # function for deleting the element of the BST
        newRoot=self.deleteDataHelper(self.root,data)
        self.root=newRoot

# I have printed BST as   ->   root : L (left data) , R (right data)

# ARRAY :
n = int(input("enter the length of the array : "))
arr = [int(ele) for ele in input("write elements of the array separated by space").split()]
deleteArr(arr)
print(arr)

# BST :
b=BST()
b.insert(10)
b.insert(5)
b.insert(7)
b.insert(6)
b.insert(8)
b.insert(12)
b.insert(11)
b.insert(15)
b.printTree()

b.deleteData(10)
b.printTree()

'''
    Space Complexity : if we have n elements then the space complexity of both the array and the
    binary search tree has space complexity of O(n).
'''

Data Structure/Comparing Binary Search and Binary Search Tree (BST).py


# -*- coding: utf-8 -*-
"""
Created on Fri Dec 16 10:23:00 2022

@author: user
"""

total = person = score =0   #total总数  person学生人数  score成绩
while(score != -1) :
    person += 1   #计算人数
    total += score   #计算总成绩
    score = eval(input("请输入第%d位学生的成绩："% person))

arg = total / (person - 1)  #计算平均分数
print("本班总成绩：%d分，平均成绩为：%5.2f 分"% (total, arg))


#!/usr/bin/python3

def safe_print_list(my_list=[], x=0):
    for item in range(0, x):
        try:
            print(f"{my_list[item]}", end="")
        except IndexError:
            return (item - 1)
    print("")
    return (x)


# -*- coding: utf-8 -*-
# @Time    : 2021/4/19 21:55
# @File    : 142. 环形链表 II.py
from leetcode import *

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        fast = slow = head
        while fast and fast.next and fast.next.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                break
        if not (fast and fast.next and fast.next.next):
            return None
        slow = head
        while slow != fast:
            slow = slow.next
            fast = fast.next
        return fast


#Function to know even or odd
def even_odd(num):
    if num % 2 ==0:
        print('Even');
    else:
        print('Odd')
    i = 1
    while i <= 9:
        num += 2
        print (num)
        i += 1

#Main Function
if __name__== '__main__':

    try:
        num = float(input('Give number: '))
        if num.is_integer():
            even_odd(int(num))
        else:
            print('Enter integer ONLY: ' )
    except ValueError:
        print('Invaid input')


"""
VERSION
- Python 3

FUNCTION
- Download images from urls.
"""

from urllib.request import urlretrieve
import os
import multiprocessing
from optparse import OptionParser
import pandas as pd
import socket

socket.setdefaulttimeout(10)

op = OptionParser()
op.add_option('--clss', action='store', type=str,
              help='The image class (e.g., vietnam war).')
op.add_option('--source', action='store', type=str,
              help='The media source.')
(opts, args) = op.parse_args()

PATH_HOME = os.path.expanduser('~') + '/Projects/iow'
PATH_DATA = PATH_HOME + '/data/IR/' + opts.source \
    + '/urls_image/' + opts.clss + '/'
PATH_OUT = PATH_HOME + '/data/DP/vision/' + opts.source + '/' \
    + opts.clss + '/'
N_THREADS = 16

if not os.path.exists(PATH_OUT):
    os.makedirs(PATH_OUT)
# ___________________________________________________________________________

def download(lock, ns, info_image):
    try:
        lock.acquire()
        ns.counter_total += 1
        lock.release()
        name_image = info_image['name']
        url_image = info_image['url']
        if name_image not in os.listdir(PATH_OUT):
            urlretrieve(url_image, PATH_OUT + name_image)
            print('Downloaded an image: %s' % name_image)
            lock.acquire()
            ns.counter += 1
            lock.release()
        else:
            # print('File already exists: %s' % name_image)
            return
    except Exception as e:
        print(e)
        return
# ___________________________________________________________________________

def main():
    infos_image = []
    for name_file in os.listdir(PATH_DATA):
        info_image = pd.read_pickle(PATH_DATA + name_file)
        infos_image.append(info_image)
    '''
    Enabling multiprocessing.
    '''
    manager = multiprocessing.Manager()
    ns = manager.Namespace()
    ns.counter = 0
    ns.counter_total = 0
    lock = manager.Lock()
    processes = multiprocessing.Pool(N_THREADS)
    from functools import partial
    func = partial(download, lock, ns)
    processes.map(func, infos_image)
    print('The image retrieval is finished (%d/%d).'
          % (ns.counter, ns.counter_total))

if __name__ == '__main__':
    main()


import torch
import numpy as np

class AverageMeter(object):
    def __init__(self):
        self.reset()

    def reset(self):
        self.val = 0
        self.avg = 0
        self.sum = 0
        self.count = 0

    def update(self, val, n=1):
        self.val = val
        self.sum += val * n
        self.count += n
        self.avg = self.sum / self.count

class Crop(object):
    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple, list))
        if isinstance(output_size, int):
            self.output_size = (output_size, output_size)
        else:
            assert len(output_size) == 2
            self.output_size = output_size

    def __call__(self, sample):
        image, label = sample["image"], sample["label"]
        top, left = sample["top"], sample["left"]
        new_h, new_w = self.output_size
        sample["image"] = image[top: top + new_h,
                          left: left + new_w]
        sample["label"] = label[top: top + new_h,
                          left: left + new_w]

        return sample

class Flip(object):
    def __call__(self, sample):
        flag_lr = sample["flip_lr"]
        flag_ud = sample["flip_ud"]
        if flag_lr == 1:
            sample["image"] = np.fliplr(sample["image"])
            sample["label"] = np.fliplr(sample["label"])
        if flag_ud == 1:
            sample["image"] = np.flipud(sample["image"])
            sample["label"] = np.flipud(sample["label"])

        return sample

class ToTensor(object):
    def __call__(self, sample):
        image, label = sample["image"], sample["label"]
        image = np.ascontiguousarray(image.transpose((2, 0, 1))[np.newaxis, :])
        label = np.ascontiguousarray(label.transpose((2, 0, 1))[np.newaxis, :])
        sample["image"] = torch.from_numpy(image).float()
        sample["label"] = torch.from_numpy(label).float()
        return sample

def normalize(x, centralize=False, normalize=False, val_range=255.0):
    if centralize:
        x = x - val_range / 2
    if normalize:
        x = x / val_range

    return x

def normalize_reverse(x, centralize=False, normalize=False, val_range=255.0):
    if normalize:
        x = x * val_range
    if centralize:
        x = x + val_range / 2

    return x


#!/usr/bin/env python

''' Script that creates input files (AssayInfo & LocusInfo)
    required by GTSeq pipeline

    input:  sequence file (fastq)
            vcf file (vcf)
            primer file (csv):
              Contains the contig #, SNP position, and
              primer seq (FWD primer only for paired-end
              seqs), with the format:
                    Contig#-POS,FWD-primer
              where Contig#-POS is the SNP name

    output: AssayInfo.txt - first input file
            LocusInfo.txt - second input file
            missing_snps.txt - file with snps that were
                               not added to prior 2 output files

    NOTE: make sure snp/contig names match between files

    usage: ./gtseq-make_infiles.py contigs.fa snp-info.vcf primers.csv

    CYP 09/26/2017
'''
import re
import sys
from Bio import SeqIO
from Bio.Seq import Seq

if len(sys.argv) != 4:
  print("ERROR: Please specify a contigs (fasta), snp (vcf), and " + \
          "primer file (csv).")
  sys.exit()

# global variables
fasta_file = open(sys.argv[1], 'r')
vcf_file = sys.argv[2]
primer_file = sys.argv[3]
vcf_dict = {}
missing_list = []
ref_allele, alt_allele = '', ''
ref_probe, alt_probe = '', ''

# change these variables as needed
FLANK = 3 # num of bps that should flank snp in probe
DEBUG = False # specify True to display DEBUG msgs
REMOVE_ADAPTER = False # specify True to remove adapter sequence
adapter = 'CGACAGGTTCAGAGTTCTACAGTCCGACGATC' # adapter to remove

# Process FASTA file
# create dictionary of contigs from fasta file
fasta_dict = SeqIO.to_dict(SeqIO.parse(fasta_file, "fasta"))
fasta_file.close()

# Process VCF file
# create dictionary of all snps and their ref and
# alt alleles found in VCF file
with open(vcf_file, 'r') as vcf:
  for line in vcf:
    if not line.startswith('#'):
      items = line.split('\t')
      # the full snp name (as it appears in
      # primer file) is the dict key
      name = items[0]+'-'+items[1]
      # items[3] = ref allele
      # items[4] = alt allele
      vcf_dict[name] = [items[3],items[4]]

# file with contents of vcf_dict
if DEBUG:
  with open('vcf_dict.txt', 'w') as out:
    for key in vcf_dict:
      out.write(key+': ')
      for value in vcf_dict[key]:
        out.write(value+' ')
      out.write('\n')

assayinfo = open("AssayInfo.txt", 'w')
locusinfo = open("LocusInfo.csv", 'w')
header = True
with open(primer_file) as prim:
  for line in prim:
    # skip header
    if header:
      header = False
      continue
    # collect the contig#, SNP position, and primer seq
    # from the primer file (format: Contig#-POS,primer)
    snp_name = line.split(',')[0]
    name_pos = snp_name.split('-')
    contig_name = name_pos[0]
    snp_pos = int(name_pos[1])
    if DEBUG: print(snp_pos)
    primer = line.split(',')[1]
    if ADD_REV_PRIMER: rev_primer = line.split(',')[2] #for including rev in assayinfo

    # if enabled, shave off adapter from each primer
    if REMOVE_ADAPTER:
      primer = primer.replace(adapter,'')
      if ADD_REV_PRIMER: rev_primer = rev_primer.replace(adapter,'')
    if ADD_REV_PRIMER: rev_primer_rc = Seq(rev_primer).reverse_complement()

    # set start and end positions for probe sequence
    probe_start = snp_pos-FLANK-1
    probe_end = snp_pos+FLANK-1

    if contig_name in fasta_dict:
        # collect probe sequence from fasta contigs dict
        fasta = fasta_dict[contig_name].seq
        probeseq = fasta[probe_start:probe_end+1]

    if not snp_name in vcf_dict:
      # if snp name cannot be found in vcf snp dictionary,
      # notify user, add to list of missing snps
      print('%s could not be found in vcf file' % snp_name)
      missing_list.append(snp_name)
    else:
      # otherwise find snp name in vcf dictionary to
      # collect reference and alternate alleles
      ref_allele = vcf_dict[snp_name][0]
      alt_allele = vcf_dict[snp_name][1]

      # create ref,alt probe sequences using the ref,alt alleles
      # and the probeseq fragment collect from the fasta file
      ref_probe = str(probeseq[:FLANK]+ref_allele+probeseq[FLANK+1:])
      alt_probe = str(probeseq[:FLANK]+alt_allele+probeseq[FLANK+1:])

      # snp_dict[snp_name] = [primer,ref_allele,alt_allele,ref_probe,alt_probe]
      # write all relevant values in specific format to each outfile
      if ADD_REV_PRIMER:
        assayinfo.write(snp_name+'\t'+primer+'\t'+ref_probe+'\t'+alt_probe+'\t'+str(rev_primer_rc)+'\n')
      else:
        assayinfo.write(snp_name+'\t'+primer+'\t'+ref_probe+'\t'+alt_probe+'\n')
      locusinfo.write(snp_name+','+ref_allele+','+alt_allele+','+ref_probe+','+alt_probe+','+primer+'\n')

# write all snps that weren't added to out files
# to a file called missing_snps.txt
with open('missing_snps.txt', 'w') as out:
  for snp in missing_list:
    out.write(snp+'\n')

assayinfo.close()
locusinfo.close()


#!/usr/bin/python
import argparse
import numpy as np
import sebastians_matplotlib_addons as splt
import os
import plenopy

parser = argparse.ArgumentParser(
    prog="plenopy/trigger/scripts/plot.py",
    description=("Plot the trigger's geometry of the plenoscope"),
)

parser.add_argument(
    "trigger_geometry_path",
    metavar="PATH",
    type=str,
    help="directory containing the trigger's geometry.",
)

args = parser.parse_args()

trg_geom_dir = os.path.abspath(args.trigger_geometry_path)
plot_dir = os.path.join(trg_geom_dir, "plot")

os.makedirs(plot_dir, exist_ok=True)

MATPLOTLIB_RCPARAMS = {
    "mathtext.fontset": "cm",
    "font.family": "STIXGeneral",
}
FIGURE_STYLE = {"rows": 720, "cols": 1280, "fontsize": 1.0}
AX_SPAN = [0.2, 0.2, 0.75, 0.75]
splt.matplotlib.rcParams.update(MATPLOTLIB_RCPARAMS)

trigger_geometry = plenopy.trigger.geometry.read(trg_geom_dir)
trigger_summation_statistics = plenopy.trigger.geometry.init_summation_statistics(
    trigger_geometry=trigger_geometry
)

stats = trigger_summation_statistics

cxy_lim = [-3.5, 3.5]

for focus in range(trigger_geometry["number_foci"]):

    fig = splt.figure(FIGURE_STYLE)
    ax = splt.add_axes(fig=fig, span=AX_SPAN)
    plenopy.plot.image.add2ax(
        ax=ax,
        I=np.array(stats["foci"][focus]["number_lixel_in_pixel"]),
        px=np.rad2deg(trigger_geometry["image"]["pixel_cx_rad"]),
        py=np.rad2deg(trigger_geometry["image"]["pixel_cy_rad"]),
        colormap="viridis",
        hexrotation=30,
        vmin=None,
        vmax=None,
        colorbar=True,
    )
    ax.set_xlim(cxy_lim)
    ax.set_ylim(cxy_lim)
    ax.set_aspect("equal")
    fig.suptitle("number lixel in pixel")
    ax.set_xlabel(r"$c_x\,/\,1^{\circ}$")
    ax.set_ylabel(r"$c_y\,/\,1^{\circ}$")
    fig.savefig(
        os.path.join(
            plot_dir, "focus_{:06d}_lixel_in_pixel_overview.jpg".format(focus),
        )
    )
    splt.close(fig)

    fig = splt.figure(FIGURE_STYLE)
    ax = splt.add_axes(fig=fig, span=AX_SPAN)
    num_lip = stats["foci"][focus]["number_lixel_in_pixel"]
    bin_edges = np.arange(np.min(num_lip), np.max(num_lip) + 1)
    splt.ax_add_histogram(
        ax=ax,
        bincounts=np.histogram(num_lip, bins=bin_edges)[0],
        bin_edges=bin_edges,
        linestyle="-",
        linecolor="k",
    )
    ax.set_xlabel("number lixel in pixel")
    ax.set_ylabel("number pixel")
    ax.semilogy()
    ax.grid(color="k", linestyle="-", linewidth=0.66, alpha=0.1)
    fig.savefig(
        os.path.join(
            plot_dir,
            "focus_{:06d}_lixel_in_pixel_histogram.jpg".format(focus),
        )
    )
    splt.close(fig)

    fig = splt.figure(FIGURE_STYLE)
    ax = splt.add_axes(fig=fig, span=AX_SPAN)
    num_pil = stats["foci"][focus]["number_pixel_in_lixel"]
    bin_edges = np.arange(np.min(num_pil), np.max(num_pil) + 1)
    splt.ax_add_histogram(
        ax=ax,
        bincounts=np.histogram(num_pil, bins=bin_edges)[0],
        bin_edges=bin_edges,
        linestyle="-",
        linecolor="k",
    )
    ax.set_xlabel("number pixel in lixel")
    ax.set_ylabel("number lixel")
    ax.semilogy()
    ax.grid(color="k", linestyle="-", linewidth=0.66, alpha=0.1)
    fig.savefig(
        os.path.join(
            plot_dir,
            "focus_{:06d}_pixel_in_lixel_histogram.jpg".format(focus),
        )
    )
    splt.close(fig)


from datetime import datetime
from typing import Any, Annotated, Optional

import structlog
from fastapi import Depends

from src.commons import set_context_value, get_ctx
from .internal.odoo_manager import OdooManager, get_odoo_provider
from .internal.odoo_repo import OdooRepo, get_odoo_repo, RedisKeys
from .internal.ordercast_manager import OrdercastManager, get_ordercast_manager
from .internal.utils import (
    has_objects,
    set_ordercast_id,
    set_user_ordercast_id,
    validate_partners,
)
from .internal.utils.builders import (
    get_partner_data,
    get_attribute_data,
    get_product_data,
    get_product_variant_data,
    get_delivery_option_data,
    get_pickup_location_data,
)
from .internal.utils.helpers import set_attribute_value_ordercast_id
from .internal.validators import (
    validate_products,
    validate_categories,
    validate_product_variants,
    validate_attributes,
    validate_delivery_options,
    validate_pickup_locations,
)
from .internal.webhook_handler import WebhookHandler

logger = structlog.getLogger(__name__)

class OdooSyncManager:
    def __init__(
        self,
        repo: OdooRepo,
        odoo_manager: OdooManager,
        ordercast_manager: OrdercastManager,
        webhook_handler: WebhookHandler,
    ) -> None:
        self.repo = repo
        self.odoo_manager = odoo_manager
        self.ordercast_manager = ordercast_manager
        self.webhook_handler = webhook_handler

    def handle_webhook(self, topic: str, **kwargs: dict[str, Any]) -> None:
        self.webhook_handler.handle(topic=topic, **kwargs)

    def sync(self) -> None:
        logger.info("Load common data")
        self.load_commons()

        logger.info("Start full syncing with Odoo.")
        self.sync_users()

        logger.info("Start receiving products from Odoo")
        self.sync_products(full_sync=True)

        logger.info(
            "Start receiving order data from Odoo => "
            "delivery methods & pickup locations"
        )
        self.sync_delivery_methods_and_pickup_locations()

        logger.info("Start sync orders with Odoo")
        self.sync_orders_with_odoo(
            from_date=self.repo.get_key(RedisKeys.LAST_SUCCESSFUL_ORDERCAST_SYNC_DATE)
        )
        logger.info("Start syncing orders with Ordercast")
        self.sync_orders_with_ordercast(
            from_date=self.repo.get_key(RedisKeys.LAST_SUCCESSFUL_ODOO_SYNC_DATE)
        )
        # self.check_deletion()?

    def sync_users(self) -> None:
        logger.info("Started syncing user's data with Ordercast")
        self.sync_users_from_odoo_to_ordercast()
        self.sync_users_from_ordercast_to_odoo()

    def sync_users_from_odoo_to_ordercast(self) -> None:
        existing_odoo_users = self.repo.get_list(key=RedisKeys.USERS)
        partners = self.odoo_manager.receive_partner_users(
            exclude_user_ids=[p.odoo_id for p in existing_odoo_users]  # type: ignore
        )
        validate_partners(
            partners=partners, ordercast_users=self.ordercast_manager.get_users()
        )

        logger.info(f"Received partners => {len(partners)}, started saving them.")

        users_to_sync = [
            get_partner_data(partner) for partner in partners if partner["email"]
        ]
        self.ordercast_manager.upsert_users(users_to_sync=users_to_sync)

        users_with_ordercast_id = set_user_ordercast_id(
            users_to_sync, source=self.ordercast_manager.get_users
        )
        self.odoo_manager.save_users(users_to_sync=users_with_ordercast_id)
        self.sync_billing(users_to_sync=users_with_ordercast_id)

    def sync_users_from_ordercast_to_odoo(self) -> None:
        users_to_sync = self.ordercast_manager.get_users_with_related_entities()
        logger.info(
            f"Received merchants => {len(users_to_sync)}, started syncing with Odoo."
        )
        self.odoo_manager.sync_users(users_to_sync)

    def sync_billing(self, users_to_sync: list[dict[str, Any]]) -> None:
        for partner in users_to_sync:
            self.ordercast_manager.create_billing_address(partner)
            self.ordercast_manager.create_shipping_address(partner)

    def sync_products(self, full_sync: bool = False) -> None:
        self.sync_categories_to_ordercast()
        self.sync_attributes_to_ordercast()
        self.sync_products_to_ordercast(full_sync=full_sync)
        self.sync_product_variants_to_ordercast(
            full_sync=full_sync,
        )

    def sync_categories_to_ordercast(self) -> None:
        categories = self.odoo_manager.get_categories()
        logger.info(
            f"""
            Received {len(categories['objects']) if categories['objects'] else 0}
            categories, start saving them.
            """
        )
        if categories:
            validate_categories(categories)
            self.ordercast_manager.save_categories(
                categories_to_sync=categories["objects"]
            )
            self.odoo_manager.save_categories(
                categories_to_sync=set_ordercast_id(
                    items=categories["objects"],
                    source=self.ordercast_manager.get_categories,
                )
            )

    def sync_attributes_to_ordercast(self) -> None:
        attributes = self.odoo_manager.get_product_attributes()

        logger.info(
            f"""
            Received {len(attributes['objects']) if attributes['objects'] else 0}
            attributes with total of
            {sum([len(a['values']) for a in attributes['objects'] if 'values' in a])}
            values, start saving them.
            """
        )

        if attributes:
            validate_attributes(attributes)
            attributes_to_sync = [
                get_attribute_data(attribute) for attribute in attributes["objects"]
            ]
            attribute_values_to_sync = {
                a["id"]: a.get("values", []) for a in attributes["objects"]
            }

            logger.info("Syncing attributes")
            self.ordercast_manager.save_attributes(
                attributes_to_sync=attributes_to_sync
            )
            attributes_with_ordercast_id = set_ordercast_id(
                items=attributes_to_sync,
                source=self.ordercast_manager.get_attributes,
            )
            self.odoo_manager.save_attributes(
                attributes_to_sync=attributes_with_ordercast_id
            )

            self.sync_attribute_values(
                attribute_values_to_sync=attribute_values_to_sync,
                attributes_with_ordercast_id=attributes_with_ordercast_id,
            )

    def sync_attribute_values(
        self,
        attribute_values_to_sync: dict[int, Any],
        attributes_with_ordercast_id: list[dict[str, Any]],
    ) -> None:
        logger.info("Syncing attribute values")

        attributes_odoo_to_ordercast_mapper = {
            a["id"]: a["ordercast_id"] for a in attributes_with_ordercast_id
        }

        self.ordercast_manager.save_attribute_values(
            attributes_odoo_to_ordercast_mapper=attributes_odoo_to_ordercast_mapper,
            attribute_values_to_sync=attribute_values_to_sync,
        )
        self.odoo_manager.save_attribute_values(
            attribute_values_to_sync=set_attribute_value_ordercast_id(
                attributes_odoo_to_ordercast_mapper=attributes_odoo_to_ordercast_mapper,
                attribute_values=[
                    value
                    for attribute_value in attribute_values_to_sync.values()
                    for value in attribute_value
                ],
                source=self.ordercast_manager.get_attribute_values,
            )
        )

    def sync_products_to_ordercast(self, full_sync: bool = False) -> None:
        last_sync_date = (
            None if full_sync else self.repo.get_key(RedisKeys.LAST_PRODUCT_SYNC)
        )
        products = self.odoo_manager.get_products(last_sync_date)

        logger.info(
            f"""Received {len(products['objects']) if has_objects(products) else 0}
            products, start saving them.
            """
        )

        if has_objects(products):
            validate_products(products)

            products_to_sync = [
                get_product_data(product, odoo_repo=self.repo)
                for product in products["objects"]
            ]

            self.ordercast_manager.save_products(products_to_sync=products_to_sync)
            self.odoo_manager.save_products(
                products_to_sync=set_ordercast_id(
                    items=products_to_sync,
                    source=self.ordercast_manager.get_products,
                    key="sku",
                )
            )

    def sync_product_variants_to_ordercast(
        self,
        full_sync: bool = False,
    ) -> None:
        last_sync_date = (
            None
            if full_sync
            else self.repo.get_key(RedisKeys.LAST_PRODUCT_VARIANT_SYNC)
        )
        product_variants = self.odoo_manager.get_product_variants(last_sync_date)
        has_product_variants = has_objects(product_variants)

        logger.info(
            f"""
            Received {len(product_variants['objects']) if has_product_variants else 0}
            products variants.
            """
        )

        if has_product_variants:
            validate_product_variants(product_variants["objects"])
            logger.info(
                """
                Starting saving product variants after
                saving categories and attributes.
                """
            )

            product_variants_to_sync = [
                get_product_variant_data(
                    product_variant=product_variant, odoo_repo=self.repo
                )
                for product_variant in product_variants["objects"]
            ]

            units = self.odoo_manager.get_units()
            self.ordercast_manager.save_product_variants(
                product_variants=product_variants_to_sync,
                units=units,
            )
            self.odoo_manager.save_product_variants(
                product_variants_to_sync=set_ordercast_id(
                    items=product_variants_to_sync,
                    source=self.ordercast_manager.get_product_variants,
                    key="slug_name",
                )
            )

    def sync_delivery_methods_and_pickup_locations(self) -> None:
        delivery_options = self.odoo_manager.receive_delivery_options()
        logger.info(
            f"""
Received {len(delivery_options['objects']) if has_objects(delivery_options) else 0}
delivery options, start saving them."""
        )
        if delivery_options:
            validate_delivery_options(delivery_options)

            delivery_options_to_sync = [
                get_delivery_option_data(delivery_option)
                for delivery_option in delivery_options["objects"]
            ]

            self.ordercast_manager.save_delivery_options(delivery_options_to_sync)
            self.odoo_manager.save_delivery_options(delivery_options_to_sync)

        pickup_locations = self.odoo_manager.receive_pickup_locations(
            odoo_repo=self.repo
        )
        logger.info(
            f"""
Received {len(pickup_locations['objects']) if has_objects(pickup_locations) else 0}
warehouses, start saving them."""
        )
        if pickup_locations:
            validate_pickup_locations(pickup_locations)

            pickup_locations_to_sync = [
                get_pickup_location_data(pickup_location)
                for pickup_location in pickup_locations["objects"]
            ]

            self.ordercast_manager.save_pickup_locations(
                pickup_locations_to_sync=pickup_locations_to_sync
            )
            self.odoo_manager.save_pickup_locations(
                pickup_locations_to_sync=pickup_locations_to_sync
            )

    def sync_orders_with_odoo(
        self,
        order_ids: Optional[list[int]] = None,
        from_date: Optional[datetime] = None,
    ) -> None:
        orders = self.ordercast_manager.get_orders_for_sync(
            odoo_repo=self.repo, order_ids=order_ids, from_date=from_date
        )
        logger.info(f"Loaded {len(orders)} orders, start sending them to Odoo.")
        _, unique_orders = self.repo.get_diff(
            compare_to=RedisKeys.ORDERS,
            comparable=RedisKeys.SYNC_ORDERCAST_ORDERS,
            entities=[o["_remote_id"] for o in orders],
        )
        logger.info(f"Syncing only unique orders => {unique_orders}")
        self.odoo_manager.sync_orders(
            [o for o in orders if o["_remote_id"] in unique_orders]
        )

    def sync_orders_with_ordercast(self, from_date: Optional[datetime] = None) -> None:
        ctx = get_ctx()
        orders = self.odoo_manager.receive_orders(
            from_date=from_date,
            orders_invoice_attach_pending=self.odoo_manager.get_orders_invoice_attach_pending(),
        )
        logger.info(
            f"Received {len(orders) if orders else 0} orders, start saving them."
        )
        if orders:
            self.ordercast_manager.sync_orders(
                orders=orders,
                default_price_rate_id=ctx["commons"]["default_price_rate_id"],
                odoo_repo=self.repo,
            )
            self.odoo_manager.save_orders(orders)

    def load_commons(self) -> None:
        default_sector_id = self.ordercast_manager.get_sector()
        default_price_rate_id = self.repo.get_key(RedisKeys.DEFAULT_PRICE_RATE_ID)
        if not default_price_rate_id:
            default_price_rate = self.ordercast_manager.get_default_price_rate()
            default_price_rate_id = default_price_rate["id"]
            self.repo.set(
                key=RedisKeys.DEFAULT_PRICE_RATE_ID, value=default_price_rate_id
            )

        set_context_value(
            value={
                "commons": {
                    "default_sector_id": default_sector_id,
                    "default_price_rate_id": default_price_rate_id,
                }
            }
        )

def get_odoo_sync_manager(
    odoo_repo: Annotated[OdooRepo, Depends(get_odoo_repo)],
    odoo_provider: Annotated[OdooManager, Depends(get_odoo_provider)],
    ordercast_manager: Annotated[OrdercastManager, Depends(get_ordercast_manager)],
) -> OdooSyncManager:
    return OdooSyncManager(
        odoo_repo, odoo_provider, ordercast_manager, WebhookHandler(odoo_provider)
    )


from .basehandler import BaseHandler

from ppmessage.api.error import API_ERR
from ppmessage.db.models import AppInfo
from ppmessage.db.models import AppUserData
from ppmessage.core.redis import redis_hash_to_dict

from ppmessage.core.constant import API_LEVEL

import json

class PPRemoveAppHandler(BaseHandler):

    def _remove(self):
        _redis = self.application.redis

        _app = redis_hash_to_dict(_redis, AppInfo, _app_uuid)
        if _app == None:
            self.setErrorCode(API_ERR.NO_APP)
            return

        if _app.get("user_uuid") != _user_uuid:
            self.setErrorCode(API_ERR.NOT_OWNER)
            return

        _key = AppUserData.__tablename__ + ".app_uuid." + _app_uuid + ".is_service_user.True"
        _services = _redis.smembers()

        _key = AppUserData.__tablename__ + ".app_uuid." + _app_uuid + ".is_service_user.False"
        _users = _redis.smembers()

        _pi = _redis.pipeline()
        _data_key_pre = AppUserData.__tablename__ + ".app_uuid." + _app_uuid
        for _user_uuid in _sevices:
            _data_key = _data_key_pre + ".user_uuid." + _user_uuid + ".is_service_user.True"
            _pi.get(_data_key)
        _data_uuid_list = _pi.execute()

        for _i in _data_uuid_list:
            _row = AppUserData(uuid=_i)
            _row.async_delete()
            _row.delete_redis_keys(_redis)

        _row = AppInfo(uuid=_app_uuid)
        _row.async_delete()
        _row.delete_redis_keys(_redis)
        return

    def initialize(self):
        self.addPermission(app_uuid=True)
        self.addPermission(api_level=API_LEVEL.PPCONSOLE)
        self.addPermission(api_level=API_LEVEL.THIRD_PARTY_CONSOLE)
        return

    def _Task(self):
        super(PPRemoveAppHandler, self)._Task()
        _request = json.loads(self.request.body)
        _app_uuid = _request.get("app_uuid")
        _user_uuid = _request.get("user_uuid")
        if _user_uuid == None or _app_uuid == None:
            self.setErrorCode(API_ERR.NO_PARA)
            return
        self._remove(_app_uuid, _user_uuid)
        return