import sys
#import json
import urllib.parse
#import urllib.request
#import shutil
import datetime
import re
import subprocess
import os
import os.path
#import glob
import signal
import threading
import queue
import pprint
#import http.client
#import PIL.Image
#import magic
import hashlib
import binascii
import redis
import errno
sys.path.append(".")
import util

windows_path = False
if "windows" in util.tokens and (util.tokens["windows"] == 1 or util.tokens["windows"] is True):
    windows_path = True

thresh_processes = 15
thresh_sleep_times = 600

if "thresh_processes" in util.tokens and util.tokens["thresh_processes"]:
    thresh_processes = util.tokens["thresh_processes"]

if "thresh_sleep_times" in util.tokens and util.tokens["thresh_sleep_times"]:
    thresh_sleep_times = util.tokens["thresh_sleep_times"]

thresh_resume = 8
thresh_same_resume = 3

thresh_redis_check = 3

timeout_s = 30

debug = False
overwrite = False
do_async = False
lockfile = None
no_lockfile = False

livelocks = []

has_errors = False
error_files = []

similar_queue = []

running = True
def signal_handler(signal, frame):
    global running
    print("Exiting...")
    running = False
    return

class Worker(threading.Thread):
    """Thread executing tasks from a given tasks queue"""
    def __init__(self, tasks):
        threading.Thread.__init__(self)
        self.tasks = tasks
        self.daemon = True
        self.start()

    def run(self):
        while running:
            func, args, kargs = self.tasks.get()
            try:
                func(*args, **kargs)
            except Exception as e:
                print(e)
            finally:
                self.tasks.task_done()

class ThreadPool:
    """Pool of threads consuming tasks from a queue"""
    def __init__(self, num_threads):
        self.tasks = queue.Queue(num_threads)
        for _ in range(num_threads): Worker(self.tasks)

    def add_task(self, func, *args, **kargs):
        """Add a task to the queue"""
        self.tasks.put((func, args, kargs))

    def wait_completion(self):
        """Wait for completion of all the tasks in the queue"""
        self.tasks.join()

def getext(urls, local=False, *args, **kwargs):
    origtype = type(urls)
    if origtype not in [list, tuple]:
        urls = [urls]

    ret = []

    for url in urls:
        if local:
            match = re.match(r".*\.(?P<ext>[^. ]*)$", url)
        else:
            match = re.match(r"[^?]*\.(?P<ext>[^?/:]*)(:[^?/]*)?$", url)

        if not match:
            ret.append(None)
            continue

        addme = match.group("ext")

        if "lower" in kwargs and kwargs["lower"]:
            addme = addme.lower()

        ret.append(addme)

    if origtype not in [list, tuple]:
        return ret[0]
    else:
        for ext in ret:
            if ext:
                return ret
        return None

similarexts = [
    ["jpg", "jpeg"],
    ["mp4", "mkv"]
]

def similarext(url1, url2):
    url1_ext = getext(url1, True).lower()
    url2_ext = getext(url2, True).lower()

    if url1_ext == url2_ext:
        return True

    for ext in similarexts:
        if url1_ext in ext and url2_ext in ext:
            return True

    return False

def getsuffix(i, array):
    if len(array) > 1:
        return " (%i:%i)" % (i + 1, len(array))
    else:
        return ""

def quote_url(link):
    link = urllib.parse.unquote(link).strip()
    scheme, netloc, path, query, fragment = urllib.parse.urlsplit(link)
    path = urllib.parse.quote(path)
    link = urllib.parse.urlunsplit((scheme, netloc, path, query, fragment)).replace("%3A", ":")
    return link

def getrequest(url, *args, **kargs):
    import urllib.request
    #request = urllib.request.Request(quote_url(url))
    request = urllib.request.Request(url)
    if (".photobucket.com" not in url and
        ".tinypic.com" not in url):
        request.add_header('User-Agent', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36')
        request.add_header('Pragma', 'no-cache')
        request.add_header('Cache-Control', 'max-age=0')
        request.add_header('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8')
    if "end_range" in kargs and kargs["end_range"] > 0:
        request.add_header('Range', 'bytes=%s-%s' % (kargs["start_range"], kargs["end_range"]))
    return request

content_type_table = {
    "image/jpeg": "jpg",
    "image/svg+xml": "svg",
    "image/x-icon": "ico",
    "video/mpeg": "mpg",
    "video/x-flv": "flv",
    "video/x-ms-asf": "asf",
    "video/x-ms-wmv": "wmv",
    "video/x-msvideo": "avi"
}

# http://stackoverflow.com/a/3431838
def md5(fname):
    hash_md5 = hashlib.md5()
    with open(fname, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

def crc32(fname):
    with open(fname, "rb") as f:
        crc = binascii.crc32(f.read()) & 0xffffffff
    return crc

def get_file_hash(fname):
    return [md5(fname), crc32(fname)]

def cmp_file_hash(hash1, hash2):
    return hash1[0] == hash2[0] and hash1[1] == hash2[1]

def fix_tumblr(x):
    if ".media.tumblr.com" in x:
       return re.sub("(.*)//[0-9]*(.media.tumblr.com/.*)", "\\1//68\\2", x)

    return x

tried_dns_cache = False

def download_real(url, output, options):
    if not running:
        return

    import urllib.request
    import http.client

    global tried_dns_cache
    if not tried_dns_cache:
        tried_dns_cache = True
        try:
            import dns_cache
            dns_cache.override_system_resolver()
        except ImportError as e:
            pass
        except Exception as e:
            print(e)

    if not options:
        options = {
            "addext": False,
            "timeout": timeout_s
        }

    if "addext" not in options:
        options["addext"] = False

    if "timeout" not in options:
        options["timeout"] = timeout_s

    if "similar" not in options:
        options["similar"] = []

    addext = options["addext"]

    if type(addext) in [list, tuple]:
        if len(addext) > 0:
            addext = addext[0]
        else:
            addext = False

    our_timeout = options["timeout"]

    retval = output

    master_times = 1
    while running:
        if master_times >= 5:
            print("Tried 5 times, giving up")
            break
        elif master_times > 1:
            print("Trying again (" + str(master_times) + "/5)")
            url = fix_tumblr(url)
        master_times += 1

        finished = False

        try:
            #with open(output, 'wb') as out_file:
            out_file = None
            content_length = -1
            old_length = -1
            times = 0
            same_length = 0
            strerr = ""
            while running:
                #if same_length > 0:
                #    print("Same length (" + str(same_length) + "/3)")
                if times > 1:
                    print("Resuming (" + str(times) + "/" + str(thresh_resume) + ")" + strerr)
                if same_length >= thresh_same_resume:
                    print("Same length " + str(thresh_same_resume) + " times, giving up")
                    break
                if times >= thresh_resume:
                    print("Resumed " + str(thresh_resume) + " times, giving up")
                    break

                times += 1

                start_r = 0
                if out_file:
                    start_r = out_file.tell()

                request = getrequest(url, start_range=start_r, end_range=content_length)
                with urllib.request.urlopen(request, timeout=our_timeout) as response:
                    for header in response.headers._headers:
                        if header[0].lower() == "content-length":
                            content_length = int(header[1])

                    content_type = response.headers.get_content_type()
                    splitted = content_type.split("/")

                    if content_type in content_type_table:
                        extension = content_type_table[content_type]
                    elif (content_type != "text/plain"
                          and content_type != response.headers.get_default_type()
                          and content_type != "application/octet-stream"):
                        extension = splitted[1]
                    elif addext:
                        extension = addext
                    else:
                        print("WARNING: no extension")
                        extension = ""

                    retval = output + "." + extension

                    if not out_file:
                        out_file = open(output + "." + extension, "wb")

                    try:
                        read_file = response.read()
                    except http.client.IncompleteRead as e:
                        read_file = e.partial

                    out_file.write(read_file)
                    out_file.flush()

                    if out_file.tell() >= content_length:
                        finished = True
                        break
                    elif out_file.tell() == old_length:
                        same_length += 1
                        strerr = " (same length " + str(same_length) + "/" + str(thresh_same_resume) + ")"
                    else:
                        strerr = ""
                        same_length = 0

                    old_length = out_file.tell()

            if finished:
                if out_file:
                    out_file.close()
                break

        except urllib.error.HTTPError as e:
            print(e)
            if e.code == 404 or e.code == 403:
                retval = None
                break
        except Exception as e:
            print(e)

    if len(options["similar"]) > 0:
        #ourcrc = crc32(retval)
        ourcrc = None
        ourmd5 = md5(retval)
        oursize = os.path.getsize(retval)

        for similar in options["similar"]:
            if os.path.getsize(similar) != oursize:
                continue

            similarmd5 = md5(similar)

            if ourmd5 == similarmd5:
                global similar_queue
                similar_queue.append(similar)

            continue

            """similarcrc = crc32(similar)

            if ourcrc == similarcrc:
                if not ourmd5:
                    ourmd5 = md5(retval)

                similarmd5 = md5(similar)

                if ourmd5 == similarmd5:
                    global similar_queue
                    similar_queue.append(similar)"""

    return retval

def download_real_cb(url, output, options, cb):
    retval = download_real(url, output, options)
    if cb:
        cb(retval)

def download(pool, url, output, options = None, cb = None):
    if do_async and pool is not None:
        pool.add_task(download_real_cb, url, output, options, cb)
    else:
        download_real_cb(url, output, options, cb)

def get_redis_key(url):
    key = os.path.abspath(url)
    if key.startswith(home):
        key = key[len(home):]
    key = "DLPATH:" + key
    return key

def get_redis_meta_str(url):
    return get_meta_str(url).encode("utf-8")

def check_redis(url):
    if not rinstance:
        return None

    meta = get_redis_meta_str(url)
    key = get_redis_key(url)

    try:
        val = rinstance.hgetall(key)
    except:
        return None

    expire_redis(key)

    if b"meta" in val and val[b"meta"] == meta:
        if b"times" in val and int(val[b"times"]) >= thresh_redis_check:
            return True
    return False

def expire_redis(key):
    if not rinstance:
        return

    try:
        rinstance.expire(key, 60*60*24*7)
    except:
        return

def update_redis(url):
    if not rinstance:
        return None

    meta = get_redis_meta_str(url)
    key = get_redis_key(url)

    val = rinstance.hgetall(key)

    if b"meta" in val and val[b"meta"] == meta:
        if b"times" not in val:
            val[b"times"] = "0"
        val[b"times"] = str(int(val[b"times"]) + 1)
    else:
        val = {
            "meta": meta,
            "times": "0"
        }

    rinstance.hmset(key, val)
    expire_redis(key)

def check_image(url):
    retval = False
    try:
        if check_redis(url):
            return True

        if os.stat(url).st_size == 0:
            return False

        try:
            import PIL.Image
        except Exception as ee:
            print(ee)

        image = PIL.Image.open(url)
        if image.format == "JPEG":
            image.load()
        else:
            image.verify()

        retval = True
    except:
        return False

    try:
        update_redis(url)
    except Exception as e:
        print(e)

    return retval

def check_video(url):
    if check_redis(url):
        return True

    if os.stat(url).st_size == 0:
        return False

    if url.endswith(".part"):
        return False

    import magic

    our_magic = magic.from_file(url, mime=True)
    if not our_magic:
        return False

    if our_magic.split("/")[0] != "video" and our_magic != "application/x-shockwave-flash":
        return False

    try:
        update_redis(url)
    except Exception as e:
        print(e)

    return True

def download_image(pool, url, output, options = None, *args, **kwargs):
    global has_errors

    if not running:
        return

    if not "url_id" in kwargs:
        kwargs["url_id"] = 0

    if not "total_times" in kwargs:
        kwargs["total_times"] = 0

    if not "same_times" in kwargs:
        kwargs["same_times"] = 0

    if not "lastcontent" in kwargs:
        kwargs["lastcontent"] = None

    if not "func" in kwargs:
        kwargs["func"] = check_image

    if kwargs["total_times"] >= 5 or kwargs["same_times"] >= 2:
        if type(url) in [list, tuple] and len(url) > (kwargs["url_id"] + 1):
            print("Tried downloading %s too many times, going to next url" % url[kwargs["url_id"]])
            kwargs["url_id"] += 1
            kwargs["total_times"] = 0
            kwargs["same_times"] = 0
            kwargs["lastcontent"] = None

            if type(options) == dict and "addext" in options and type(options["addext"]) in [list, tuple]:
                options["addext"] = options["addext"][1:]

            download_image(pool, url, output, options, *args, **kwargs)
            return

        print("Tried downloading %s too many times, stopping" % str(url))
        has_errors = True
        error_files.append(url)
        return

    kwargs["total_times"] += 1

    oldoutput = output

    def download_image_inner(output):
        global has_errors
        if not output or not os.path.exists(output):
            print(str(output) + " does not exist? (URL: %s)" % url)

            if type(url) in [list, tuple] and len(url) > (kwargs["url_id"] + 1):
                print("Trying next url")
                kwargs["url_id"] += 1
                kwargs["total_times"] = 0
                kwargs["same_times"] = 0
                kwargs["lastcontent"] = None

                if type(options) == dict and "addext" in options and type(options["addext"]) in [list, tuple]:
                    options["addext"] = options["addext"][1:]

                download_image(None, url, oldoutput, options, *args, **kwargs)
                return

            has_errors = True
            error_files.append(url)
            return

        if kwargs["func"](output):
            return

        with open(output, "rb") as out_file:
            content = out_file.read()

        if content == kwargs["lastcontent"]:
            kwargs["same_times"] += 1
        else:
            kwargs["lastcontent"] = content
            kwargs["same_times"] = 0

        download_image(None, url, oldoutput, options, *args, **kwargs)

    newurl = url
    if type(newurl) in [list, tuple]:
        newurl = url[kwargs["url_id"]]

    download(pool, newurl, output, options, download_image_inner)

def download_video(pool, url, output, options = None, *args, **kwargs):
    kwargs["func"] = check_video
    download_image(pool, url, output, options, *args, **kwargs)

def geturl(url):
    import urllib.request
    try:
        req = urllib.request.Request(url, method="HEAD")
        resp = urllib.request.urlopen(req)
        return resp.geturl()
    except Exception as e:
        print(e)

def sanitize_path(text):
    if windows_path:
        return text.rstrip(".").rstrip(" ").replace(":", "-").replace('"', "'").replace("<", "(lt)").replace(">", "(gt)").replace("\\", " (bslash) ").replace("|", "(pipe)").replace("?", "(qmark)").replace("*", "(asterisk)")
    return text

def remext(file1):
    return re.sub(r"\.[^. ]*$", "", file1)
    #return os.path.splitext(file1)[0]

def similar_filename(file1, file2):
    return os.path.basename(remext(file1)) == os.path.basename(file2) # hack!!

def old_fsify_base(text):
    return sanitize_path(text.replace("\n", " ").replace("\r", " ").replace("/", " (slash) "))

def fsify_base(text):
    return sanitize_path(text.replace("\n", " ").replace("\r", " ").replace("/", "∕"))

def old_fsify(text):
    return sanitize_path(old_fsify_base(text)[:50])

def old_fsify_album(text):
    return sanitize_path(old_fsify_base(text)[:100].strip())

def fsify_album_oldbase(text):
    return sanitize_path(old_fsify_base(text)[:200].strip())

def fsify_album(text):
    return sanitize_path(fsify_base(text)[:200].strip())

def sanitize_caption(entry_caption, entry):
    authorcaption = ""
    if "author" in entry and entry["author"] != jsond["author"] and not is_index:
        authorcaption = "[@" + entry["author"] + "] "

    if not entry_caption or len(entry_caption) == 0:
        newcaption = ""
    else:
        #oldcaption = " " + old_fsify(entry_caption)
        newcaption = " " + authorcaption + old_fsify_album(entry_caption)

        try:
            encoded = newcaption.encode("utf-8")
            encoded_len = len(encoded)
        except Exception as e:
            encoded_len = 10000

        if encoded_len > 200:
            newcaption = " " + authorcaption + old_fsify(entry_caption)

    return newcaption

def get_meta_str(f):
    st = os.stat(f)

    return "MOD: " + str(st.st_mtime) + " SIZE: " + str(st.st_size)

def getdirs(base):
    return [x[0] for x in os.walk(base)]

def file_exists(f, dirs):
    for d in dirs:
        for i in os.listdir(d):
            if similar_filename(i, f):
                return True
    return False

def video_exists(f, dirs):
    for d in dirs:
        for i in os.listdir(d):
            if similar_filename(i, f) and check_video(os.path.join(d, i)):
                return True
    return False

def image_exists(f, dirs):
    for d in dirs:
        for i in os.listdir(d):
            if similar_filename(i, f) and check_image(os.path.join(d, i)):
                if debug:
                    print("[DEBUG] Exists in " + d)
                return True
    return False

from subprocess import check_output
def get_pid(name):
    return check_output(["pgrep","-f",name]).split()
import time

def process_exists_proc(pid):
    return os.path.exists("/proc/" + str(pid))

# https://stackoverflow.com/a/6940314
def pid_exists(pid):
    """Check whether pid exists in the current process table.
    UNIX only.
    """
    try:
        pid = int(pid)
    except Exception as err:
        print(err)
        return True

    if pid < 0:
        return False
    if pid == 0:
        # According to "man 2 kill" PID 0 refers to every process
        # in the process group of the calling process.
        # On certain systems 0 is a valid PID but we have no way
        # to know that in a portable fashion.
        raise ValueError('invalid PID 0')
    try:
        os.kill(pid, 0)
    except OSError as err:
        if err.errno == errno.ESRCH:
            # ESRCH == No such process
            return False
        elif err.errno == errno.EPERM:
            # EPERM clearly means there's a process to deny access to
            return True
        else:
            # According to "man 2 kill" possible error values are
            # (EINVAL, EPERM, ESRCH)
            raise
    else:
        return True

def process_exists(pid):
    return pid_exists(pid)

subprocesses = []

def run_subprocess(arr, wait=True):
    p = subprocess.Popen(arr)
    if not do_async or wait:
        p.wait()
    else:
        subprocesses.append(p)

def get_processes_amt():
    dir = "/tmp/"
    files = os.listdir(dir)
    amt = 0
    for file in files:
        if file.startswith(".tdownload."):
            process = file.split(".")[-1]
            #if not os.path.exists("/proc/" + process):
            if not process_exists(process):
                print("Process for /tmp/" + file + " doesn't exist")
                try:
                    os.unlink("/tmp/" + file)
                except Exception as e:
                    print(e)
            else:
                amt += 1
    return amt

if __name__ == "__main__":
    myjson = sys.stdin.read()
    sys.stdin.close()

    if len(sys.argv) > 1 and sys.argv[1] == "debug":
        debug = True
        sys.argv = sys.argv[1:]

    if len(sys.argv) > 1 and sys.argv[1] == "overwrite":
        overwrite = True
        sys.argv = sys.argv[1:]

    if len(sys.argv) > 1 and sys.argv[1] == "async":
        do_async = True
        no_lockfile = True

        amt = 10
        if len(sys.argv) > 2:
            amt = int(sys.argv[2])

        pool = ThreadPool(amt)
    else:
        pool = None

    try:
        rinstance = redis.StrictRedis(host='localhost')
    except:
        rinstance = None

    processes = get_processes_amt()
    times = 0
    while processes > thresh_processes and not no_lockfile and running:
        time.sleep(2)
        processes = get_processes_amt()
        times += 1
        if times > thresh_sleep_times:
            no_lockfile = True
            break

    if not running:
        sys.exit()

    if not no_lockfile:
        lockfile = "/tmp/.tdownload." + str(os.getpid())
        open(lockfile, 'a').close()

    signal.signal(signal.SIGINT, signal_handler)

    #jsond = json.loads(myjson)
    jsond = util.ujson.loads(myjson)
    #print(jsond)

    if "no_dl" in jsond["config"] and jsond["config"]["no_dl"]:
        sys.exit()

    prefix = "~/Pictures/social/"
    if "prefix" in util.tokens:
        prefix = util.tokens["prefix"]
    if "dl_prefix" in jsond["config"] and len(jsond["config"]["dl_prefix"]) > 0:
        prefix = jsond["config"]["dl_prefix"]
    home = os.path.abspath(os.path.expanduser(prefix))

    generator = jsond["config"]["generator"]
    thedirgen = home + "/" + generator + "/"
    thedirbase = thedirgen + sanitize_path(jsond["author"]) + "/"

    is_index = False
    if "is_index" in jsond["config"] and jsond["config"]["is_index"]:
        is_index = True

    no_videodl = False
    if "no_videodl" in jsond["config"] and jsond["config"]["no_videodl"]:
        no_videodl = True

    if not is_index:
        if not os.path.exists(thedirbase):
            os.makedirs(thedirbase, exist_ok=True)
            filesbase = []
        else:
            filesbase = os.listdir(thedirbase)
        dirs = getdirs(thedirbase)
    else:
        filesbase = []
        dirs = []

    current_id = 1
    all_entries = len(jsond["entries"])

    for entry in jsond["entries"]:
        if not running:
            break

        our_id = current_id
        current_id = current_id + 1

        if (not entry["images"] or len(entry["images"]) <= 0) and (not entry["videos"] or len(entry["videos"]) <= 0):
            continue

        if is_index:
            thedirbase = thedirgen + sanitize_path(entry["author"]) + "/"
            if not os.path.exists(thedirbase):
                os.makedirs(thedirbase, exist_ok=True)
                filesbase = []
            else:
                filesbase = os.listdir(thedirbase)
            dirs = getdirs(thedirbase)

        entry_caption = entry["caption"]
        if "media_caption" in entry:
            entry_caption = entry["media_caption"]

        """authorcaption = ""
        if "author" in entry and entry["author"] != jsond["author"]:
            authorcaption = "[@" + entry["author"] + "] "

        if not entry_caption or len(entry_caption) == 0:
            newcaption = ""
        else:
            #oldcaption = " " + old_fsify(entry_caption)
            newcaption = " " + authorcaption + old_fsify(entry_caption)"""
        newcaption = sanitize_caption(entry_caption, entry)

        newdate = datetime.datetime.fromtimestamp(entry["date"]).isoformat()

        if "album" in entry and entry["album"]:
            similardirs = []

            oldthedir = thedirbase + old_fsify_album(entry["album"]) + "/"
            similardirs.append(oldthedir)
            oldthedir = thedirbase + fsify_album_oldbase(entry["album"]) + "/"
            similardirs.append(oldthedir)

            thedir = thedirbase + fsify_album(entry["album"]) + "/"

            similardir = None
            if "similaralbum" in entry:
                similardir = thedirbase + fsify_album(entry["similaralbum"]) + "/"
                similardirs.append(similardir)
                similardir = thedirbase + old_fsify_album(entry["similaralbum"]) + "/"
                similardirs.append(similardir)
                similardir = thedirbase + fsify_album_oldbase(entry["similaralbum"]) + "/"
                similardirs.append(similardir)

            if not os.path.exists(thedir):
                renamed_similar = False
                for similardir in similardirs:
                    if similardir == thedir or not os.path.exists(similardir):
                        continue
                    print("Renaming " + similardir + " to " + thedir)
                    os.rename(similardir, thedir)
                    if similardir in dirs:
                        dirs.remove(similardir)
                    if similardir[:-1] in dirs: # to remove the trailing /
                        dirs.remove(similardir[:-1])
                    dirs.append(thedir)
                    files = os.listdir(thedir)
                    renamed_similar = True
                    break

                if not renamed_similar:
                    os.makedirs(thedir, exist_ok=True)
                    dirs.append(thedir)
                    files = []
            else:
                files = os.listdir(thedir)
        else:
            thedir = thedirbase
            files = filesbase

        if not entry["images"]:
            entry["images"] = []

        for i, image in enumerate(entry["images"]):
            if not running:
                break

            if debug:
                print("[DEBUG] " + str(our_id) + " " + str(i))
                pprint.pprint(entry)

            #imageurl = geturl(image)
            imageurl = image

            ext = getext(imageurl)

            ##if ext:
            ##    dotext = "." + ext
            ##else:
            ##    dotext = ""

            suffix = getsuffix(i, entry["images"])

            #output = "(%s)%s%s%s" % (newdate, newcaption, suffix, dotext)
            output = sanitize_path("(%s)%s%s" % (newdate, newcaption, suffix))

            similaroutput = None
            if "similarcaption" in entry:
                newsimilarcaption = sanitize_caption(entry["similarcaption"], entry)
                similaroutput = sanitize_path("(%s)%s%s" % (newdate, newsimilarcaption, suffix))

            fullout = thedir + output

            exists = False
            for file_ in files:
                if similar_filename(file_, output) and check_image(os.path.join(thedir, file_)):
                    exists = True
                    break

            if exists and not overwrite:
                if debug:
                    print("[DEBUG] exists")
                continue

            similar = []
            if similaroutput:
                for file_ in files:
                    if similar_filename(file_, similaroutput) and check_image(os.path.join(thedir, file_)):
                        similar.append(os.path.join(thedir, file_))

            #if os.path.exists(fullout):
            #    continue

            exists = False
            for file_ in filesbase:
                if similar_filename(file_, output) and check_image(os.path.join(thedirbase, file_)):
                    exists = True
                    break

            renamed = False
            if exists:
                if debug:
                    print("[DEBUG] similar exists")

                oext = getext(thedirbase + file_, True)
                if not ext or True: # hackish
                    if oext:
                        if thedirbase + file_ != fullout + "." + oext:
                            os.rename(thedirbase + file_, fullout + "." + oext)
                            renamed = True
                    else:
                        if thedirbase + file_ != fullout:
                            os.rename(thedirbase + file_, fullout)
                            renamed = True
                            print("WARNING: no extension for " + file_)
                else:
                    if thedirbase + file_ != fullout:
                        os.rename(thedirbase + file_, fullout)
                        renamed = True

                if renamed:
                    print("[RN:IMAGE] " + output +" (%i/%i)" % (our_id, all_entries))
                    continue

            if image_exists(output, dirs) and not overwrite:
                continue

            sys.stdout.write("[DL:IMAGE] " + output + " (%i/%i)... " % (our_id, all_entries))
            sys.stdout.flush()

            download_options = {
                "addext": ext,
                "similar": similar
            }
            download_image(pool, imageurl, fullout, download_options)
            ##if ext == "":
            ##    download_image(pool, imageurl, fullout, {"addext": True})
            ##else:
            ##    download_image(pool, imageurl, fullout, {"addext": False})
            #print ("Downloaded image " + output + " (%i/%i)" % (our_id, all_entries))
            print("Done")

        if not entry["videos"] or no_videodl:
            entry["videos"] = []

        for i, video in enumerate(entry["videos"]):
            if not running:
                break
            #ext = getext(video["video"])

            live_video = False
            if "live" in video and video["live"]:
                live_video = True
                #continue

            streamlink_live = False
            if "live_streamlink" in video and video["live_streamlink"]:
                streamlink_live = True

            suffix = getsuffix(i, entry["videos"])

            video_urls = video["video"]
            if type(video_urls) not in (list, tuple):
                video_urls = [video_urls]
            if "video_dash" in video:
                if video["video_dash"][0] == "<":
                    video["video_dash"] = "data:application/dash+xml," + urllib.parse.quote(video["video_dash"], safe="")
                video_urls.insert(0, video["video_dash"])
            if len(video_urls) == 0:
                print("0 videos")
                continue
            urls = []
            for video_url in video_urls:
                #urls.append(quote_url(video_url))
                urls.append(video_url)
            mymatch = re.match(r".*twitter.com/i/videos/(?P<id>[0-9]*)", urls[0])
            if mymatch:
                urls[0] = "http://twitter.com/i/videos/tweet/%s" % mymatch.group("id")

            output = sanitize_path("(%s)%s%s" % (newdate, newcaption, suffix))
            fullout = thedir + output

            if "coauthors" in entry and type(entry["coauthors"]) == list:
                try:
                    for coauthor in entry["coauthors"]:
                        coauthor_filename = fullout + ".mp4.coauthor." + coauthor
                        if not os.path.exists(coauthor_filename):
                            open(coauthor_filename, 'a').close()
                except Exception:
                    pass

            exists = False
            existing_file = None
            for file_ in files:
                fullpath = os.path.join(thedir, file_)
                if file_.startswith(output):  # and similar_filename(file_, output):
                    #if not live_video:
                    #    if similar_filename(file_, output) and check_video(fullpath):
                    #        exists = True
                    #        break
                    #else:
                    #    #print(file_)
                    if re.search(r"\.tdownload\.[0-9]*$", file_):
                        #print("TDOWNLOAD")
                        process = file_.split(".")[-1]
                        if not process_exists(process):
                            print("Process for " + str(fullpath) + " doesn't exist")
                            try:
                                os.unlink(fullpath)
                            except Exception as e:
                                print(e)
                        else:
                            exists = True
                            break
                    elif similar_filename(file_, output) and check_video(fullpath):
                        exists = True
                        existing_file = fullpath
                        break

            if exists and not overwrite:
                continue

            if live_video:
                if "no_livedl" in jsond["config"] and jsond["config"]["no_livedl"]:
                    continue

                sys.stdout.write("[DL:LIVE] " + output + " (%i/%i)... " % (our_id, all_entries))
                sys.stdout.flush()

                fullout = fullout + ".mp4"

                livelock = fullout + ".tdownload." + str(os.getpid())
                open(livelock, 'a').close()

                livelocks.append(livelock)

                cmdline = [sys.executable, os.path.join(os.path.dirname(__file__), "iglivedl.py"), urls[0], "--output", fullout]

                if "no_live_cleanup" in util.tokens and util.tokens["no_live_cleanup"]:
                    cmdline.append("--no-cleanup")

                run_subprocess(cmdline)
                print("Done")
                continue

            if streamlink_live:
                if "no_livedl" in jsond["config"] and jsond["config"]["no_livedl"]:
                    continue

                sys.stdout.write("[DL:LIVE] " + output + " (%i/%i)... " % (our_id, all_entries))
                sys.stdout.flush()

                fullout = fullout + ".mp4"

                livelock = fullout + ".tdownload." + str(os.getpid())
                open(livelock, 'a').close()

                livelocks.append(livelock)

                cmdline = ["streamlink", "--default-stream", "best", "--hls-live-restart", "--hls-segment-threads", "3"]

                if "headers" in video and type(video["headers"]) == dict:
                    for header in video["headers"]:
                        cmdline.append("--http-header")
                        cmdline.append(header + "=" + video["headers"][header])

                cmdline.append(urls[0])
                cmdline.append("-o")
                cmdline.append(fullout)

                run_subprocess(cmdline, wait=False)
                print("Done")
                continue

            sys.stdout.write("[DL:VIDEO] " + output + " (%i/%i)... " % (our_id, all_entries))
            sys.stdout.flush()

            if (jsond["config"]["generator"] == "instagram" and "/f/instagram/v/" in urls[0]) or re.search(r"\.mp4(?:\?.*)?$", urls[0]):
                if not re.search(r"\.mp4(?:\?.*)?$", urls[0]):
                    fullout = fullout + ".mp4"

                #if os.path.exists(fullout):
                if video_exists(output, dirs):
                    continue

                download_video(pool, urls, fullout)
            else:
                newfullout = fullout + ".%(ext)s"

                """p = subprocess.Popen(["youtube-dl", url, "-o", fullout])

                if not do_async:
                    p.wait()"""
                livelock = fullout + ".tdownload." + str(os.getpid())
                open(livelock, 'a').close()

                livelocks.append(livelock)

                if exists and existing_file:
                    os.remove(existing_file)

                cmdline = ["youtube-dl", urls[0], "-o", newfullout]

                if "headers" in video and type(video["headers"]) == dict:
                    for header in video["headers"]:
                        cmdline.append("--add-header")
                        cmdline.append(header + ":" + video["headers"][header])

                run_subprocess(cmdline)

            #print("Downloaded video " + output + " (%i/%i)" % (our_id, all_entries))
            print("Done")
            sys.stdout.flush()

    if do_async:
        pool.wait_completion()

    our_id = 1
    all_entries = len(similar_queue)
    for similar_file in similar_queue:
        sys.stdout.write("[SIMILAR] Removing " + os.path.basename(similar_file) + " (%i/%i) ... " % (our_id, all_entries))
        our_id += 1
        sys.stdout.flush()
        try:
            os.remove(similar_file)
        except Exception as e:
            print(e)
        print("Done")

    for sub in subprocesses:
        sub.wait()

    if lockfile:
        try:
            os.unlink(lockfile)
        except Exception as e:
            print(e)

    for livelock in livelocks:
        try:
            os.unlink(livelock)
        except Exception as e:
            print(e)

    if not has_errors:
        print("[FINAL] Done")
    else:
        print("[FINAL,ERRORS] Done:")
        pprint.pprint(error_files)

    sys.stdout.flush()


#Program to tanslate languages.This uses google translate module to do the transilation
#Created by Aswin KS

import googletrans
from tkinter import *  #for creating GUI
from tkinter import messagebox #for message box
from googletrans import Translator
from numpy import common_type
import pyperclip #for copy to clipboard

#-----Method to translate--------------------
def transilate():
    dest.delete(0,END)
    translator = Translator()

    input=source_input.get()
    print(input)
    destination=menu.get()
    result = translator.translate(input,dest=destination)
    dest.insert(0,result.text)

#-----Method to Copy text to clipboard--------------------
def copyclip():
    op=dest.get()
    pyperclip.copy(op)   #To copy password to clipboard
    messagebox.showinfo(title="Data Copied", message="Data Copied to Clipboard")

#--------------------------------------------UI setup---------------------------------------------------------------
window=Tk()

#Title bar
window.title("Aswin's Transilator")
window.resizable(0,0)  #to remove maximise button
window.config(padx=50,pady=50)
canvas=Canvas(height=200,width=300)

#image
logo=PhotoImage(file="D:\\Devops\\Python-Begineer\Real-world-Projects\\Language-Transilator\\trans.png") #chnage the path accordingly
canvas.create_image(80,80,image=logo)
canvas.grid(row=4,column=3)

# Create Dropdown menu
choices = { 'English','Hindi','Gujarati','Spanish','German','malayalam'}
menu= StringVar()
menu.set("Select source Language")
drop1 = OptionMenu(window,menu,*choices)
drop1.grid(row=0,column=1)

#label for source
source=Label(text="Enter the source text: ")
source.grid(row=1,column=1)

#inputbox for source
source_input=Entry(width=70)
source_input.grid(row=1,column=2)

# Create Dropdown menu
menu= StringVar()
menu.set("Select Destination Language")
drop2 = OptionMenu(window,menu,*choices)
drop2.grid(row=2,column=1)

#Inputbox for Dest
dest=Entry(width=70)
dest.grid(row=2,column=2)

#button for transilate calling transilate method()
search_button=Button(text="Translate",command=transilate)
search_button.grid(row=1,column=3)

#button for copy calling copyclip method()
copy_button=Button(text="Copy",command=copyclip)
copy_button.grid(row=2,column=3)

#method for printing about
def print_about():
    messagebox.showinfo(title="About", message="Created by Aswin KS. Find More @ https://github.com/aswinks1995")

#button for About
about_button=Button(text="About",command=print_about)
about_button.grid(row=4,column=1)

window.mainloop()


from database import db
from datetime import date
from sqlalchemy import and_
from .inventoryModel import Inventory
from ..theatre.theatreRepository import get_theatre_by_id
import os, sys

# Add the project's root directory to the Python path
current_dir = os.path.dirname(os.path.realpath(__file__))
project_root = os.path.abspath(os.path.join(current_dir, '../../../'))
sys.path.insert(0, project_root)

# Now you can import the required module
from utils.errors import *

from datetime import datetime

#add new inventory
def add_inventory(theatre_id, show_id, date, start_time, end_time, retail_price, selling_price, is_active=True):
    try:
        theatre = get_theatre_by_id(theatre_id)
        date_obj = datetime.strptime(date, "%Y-%m-%d").date()
        start_time_obj = datetime.strptime(start_time, "%H:%M:%S").time()
        end_time_obj = datetime.strptime(end_time, "%H:%M:%S").time()

        new_inventory = Inventory(
            theatre_id=theatre_id,
            show_id=show_id,
            date=date_obj,
            start_time=start_time_obj,
            end_time=end_time_obj,
            retail_price=retail_price,
            selling_price=selling_price,
            is_active=is_active,
            available_seats=theatre.capacity
        )
        db.session.add(new_inventory)
        db.session.commit()
        return new_inventory
    except Exception as err:
        return {
            'success': False,
            'error': str(err),
        }

#get all inventorys
def get_all_inventorys():
    try:
        inventorys = Inventory.query.filter(Inventory.date >= date.today()).all()
        if inventorys:
            return inventorys  # Return the list of inventory objects
        raise Exception(ERROR_MESSAGES["INVENTORY_NOT_FOUND"])
    except Exception as err:
        raise Exception(str(err))

#delete inventory
def delete_inventory_by_id(id):
    try:
        # Check if the inventory with the given ID exists
        inventory_to_delete = Inventory.query.get(id)

        if inventory_to_delete:
            db.session.delete(inventory_to_delete)
            db.session.commit()
            return {'success': True, 'message': 'Inventory deleted successfully'}
        else:
            raise Exception(ERROR_MESSAGES["INVENTORY_NOT_FOUND"])
    except Exception as err:
        return {'success': False, 'error': str(err)}

# Update inventory by ID
def update_inventory(id, theatre_id, show_id, date, start_time, end_time, retail_price, selling_price, is_active):
    try:
        # Check if the inventory with the given ID exists
        inventory_to_update = Inventory.query.get(id)

        if inventory_to_update:
            # Update the inventory attributes
            date_obj = datetime.strptime(date, "%Y-%m-%d").date()
            start_time_obj = datetime.strptime(start_time, "%H:%M:%S").time()
            end_time_obj = datetime.strptime(end_time, "%H:%M:%S").time()

            inventory_to_update.theatre_id = theatre_id
            inventory_to_update.show_id = show_id
            inventory_to_update.date = date_obj
            inventory_to_update.start_time = start_time_obj
            inventory_to_update.end_time = end_time_obj
            inventory_to_update.retail_price = retail_price
            inventory_to_update.selling_price = selling_price
            inventory_to_update.is_active = is_active

            db.session.commit()
            return {
                'success': True,
                'message': 'Inventory updated successfully'
            }
        else:
            raise Exception(ERROR_MESSAGES["INVENTORY_NOT_FOUND"])
    except Exception as err:
        return {
            'success': False,
            'error': str(err),
        }

# Update inventory by ID
def update_inventory_count(id,count):
    try:
        # Check if the inventory with the given ID exists
        inventory_to_update = Inventory.query.get(id)

        if inventory_to_update:
            inventory_to_update.available_seats=count
            db.session.commit()
            return {
                'success': True,
                'message': 'Inventory updated successfully'
            }
        else:
            raise Exception(ERROR_MESSAGES["INVENTORY_NOT_FOUND"])
    except Exception as err:
        return {
            'success': False,
            'error': str(err),
        }

#find inventory by theatre
def find_inventory_by_theatre(theatre_id):
    try:
        inventorys = Inventory.query.filter_by(theatre_id=theatre_id)
        if inventorys:
            return inventorys  # Return the list of inventory objects
        raise Exception(ERROR_MESSAGES["INVENTORY_NOT_FOUND"])
    except Exception as err:
        raise Exception(str(err))

#find inventory by show
def find_inventory_by_show(show_id):
    try:
        inventorys = Inventory.query.filter_by(show_id=show_id)
        if inventorys:
            return inventorys  # Return the list of inventory objects
        raise Exception(ERROR_MESSAGES["INVENTORY_NOT_FOUND"])
    except Exception as err:
        raise Exception(str(err))

#find inventory by start_time
def find_inventory_by_start_time(start_time):
    try:
        start_time_obj = datetime.strptime(start_time, "%H:%M:%S").time()
        inventorys = Inventory.query.filter_by(start_time=start_time_obj)
        if inventorys:
            return inventorys  # Return the list of inventory objects
        raise Exception(ERROR_MESSAGES["INVENTORY_NOT_FOUND"])
    except Exception as err:
        raise Exception(str(err))

#find inventory by id
def find_inventory_by_id(id):
    try:
        inventory = Inventory.query.get(id)
        if inventory:
            return inventory  # Return the list of inventory objects
        raise Exception(ERROR_MESSAGES["INVENTORY_NOT_FOUND"])
    except Exception as err:
        raise Exception(str(err))


'''                            VARIANT OF KNAPSACK                               '''

'''
Given string:
a = "agbcba"
find the longest palindromic subsequence in "a"
LPS(a) = LCS(a,reverse(a))
'''
def lcs_tabulate(x,y,m,n):
    t = [[-1 for j in range(n+1)] for i in range(m+1)]
    for i in range(m+1):
        for j in range(0,n+1):
            if i==0 or j==0:
                t[i][j] = 0
            elif x[i-1] == y[j-1]:
                t[i][j] = 1 + t[i-1][j-1]
            else:
                t[i][j] = max(
                    t[i-1][j],
                    t[i][j-1]
                )
    return t[m][n]

if __name__ == "__main__":
    x =  "agbcba"
    print("LPS", lcs_tabulate(x,x[::-1],len(x),len(x)))


"""Implement the BFS algorithm"""

# ----> Create a Node Class with data as the arhument

# ----> Create Linked List class with the operations as:
#        getting the head node
#        checking if it is empty list
#        inserting at the head
#        inserting at the tail
#        checking the length
#        printing the list
#        deleting the head
#        deleting
#        searching in the linked list
#        removing the duplicates

# ----> Create a Queue class with the operations as:
#        check if it is empty
#        accessing the front
#        accessing the back
#        Enqueing
#        Dequeing

# ----> Create a Stack class with the operations as:
#        checking if it is empty
#        accessing the top element
#        checking the size of the stack
#        pushing the element
#        popping the element

# ----> Creating the Graph class with the operations:
#        adding the edge
#        printing the graph

# ----> Creating a Graph object

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class Queue:
    def __init__(self):
        self.queue_list = []

    def is_empty(self):
        return len(self.queue_list) == 0

    def front(self):
        if self.is_empty():
            return None
        return self.queue_list[0]

    def back(self):
        if self.is_empty():
            return None
        return self.queue_list[-1]

    def enqueue(self, value):
        self.queue_list.append(value)

    def dequeue(self):
        if self.is_empty():
            return None
        front = self.front()
        self.queue_list.remove(self.front())
        return front

class Stack:
    def __init__(self):
        self.stack_list = []

    def is_empty(self):
        return len(self.stack_list) == 0

    def top(self):
        if self.is_empty():
            return None
        return self.stack_list[-1]

    def size(self):
        return len(self.stack_list)

    def push(self, value):
        return self.stack_list.append(value)

    def pop(self):
        if self.is_empty():
            return None
        return self.stack_list.pop()

class LinkedList:
    def __init__(self):
        self.head = None

    def get_head(self):
        return self.head

    def is_empty(self):
        if not self.head:
            return True
        return False

    def insert_at_head(self, data):
        new_node = Node(data)
        if self.is_empty():
            self.head = new_node
            return self.head

        new_node.next = self.head
        self.head = new_node
        return self.head

    def insert_at_tail(self, data):
        new_node = Node(data)
        if not self.head:
            self.head = new_node
            return

        curr = self.head
        while curr.next is not None:
            curr = curr.next

        curr.next = new_node
        return

    def length(self):
        if self.is_empty():
            return None

        count = 0
        curr = self.head
        while curr is not None:
            count += 1
            curr = curr.next

        return count

    def print_list(self):
        if self.is_empty():
            print("List is Empty")
            return None

        curr = self.head
        while curr.next is not None:
            print(curr.data, end=" -> ")
            curr = curr.next

        print(curr.data, " -> None")
        return True

    def delete_at_head(self):
        first_element = self.head
        if first_element is not None:
            self.head = first_element.next
            first_element.next = None
        return

    def delete(self, value):
        deleted = False
        if self.is_empty():
            print("List is Empty")
            return deleted
        curr = self.head
        prev = None
        if curr.data is value:
            self.delete_at_head()
            deleted = True
            return deleted

        while curr.data is not None:
            if value is curr.data:
                prev.next = curr.next
                curr.next = None
                deleted = True
                break
            prev = curr
            curr = curr.next

        return deleted

    def search(self, data):
        if self.is_empty():
            print("List is Empty")
            return None

        curr = self.head
        while curr is not None:
            if curr.data is data:
                return curr
            curr = curr.next

        print(data, " is not in List!")
        return None

    def remove_duplicates(self):
        if self.is_empty():
            return

        if self.get_head().next is None:
            return

        outer_node = self.get_head()
        while outer_node:
            inner_node = outer_node
            while inner_node:
                if inner_node.next:
                    if outer_node.data == inner_node.next.data:
                        new_next = inner_node.next.next
                        inner_node.next = new_next
                    else:
                        inner_node = inner_node.next
                else:
                    inner_node = inner_node.next
            outer_node = outer_node.next
        return

class Graph:
    def __init__(self, vertices):
        self.vertices = vertices
        self.array = []
        for i in range(vertices):
            temp = LinkedList()
            self.array.append(temp)

    def add_edge(self, source, destination):
        if (source < self.vertices) and (destination < self.vertices):
            self.array[source].insert_at_head(destination)

    def print_graph(self):
        print(">>Adjacency List of Directed Graph<<")

        for i in range(self.vertices):
            print("|", i, end=" | => ")
            temp = self.array[i].get_head()
            while temp is not None:
                print("[", temp.data, end=" ] -> ")
                temp = temp.next_element
            print("None")

def bfs_traversal_helper(g, source, visited):
    result = ""

    queue = Queue()
    queue.enqueue(source)
    visited[source] = True

    while (queue.is_empty()) is False:
        curr = queue.dequeue()
        result += str(curr)
        temp = g.array[curr].head
        while temp is not None:
            if visited[temp.data] is False:
                queue.enqueue(temp.data)
                visited[temp.data] = True
            temp = temp.next
    return result, visited

def bfs_traversal(g, source):
    result = ""
    num_of_vertices = g.vertices
    if num_of_vertices == 0:
        return result
    visited = []
    for i in range(num_of_vertices):
        visited.append(False)
    result, visited = bfs_traversal_helper(g, source, visited)

    for i in range(num_of_vertices):
        if visited[i] is False:
            result_new, visited = bfs_traversal_helper(g, i, visited)
            result += result_new
    return result

g = Graph(4)
num_of_vertices = g.vertices

if num_of_vertices == 0:
    print("Graph is empty")
elif num_of_vertices < 0:
    print("Graph cannot have negative vertices")
else:
    g.add_edge(0, 1)
    g.add_edge(0, 2)
    g.add_edge(1, 3)
    g.add_edge(2, 3)

    print(bfs_traversal(g, 0))


from graphviz import Digraph

#class ExecuteTaskGraph(task_graph):

    #def __init__(self, task_graph):
        #print ('init ExecuteTaskGraph')

    #def execute_tool(tool_name, tool_args):
        #args = ast.literal_eval(tool_args)
        #tool_instance = globals()[tool_name]()
        #tool_result = tool_instance.run( tool_args)
        #return tool_result

def visualize_knowledge_graph(kg):
    print (kg.keys())
    dot = Digraph(comment="Knowledge Graph")

    # Add nodes
    for node in kg['nodes']:
        dot.node(str(node['id']), node['label'], color=node['color'])

    # Add edges
    for edge in kg['edges']:
        #dot.edge(str(edge['source']), str(edge['target']), label=edge['label'], color=edge['color'])
        dot.edge(str(edge['source']), str(edge['target']), label=edge['label'])

    # Render the graph
    #dot.render("knowledge_graph.gv", view=True)
    return dot

def generate_graphviz(data):
    dot = Digraph()

    # Create nodes
    for item in data:
        task_id = str(item['id'])
        task_name =  f"{task_id}: {item['task']}"
        tool_name = f"{item['tool_name']}"
        dot.node(task_id, task_name + "\n Tool to Use:" + tool_name)

    # Create edges
    for item in data:
        task_id = str(item['id'])
        subtasks = item['subtasks']
        for subtask in subtasks:
            dot.edge(task_id, str(subtask))
            #dot.edge(str(subtask), task_id)
    # Render the graph
    #dot.format = 'png'
    dot.attr(size=f"{10},{10}", dpi='50')
    dot.render('graph', view=True, cleanup=True, format='svg')

def code_to_work ():

    # Generate the graph
    generate_graphviz(plan.dict()['task_graph'])

    from IPython.display import Image

    from IPython.display import Image, display, HTML, SVG

    # Path to your image
    image_path = 'graph.png'

    # Desired height for the image (in pixels)
    image_height = 1000
    image_width = 1000
    # Create an HTML tag with the image and set the height
    image_html = f'<img src="{image_path}" style="height:{image_height}px; width:{image_width}px">'

    # Display the image in the notebook
    #display(HTML(image_html))

    #display(Image(filename='graph.png', width=1000, height=2000)    )
    display(SVG(filename='graph.svg')    )


__version__ = '2.1.2'

class Table:
    """
table_data: 'list[list[any]]' - Data for the table

width: int - Width of a cell, auto by default

height: int - Height of cell, 1 by default.

align: str - Horizontal: 'w' for west, 'e' - for east, 'c' - for center (center is kinda iffy.)
             Vertical: 'T' for fop, 'B' for bottom, 'C' for center, ('WB' by default)

    """

    # valid kwargs: width: int = 0, height: int = 1, align: str = 'WB'
    def __init__(self, table_data: list | dict, **kwargs):
        """
table_data: 'list[list[any]]' - Data for the table

width: int - Width of a cell, auto by default

height: int - Height of cell, 1 by default.

align: str - Horizontal: 'w' for west, 'e' - for east, 'c' - for center
                 Vertical: 'T' for fop, 'B' for bottom, 'C' for center, ('WT' by default)

        """
        if type(table_data) is dict:
            table_data = list(table_data.items())
        width, height, align = kwargs.get('width', auto(table_data)), kwargs.get('height', 1), kwargs.get('align', 'wt')
        self._table = TableInternal(table_data, width, height, align)

    def make(self) -> str:
        """
        Generates the table.
        Returns string
        usage:
            1. Turn list into table: table1 = Table(data)
            2. print the table :     print(table1.make())
        """
        return self._table.make()

class TableInternal:
    """
    Internal class
    Use facade pls
    """

    def __init__(self, tabledata, item_length, cell_height, align):
        # Should I maker this a dataclass?
        self.tabledata = tabledata
        self.item_length = item_length
        self.cell_height = cell_height
        self.align = align.lower()

    def get_datarow(self, row_data: list) -> str:
        row = ''
        for data_item in row_data:
            frame_space = abs(self.item_length - len(str(data_item)))
            row += '│'
            if self.align[0] == 'w':  # Horizontal align west
                row += f'{data_item}' + frame_space * " "
            elif self.align[0] == 'e':  # Horizontal align east
                row += frame_space * " " + f'{data_item}'
            elif self.align[0] == 'c':  # Horizontal align center
                half_length = self.item_length // 2
                if frame_space % 2 == 0:
                    row += (frame_space - half_length) * " " + f'{data_item}' + (frame_space - half_length) * " "
                else:
                    row += (frame_space - half_length) * " " + f'{data_item}' + (frame_space - half_length + 1) * " "
            else:
                raise ValueError(("Invalid horizontal alignment", self.align[0], "Must be 'E', 'W' or 'C'"))
        row += '│\n'
        empty_row = len(self.tabledata[0]) * ("│" + self.item_length * " ") + "│\n"
        if self.align[1] == 't':  # Horizontal align Top
            row = row + (self.cell_height - 1) * empty_row
        elif self.align[1] == 'b':  # Horizontal align Bottom
            row = (self.cell_height - 1) * empty_row + row
        elif self.align[1] == 'c':  # Horizontal align Center
            half_length = self.cell_height // 2
            row = (self.cell_height - half_length - 1) * empty_row + row + half_length * empty_row
        else:
            raise ValueError(("Invalid vertical alignment", self.align[1], "Must be 'T', 'B' or 'C'"))
        return row

    def get_nondata_row(self, separator) -> str:
        nondata_row = separator[0]  # head: '┌┬┐' | foot: '└┴┘' | separator: '├┼┤'
        nondata_row += self.item_length * "─"
        for _ in range(len(self.tabledata[0]) - 1):
            nondata_row += separator[1]
            nondata_row += self.item_length * "─"
        nondata_row += separator[2] + '\n'
        return nondata_row

    def make(self) -> str:
        str_table = self.get_nondata_row('┌┬┐')  # Head
        separator_row = self.get_nondata_row('├┼┤')  # Separator row
        for row in self.tabledata:  # Main content
            str_table += self.get_datarow(row)
            if row != self.tabledata[-1]:
                str_table += separator_row
        str_table += self.get_nondata_row('└┴┘')  # Footer
        return str_table

def auto(data):
    """Finds the longest entry and returns its length."""
    lengths = []
    for row in data:
        for entry in row:
            lengths.append(len(str(entry)))
    return max(lengths)


import scooby
import unittest
from SimPEG import Report

class TestReport(unittest.TestCase):
    def test_version_defaults(self):
        # Reporting is now done by the external package scooby.
        # We just ensure the shown packages do not change (core and optional).
        out1 = Report()
        out2 = scooby.Report(
            core=[
                "SimPEG",
                "discretize",
                "pymatsolver",
                "numpy",
                "scipy",
                "sklearn",
                "matplotlib",
                "empymod",
                "geoana",
                "pandas",
            ],
            # Optional packages.
            optional=[
                "cython",
                "pydiso",
                "numba",
                "dask",
                "sympy",
                "IPython",
                "ipywidgets",
                "plotly",
                "vtk",
                "utm",
                "memory_profiler",
            ],
            ncol=3,
            text_width=80,
            sort=False,
        )

        # Ensure they're the same; exclude initial time to avoid errors due
        # to second-change.
        assert out1.__repr__()[115:] == out2.__repr__()[115:]

if __name__ == "__main__":
    unittest.main()


# coding=utf-8

import threading
import logging
import argparse
import sys
import atexit
import mimetypes
import pkg_resources
import gettext

from . import config, utils
from . import coordinator
from .__version__ import __version__
from .channel import EFBChannel
from .middleware import EFBMiddleware

# gettext.install('ehforwarderbot', 'locale')
coordinator.translator = gettext.translation('ehforwarderbot',
                                             pkg_resources.resource_filename('ehforwarderbot', 'locale'),
                                             fallback=True)

_ = coordinator.translator.gettext
ngettext = coordinator.translator.ngettext

if sys.version_info < (3, 6):
    raise Exception(_("Python 3.6 or higher is required. Your version is {version}.").format(version=sys.version))

description = _("EH Forwarder Bot is an extensible chat tunnel framework which allows "
                "users to contact people from other chat platforms, and ultimately "
                "remotely control their accounts in other platforms.")

parser = argparse.ArgumentParser(description=description,
                                 epilog="GitHub: https://github.com/blueset/ehForwarderBot")

parser.add_argument("-v", '--verbose', action='store_true',
                    help=_("Enable verbose mode. (Level: Debug)"))
parser.add_argument("-V", "--version", action='store_true',
                    help=_("Show version numbers and exit."))
parser.add_argument("-p", "--profile",
                    help=_("Choose a profile to start with."),
                    default="default")

def stop_gracefully():
    logger = logging.getLogger(__name__)
    if isinstance(coordinator.master, EFBChannel):
        coordinator.master.stop_polling()
        logger.debug("Stop signal sent to master: %s" % coordinator.master.channel_name)
    for i in coordinator.slaves:
        if isinstance(coordinator.slaves[i], EFBChannel):
            coordinator.slaves[i].stop_polling()
            logger.debug("Stop signal sent to slave: %s" % coordinator.slaves[i].channel_name)
    if coordinator.master_thread and coordinator.master_thread.is_alive():
        coordinator.master_thread.join()
    for i in coordinator.slave_threads.values():
        if i.is_alive():
            i.join()

def init():
    """
    Initialize all channels.
    """

    logger = logging.getLogger(__name__)

    # Initialize mimetypes library
    mimetypes.init([pkg_resources.resource_filename('ehforwarderbot', 'mimetypes')])

    # Initialize all channels
    # (Load libraries and modules and init them with Queue `q`)

    conf = config.load_config()

    for i in conf['slave_channels']:
        logger.log(99, "\x1b[0;37;46m %s \x1b[0m", _("Initializing slave {}...").format(i))

        cls = utils.locate_module(i, 'slave')
        instance_id = i.split('#', 1)[1:]
        instance_id = (instance_id and instance_id[0]) or None
        coordinator.add_channel(cls(instance_id=instance_id))

        logger.log(99, "\x1b[0;37;42m %s \x1b[0m",
                   _("Slave channel {name} ({id}) # {instance_id} is initialized.")
                   .format(name=cls.channel_name, id=cls.channel_id,
                           instance_id=instance_id))

    logger.log(99, "\x1b[0;37;46m %s \x1b[0m",
               _("Initializing master {}...").format(conf['master_channel']))
    instance_id = conf['master_channel'].split('#', 1)[1:]
    instance_id = (instance_id and instance_id[0]) or None
    coordinator.add_channel(utils.locate_module(conf['master_channel'], 'master')
                            (instance_id=instance_id))
    logger.log(99, "\x1b[0;37;42m %s \x1b[0m",
               _("Master channel {name} ({id}) # {instance_id} is initialized.")
               .format(name=coordinator.master.channel_name,
                       id=coordinator.master.channel_id,
                       instance_id=instance_id))

    logger.log(99, "\x1b[1;37;42m %s \x1b[0m", _("All channels initialized."))
    for i in conf['middlewares']:
        logger.log(99, "\x1b[0;37;46m %s \x1b[0m", _("Initializing middleware {}...").format(i))
        cls = utils.locate_module(i, 'middleware')

        instance_id = i.split('#', 1)[1:]
        instance_id = (instance_id and instance_id[0]) or None
        coordinator.add_middleware(cls(instance_id=instance_id))
        logger.log(99, "\x1b[0;37;42m %s \x1b[0m",
                   _("Middleware {name} ({id}) # {instance_id} is initialized.")
                   .format(name=cls.middleware_name, id=cls.middleware_id,
                           instance_id=instance_id))

    logger.log(99, "\x1b[1;37;42m %s \x1b[0m", _("All middlewares are initialized."))

    coordinator.master_thread = threading.Thread(target=coordinator.master.poll)
    coordinator.slave_threads = {key: threading.Thread(target=coordinator.slaves[key].poll)
                                 for key in coordinator.slaves}

def poll():
    """
    Start threads for polling
    """
    coordinator.master_thread.start()
    for i in coordinator.slave_threads:
        coordinator.slave_threads[i].start()

def main():
    args = parser.parse_args()

    if getattr(args, "version", None):
        versions = _("EH Forwarder Bot\n"
                     "Version: {version}\n"
                     "Python version:\n"
                     "{py_version}").format(version=__version__, py_version=sys.version)
        try:
            conf = config.load_config()
            # Master channel
            master_channel: EFBChannel = utils.locate_module(conf['master_channel'], 'master')
            instance_id = conf['master_channel'].split('#', 1)[1:]
            instance_id = (instance_id and instance_id[0]) or _("Default profile")
            versions += "\n\n" + _("Master channel:") + "\n    " + _("{name} ({id}) {version} # {instance_id}") \
                .format(name=master_channel.channel_name,
                        id=master_channel.channel_id,
                        version=master_channel.__version__,
                        instance_id=instance_id)
            versions += "\n\n" + ngettext("Slave channel:", "Slave channels:", len(conf['slave_channels']))
            for i in conf['slave_channels']:
                instance_id = i.split('#', 1)[1:]
                instance_id = (instance_id and instance_id[0]) or _("Default profile")
                slave_channel: EFBChannel = utils.locate_module(i, 'slave')
                versions += "\n    " + _("{name} ({id}) {version} # {instance_id}") \
                            .format(name=slave_channel.channel_name,
                                    id=slave_channel.channel_id,
                                    version=slave_channel.__version__,
                                    instance_id=instance_id)
            versions += "\n\n" + ngettext("Middleware:", "Middlewares:", len(conf['middlewares']))
            if conf['middlewares']:
                for i in conf['middlewares']:
                    instance_id = i.split('#', 1)[1:]
                    instance_id = (instance_id and instance_id[0]) or _("Default profile")
                    middleware: EFBMiddleware = utils.locate_module(i, 'middleware')
                    versions += "\n    " + _("{name} ({id}) {version} # {instance_id}") \
                                .format(name=middleware.middleware_name,
                                        id=middleware.middleware_name,
                                        version=middleware.__version__,
                                        instance_id=instance_id)
            else:
                versions += "\n    " + _("No middleware is enabled.")
        finally:
            print(versions)
    else:
        if getattr(args, "verbose", None):
            level = logging.DEBUG
        else:
            level = logging.ERROR
        logging.basicConfig(format='%(asctime)s [%(levelname)s]: %(name)s (%(module)s.%(funcName)s; '
                                   '%(filename)s:%(lineno)d) \n    %(message)s', level=level)
        logging.root.setLevel(level)

        # signal.signal(signal.SIGINT, stop_gracefully)
        # signal.signal(signal.SIGTERM, stop_gracefully)
        atexit.register(stop_gracefully)

        if args.profile:
            coordinator.profile = str(args.profile)

        init()
        poll()

if __name__ == '__main__':
    main()


"""Credit Calculator Project"""

# Libraries
import math
import argparse

class CreditCalculator:
    def __init__(self):
        """Initialize the class"""
        self.payment: float = 0
        self.principal: float = 0
        self.interest: float = 0
        self.overpayment: float = 0
        self.periods = 0

    def annuity_mon_pay(self):
        """Calculate annuity monthly payment"""
        i = self.interest / (12 * 100)
        a = math.ceil(self.principal * (i * pow(1 + i, self.periods)) / (pow(1 + i, self.periods) - 1))
        print(f'Your annuity payment = {a}!')
        self.overpayment = a * self.periods - self.principal
        print(f'Overpayment = {self.overpayment}')

    def diff_pay(self):
        """Calculate differentiated monthly payment"""
        i = self.interest / (12 * 100)
        for m in range(1, self.periods + 1):
            d = math.ceil(self.principal / self.periods + i *
                          (self.principal - (self.principal * (m - 1)) / self.periods))
            print(f'Month {m}: paid out {d}')
            self.overpayment += d
        print(f'Overpayment = {self.overpayment - self.principal}')

    def num_mon_pay(self):
        """Calculate the number of months to repay the credit"""
        i = self.interest / (12 * 100)
        n = math.ceil(math.log(self.payment / (self.payment - i * self.principal), 1 + i))
        years = n // 12
        months = n % 12
        self.overpayment = self.payment * n - self.principal
        if years == 0:
            print(f'You need {months} months to repay this credit!')
        elif months == 0:
            print(f'You need {years} years to repay this credit!')
        else:
            print(f'You need {years} years and {months} months to repay this credit!')
        print(f'Overpayment = {self.overpayment}')

    def loan_principal(self):
        """Calculate the loan principal"""
        i = self.interest / (12 * 100)
        p = math.floor(self.payment / ((i * pow(1 + i, self.periods)) / (pow(1 + i, self.periods) - 1)))
        print(f'Your loan principal = {p}!')
        self.overpayment = self.payment * self.periods - p
        print(f'Overpayment = {self.overpayment}')

    def parser(self):
        """Parse the arguments and call the corresponding method"""
        if args.type == 'annuity':
            if args.payment is not None and args.principal is not None and args.interest is not None:
                self.payment = args.payment
                self.principal = args.principal
                self.interest = args.interest
                self.num_mon_pay()
            elif args.payment is not None and args.periods is not None and args.interest is not None:
                self.payment = args.payment
                self.periods = args.periods
                self.interest = args.interest
                self.loan_principal()
            elif args.principal is not None and args.periods is not None and args.interest is not None:
                self.principal = args.principal
                self.periods = args.periods
                self.interest = args.interest
                self.annuity_mon_pay()
            else:
                print('Incorrect parameters')
        elif args.type == 'diff':
            if args.type is None or args.interest is None or args.principal is None \
                    or args.periods is None:
                print('Incorrect parameters')
            elif args.principal and args.periods and args.interest:
                self.principal = args.principal
                self.periods = args.periods
                self.interest = args.interest
                self.diff_pay()
            else:
                print('Incorrect parameters')
        else:
            print('Incorrect parameters')

# Main
if __name__ == '__main__':
    credit_calc = CreditCalculator()
    parser = argparse.ArgumentParser()
    # Add the arguments to the parser
    parser.add_argument('--type', type=str, help='type of payment')
    parser.add_argument('--payment', type=float, help='monthly payment')
    parser.add_argument('--principal', type=float, help='credit principal')
    parser.add_argument('--periods', type=int, help='the number of payment periods')
    parser.add_argument('--interest', type=float, help='the loan interest rate')
    args = parser.parse_args()
    credit_calc.parser()


from typing import Type

import torch

from loss import temperature
from patch import Patch

class ContrastiveWeightedKNN():
    def __init__(self, contrastive_model: Type[torch.nn.Module], train, train_subset_ratio=.5, k=10):
        self.contrastive_model = contrastive_model
        self.train_x, self.train_y = train
        self.train_subset_ratio = train_subset_ratio
        self.k = k

    def shuffle(self):
        random_order = torch.randperm(self.train_x.shape[0])
        self.train_x = self.train_x[random_order]
        self.train_y = self.train_y[random_order]

    def w_knn(self, test_x):
        train_x = self.train_x[:(int)(self.train_subset_ratio * len(self.train_x))]
        train_y = self.train_y[:(int)(self.train_subset_ratio * len(self.train_y))]

        _c = Patch.classes
        _cn = 2
        weights = torch.mm(test_x, train_x.t())  # |test| x |train|
        kweights, k_train_indices = torch.topk(weights, self.k, dim=-1)  # |test| x k
        kweights = (kweights / temperature).exp()
        klabels = train_y[k_train_indices, :]  # |test| x k x c
        klabels = torch.nn.functional.one_hot(klabels.long(), num_classes=_cn)  # |test| x k x c x _cn
        voting = kweights.unsqueeze(-1).unsqueeze(-1) * klabels
        voting = torch.sum(voting, dim=1)
        voting = torch.argsort(voting, dim=-1, descending=True)

        return voting[:, :, 0]

    def __call__(self, x):
        x = x.cuda(non_blocking=True)
        features, _ = self.contrastive_model(x)
        prediction = self.w_knn(features)
        return prediction


import os
import sys
import unittest

# Make paths absolute
if sys.argv[0] == "/":
    topdir = sys.argv[0]
else:
    topdir = os.path.dirname(os.path.abspath(__file__))
topdir = os.path.abspath(os.path.dirname(topdir + "/../../../.."))

if topdir not in sys.path:
    sys.path.append(topdir)

from spacewalk.common import rhnTranslate

class Tests(unittest.TestCase):
    # pylint: disable=R0904

    @staticmethod
    def _setup(lang):
        domain = "unit-test"
        localedir = "%s/common/test/unit-test/translations" % topdir
        rhnTranslate.cat.set(domain=domain, localedir=localedir)
        rhnTranslate.cat.setlangs(lang)

    def _test(self, lang, s, target):
        self._setup(lang)
        ss = rhnTranslate._(s)
        self.assertEqual(ss, target)

    def test_setlangs_en(self):
        "Tests setting the language to en"
        lang = "en"
        self._setup(lang)
        langs = rhnTranslate.cat.getlangs()
        self.failUnless(langs[0] == lang)

    def test_setlangs_ro(self):
        "Tests setting the language to ro"
        lang = "ro"
        self._setup(lang)
        langs = rhnTranslate.cat.getlangs()
        self.failUnless(langs[0] == lang)

    def test_setlangs_go(self):
        """Tests setting the language to go (does not exist)"""
        lang = "go"
        self._setup(lang)
        langs = rhnTranslate.cat.getlangs()
        if hasattr(sys, "version_info"):
            # On python 1.5.2 we don't really get an idea what the language
            # is, so it's ok to check for the first component
            self.failIf(langs[0] == lang, "Language is %s" % langs[0])
        else:
            self.failUnless(langs[0] == lang, "Language is %s" % langs[0])

    def test_en_1(self):
        "Tests plain English messages"
        lang = 'en'
        s = "Good day"
        target = s
        self._test(lang, s, target)

    def test_en_2(self):
        "Tests plain English messages"
        lang = 'en'
        s = "How do you do?"
        target = s
        self._test(lang, s, target)

    def test_en_3(self):
        "Tests plain English messages"
        lang = 'en'
        s = "What should I do now?"
        target = s
        self._test(lang, s, target)

    def test_en_missing_1(self):
        "Tests plain English messages that are not in the translation files"
        lang = 'en'
        s = "This string doesn't exist in the translation"
        target = s
        self._test(lang, s, target)

    def test_ro_1(self):
        "Tests plain English messages translated to Romanian"
        lang = 'ro'
        s = "Good day"
        target = "Buna ziua"
        self._test(lang, s, target)

    def test_ro_2(self):
        "Tests plain English messages translated to Romanian"
        lang = 'ro'
        s = "How do you do?"
        target = "Ce mai faceti?"
        self._test(lang, s, target)

    def test_ro_3(self):
        "Tests plain English messages translated to Romanian"
        lang = 'ro'
        s = "What should I do now?"
        target = "Ce sa fac acum?"
        self._test(lang, s, target)

    def test_ro_missing_1(self):
        "Tests plain English messages that are not in the translation files (ro)"
        lang = 'ro'
        s = "This string doesn't exist in the translation"
        target = s
        self._test(lang, s, target)

    def test_go_1(self):
        "Tests plain English messages translated in the mythical go language"
        lang = 'en'
        s = "Good day"
        target = s
        self._test(lang, s, target)

    def test_go_2(self):
        "Tests plain English messages translated in the mythical go language"
        lang = 'en'
        s = "How do you do?"
        target = s
        self._test(lang, s, target)

    def test_go_3(self):
        "Tests plain English messages translated in the mythical go language"
        lang = 'en'
        s = "What should I do now?"
        target = s
        self._test(lang, s, target)

    def test_go_missing_1(self):
        "Tests plain English messages that are not in the translation files (go)"
        lang = 'en'
        s = "This string doesn't exist in the translation"
        target = s
        self._test(lang, s, target)

if __name__ == '__main__':
    sys.exit(unittest.main() or 0)


import torch
import torch.optim as optim
from lib.progressbar import progress_bar

def train_epoch(model, criterion, optimizer, train_loader, device=torch.device('cuda'), dtype=torch.float):
    model.train()
    train_loss = 0

    for batch_idx, (inputs, targets) in enumerate(train_loader):
        inputs, targets = inputs.to(device, dtype), targets.to(device, dtype)
        #print("TARGETS:", targets)
        optimizer.zero_grad()
        outputs = model(inputs)
        #print("OUTPUTS:", outputs)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()
        #print("LOSS:",loss.item())
        #quit()

        train_loss += loss.item()
        progress_bar(batch_idx, len(train_loader),
                     'Loss: {0:.4e}'.format(train_loss/(batch_idx+1)))    #
        #print('loss: {0: .4e}'.format(train_loss/(batch_idx+1)))

def val_epoch(model, criterion, val_loader, device=torch.device('cuda'), dtype=torch.float):
    model.eval()
    val_loss = 0

    with torch.no_grad():
        for batch_idx, (inputs, targets) in enumerate(val_loader):
            inputs, targets = inputs.to(
                device, dtype), targets.to(device, dtype)
            outputs = model(inputs)
            loss = criterion(outputs, targets)

            val_loss += loss.item()
            progress_bar(batch_idx, len(val_loader),
                         'Val Loss: {0:.4e}'.format(val_loss/(batch_idx+1)))
            #print('loss: {0: .4e}'.format(val_loss/(batch_idx+1)))

def test_epoch(model, test_loader, result_collector, device=torch.device('cuda'), dtype=torch.float):
    model.eval()

    with torch.no_grad():
        for batch_idx, (inputs, targets, extra) in enumerate(test_loader):
            ## test_loader should have a final transformer which returns
            ## `y_gt_mm_not_centered` i.e. straight from dataset
            ## `x_cropped_scaled` i.e. no aug but perform crop based on CoM (from dataset) and scale according
            ## to the model requirements i.e 128x128
            ## `CoM point` <-- this will be used to recover back y_pred_mm_not_centered
            # y_pred_std_centered # in mm but standardised
            # values are i.e. [-1, 1]
            ## need special function forward eval to now also pass through last pca layer ans get back
            ## 1, 63  instead of 1, 30
            outputs = model.forward_eval(inputs.to(device, dtype))

            # result college will convert y ->
            result_collector((inputs, outputs, targets, extra))

            progress_bar(batch_idx, len(test_loader))

def test_epoch_dropout(model, test_loader, result_collector, device=torch.device('cuda'), dtype=torch.float, output_rate=10):
	'''
		use dropouts to produce output_rate outputs for each input and then use the mean as output
		we could additionally do the above and supply multiple outcomes.

		important please use batch_size=1 for data_loader!

		`output_rate` => number of evaluations or outputs per input sample
	'''
	## special case here we will be doing dropout and using the mean value ideally...
	## in this case all dropouts are used, maybe in future we would want to use only one dropout
	model.train()

	with torch.no_grad():
		for batch_idx, (inputs, targets, extra) in enumerate(test_loader):
			assert(targets.shape[0] == 1) # only works currently for batch_size=1
			output_set = torch.cat([model.forward_eval(inputs.to(device, dtype)) for _ in range(output_rate)])

			#print("Set shape: ", output_set.shape)

			## do torch.mean in the right axis
			output_mean = torch.mean(output_set, dim=0, keepdim=True)

			#print("Mean Shape: ", output_mean.shape)
			#quit()

			# result college will convert y ->
			result_collector((inputs, output_mean, targets, extra))

			progress_bar(batch_idx, len(test_loader))


import select
import os
import subprocess
from collections import namedtuple
import logging

# project
from .exceptions import (
    AzureHostedCommandException,
    AzureHostedCommandNotFoundException
)

logger = logging.getLogger('Azure_LI_Services')

class Command(object):
    """
    **Implements command invocation**

    An instance of Command provides methods to invoke external
    commands in blocking and non blocking mode. Control of
    stdout and stderr is given to the caller
    """
    @classmethod
    def run(self, command, custom_env=None, raise_on_error=True):
        """
        Execute a program and block the caller. The return value
        is a hash containing the stdout, stderr and return code
        information. Unless raise_on_error is set to false an
        exception is thrown if the command exits with an error
        code not equal to zero

        Example:

        .. code:: python

            result = Command.run(['ls', '-l'])

        :param list command: command and arguments
        :param list custom_env: custom os.environ
        :param bool raise_on_error: control error behaviour

        :return:
            Contains call results in command type

            .. code:: python

                command(output='string', error='string', returncode=int)

        :rtype: namedtuple
        """
        from .path import Path
        command_type = namedtuple(
            'command', ['output', 'error', 'returncode']
        )
        environment = os.environ
        if custom_env:
            environment = custom_env
        if not Path.which(
            command[0], custom_env=environment, access_mode=os.X_OK
        ):
            message = 'Command "%s" not found in the environment' % command[0]
            if not raise_on_error:
                return command_type(
                    output=None,
                    error=None,
                    returncode=-1
                )
            else:
                raise AzureHostedCommandNotFoundException(message)
        try:
            logger.info('Calling: {0}'.format(command))
            process = subprocess.Popen(
                command,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                env=environment
            )
        except Exception as e:
            raise AzureHostedCommandException(
                '%s: %s: %s' % (command[0], type(e).__name__, format(e))
            )
        output, error = process.communicate()
        if process.returncode != 0 and not error:
            error = bytes(b'(no output on stderr)')
        if process.returncode != 0 and not output:
            output = bytes(b'(no output on stdout)')
        if process.returncode != 0 and raise_on_error:
            raise AzureHostedCommandException(
                '%s: stderr: %s, stdout: %s' % (
                    command[0], error.decode(), output.decode()
                )
            )
        return command_type(
            output=output.decode(),
            error=error.decode(),
            returncode=process.returncode
        )

    @classmethod
    def call(self, command, custom_env=None):
        """
        Execute a program and return an io file handle pair back.
        stdout and stderr are both on different channels. The caller
        must read from the output file handles in order to actually
        run the command. This can be done using the CommandIterator
        from command_process

        Example:

        .. code:: python

            process = Command.call(['ls', '-l'])

        :param list command: command and arguments
        :param list custom_env: custom os.environ

        :return:
            Contains process results in command type

            .. code:: python

                command(
                    output='string', output_available=bool,
                    error='string', error_available=bool,
                    process=subprocess
                )

        :rtype: namedtuple
        """
        from .path import Path
        environment = os.environ
        if custom_env:
            environment = custom_env
        if not Path.which(
            command[0], custom_env=environment, access_mode=os.X_OK
        ):
            raise AzureHostedCommandNotFoundException(
                'Command "%s" not found in the environment' % command[0]
            )
        try:
            logger.info('Calling: {0}'.format(command))
            process = subprocess.Popen(
                command,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                env=environment
            )
        except Exception as e:
            raise AzureHostedCommandException(
                '%s: %s' % (type(e).__name__, format(e))
            )

        def output_available():
            def _select():
                descriptor_lists = select.select(
                    [process.stdout], [], [process.stdout], 1e-4
                )
                readable = descriptor_lists[0]
                exceptional = descriptor_lists[2]
                if readable and not exceptional:
                    return True
            return _select

        def error_available():
            def _select():
                descriptor_lists = select.select(
                    [process.stderr], [], [process.stderr], 1e-4
                )
                readable = descriptor_lists[0]
                exceptional = descriptor_lists[2]
                if readable and not exceptional:
                    return True
            return _select

        command = namedtuple(
            'command', [
                'output', 'output_available',
                'error', 'error_available',
                'process'
            ]
        )
        return command(
            output=process.stdout,
            output_available=output_available(),
            error=process.stderr,
            error_available=error_available(),
            process=process
        )


#!/usr/bin/env python

"""Utilitys for working with chemicals arrays.

   Author: Dominic Phillips (dominicp6)
"""

import os
import contextlib
import psutil
import sys
import traceback
from typing import Optional, Callable, Union, TextIO

import numpy as np
import numpy.typing as npt
import scipy.interpolate as interpolate
from scipy.ndimage import gaussian_filter1d

def print_file_contents(file: Union[TextIO, str]):
    if isinstance(file, str):
        with open(file, 'r') as f:
            print(f.read())

    else:
        print(file.read())

def printlog(message: str, log_file: str):
    print(message)
    with open(log_file, 'a') as f:
        print(message, file=f)

def exception_traceback():
    ex_type, ex_value, ex_traceback = sys.exc_info()

    # Extract unformatter stack traces as tuples
    trace_back = traceback.extract_tb(ex_traceback)

    # Format stacktrace
    stack_trace = list()

    for trace in trace_back:
        stack_trace.append(
            "File : %s , Line : %d, Func.Name : %s, Message : %s" % (trace[0], trace[1], trace[2], trace[3]))

    print("Exception type : %s " % ex_type.__name__)
    print("Exception message : %s" % ex_value)
    print("Stack trace : %s" % stack_trace)

def count_files(directory: str, suffix: str) -> int:
    """
    Count the number of files with a given suffix in a directory.

    :param directory: The directory to search.
    :param suffix: The suffix to search for.
    :return: The number of files with that suffix in the directory.
    """
    # List all files in the directory
    files = os.listdir(directory)

    # Select only the files with the given suffix
    files_with_suffix = [f for f in files if f.endswith(f'.{suffix}')]

    # Return the number of files with the suffix
    return len(files_with_suffix)

def list_files(directory: str, suffix: str) -> list[str]:
    """
    Lists the files with a given suffix in a directory.

    :param directory: The directory to search.
    :param suffix: The suffix to search for.
    :return: The list of files with that suffix in the directory.
    """
    # List all files in the directory
    files = os.listdir(directory)

    # Select only the files with the given suffix
    files_with_suffix = [f for f in files if f.endswith(f'.{suffix}')]

    # Return the files
    return files_with_suffix

def assert_kwarg(kwargs: dict, kwarg: str, obj_name: str):
    try:
        assert kwargs[kwarg] is not None
    except Exception:
        raise ValueError(f"{kwarg} must be provided to {obj_name}")

def supress_stdout(func):
    def wrapper(*a, **ka):
        with open(os.devnull, 'w') as devnull:
            with contextlib.redirect_stdout(devnull):
                return func(*a, **ka)

    return wrapper

def select_file_option(options: list, file_type: str) -> int:
    """
    Selects a file from a list of options.
    """
    if len(options) > 1:
        print(f"{len(options)} {file_type} files found in the given directory:")
        for idx, file in enumerate(options):
            print(f"[{idx + 1}] {file}")
        while True:
            selection = input(f"Which {file_type} file do you want to use? ")
            valid_selections = [str(idx + 1) for idx in range(len(options))]
            if selection not in valid_selections:
                print(f"Input not recognised; must be one of {valid_selections}")
            else:
                break
    else:
        selection = '1'

    return int(selection) - 1  # -1 to allow for correct indexing

def check_if_memory_available(file):
    """
    Checks if the system has available memory for loading the requested file.
    """
    file_stat = os.stat(file)
    file_size = file_stat.st_size
    available_memory = psutil.virtual_memory()[1]
    if file_size > 0.9 * available_memory:
        raise MemoryError(f"Loading the file {file} would use more than 90% of "
                          f"the available memory ({round(file_size / 10 ** 9, 1)}/{round(available_memory / 10 ** 9, 1)}Gb).")
    else:
        pass

def replace_inf_with_nan(array: np.array) -> np.array:
    for idx, entry in enumerate(array):
        if entry == np.inf or entry == -np.inf:
            array[idx] = np.nan

    return array

def remove_nans(data: np.array, axis: int = 1) -> np.array:
    num_nans = np.count_nonzero(np.isnan(data))
    if num_nans > 0:
        axis_str = "rows" if axis == 1 else "columns"
        print(f"{num_nans} NaNs detected, removing {axis_str} with NaNs.")
        data = data[~np.isnan(data).any(axis=1), :]

    return data

def rms_interval(array: np.array) -> float:
    return np.sqrt(np.mean([(array[i]-array[i+1])**2 for i in range(len(array)-1)]))

def vector_rmsd(x: np.array, y: np.array) -> float:
    return np.sqrt(np.mean((x - y) ** 2))

def linear_interp_coordinate_data(x_data: npt.NDArray[np.float64], y_data: npt.NDArray[np.float64], x_to_evaluate: float) -> float:
    x_min = min(x_data)
    x_max = max(x_data)

    if x_min <= x_to_evaluate <= x_max:
        # interpolate
        x_low = max([x for x in x_data if x <= x_to_evaluate])
        i_low = np.where(x_data == x_low)
        x_high = min([x for x in x_data if x > x_to_evaluate])
        i_high = np.where(x_data == x_high)
        interpolation_distance = (x_to_evaluate - x_low) / (x_high - x_low)

        return float(y_data[i_low] + (y_data[i_high] - y_data[i_low]) * interpolation_distance)

    elif x_to_evaluate < x_min:
        # extrapolate
        return float(y_data[0] - (x_min - x_to_evaluate) * (y_data[1] - y_data[0]) / (x_data[1] - x_data[0]))

    elif x_to_evaluate > x_max:
        # extrapolate
        return float(y_data[-1] + (x_to_evaluate - x_max) * (y_data[-1] - y_data[-2]) / (x_data[-1] - x_data[-2]))

def gaussian_smooth(x: np.array, y: np.array, dx: float, sigma: float):
    interp = interpolate.interp1d(x, y, fill_value='extrapolate')
    interpolated_x = np.arange(min(x), max(x)+dx/2, dx)
    sigma_gaussian = sigma / dx
    smoothed_y = gaussian_filter1d(interp(interpolated_x), sigma_gaussian, mode='reflect')

    return interpolated_x, smoothed_y

def select_lowest_minima(minima_array: np.array, function: Callable, n: Optional[int] = 2) -> np.array:
    value_array = []
    for minima in minima_array:
        value_array.append(function(*minima))
    idx = np.argsort(value_array)[:n]

    return np.array([minima_array[i] for i in idx])


class Vehicle:
    def __init__(self,milege,cost):
        self.milege = milege
        self.cost = cost

    def show_vehicle_details(self):
        print("The milege of vehicle is:",self.milege)
        print("The cost of vehicle is:", self.cost)

class Car(Vehicle):
    def __init__(self,milege,cost,color,horsepower):
        super(). __init__(milege,cost)
        self.color = color
        self.horsepower = horsepower
        print("The color of Car is:", self.color)
        print("The Vehicle HorsePower is:",self.horsepower)

c1 = Car(50, "25lac", "White","2000HP")
c1.show_vehicle_details()


# 5. Write a function that has 3 parameters: source (a path to a file), directory (a path to a directory) and
# buffer_size (an integer). The function will copy the given file into the given directory, using a buffer of the
# third parameter size, in bytes.

import os

def five(source, directory, buffer_size):
    try:
        fo = open(directory + '/' + source, "wt")
    except:
        print('coulnt create new file in directory')
    try:
        f = open(source, 'r+')
    except:
        print('couldnt open source')

    file_size = os.path.getsize(source)
    while file_size > 0:
        line = f.readline(buffer_size)
        file_size -= buffer_size
        fo.write(line)
    f.close()

five('test_file.txt', 'test_dir', 5)