import datetime
import inspect
import json
import logging
from typing import Callable, Dict, List, Union

_JSON_INDENT = 4
_JSON_SEPERATORS = (",", ": ")

_DEPTH_RECURSION_DEFAULT = 1
_DEPTH_RECURSION_GET_LOGGER = 2
_DEPTH_RECURSION_JSON_LOGGER = 3

_LOGGING_LEVEL = logging.INFO if not __debug__ else logging.DEBUG
_FORMATTER_STR_DETAILED = (
    "%(asctime)s (PID:%(process)d) %(levelname)s %(name)s: %(message)s"
)
# _FORMATTER_STR_SIMPLE = "%(name)s  %(message)s"
_FORMATTER_STR = _FORMATTER_STR_DETAILED

def get_method_name(
    module_name: str = None,
    class_name: str = None,
    depth_recursion: int = _DEPTH_RECURSION_DEFAULT,
) -> str:
    """Retrieves a method name with a module name and class name.

    :param module_name: Module name
    :type module_name: str

    :param class_name: Class name
    :type class_name: str

    :param depth_recursion: Depth of recursive call for call stacks (>=1)
    :type depth_recursion: int

    :return: Method name
    :rtype: str
    """
    if depth_recursion < 1:
        raise ValueError(f"depth_recursion is not natural number. - {depth_recursion}")

    # Gets an appropriate frame stack where the logger is called.
    f_stack = inspect.currentframe()
    for _ in range(depth_recursion):
        f_stack = f_stack.f_back

    if f_stack is None:
        raise ValueError("Reached the call stack limit.")

    method_name = f_stack.f_code.co_name

    if module_name is None and class_name is None:
        return method_name
    elif module_name is None:
        return f"{class_name}.{method_name}"
    elif class_name is None:
        return f"{module_name}.{method_name}"
    else:
        return f"{module_name}.{class_name}.{method_name}"

def _logging_base_decorator(func_logging_decorator: Callable) -> Callable:
    """Decorator Function with Parameters.

    :param func_logging_system: Function object for Decoration
    :type func_logging_system: Function object

    :return:  Wrapper function's object
    :rtype: Callable
    """

    def wrapper(*args, **kwargs):
        def wrapper_logging_decorator(func_get_logger):
            return func_logging_decorator(func_get_logger, *args, **kwargs)

        return wrapper_logging_decorator

    return wrapper

@_logging_base_decorator
def _logging_decorator(
    func_get_logger: Callable, level: int = _LOGGING_LEVEL, is_propagate: bool = False
) -> Callable:
    """Decorator Function for Python Logging.

    :param func_get_logger: Function object for Decoration
    :type func_get_logger: function

    :param level: Logging Level
    :type level: int

    :param is_propagate: Need Propagation or not (False: Not propagate / True: Propagate)
    :type is_propagate: bool

    :return Wrapper function's object
    :rtype: Callable
    """
    handler = logging.StreamHandler()

    handler.setLevel(level)

    formatter = logging.Formatter(_FORMATTER_STR)
    handler.setFormatter(formatter)

    def wrapper(name):
        logger = func_get_logger(name)
        if handler is not None:
            logger.addHandler(handler)
        logger.setLevel(level)
        logger.propagate = is_propagate
        return logger

    return wrapper

@_logging_decorator()
def get_logger(name: str) -> logging.Logger:
    """Gets a logger with the name.

    :param name: Name of the logger
    :type name: str

    :return Logger
    :rtype: logging.Logger
    """
    return logging.getLogger(name=name)

def get_default_logger() -> logging.Logger:
    """Gets a logger with the method name.

    :return Logger
    :rtype: logging.Logger
    """
    return get_logger(name=get_method_name(depth_recursion=_DEPTH_RECURSION_GET_LOGGER))

def get_class_default_logger(
    class_name: str, module_name: str = None
) -> logging.Logger:
    """Gets a logger with the class name.

    :param class_name: Class name.
    :type class_name: str

    :param class_name: (optional) Module name.
    :type class_name: str

    :return Logger
    :rtype: logging.Logger
    """
    return get_logger(
        name=get_method_name(
            module_name=module_name,
            class_name=class_name,
            depth_recursion=_DEPTH_RECURSION_GET_LOGGER,
        )
    )

def _json_serialize(obj: object) -> str:
    """Serializes the given object

    :param obj: obj
    :type obj: object

    :return iso-formatted obj
    :rtype: str
    """
    if isinstance(obj, (datetime.datetime, datetime.date)):
        return obj.isoformat()
    raise TypeError(f"Type {type(obj)} not serializable")

def _json_dumps(json_items: Union[List[object], Dict[object, object]]) -> str:
    """Dumps as a JSON format.

    :param json_items: Items to be converted to a JSON format.
    :type json_items: list or dict

    :return JSON formatted items.
    :rtype: str
    """
    return json.dumps(
        json_items,
        indent=_JSON_INDENT,
        ensure_ascii=False,
        sort_keys=True,
        separators=_JSON_SEPERATORS,
        default=_json_serialize,
    )

def json_logger(
    level: int,
    json_items: Union[List[object], Dict[object, object]],
    module_name: str = None,
    class_name: str = None,
    depth_recursion: int = 2,
    msg: str = None,
) -> None:
    """Logs the given json string.

    :param level: Logging level.
    :type level: int

    :param json_items: Items to be converted to a JSON format.
    :type json_items: list or dict

    :param module_name: Module name.
    :type module_name: str

    :param class_name: Class name.
    :type class_name: str

    :param depth_recursion: Depth recursion.
    :type depth_recursion: int

    :param msg: Logging message.
    :type msg: str
    """
    get_logger(
        get_method_name(
            module_name=module_name,
            class_name=class_name,
            depth_recursion=depth_recursion,
        )
    ).log(level=level, msg=msg)
    get_logger(
        get_method_name(
            module_name=module_name,
            class_name=class_name,
            depth_recursion=depth_recursion,
        )
    ).log(level=level, msg=_json_dumps(json_items))

def json_logger_debug(
    json_items: Union[List[object], Dict[object, object]],
    module_name: str = None,
    class_name: str = None,
    msg: str = None,
) -> None:
    """Logs the given json string as DEBUG.

    :param json_items: Items to be converted to a JSON format.
    :type json_items: list or dict

    :param module_name: Module name.
    :type module_name: str

    :param class_name: Class name.
    :type class_name: str

    :param msg: Logging message.
    :type msg: str
    """
    json_logger(
        level=logging.DEBUG,
        json_items=json_items,
        module_name=module_name,
        class_name=class_name,
        depth_recursion=_DEPTH_RECURSION_JSON_LOGGER,
        msg=msg,
    )

def json_logger_info(
    json_items: Union[List[object], Dict[object, object]],
    module_name: str = None,
    class_name: str = None,
    msg: str = None,
) -> None:
    """Logs the given json string as INFO.

    :param json_items: Items to be converted to a JSON format.
    :type json_items: list or dict

    :param module_name: Module name.
    :type module_name: str

    :param class_name: Class name.
    :type class_name: str

    :param msg: Logging message.
    :type msg: str
    """
    json_logger(
        level=logging.INFO,
        json_items=json_items,
        module_name=module_name,
        class_name=class_name,
        depth_recursion=_DEPTH_RECURSION_JSON_LOGGER,
        msg=msg,
    )

def json_logger_warning(
    json_items: Union[List[object], Dict[object, object]],
    module_name: str = None,
    class_name: str = None,
    msg: str = None,
) -> None:
    """Logs the given json string as WARNING.

    :param json_items: Items to be converted to a JSON format.
    :type json_items: list or dict

    :param module_name: Module name.
    :type module_name: str

    :param class_name: Class name.
    :type class_name: str

    :param msg: Logging message.
    :type msg: str
    """
    json_logger(
        level=logging.WARNING,
        json_items=json_items,
        module_name=module_name,
        class_name=class_name,
        depth_recursion=_DEPTH_RECURSION_JSON_LOGGER,
        msg=msg,
    )

def json_logger_error(
    json_items: Union[List[object], Dict[object, object]],
    module_name: str = None,
    class_name: str = None,
    msg: str = None,
) -> None:
    """Logs the given json string as ERROR.

    :param json_items: Items to be converted to a JSON format.
    :type json_items: list or dict

    :param module_name: Module name.
    :type module_name: str

    :param class_name: Class name.
    :type class_name: str

    :param msg: Logging message.
    :type msg: str
    """
    json_logger(
        level=logging.ERROR,
        json_items=json_items,
        module_name=module_name,
        class_name=class_name,
        depth_recursion=_DEPTH_RECURSION_JSON_LOGGER,
        msg=msg,
    )

def json_logger_critical(
    json_items: Union[List[object], Dict[object, object]],
    module_name: str = None,
    class_name: str = None,
    msg: str = None,
) -> None:
    """Logs the given json string as CRITICAL.

    :param json_items: Items to be converted to a JSON format.
    :type json_items: list or dict

    :param module_name: Module name.
    :type module_name: str

    :param class_name: Class name.
    :type class_name: str

    :param msg: Logging message.
    :type msg: str
    """
    json_logger(
        level=logging.CRITICAL,
        json_items=json_items,
        module_name=module_name,
        class_name=class_name,
        depth_recursion=_DEPTH_RECURSION_JSON_LOGGER,
        msg=msg,
    )


from lib.processors import findFaceGetPulse
import networkx as nx

"""
Simple tool to visualize the design of the real-time image analysis

Everything needed to produce the graph already exists in an instance of the
assembly.
"""

#get the component/data dependancy graph (depgraph) of the assembly
assembly = findFaceGetPulse()
graph = assembly._depgraph._graph

#prune a few unconnected nodes not related to the actual analysis
graph.remove_node("@xin")
graph.remove_node("@xout")
graph.remove_node("driver")

#plot the graph to disc as a png image
ag = nx.to_agraph(graph)
ag.layout('dot')
ag.draw('design.png')


from konlpy.tag import Kkma, Okt
from pandas import DataFrame as df
from gensim.models.word2vec import Word2Vec
import pandas as pd
import logging
import time
import re
import os
import matplotlib as mpl
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.cluster import DBSCAN

start = time.time()
logging.basicConfig(
    format='%(asctime)s : %(levelname)s : %(message)s',
    level=logging.INFO)

kkma = Kkma()
mc = Okt()

def word2vec():
    word_list = []
    path_dir = "C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\리포트 2013~2015 pkl"
    file_list = os.listdir(path_dir)
    file_list.sort()
    print(file_list)
    for i in file_list:
        df = pd.read_pickle("C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\리포트 2013~2015 pkl\\%s" % i)
        for j in df['sentences']:
            if len(j) > 1:
                #print(j)
                word_list.append(j)

    print(len(word_list))
    #print(word_list)
    embedding_model = Word2Vec(word_list, size=200, window=10, min_count=5, iter=500, sg=1, sample=1e-3, hs=0)

    # embedding_model2 = Word2Vec.load('C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\word2vec_model\\stock_summary_model_01.model')
    # embedding_model2.wv.save_word2vec_format("C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\word2vec_model\\word_vector_sample.bin", binary=True)

    # model2 = Word2Vec.load('C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\word2vec_model\\2013~2015_report_size20_win10_min5_iter500_hs0_intersect_ko2')
    # model2.wv.save_word2vec_format("C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\word2vec_model\\ko\\2013~2015_report_size20_win10_min5_iter500_hs0_intersect_ko2.bin", binary=True)
    #
    # prev_model = 'C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\word2vec_model\\ko\\2013~2015_report_size20_win10_min5_iter500_hs0_intersect_ko2.bin'
    # embedding_model.intersect_word2vec_format(fname=prev_model, lockf=1.0, binary=True)
    model_name = "2013~2015_report_size200_win10_min5_iter500_hs0"
    embedding_model.save('C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\word2vec_model\\%s' % model_name)
    word_vector = embedding_model.wv

def tsne_plot(model):
    labels = []
    tokens = []

    mpl.rcParams['axes.unicode_minus'] = False
    plt.rc('font', family='NanumGothic')

    for word in model.wv.vocab:
        tokens.append(model[word])
        labels.append(word)

    print(labels)
    print(len(labels))
    tsne_model = TSNE(perplexity=40, n_components=2, init='pca', n_iter=2500, random_state=23)
    new_values = tsne_model.fit_transform(tokens)

    x = []
    y = []
    for value in new_values[:300]:
        x.append(value[0])
        y.append(value[1])

    plt.figure(figsize=(16, 16))
    for i in range(len(x)):
        plt.scatter(x[i], y[i])
        plt.annotate(labels[i],
                     xy=(x[i], y[i]),
                     xytext=(5, 2),
                     textcoords='offset points',
                     ha='right',
                     va='bottom')
    plt.show()

def cluster(model, file, model_name):

    result = model.wv  # 어휘의 feature vector
    topic = pd.read_pickle('C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\토픽 모델링 결과\\%s' % file)
    #print(result.vocab.keys())
    #vocabs = result.vocab.keys()
    vocabs = []
    for i in topic['sentences']:
        for j in i:
            vocabs.append(j)

    print(len(vocabs))
    # clean_file = open('C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\클러스터전처리.txt', 'r')
    # lines = clean_file.readlines()
    # clean_file.close()
    # remove_list = lines[0].split(', ')

    remove_list = []
    word_vectors = []
    clean_vocabs = []

    for i in vocabs:
        for remove in remove_list:
            i = re.sub(remove, '', i)
        if len(i) > 1:
            clean_vocabs.append(i)

    for v in clean_vocabs:
        try:
            word_vectors.append(result[v])
        except:
            print(v)
            clean_vocabs.remove(v)

    num_clusters = 50  # int(len(clean_vocabs) / 5)  # int(word_vectors.shape[0]/50) # 어휘 크기의 1/5나 평균 5단어
    print(num_clusters)
    num_clusters = int(num_clusters)

    kmeans_clustering = KMeans(n_clusters=num_clusters)
    idx = kmeans_clustering.fit_predict(word_vectors)
    #idx = DBSCAN(eps=1000, min_samples=2).fit(word_vectors)
    print(id)
    idx = list(idx)
    print(len(vocabs))
    print(len(idx))
    names = clean_vocabs
    print(names)
    word_centroid_map = {names[i]: idx[i] for i in range(len(idx))}

    dfIndustry = pd.DataFrame(columns=["cluster", "keyword"])
    for c in range(num_clusters):
        # 클러스터 번호를 출력
        print("\ncluster {}".format(c))

        words = []
        cluster_values = list(word_centroid_map.values())
        for i in range(len(cluster_values)):
            if (cluster_values[i] == c):
                words.append(list(word_centroid_map.keys())[i])

        if len(words) == 1:
            print(words)

        rowIndustry = [c, words]
        dfIndustry.loc[len(dfIndustry)] = rowIndustry

    print(dfIndustry)

    clean_file = open('C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\클러스터전처리.txt', 'r')
    lines = clean_file.readlines()
    clean_file.close()
    remove_list = lines[0].split(', ')

    count = 0
    for i in dfIndustry['keyword']:
        clean_v = []
        for j in i:
            print(j)
            for remove in remove_list:
                j = re.sub(remove, '', j)
            if len(j) > 1:
                clean_v.append(j)

        dfIndustry['keyword'][count] = clean_v
        count += 1

    print(dfIndustry)

    print("time: ", time.time() - start)
    dfIndustry.to_pickle("C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\클러스터링최종\\군집_%s.pkl" % (model_name))

word2vec()
#tsne_plot(model)

# path_dir = "C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\토픽 모델링 결과"
# file_list = os.listdir(path_dir)
# file_list.sort()
# print(file_list)
#
# for file in file_list:
#     cluster(model, file)

# model_name = '2013~2015_report_size20_win20_min5_iter1000_hs0'
# model = Word2Vec.load('C:\\Users\\gusals\\Desktop\\현민\\딥러닝 특론\\word2vec_model\\%s' % model_name)
# file = '3년.pkl'
#
# cluster(model, file, model_name)
#sim(['기계', '펄프'], model)


'''
mobile monkey
'''
import time
from typing import List
from threading import Thread
import config_reader as config
import emulator_manager
import api_commands
from telnet_connector import TelnetAdb
from telnet_connector import GsmProfile
from telnet_connector import NetworkDelay
from telnet_connector import NetworkStatus
from emulator import Emulator
from fuzz_context import Fuzzer
from adb_settings import Airplane, KeyboardEvent, UserRotation
# import adb_settings as AdbSettings
import util
from adb_monkey import AdbMonkey
from apk import Apk
from adb_logcat import Logcat, TestType, FatalWatcher
from log_analyzer import Analyzer

PRINT_FLAG = True
TIME_PRINT_FLAG = True
emulator_model = config.EMULATOR_NAME
emulator_port = config.EMULATOR_PORT
contextual_events = 0
WILL_MONKEY = True

def start_emulator() -> bool:
    '''
        starts emulator
    '''
    global emulator_model
    if emulator_manager.adb_instances_manager():
        util.debug_print('already emulators are running.', flag=PRINT_FLAG)
        return True
    else:
        util.debug_print(
            str.format("No emulator instance running. starting {} at port {}",
                       emulator_model, emulator_port), flag=PRINT_FLAG)
        api_commands.adb_start_server_safe()
        emulator_manager.emulator_start_avd(emulator_port, emulator_model)
        # subprocess.Popen([command,
        #                   '-port', str(emulator_port), '-avd',
        #                   emulator_name, '-use-system-libs'],
        #                  stdout=subprocess.PIPE)
        emulator_manager.check_avd_booted_completely(emulator_port)
        return True

def threads_to_run(emulator: Emulator, apk: Apk, fuzz: Fuzzer,
                   will_monkey: bool) -> List:
    '''
        runs the threads after checking permissions.
    '''
    threads = []
    global contextual_events
    util.debug_print(apk.permissions, flag=PRINT_FLAG)
    emulator_name = 'emulator-' + str(emulator.port)
    if "android.permission.INTERNET" in apk.permissions or \
            "android.permission.ACCESS_NETWORK_STATE" in apk.permissions:
        util.debug_print("Internet permission detected", flag=PRINT_FLAG)
        network_delay_interval_events = fuzz.generate_step_interval_event(
            NetworkDelay)
        # print(network_delay_interval_events)
        contextual_events += len(network_delay_interval_events)
        threads.append(Thread(target=fuzz.random_network_delay, args=(
            config.LOCALHOST, emulator, network_delay_interval_events)))
        network_speed_interval_event = fuzz.generate_step_interval_event(
            NetworkStatus)
        # print(network_speed_interval_event)
        contextual_events += len(network_speed_interval_event)
        threads.append(Thread(target=fuzz.random_network_speed, args=(
            config.LOCALHOST, emulator, network_speed_interval_event)))

        airplane_mode_interval_events = fuzz.generate_step_interval_event(
            Airplane)
        # print(airplane_mode_interval_events)
        contextual_events += len(airplane_mode_interval_events)
        threads.append(Thread(
            target=fuzz.random_airplane_mode_call,
            args=(emulator_name, airplane_mode_interval_events)))

    if "android.permission.ACCESS_NETWORK_STATE" in apk.permissions:
        util.debug_print("access_network_state detected", flag=PRINT_FLAG)
        gsm_profile_interval_events = fuzz.generate_step_uniforminterval_event(
            GsmProfile)
        contextual_events += len(gsm_profile_interval_events)
        threads.append(Thread(target=fuzz.random_gsm_profile, args=(
            config.LOCALHOST, emulator,
            config.UNIFORM_INTERVAL, gsm_profile_interval_events)))

    user_rotation_interval_events = fuzz.generate_step_interval_event(
        UserRotation)
    contextual_events += len(user_rotation_interval_events)
    threads.append(Thread(
        target=fuzz.random_rotation, args=((emulator_name,
                                            user_rotation_interval_events))))

    key_event_interval_events = fuzz.generate_step_interval_event(
        KeyboardEvent)
    contextual_events += len(key_event_interval_events)
    threads.append(Thread(
        target=fuzz.random_key_event, args=((emulator_name,
                                             key_event_interval_events))))
    if will_monkey:
        monkey = AdbMonkey(emulator, apk,
                           config.SEED, config.DURATION)
        thread_monkey = Thread(target=monkey.start_monkey)

        threads.append(thread_monkey)
    return threads

def run(apk: Apk, emulator_name: str, emulator_port: int):
    '''
        runs things
    '''
    to_kill = False
    to_test = True

    to_full_run = True
    wipe_after_finish = False
    # test_time_seconds = 30
    if not start_emulator():
        return
    emulator = emulator_manager.get_adb_instance_from_emulators(emulator_name)
    # emulator_name = 'emulator-' + emulator.port

    telnet_connector = TelnetAdb(config.LOCALHOST, emulator.port)
    # apk = Apk(config.APK_FULL_PATH)
    # api_commands.adb_uninstall_apk(emulator, apk)
    # api_commands.adb_install_apk(emulator, apk)

    # api_commands.adb_start_launcher_of_apk(emulator, apk)
    log = Logcat(emulator, apk, TestType.MobileMonkey)

    # api_commands.adb_pidof_app(emulator, apk)

    if to_kill:
        telnet_connector.kill_avd()
        quit()
    if not to_test:
        return

    log.start_logcat()

    fuzz = Fuzzer(config.MINIMUM_INTERVAL,
                  config.MAXIMUM_INTERVAL, config.SEED,
                  config.DURATION, FatalWatcher(log.file_address))
    # log.experimental_start_logcat(fuzz)
    # fuzz.print_intervals_events()
    threads = threads_to_run(emulator, apk, fuzz, WILL_MONKEY)
    # log_thread = Thread(target=log.start, args=(fuzz,))
    global contextual_events
    print("Total contextual events: " + str(contextual_events))
    # print(threads)
    # return
    # device = AdbSettings.AdbSettings('emulator-' + adb_instance.port)
    # triggers = [fuzz.set_continue_network_speed,
    #             fuzz.set_continue_gsm_profile,
    #             fuzz.set_continue_network_delay]
    # thread_test = Thread(target=time_to_test, args=[
    #     test_time_seconds, triggers, ])

    # thread_fuzz_delay = Thread(target=fuzz.random_network_delay, args=(
    #     config.LOCALHOST, emulator.port,))
    # thread_fuzz_profile = Thread(target=fuzz.random_gsm_profile, args=(
    #     config.LOCALHOST, emulator.port, 12,))
    # thread_fuzz_speed = Thread(target=fuzz.random_network_speed, args=(
    #     config.LOCALHOST, emulator.port,))
    # thread_fuzz_rotation = Thread(
    #     target=fuzz.random_rotation, args=((emulator_name,)))
    # thread_fuzz_airplane = Thread(
    #     target=fuzz.random_airplane_mode_call, args=(emulator_name,))
    # monkey = AdbMonkey(emulator, config.APP_PACKAGE_NAME,
    #                    config.SEED, config.DURATION)
    # thread_monkey = Thread(target=monkey.start_monkey)
    if to_full_run:

        util.debug_print(
            "started testing at {}".format(time.ctime()), flag=TIME_PRINT_FLAG)
        [thread.start() for thread in threads]
        # log_thread.start()

        [thread.join() for thread in threads]
        # log.log_process.kill()
        # log.stop_logcat()
        # log_thread.join()
    # thread_monkey.start()
    # thread_fuzz_rotation.start()
    # thread_fuzz_delay.start()
    # thread_fuzz_profile.start()
    # thread_fuzz_speed.start()
    # thread_fuzz_airplane.start()
    # thread_test.start()
    # thread_test.join()
    # thread_fuzz_delay.join()
    # thread_fuzz_profile.join()
    # thread_fuzz_speed.join()
    # thread_fuzz_rotation.join()
    # thread_fuzz_airplane.join()
    # thread_monkey.join()
    # telnet_connector.kill_avd()
    api_commands.adb_stop_activity_of_apk(emulator, apk)
    log.stop_logcat()
    api_commands.adb_uninstall_apk(emulator, apk)
    util.debug_print(
        'Finished testing and uninstalling app at {}'.format(time.ctime()),
        flag=TIME_PRINT_FLAG)
    print(Analyzer(log.file_address))
    if wipe_after_finish:
        print("successfully completed testing app. Closing emulator")
        telnet_connector.kill_avd()
        emulator_manager.emulator_wipe_data(emulator)

if __name__ == '__main__':
    import os

    dir = os.path.dirname(__file__)

    StopFlagWatcher = os.path.join(dir, 'test/StopFlagWatcher')

    file = open(StopFlagWatcher, 'w')
    file.truncate()
    file.close()
    run(Apk(config.APK_FULL_PATH), config.EMULATOR_NAME, config.EMULATOR_PORT)


"""
Series of galactic operations (doesn't that sound cool?!).

...as in converting coordinates, calculating DM etc.
"""
from datetime import timedelta
import ctypes as C
import math
import numpy as np
import os
import pandas as pd
import random

from frbpoppy.paths import paths

# Import fortran libraries
uni_mods = os.path.join(paths.models(), 'universe/')
dm_mods = os.path.join(paths.models(), 'ne2001/')
loc = os.path.join(dm_mods, 'libne2001.so')
ne2001lib = C.CDLL(loc)
ne2001lib.dm_.restype = C.c_float

def frac_deg(ra, dec):
    """Convert coordinates expressed in hh:mm:ss to fractional degrees."""
    # Inspired by Joe Filippazzo calculator
    rh, rm, rs = [float(r) for r in ra.split(':')]
    ra = rh*15 + rm/4 + rs/240
    dd, dm, ds = [float(d) for d in dec.split(':')]
    if dd < 0:
        sign = -1
    else:
        sign = 1
    dec = dd + sign*dm/60 + sign*ds/3600
    return ra, dec

def lb_to_xyz(gl, gb, dist):
    """
    Convert galactic coordinates to galactic XYZ.

    Args:
        l (float): Galactic longitude [fractional degrees]
        b (float): Galactic latitude [fractional degrees]
        dist (float): Distance to source [Gpc]

    Returns:
        gx, gy, gz: Galactic XYZ [Gpc]

    """
    rsun = 8.5e-6  # Gpc

    L = np.radians(gl)
    B = np.radians(gb)

    gx = dist * np.cos(B) * np.sin(L)
    gy = rsun - dist * np.cos(B) * np.cos(L)
    gz = dist * np.sin(B)

    return gx, gy, gz

def lb_to_radec(l, b):
    """
    Convert galactic coordinates to RA, Dec.

    Formulas from 'An Introduction to Modern Astrophysics (2nd Edition)' by
    Bradley W. Carroll, Dale A. Ostlie (Eq. 24.19 onwards).

    NOTE: This function is not as accurate as the astropy conversion, nor as
    the Javascript calculators found online. However, as using astropy was
    prohibitively slow while running over large populations, frbpoppy uses this
    function. While this function is not as accurate, the under/over
    estimations of the coordinates are equally distributed meaning the errors
    cancel each other in the limit of large populations.

    Args:
        l (float): Galactic longitude [fractional degrees]
        b (float): Galactic latitude [fractional degrees]

    Returns:
        ra, dec (float): Right ascension and declination [fractional degrees]

    """
    gl = np.radians(l)
    gb = np.radians(b)

    # Coordinates of the galactic north pole (J2000)
    a_ngp = np.radians(12.9406333 * 15.)
    d_ngp = np.radians(27.1282500)
    l_ngp = np.radians(123.9320000)

    sd_ngp = np.sin(d_ngp)
    cd_ngp = np.cos(d_ngp)
    sb = np.sin(gb)
    cb = np.cos(gb)

    # Calculate right ascension
    y = cb*np.sin(l_ngp - gl)
    x = cd_ngp*sb - sd_ngp*cb*np.cos(l_ngp - gl)
    ra = np.arctan2(y, x) + a_ngp
    ra = np.degrees(ra) % 360

    # Calculate declination
    dec = np.arcsin(sd_ngp*sb + cd_ngp*cb*np.cos(l_ngp - gl))
    dec = np.degrees(dec) % 360.
    dec[dec > 270] = -(360 - dec[dec > 270])

    return ra, dec

def radec_to_lb(ra, dec, frac=False):
    """
    Convert from ra, dec to galactic coordinates.

    Formulas from 'An Introduction to Modern Astrophysics (2nd Edition)' by
    Bradley W. Carroll, Dale A. Ostlie (Eq. 24.16 onwards).

    NOTE: This function is not as accurate as the astropy conversion, nor as
    the Javascript calculators found online. However, as using astropy was
    prohibitively slow while running over large populations, we use this
    function. While this function is not as accurate, the under/over
    estimations of the coordinates are equally distributed meaning the errors
    cancel each other in the limit of large populations.

    Args:
        ra (string): Right ascension given in the form '19:06:53'
        dec (string): Declination given in the form '-40:37:14'
        frac (bool): Denote whether coordinates are already fractional or not
    Returns:
        gl, gb (float): Galactic longitude and latitude [fractional degrees]

    """
    if not frac:
        ra, dec = frac_deg(ra, dec)

    a = np.radians(ra)
    d = np.radians(dec)

    # Coordinates of the galactic north pole (J2000)
    a_ngp = np.radians(12.9406333 * 15.)
    d_ngp = np.radians(27.1282500)
    l_ngp = np.radians(123.9320000)

    sd_ngp = np.sin(d_ngp)
    cd_ngp = np.cos(d_ngp)
    sd = np.sin(d)
    cd = np.cos(d)

    # Calculate galactic longitude
    y = cd*np.sin(a - a_ngp)
    x = cd_ngp*sd - sd_ngp*cd*np.cos(a - a_ngp)
    gl = - np.arctan2(y, x) + l_ngp
    gl = np.degrees(gl) % 360

    # Shift so in range -180 to 180
    if isinstance(gl, np.ndarray):
        gl[gl > 180] = -(360 - gl[gl > 180])
    else:
        if gl > 180:
            gl = -(360 - gl)

    # Calculate galactic latitude
    gb = np.arcsin(sd_ngp*sd + cd_ngp*cd*np.cos(a - a_ngp))
    gb = np.degrees(gb) % 360

    if isinstance(gb, np.ndarray):
        gb[gb > 270] = -(360 - gb[gb > 270])
    else:
        if gb > 270:
            gb = -(360 - gb)

    return gl, gb

def separation(ra_1, dec_1, ra_2, dec_2):
    """Separation between points on sky [degrees].

    Using a special case of the Vincenty formula for an ellipsoid with equal
    major and minor axes.

    See https://en.wikipedia.org/wiki/Great-circle_distance for more info.

    """
    # Convert to radians
    ra_1 = np.deg2rad(ra_1)
    dec_1 = np.deg2rad(dec_1)
    ra_2 = np.deg2rad(ra_2)
    dec_2 = np.deg2rad(dec_2)

    # Shortcuts
    sdr = np.sin(ra_2 - ra_1)
    cdr = np.cos(ra_2 - ra_1)
    cd1 = np.cos(dec_1)
    cd2 = np.cos(dec_2)
    sd1 = np.sin(dec_1)
    sd2 = np.sin(dec_2)

    # Calculation
    upper = np.sqrt((cd2*sdr)**2 + (cd1*sd2 - sd1*cd2*cdr)**2)
    lower = sd1*sd2 + cd1*cd2*cdr
    sep = np.arctan2(upper, lower)

    return np.rad2deg(sep)

def ne2001_dist_to_dm(dist, gl, gb):
    """
    Convert position to a dispersion measure using NE2001.

    Args:
        dist (float): Distance to source [Gpc]. Distance will be cut at 100kpc,
                      as NE2001 can not cope with larger distances. This value
                      should be more than enough to clear the Milky Way.
        gl (float): Galactic longitude [fractional degrees]
        gb (float): Galactic latitude [fractional degrees]
    Returns:
        dm (float): Dispersion measure [pc*cm^-3]

    """
    dist *= 1e6  # Convert from Gpc to kpc

    # NE2001 gives errors if distance input is too large! 100 kpc ought to be
    # enough to clear the galaxy.
    if dist > 100:
        dist = 100

    dist = C.c_float(dist)
    gl = C.c_float(gl)
    gb = C.c_float(gb)
    inpath = C.create_string_buffer(dm_mods.encode())
    linpath = C.c_int(len(dm_mods))

    dm = ne2001lib.dm_(C.byref(dist),
                       C.byref(gl),
                       C.byref(gb),
                       C.byref(C.c_int(4)),
                       C.byref(C.c_float(0.0)),
                       C.byref(inpath),
                       C.byref(linpath)
                       )

    return dm

def ne2001_get_smtau(dist, gl, gb):
    """
    Use the NE2001 model to calculate scattering measure.

    Calculations based on work presented in Cordes & Lazio
    (1991, DOI: 10.1086/170261)

    Args:
        dist (array): Distance to source [kpc]. Distance will be cut at 100 kpc
                      as NE2001 can not cope with larger distances. Therefore
                      the calculated scattering will only be that from the
                      Milky Way.
        gl (array): Galactic longitude [fractional degrees]
        gb (array): Galactic latitude [fractional degrees]
    Returns:
        sm (array): Scattering measure
        smtau (array): Scattering measure, but unsure why different to sm

    """
    # NE2001 gives errors if distance input is too large! 100 kpc ought to be
    # enough to clear the galaxy.
    dist[dist > 100] = 100
    sms = np.ones_like(dist)
    smtaus = np.ones_like(dist)

    for i, d in enumerate(dist):

        disti = C.c_float(d)
        # Note the galactic coordinates need to be given in radians
        gli = C.c_float(math.radians(gl[i]))
        gbi = C.c_float(math.radians(gb[i]))

        ndir = C.c_int(-1)
        sm = C.c_float(0.)
        smtau = C.c_float(0.)

        inpath = C.create_string_buffer(dm_mods.encode())
        linpath = C.c_int(len(dm_mods))

        ne2001lib.dmdsm_(C.byref(gli),
                         C.byref(gbi),
                         C.byref(ndir),
                         C.byref(C.c_float(0.0)),
                         C.byref(disti),
                         C.byref(C.create_string_buffer(' '.encode())),
                         C.byref(sm),
                         C.byref(smtau),
                         C.byref(C.c_float(0.0)),
                         C.byref(C.c_float(0.0)),
                         C.byref(inpath),
                         C.byref(linpath)
                         )

        sms[i], smtaus[i] = sm.value, smtau.value

    return sms, smtaus

def ne2001_scint_time_bw(dist, gl, gb, freq):
    """
    Use the NE2001 model to get the diffractive scintillation timescale.

    Args:
        dist (array): Distance to source [Gpc]. Distance will be cut at 100 kpc
                      as NE2001 can not cope with larger distances. Therefore
                      the calculated scintillation timescale will only be that
                      from the Milky Way.
        gl (array): Galactic longitude [fractional degrees]
        gb (array): Galactic latitude [fractional degrees]
        freq (float): Observing frequency [MHz]
    Returns:
        scint_time (float): Diffractive scintillation timescale [Hz]
        scint_bw (float): Scintillation bandwidth [Hz]

    """
    dist *= 1e6  # Convert from Gpc to kpc

    sm, smtau = ne2001_get_smtau(dist, gl, gb)

    scint_time = np.ones_like(dist)
    scint_time[smtau <= 0.] = float('NaN')
    # Eq. 46 of Cordes & Lazio 1991, ApJ, 376, 123 uses coefficient 3.3
    # instead of 2.3. They do this in the code and mention it explicitly,
    # so I trust it! <- From psrpoppy
    scint_time[smtau > 0.] = 3.3 * (freq/1e3)**1.2 * smtau**(-0.6)

    scint_bw = np.ones_like(dist)
    scint_bw[sm <= 0.] = float('NaN')
    # (eq. 48)
    scint_bw[sm > 0.] = 223. * (freq/1e3)**4.4 * sm**(-1.2) / dist

    return scint_time, scint_bw

def scatter_bhat(dm, offset=-6.46, scindex=-3.86, freq=1400.0):
    """
    Calculate scattering timescale (values default to those from Bhat et al.
    (2004, DOI:10.1086/382680) and to simluate the scatter around this
    relationship, draw from a Gaussian around this value.

    Args:
        dm (array): Dispersion measure [pc*cm^-3]
        offset (float): Offset of scattering relationship. Defaults to -6.46
        scindex (float): Scattering index. Defaults to -3.86
        freq (float): Frequency at which to evaluate scattering time [MHz].
                      Defaults to 1400 MHz
    Returns:
        array: Scattering timescale [ms]

    """
    log_t = offset + 0.154*np.log10(dm) + 1.07*np.log10(dm)**2
    log_t += scindex*np.log10(freq/1e3)

    # Width of Gaussian distribution based on values given Lorimer et al (2008)
    t_scat = 10**np.random.normal(log_t, 0.8)

    return t_scat

def load_T_sky():
    """
    Read the Haslam sky temperature map into a list.

    ... from which temperatures can
    be retrieved. The temperature sky map is given in the weird units of
    HealPix, and despite looking up info on this coordinate system, I don't
    have the foggiest idea of how to transform these to galactic coordinates. I
    have therefore directly copied the following code from psrpoppy in the
    assumption Sam Bates managed to figure it out.

    Returns:
        t_sky_list (list): List of sky temperatures in HealPix? coordinates?

    """
    model = os.path.join(os.path.dirname(__file__), '../data/models/tsky/')
    path = os.path.join(model, 'haslam_2014.dat')

    t_sky_list = []
    with open(path) as f:
        for line in f:
            str_idx = 0
            while str_idx < len(line):
                # each temperature occupies space of 5 chars
                temp_string = line[str_idx:str_idx+5]
                try:
                    t_sky_list.append(float(temp_string))
                except ValueError:
                    pass
                str_idx += 5

    return t_sky_list

class Redshift:
    """Class for converting redshift to other distance measures."""

    def __init__(self, z, H_0=67.74, W_m=0.3089, W_v=0.6911):
        """
        Convert redshift to a various measures.

        Based on James Schombert's python implementation of Edward L. Wright's
        cosmology calculator.

        Args:
            z (array): Redshift
            self.H_0 (float, optional): Hubble parameter.
            self.W_m (float, optional): Omega matter.
            self.W_v (float, optional): Omega vacuum.

        Returns:
            array: One of the distance measures [Gpc], or comoving volume from
                Earth to the source [Gpc^3]

        """
        self.z = z
        self.H_0 = H_0
        self.W_m = W_m
        self.W_v = W_v

        # Initialize constants
        self.W_r = 0.4165/(self.H_0*self.H_0)  # Omega radiation
        self.W_k = 1.0 - self.W_m - self.W_r - self.W_v  # Omega curvature
        self.c = 299792.458  # Velocity of light [km/sec]
        self.dcmr = 0.
        self.az = 1/(1+self.z)

        # Distance measures
        self.dc_mpc = None
        self.dl_mpc = None

    def dist_co(self):
        """Calculate the corresponding comoving distance [Gpc]."""
        n = 1000

        for i in range(n):
            a = self.az+(1-self.az)*(i+0.5)/n
            s = sum([self.W_k, self.W_m/a, self.W_r/(a*a), self.W_v*a*a])
            adot = np.sqrt(s)
            self.dcmr += 1/(a*adot)

        self.dcmr = (1.-self.az)*self.dcmr/n

        self.dc_mpc = (self.c/self.H_0)*self.dcmr  # Comoving distance [Mpc]

        return self.dc_mpc*1e-3  # Convert to Gpc

    def dist_lum(self):
        """Calculate the corresponding luminosity distance [Gpc]."""
        if self.dc_mpc is None:
            self.dist_co()

        # Calculate luminosity distance
        ratio = np.ones_like(self.dcmr)
        x = np.sqrt(abs(self.W_k))*self.dcmr

        mask = (x > 0.1)

        if self.W_k > 0:
            ratio[mask] = 0.5*(np.exp(x[mask])-np.exp(-x[mask]))/x[mask]
        else:
            ratio[mask] = np.sin(x[mask])/x[mask]

        y = x*x
        if self.W_k < 0:
            y = -y
        ratio[~mask] = 1. + y[~mask]/6. + y[~mask]*y[~mask]/120.

        dcmt = ratio*self.dcmr
        da = self.az*dcmt
        dl = da/(self.az*self.az)
        self.dl_mpc = (self.c/self.H_0)*dl  # Luminosity distance [Mpc]

        return self.dl_mpc*1e-3  # Covert to Gpc

    def vol_co(self):
        """Calculate the corresponding comoving volume [Gpc^3]."""
        if self.dl_mpc is None:
            self.dist_lum()

        ratio = np.ones_like(self.dcmr)
        x = math.sqrt(abs(self.W_k))*self.dcmr

        mask = (x > 0.1)

        if self.W_k > 0:
            n = (0.125*(np.exp(2.*x[mask])-np.exp(-2.*x[mask]))-x[mask]/2.)
            ratio[mask] = n/(x[mask]**3/3)
        else:
            ratio[mask] = (x[mask]/2. - np.sin(2.*x[mask])/4.)/(x[mask]**3/3)

        y = x*x
        if self.W_k < 0:
            y = -y
        ratio[~mask] = 1. + y[~mask]/5. + (2./105.)*y[~mask]*y[~mask]

        v_cm = ratio*self.dcmr**3/3
        self.v_gpc = 4.*math.pi*((1e-3*self.c/self.H_0)**3)*v_cm

        return self.v_gpc

def z_to_d_approx(z, H_0=67.74):
    """
    Calculate distance in Gpc from a redshift.

    Only holds for z <= 2. Formulas from 'An Introduction to Modern
    Astrophysics (2nd Edition)' by Bradley W. Carroll, Dale A. Ostlie.
    (Eq. 27.7)

    Args:
        z (float): Redshift
        H_0 (float, optional): Hubble parameter. Defaults to 67.74
    Returns:
        dist (float): Associated distance [Gpc]
    """
    c = 299792.458  # Velocity of light [km/sec]
    zsq = (z+1)**2
    dist = c/H_0 * (zsq - 1)/(zsq + 1)
    dist /= 1e3  # Mpc -> Gpc
    return dist

def dist_to_z(dist, H_0=67.74):
    """
    Calculate redshift from a distance in Gpc.

    Only holds for z <= 2. Formulas from 'An Introduction to Modern
    Astrophysics (2nd Edition)' by Bradley W. Carroll, Dale A. Ostlie.
    (Eq. 27.7)

    Args:
        dist (float): Distance [Gpc].
        H_0 (float, optional): Hubble parameter. Defaults to 67.74
    Returns:
        z (float): Associated redshift
    """
    c = 299792.458  # Velocity of light [km/sec]
    dist *= 1e3  # Gpc -> Mpc
    dhc = dist*H_0/c
    det = math.sqrt(1 - dhc**2)
    z = -(det + dhc - 1)/(dhc - 1)
    return z

def datetime_to_julian(date):
    """Convert a datetime object into julian float.

    See https://aa.usno.navy.mil/faq/docs/JD_Formula.php for more info.

    Args:
        date (datetime-object): Date in question

    Returns:
        float: Julian calculated datetime.

    """
    # Add support for numpy arrays of datetime64
    if np.issubdtype(date.dtype, np.datetime64):
        date = pd.to_datetime(date)

    # Define terms
    y = date.year
    m = date.month
    d = date.day
    h = date.hour
    min = date.minute
    sec = date.second

    # Calculate julian day number
    jdn = 367*y - ((7*(y + ((m+9)/12).astype(int)))/4).astype(int)
    jdn += ((275*m)/9).astype(int) + d + 1721013.5
    # Add fractional day
    jd = jdn + h/24 + min/1440 + sec/86400

    # Convert to a numpy array
    if isinstance(jd, pd.Float64Index):
        jd = jd.values

    return jd

def datetime_to_gmst(date):
    """Calculate Greenwich Mean Sidereal Time.

    See https://aa.usno.navy.mil/faq/docs/GAST.php for more info.
    """
    jd = datetime_to_julian(date)
    return ((18.697374558 + 24.06570982441908*(jd - 2451545))*15) % 360

def random_date(start, end):
    """Generate a random datetime between two datetime objects."""
    delta = end - start
    int_delta = (delta.days * 24 * 60 * 60) + delta.seconds
    random_second = random.randrange(int_delta)
    return start + timedelta(seconds=random_second)

def coord_to_offset(xref, yref, x, y):
    """
    Convert point (x, y) to projected offset from reference (xref, yref).

    Makes use of a gnomonic projection: see both
        https://github.com/LSSTDESC/Coord/blob/master/coord/celestial.py
        http://mathworld.wolfram.com/GnomonicProjection.html

    Args:
        xref (array): Reference RA or Az [rad]
        yref (array): Reference Dec or Alt [rad]
        x (array): Target RA or Az [rad]
        y (array): Target Dec or Alt [rad]

    Returns:
        array, array: x and y offset [rad]

    """
    # Define convenience numbers
    sinxref = np.sin(xref)
    sinx = np.sin(x)
    cosxref = np.cos(xref)
    cosx = np.cos(x)

    sinyref = np.sin(yref)
    siny = np.sin(y)
    cosyref = np.cos(yref)
    cosy = np.cos(y)

    # Sine and cosine of shift in x
    cosdx = (cosxref * cosx) + (sinxref * sinx)
    sindx = (cosxref * sinx) - (sinxref * cosx)

    # Projection effect cosine
    cosc = sinyref * siny + cosyref * cosy * cosdx

    # Projected offsets
    dx = (cosy * sindx) / cosc
    dy = (cosyref * siny - sinyref * cosy * cosdx) / cosc

    if isinstance(cosc, np.ndarray):
        dx[cosc < 0] = np.nan
        dy[cosc < 0] = np.nan
    elif cosc < 0:
        dx, dy = np.nan, np.nan

    return dx, dy

def hadec_to_azalt(ha, dec, lat):
    """
    Convert hour angle and declination to azimuth and altitude.

    Args:
        ha (array): Hour angle [rad]
        dec (array): Declination [rad]
        lat (float): Latitude [rad]

    Returns:
        array, array: az, alt [rad]

    """
    # Ha and dec should be same type
    assert type(ha) == type(dec)

    # Altitude
    sinalt = np.sin(dec) * np.sin(lat) + np.cos(dec) * np.cos(lat) * np.cos(ha)
    alt = np.arcsin(sinalt)

    # Azimuth (note this uses altitude)
    cosaz = (np.sin(dec)-np.sin(alt)*np.sin(lat)) / (np.cos(alt)*np.cos(lat))

    convert_to_float = False
    if isinstance(cosaz, float):
        cosaz = np.array([cosaz])
        convert_to_float = True

    # Numerical instability can cause cosaz > 1
    cosaz[cosaz > 1] = 1
    cosaz[cosaz < -1] = -1
    az = np.arccos(cosaz)

    # Sign of azimuth is lost, but can be recovered using the input hour angle
    mask = np.sin(ha) > 0
    az[mask] = 2*np.pi - az[mask]

    # Convert back to float if input was float
    if convert_to_float:
        az = float(az)

    return az, alt

def in_region(ra, dec, gl, gb,
              ra_min=0, ra_max=360,
              dec_min=-90, dec_max=90,
              gl_min=-180, gl_max=180,
              gb_min=-90, gb_max=90):
    """
    Check if the given frbs are within the survey region.

    Args:
        ra, dec, gl, gb (float): Coordinates to check whether in region

    Returns:
        array: Boolean mask denoting whether frbs are within survey region

    """
    # Create mask with False
    mask = np.ones_like(ra, dtype=bool)

    # Ensure in correct format
    gl[gl > 180.] -= 360.

    # Create region masks
    gl_limits = (gl > gl_max) | (gl < gl_min)
    gb_limits = (gb > gb_max) | (gb < gb_min)
    ra_limits = (ra > ra_max) | (ra < ra_min)
    dec_limits = (dec > dec_max) | (dec < dec_min)

    mask[gl_limits] = False
    mask[gb_limits] = False
    mask[ra_limits] = False
    mask[dec_limits] = False

    return mask

def calc_sky_radius(area):
    """Determine the radius [deg] along the sky of an area [sq. degrees]."""
    # Check whether the full sky
    if np.allclose(area, 4*np.pi*(180/np.pi)**2):
        return 180
    else:
        cos_r = (1 - (area*np.pi)/(2*180**2))
        # Suppressing warnings when cos_r is invalid (will nan anyway)
        with np.errstate(invalid='ignore'):
            return np.rad2deg(np.arccos(cos_r))

def calc_sky_area(radius):
    """Determine the area [sq. degree] of a radius [deg] along the sky."""
    return (1 - np.cos(np.deg2rad(radius)))*(2*180**2)/np.pi


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

# Librerias
import re
import sys
import json
import string
import random
import operator
import unicodedata

sys.stdout.encoding
'UTF-8'

# Libreria NLTK
import nltk
nltk.download('punkt')
nltk.download('stopwords')

from nltk.tokenize.toktok import ToktokTokenizer
from nltk.metrics.distance import edit_distance
from nltk.stem.snowball import SnowballStemmer
from nltk.corpus import stopwords

# Libreria para Grafo
import matplotlib.pyplot as plt
import networkx as nx
import random

# Variables
miniparams = []
parametros = []
nombreArchivo = "wikipedia_es_abstracts.txt" # "OWesA.txt"

# Stemmer en Español
stemmer = SnowballStemmer("spanish")

# Recorrer parametros
for i in range(len(sys.argv)):
	# Validar
	if 0 < i:
		# Recuperar paramtros
		parm = str(sys.argv[i].strip().lower())
		# Validar asignación
		if parm not in parametros:
			# Asignar parametro
			parametros.append(parm)
			miniparams.append(stemmer.stem(parm))

# Iniciar Tokenizador
toktok = ToktokTokenizer()
# Crear Tokenizar de oraciones
esTokenizarOraciones = nltk.data.load("tokenizers/punkt/spanish.pickle")

# Generar lista de palabras funcionales
listPalabrasfuns = stopwords.words("spanish")
listPuntuaciones = list(string.punctuation)
listPuntuaciones.append("¿")
listPuntuaciones.append("¡")
listPuntuaciones.append("ja")
listPuntuaciones.append("yme")
listPuntuaciones.append("yczna")
listPuntuaciones.append("así")

# Función de tokenización
def leer_archivo (archivo, params):
	# Variables
	documento = {}
	# Leer documento
	with open(archivo, 'r', encoding="utf-8") as myFile: # open(archivo, encoding="latin-1")
		# Recuperar lineas del texto
		lins_arch = myFile.readlines()
		list_temp_parr = []
		contador = 0
		# Recorrer parrafos del archivo
		for parrafo in lins_arch:
			# Dividir por \t
			list_segmentos = parrafo.split("\t")
			# Validar tamaño
			if 2 <= len(list_segmentos):
				# Variable
				textoObjetivo = ""
				# Titulo divido por :
				list_titulo = list_segmentos[0].split(":")
				titulo = ""
				# Validar titulo
				if 2 <= len(list_titulo):
					titulo = list_titulo[1].lower()
				# Titulo divido por \n
				list_parrafo = list_segmentos[1].split("\n")
				# Validar parrafo
				textoObjetivo = ""
				if 1 <= len(list_parrafo):
					textoObjetivo = list_parrafo[0].lower()
				# Validar asginación de parrafo
				bandera = False
				for prm in params:
					if textoObjetivo.find(prm) >= 0:
						bandera = True
				# Validar asignación de parrafo
				if bandera and textoObjetivo not in list_temp_parr:
					# Agregar el parrafo a la variable documento
					documento[contador] = { "T": titulo, "P": textoObjetivo}
					list_temp_parr.append(textoObjetivo)
					contador = contador + 1
		# Cerrar archivo
		myFile.close()
	# Regresar json de documentos
	return documento

# Leer archivo
listParrafos = leer_archivo(nombreArchivo, miniparams)

# Función para buscar palabras objetivo conforme un patron
def buscar_coincidencias(list_pals_funs, list_punts, texto, one_pos, final_star_pos, expresion, dicc_de_rel = None):
	# Validar existencia de diccionario
	if dicc_de_rel == None:
		dicc_de_rel = {}
	# Crear patron
	patron_exp = re.compile(expresion)
	# Buscar coincidencias del patron en el texto
	list_matches = patron_exp.findall(texto)
	# Recorrer maches
	for mi_match in list_matches:
		# Lista de palabras de interes temporal
		list_of_temp_words = []
		# Recorrer palabras de match
		for temp_i in range(len(mi_match)):
			# Validar match
			if (temp_i == one_pos) or (temp_i >= final_star_pos):
				# Recuperar palabra relacionada
				temp_word = mi_match[temp_i]
				temp_word = re.sub(', ', '', temp_word)
				temp_word = re.sub('y ', '', temp_word)
				temp_word = temp_word.strip()
				# Validar resguardo
				if temp_word != '' and temp_word not in list_pals_funs and temp_word not in list_punts and temp_word not in list_of_temp_words:
					# Resguardar palabra relacionada
					list_of_temp_words.append(temp_word)
		if len(list_of_temp_words) > 1:
			# Recorrer palabras temporales
			my_temp_w = list_of_temp_words[0]
			# Validar existencia de parametro en diccionario
			if my_temp_w not in dicc_de_rel:
				# Lista de palabras de interes temporal
				dicc_de_rel[my_temp_w] = []
			# SubRecorrido de palabras temporales
			for m_sbtmp_w in list_of_temp_words:
				# Validar existencia en diccionario
				if m_sbtmp_w not in dicc_de_rel[my_temp_w]:
					# Guardar palabra temporal
					dicc_de_rel[my_temp_w].append(m_sbtmp_w)
	# Regresar resultados
	return dicc_de_rel

# Variable diccionario de relaciones
diccDeRel = {}

# Recorrer parrafos en diccionario
for key,value in listParrafos.items():
	# Revisar patrones y actualizar diccionario
	diccDeRel = buscar_coincidencias(listPalabrasfuns, listPuntuaciones, value["P"], 1, 3, '(las|la|los|el)*(\s*\w+) (son un|son una|es un|es una|fueron un|fueron una|fue un|fue una){1} (\w+)', diccDeRel)
	diccDeRel = buscar_coincidencias(listPalabrasfuns, listPuntuaciones, value["P"], 0, 2, '(\w+) (tal como|así tambien|así como|como por|por ejemplo|tambien conocida como|tambien conocido como|tal como:|como:|como){1} (\w+)*(,\s*\w+)*(\s*y\s*\w+)*', diccDeRel)
	diccDeRel = buscar_coincidencias(listPalabrasfuns, listPuntuaciones, value["P"], 0, 4, '(\w+) (es|forma|forman|son){1} (parte){1} (del|de las|de los|de el|de una|de un|de){1} (\w+)', diccDeRel)
	diccDeRel = buscar_coincidencias(listPalabrasfuns, listPuntuaciones, value["P"], 0, 5, '(\w+)(\s*le|\s*es|\s*son)* (perteneciente(s)*|pertenecen|pertenece|a){1} (la|al|a)* (\w+)', diccDeRel)
	diccDeRel = buscar_coincidencias(listPalabrasfuns, listPuntuaciones, value["P"], 0, 1, '(\w+)(,\s*\w+)*(\s*y\s*\w+)', diccDeRel)

# Variables
list_nodos = []
list_aristas = []

# Recorrer diccionario de relaciones
for key,arry in diccDeRel.items():
	# Validar si agregar nodo
	if key not in list_nodos:
		# Agregar nodo
		list_nodos.append(key)
	# Recorrer relaciones de la llave
	for value in arry:
		# Generar tupla
		tempTupla = (key, value)
		# Validar si agregar tupla
		if tempTupla not in list_aristas:
			# Agregar nodo
			list_aristas.append(tempTupla)
		# Validar si agregar nodo
		if value not in list_nodos:
			# Agregar nodo
			list_nodos.append(value)

# Crea grafica
Grafico = nx.DiGraph()

# Vertices
Grafico.add_nodes_from(list_nodos)

# Aristas
Grafico.add_edges_from(list_aristas)

posicion = nx.spring_layout(Grafico)
nx.draw_networkx_labels(Grafico, posicion, labels=dict([(nodo, nodo) for nodo in list_nodos]))

# Dibuja la gráfica
nx.draw(Grafico, posicion)

# Muestra en pantalla lo dibujado
plt.show()


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

class Migration(migrations.Migration):

    dependencies = [
        ('authentication_module', '0004_auto_20160801_2318'),
    ]

    operations = [
        migrations.AddField(
            model_name='customuser',
            name='tipo_cuenta',
            field=models.CharField(default=b'C', max_length=1, choices=[(b'C', b'COMERCIANTE'), (b'M', b'MUNICIPIO')]),
        ),
    ]


#coding=utf-8
"""
imgcv
"""

from setuptools import setup
from setuptools import find_packages

install_requires = [
]

setup(
    name = "imgcv",
    version = "1.0.0",
    description = 'image computer visior',
    author='Hyxbiao',
    author_email="hyxbiao@gmail.com",
    packages = find_packages(),
    entry_points={
        'console_scripts': [
            'imgcv = imgcv.tools.imgcv:main',
            'imgbrowser = imgcv.tools.imgbrowser:main',
        ]
    },
    install_requires = install_requires,
    zip_safe = False,
)


import os
import sys
from xml.etree import ElementTree

def isPlaylistUpdated(cmusPlaylistFile, jellyfinMusicPathArray) :
    cmusMusicPathArray = open(cmusPlaylistFile, 'r').read().splitlines()

    if len(cmusMusicPathArray) != len(jellyfinMusicPathArray) :
        return True

    length = len(cmusMusicPathArray)

    for i in range(0, length) :
        if cmusMusicPathArray[i] != jellyfinMusicPathArray[i].text :
            return True

    return False

def updateFile(cmusPlaylistFile, musicPathArray) :
    print('updating or creating ' + cmusPlaylistFile)
    string = ''
    for path in musicPathArray :
        string += path.text + '\n'

    with open(cmusPlaylistFile, 'w') as sw :
        sw.write(string)

JELLYFIN_PLAYLIST_PATH = sys.argv[1]
CMUS_PLAYLIST_PATH = sys.argv[2]

for playlist in os.listdir(JELLYFIN_PLAYLIST_PATH) :

    playlistFile = os.path.join(JELLYFIN_PLAYLIST_PATH, playlist)
    playlistFile = os.path.join(playlistFile, 'playlist.xml')

    if os.path.isfile(playlistFile) :
        dom = ElementTree.parse(playlistFile)
        paths = dom.findall('PlaylistItems/PlaylistItem/Path')

        cmusPlaylistFile = os.path.join(CMUS_PLAYLIST_PATH, playlist)

        if (not os.path.isfile(cmusPlaylistFile)) or isPlaylistUpdated(cmusPlaylistFile, paths) :
            updateFile(cmusPlaylistFile, paths)

# checkIfPlaylistUpdated('/home/nate/.config/cmus/playlists/test', None)


import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Flatten
from keras.datasets import mnist
from keras.utils import to_categorical
import matplotlib.pyplot as plt

# 载入MNIST数据集
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# 将像素值标准化到 0 到 1 之间
x_train, x_test = x_train / 255.0, x_test / 255.0

# 对标签进行独热编码
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)

# 显示第一张图片
plt.imshow(x_train[0], cmap='gray')
plt.title(f"Label: {np.argmax(y_train[0])}")  # 显示标签
plt.show()
# 构建模型
model = Sequential()
model.add(Flatten(input_shape=(28, 28)))  # 将 28x28 的图像展平成一维数组
model.add(Dense(128, activation='relu'))  # 具有128个神经元和ReLU激活函数的隐藏层
model.add(Dense(10, activation='softmax'))  # 具有10个神经元（用于10个类别）和softmax激活函数的输出层

# 编译模型
model.compile(optimizer='adam',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# 训练模型
model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test))

# 在测试集上评估模型
test_loss, test_acc = model.evaluate(x_test, y_test)
print(f'测试准确率：{test_acc}')


import unittest

import mock
import time
from copy import deepcopy

from gorynych.common.domain import events
from gorynych.common.exceptions import DomainError
from gorynych.info.domain.test.helpers import create_contest
from gorynych.info.domain import contest, person, race
from gorynych.common.domain.types import Address, Name, Country
from gorynych.info.domain.ids import PersonID, RaceID, TrackerID, TransportID

class MockedPersonRepository(mock.Mock):
    '''
    Necessary only for tracker assignment.
    '''
    def get_by_id(self, key):
        person = mock.Mock()
        person.name = Name('name', 'surname')
        person.country = Country('RU')
        if key == 'person1':
            person.tracker = 'tracker1'
        elif key == 'person2':
            person.tracker = 'tracker2'
        elif key == 'person3':
            person.tracker = None
        return person

class ContestFactoryTest(unittest.TestCase):

    def test_contestid_successfull_contest_creation(self):
        cont = create_contest(1, 2)
        self.assertIsInstance(cont.address, Address)
        self.assertEqual(cont.title, 'Hello world')
        self.assertEqual(cont.country, 'RU')
        self.assertEqual(cont.timezone, 'Europe/Moscow')
        self.assertEqual(cont.place, 'Yrupinsk')
        self.assertEquals((cont.start_time, cont.end_time), (1, 2))
        self.assertIsInstance(cont.id, contest.ContestID)
        self.assertIsNone(cont._id)

        cont2 = create_contest(3, 4)
        self.assertNotEqual(cont.id, cont2.id,
                            "Contest with the same id has been created.")

    def test_str_successfull_contest_creation(self):
        cont = create_contest(1, 3, id='cnts-130422-12345')
        self.assertEqual(cont.end_time, 3)
        self.assertEqual(cont.start_time, 1)
        self.assertEqual(cont.id, 'cnts-130422-12345')

    def test_unsuccessfull_contest_creation(self):
        self.assertRaises(ValueError, create_contest, 3, 1,
                          "Contest can be created with wrong times.")

class EventsApplyingTest(unittest.TestCase):
    def test_ContestRaceCreated(self):
        cont = create_contest(1, 2)
        rid = RaceID()
        ev = events.ContestRaceCreated(cont.id, rid)
        self.assertRaises(AssertionError, cont.apply, ev)
        cont.apply([ev])
        self.assertEqual(len(cont.race_ids), 1)
        cont.apply([ev])
        self.assertEqual(len(cont.race_ids), 1)
        rid = RaceID()
        ev = events.ContestRaceCreated(cont.id, rid)
        cont.apply([ev])
        self.assertEqual(len(cont.race_ids), 2)

class ContestTest(unittest.TestCase):
    @mock.patch('gorynych.common.infrastructure.persistence.event_store')
    def test_register_paraglider(self, patched):
        event_store = mock.Mock()
        patched.return_value = event_store
        cont = create_contest(1, 2)
        p1 = person.PersonID()
        c = cont.register_paraglider(p1, 'mantrA 9', '747')

        self.assertIsInstance(c, contest.Contest)
        self.assertEqual(len(cont._participants), 1)
        self.assertEqual(len(cont.paragliders), 1)
        self.assertIsInstance(cont.paragliders, dict, "It must be dict.")
        self.assertEqual(cont._participants[p1]['role'], 'paraglider')
        self.assertEqual(cont._participants[p1]['glider'], 'mantra')
        self.assertEqual(cont._participants[p1]['contest_number'], 747)

        p2 = person.PersonID()
        cont.register_paraglider(p2, 'mantrA 9', '757')
        self.assertEqual(len(cont._participants), 2)
        self.assertEqual(cont._participants[p2]['role'], 'paraglider')
        self.assertEqual(cont._participants[p2]['glider'], 'mantra')
        self.assertEqual(cont._participants[p2]['contest_number'], 757)

        # Check contest numbers uniqueness.
        self.assertRaises(ValueError, cont.register_paraglider, 'person3',
            'mantrA 9', '757')

        mock_calls = event_store.mock_calls
        self.assertEqual(len(mock_calls), 2)
        self.assertEqual(mock_calls[-1], mock.call.persist(
            events.ParagliderRegisteredOnContest(p2, cont.id)))
        self.assertEqual(mock_calls[-2], mock.call.persist(
            events.ParagliderRegisteredOnContest(p1, cont.id)))

    def test_times_changing(self):
        cont = create_contest(1, '15')
        cont.start_time = '2'
        self.assertEqual(cont.start_time, 2)
        cont.end_time = '8'
        self.assertEqual(cont.end_time, 8)
        self.assertRaises(ValueError, setattr, cont, 'start_time', 8)
        self.assertRaises(ValueError, setattr, cont, 'start_time', 9)
        self.assertRaises(ValueError, setattr, cont, 'end_time', 2)
        self.assertRaises(ValueError, setattr, cont, 'end_time', 1)
        cont.change_times('10', '16')
        self.assertEqual((cont.start_time, cont.end_time), (10, 16))
        self.assertRaises(ValueError, cont.change_times, '10', '8')

    def test_change_title(self):
        cont = create_contest(1, '15')
        cont.title = '  hello moOn  '
        self.assertEqual(cont.title, 'hello moOn')

    def test_change_address(self):
        cont = create_contest(1, '15')
        cont.place = 'Severodvinsk'
        self.assertEqual(cont.place, 'Severodvinsk')
        cont.country = 'tw'
        self.assertEqual(cont.country, 'TW')
        cont.hq_coords = (15, 0)
        self.assertEqual(cont.hq_coords, (15, 0))

class ContestTestWithRegisteredParagliders(unittest.TestCase):

    def setUp(self):
        self.p1_id = person.PersonID()
        self.p2_id = person.PersonID()
        self.p3_id = person.PersonID()
        @mock.patch('gorynych.common.infrastructure.persistence.event_store')
        def fixture(patched):
            patched.return_value = mock.Mock()
            cont  = create_contest(1, 15)
            cont.register_paraglider(self.p2_id, 'mantrA 9', '757')
            cont.register_paraglider(self.p1_id, 'gIn 9', '747')
            person1 = cont._participants[self.p1_id]
            person2 = cont._participants[self.p2_id]
            return cont, person1, person2
        try:
            self.cont, self.person1, self.person2 = fixture()
        except:
            raise unittest.SkipTest("ERROR: need contest with paragliders "
                                    "for test.")

    def tearDown(self):
        del self.cont
        del self.person1
        del self.person2

    def test_correct_change_participant_data(self):
        self.cont.change_participant_data(self.p1_id, glider='ajAx  ',
            contest_number='0')
        self.assertEqual(self.person1['glider'], 'ajax')
        self.assertEqual(self.person1['contest_number'], 0)

    def test_no_data(self):
        self.assertRaises(ValueError, self.cont.change_participant_data,
            'person2')

    def test_wrong_parameters(self):
        self.assertRaises(ValueError, self.cont.change_participant_data,
            'person3', contest_number=9, glider='ajax')
        self.cont.change_participant_data(self.p1_id, cotest_number=9,
            glider='aJax')
        self.assertEqual(self.person1['contest_number'], 747)
        self.assertEqual(self.person1['glider'], 'ajax')

    def test_violate_invariants(self):
        self.assertRaises(ValueError, self.cont.change_participant_data,
            'person1', contest_number='757')

class ParagliderTest(unittest.TestCase):
    def test_success_creation(self):
        p_id = PersonID()
        t_id = TrackerID(TrackerID.device_types[0], '123456789012345')
        p = race.Paraglider(p_id, Name('Vasya', 'Pupkin'),
                            Country('RU'), 'Mantra 9', 15, t_id)
        self.assertEqual(p.person_id, p_id)
        self.assertEqual(p.glider, 'mantra')
        self.assertEqual(p.contest_number, 15)
        self.assertEqual(p.tracker_id, t_id)

@mock.patch('gorynych.common.infrastructure.persistence.event_store')
class ContestServiceTest(unittest.TestCase):

    def setUp(self):
        self.cont = create_contest(time.time(), time.time() + 3600)

    def test_register_paraglider(self, patched):
        event_store = mock.Mock()
        patched.return_value = event_store

        alone_cont = deepcopy(self.cont)
        pid = PersonID()
        populated_cont = self.cont.register_paraglider(pid,
                                                       'glider',
                                                       11)
        self.assertFalse(alone_cont.paragliders)
        self.assertTrue(populated_cont.paragliders)

        pgl = populated_cont.paragliders
        self.assertEquals(pgl.keys()[0], pid)
        self.assertEquals(pgl[pid]['role'], 'paraglider')
        self.assertEquals(pgl[pid]['glider'], 'glider')
        self.assertEquals(pgl[pid]['contest_number'], 11)

    def test_add_transport(self, patched):
        event_store = mock.Mock()
        patched.return_value = event_store

        alone_cont = deepcopy(self.cont)
        tid = TransportID()
        populated_cont = self.cont.add_transport(tid)

        self.assertFalse(alone_cont.transport)
        self.assertIn(tid, populated_cont.transport)

    def test_change_paraglider(self, patched):
        event_store = mock.Mock()
        patched.return_value = event_store

        pid = PersonID()
        cont = self.cont.register_paraglider(pid,
                                             'glider',
                                             11)
        changed_cont = contest.change_participant(cont, dict(glider='noglider',
                                                             contest_number=21,
                                                             person_id=pid))

        pgl = changed_cont.paragliders

        self.assertEquals(pgl.keys()[0], pid)
        self.assertEquals(pgl[pid]['glider'], 'noglider')
        self.assertEquals(pgl[pid]['contest_number'], 21)

    def test_add_winddummy(self, patched):
        event_store = mock.Mock()
        patched.return_value = event_store

        pid = PersonID()
        cont = self.cont.add_winddummy(pid)
        wdms = cont.winddummies
        self.assertEquals(wdms, [pid])

    def test_get_winddummy(self, patched):
        event_store = mock.Mock()
        patched.return_value = event_store

        pid = PersonID()
        cont = self.cont.add_winddummy(pid)
        self.assertEquals(cont.get_winddummy(pid), pid)
        pid2 = PersonID()
        self.assertRaises(DomainError, cont.get_winddummy, pid2)


from reportlab.lib import colors
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.platypus import Paragraph
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.enums import TA_JUSTIFY

def generate_prescription(patient_name, doctor_name, medicine_list, logo_path, rx_path, signature_path, prescription_no, consultation_no, doctor_email, medconnect_id, reg_no, doctor_location, patient_id, patient_location, date, remarks, doctor_title):
    # Create a file named after the patient
    filename = f"{patient_name}_prescription.pdf"
    c = canvas.Canvas(filename, pagesize=A4, bottomup=1)

    # MedConnect Logo
    c.setFillColor(colors.white)
    c.drawImage(logo_path, 12, 759 - 16, width=169, height=83.12)

    # Rx logo
    c.drawImage(rx_path, 595-60-12, 759 - 4, width=60, height=60)

    # Document Nos.
    c.setFillColor(colors.black)

    c.setFont("Courier", 13)
    c.drawRightString(525, 735 , "Prescription No:")
    c.setFont("Courier", 13)
    c.drawRightString(570, 735, f"#{prescription_no}")

    c.drawRightString(525, 720 , "Consultation No:")
    c.setFont("Courier", 13)
    c.drawRightString(570, 720, f"#{consultation_no}")

    #Doctor Details
    c.setFillColor(colors.black)
    c.setFont("Courier", 13)

    c.drawRightString(570, 680 , "+917738118110")

    c.drawRightString(570, 665 , f"{doctor_email}")

    c.drawRightString(450, 650 , "MedConnect Id.: ")
    c.setFont("Courier", 13)
    c.drawRightString(570, 650 , f"{medconnect_id}")

    c.drawRightString(450, 635 , "Reg. No.: ")
    c.setFont("Courier", 13)
    c.drawRightString(570, 635 , f"{reg_no}")

    #Doctor Headers
    c.setFont("Courier-Bold", 21)
    c.drawString(12, 680, f"Dr. {doctor_name}")
    c.setFont("Courier", 13)
    c.drawString(12, 665 , f"{doctor_location}")

    # Patient Details
    c.setFont("Courier", 13)
    c.drawString(12, 620 , "Patient Id:")
    c.drawString(100, 620 , f"# {patient_id}")
    c.drawString(12, 605 , "Patient:")
    c.setFont("Courier-Bold", 13)
    c.drawString(100, 605 , f"{patient_name}")
    c.setFont("Courier", 13)
    c.drawString(100, 590 , f"{patient_location}")
    c.drawString(12, 575 , "Date:")
    c.drawString(100, 575 , f"{date}")

    c.setFont("Courier-Bold", 18)
    c.drawString(12, 530 , "Treatment Advised")

    # Add a table with the medicine list
    c.setFont("Courier-Bold", 13)
    c.drawString(12, 500, "Type")
    c.drawString(97, 500, "Medicine")
    c.drawString(242, 500, "Power")
    c.drawString(346, 500, "Frequency")
    c.drawString(440, 500, "Remarks")
    c.setStrokeColor(colors.grey)
    c.line(12, 490, 570, 490)
    c.setFont("Courier", 13)
    for i, medicine in enumerate(medicine_list):
        c.drawString(12, 475 - i * 25, medicine[0])
        c.drawString(97, 475 - i * 25, medicine[1])
        c.drawString(242, 475 - i * 25, medicine[2])
        c.drawString(346, 475 - i * 25, medicine[3])
        c.drawString(440, 475 - i * 25, medicine[4])

    c.setFont("Courier-Bold", 18)
    c.drawString(12, 280 , "Next Investigation / Other Remarks")
    style = getSampleStyleSheet()["Normal"]
    style.fontName = "Courier"
    style.fontSize = 12
    style.alignment = TA_JUSTIFY

    p = Paragraph(remarks, style)
    p.wrapOn(c, 558, 100)
    p.drawOn(c, 12, 140)

    c.drawImage(signature_path, 456, 66, width=86, height=39)
    c.setFont("Courier-Bold", 13)
    c.drawRightString(570, 47 , f"{doctor_name}")
    c.setFont("Courier", 10)
    c.drawRightString(570, 33 , f"{doctor_title}")

    c.line(12, 18, 570, 18)
    c.setFont("Courier", 10)
    c.drawString(12, 5, "Thank you for choosing MedConnect. Have a Healthy Day!")

    # Save the PDF
    c.save()

medicine_list = [
    ("Tablet","Paracetamol", "500 mg", "1-0-1", "-"),
    ("Tablet","Dolo", "10 mg", "0-1-0", "-"),
    ]

remarks = "Take rest. Do not work more."

generate_prescription("Prem Kothawle", "Dr. Shubham Saroj", medicine_list, "medconnect_logo.jpg", "rx_logo.jpg", "Shubham_Sign.jpeg", "1258", "1279", "drssaroj@gmail.com", "86438648464", "123454321234", "Thane, India", "2547", "Nere, India", "10 February 2023", remarks, "M.B.B.S")

# patient_name, doctor_name, medicine_list, logo_path, rx_path, signature_path, prescription_no, consultation_no, doctor_email, medconnect_id, reg_no, doctor_location, patient_id, patient_location, date, remarks, doctor_title


"""
Plot.py
Created 21/12/2021
"""

from juzzyPython.generic.Tuple import Tuple
from juzzyPython.generalType2zSlices.sets.GenT2MF_Interface import GenT2MF_Interface
from juzzyPython.type1.sets.T1MF_Interface import T1MF_Interface
from juzzyPython.generalType2zSlices.sets.GenT2MF_Triangular import GenT2MF_Triangular
from juzzyPython.intervalType2.sets.IntervalT2MF_Interface import IntervalT2MF_Interface
from juzzyPython.generalType2zSlices.sets.GenT2MF_Trapezoidal import GenT2MF_Trapezoidal
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from typing import List
plt.rcParams.update({'figure.max_open_warning': 0})

class Plot:
    """
    Class Plot:
    Uses the matplotlib to plot various graphs

    Parameters: None

    Funtions:
        plotControlSurface
        show
        figure
        title
        legend
        discretize
        plotMF
    """

    def __init__(self) -> None:
        self.colorList = ['tab:blue','tab:orange','tab:green','tab:red','tab:purple','tab:brown','tab:pink','tab:gray','tab:olive','tab:cyan']

    def show(self):
        """Show all the figures created"""
        plt.show()

    def figure(self):
        """Create a new plot to draw upon"""
        self.fig = plt.figure()

    def figure3d(self):
        """Create a new 3d plot to draw upon"""
        self.fig, self.ax = plt.subplots(subplot_kw={"projection": "3d"})

    def title(self,title: str):
        """Set the title of the current figure"""
        plt.title(title)

    def legend(self):
        """Add legend to the current figure"""
        plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
        plt.tight_layout()

    def plotControlSurface(self,x: List[float],y: List[float],z: List[List[float]],xLabel: str,yLabel: str,zLabel: str) -> None:
        """Plot a 3D surface showcasing the relationship between input (x,y) and output z"""
        fig, ax = plt.subplots(subplot_kw={"projection": "3d"})

        x,y = np.meshgrid(x,y)
        ax.plot_surface(np.asarray(x), np.asarray(y),np.asarray(z))
        ax.set_xlabel(xLabel)
        ax.set_ylabel(yLabel)
        ax.set_zlabel(zLabel)
        plt.title("Control Surface")

    def plotMF2(self,xaxis: str,name: str,sets: IntervalT2MF_Interface,xDisc: int,addExtraEndPoints: bool) -> None:
        x = self.discretize(sets.getSupport(),xDisc)
        y1 = [0] * xDisc
        y2 = [0] * xDisc

        for i in range(xDisc):
            temp = sets.getFS(x[i])
            y1[i] = temp.getRight()
            y2[i] = temp.getLeft()

        if addExtraEndPoints:
            x2 = [0.0] * (len(x)+2)
            y1b = [0.0] * (len(y1)+2)
            y2b = [0.0] * (len(y2)+2)

            x2[0] = sets.getSupport().getLeft()
            x2[-1] = sets.getSupport().getRight()

            y1b[0] = 0.0
            y1b[len(y1)-1] = 0.0
            y2b[0] = 0.0
            y2b[len(y2)-1] = 0.0

            for i in range(len(x)):
                x2[i+1] = x[i]
                y1b[i+1] = y1[i]
                y2b[i+1] = y2[i]
            x = x2
            y1 = y1b
            y2 = y2b
        ax = plt.gca()
        color = next(ax._get_lines.prop_cycler)['color']
        plt.plot(x,y1,label=name+"_upper", color = color)
        plt.plot(x,y2,label=name+"_lower", color = color, alpha=0.5)
        #plt.xlim(xAxisRange.getLeft(),xAxisRange.getRight())
        #plt.ylim(yAxisRange.getLeft(),yAxisRange.getRight())
        plt.ylabel("μ")
        plt.xlabel(xaxis)

    def plotMF(self,xaxis: str,name: str,sets: T1MF_Interface,xDisc: int,xAxisRange: Tuple,yAxisRange: Tuple,addExtraEndPoints: bool) -> None:
        """Plot a membership function on the current figure"""
        x = self.discretize(sets.getSupport(),xDisc)
        y = [0] * xDisc
        for i in range(xDisc):
            y[i] = sets.getFS(x[i])

        if addExtraEndPoints:
            x2 = [0.0] * (len(x)+2)
            y2 = [0.0] * (len(y)+2)
            x2[0] = sets.getSupport().getLeft()
            x2[-1] = sets.getSupport().getRight()
            for i in range(len(x)):
                x2[i+1] = x[i]
                y2[i+1] = y[i]
            x = x2
            y = y2
        plt.plot(x,y,label=name)
        plt.xlim(xAxisRange.getLeft(),xAxisRange.getRight())
        plt.ylim(yAxisRange.getLeft(),yAxisRange.getRight())
        plt.ylabel("μ")
        plt.xlabel(xaxis)

    def plotMFasLines(self,sets: GenT2MF_Interface,xDisc: int) -> None:
        self.ax.set_xlabel("x")
        self.ax.set_ylabel("y")
        self.ax.set_zlabel("z")

        x = self.discretize(sets.getSupport(),xDisc)
        y1 = [[0 for c in range(xDisc)] for r in range(sets.getNumberOfSlices())]
        y2 = [[0 for c in range(xDisc)] for r in range(sets.getNumberOfSlices())]
        z1 = [[0 for c in range(xDisc)] for r in range(sets.getNumberOfSlices())]
        z2 = [[0 for c in range(xDisc)] for r in range(sets.getNumberOfSlices())]

        for zLevel in range(sets.getNumberOfSlices()):
            for i in range(xDisc):
                temp = sets.getZSlice(zLevel).getFS(x[i])
                y1[zLevel][i] = temp.getRight()
                y2[zLevel][i] = temp.getLeft()
                if zLevel==0:
                    z1[zLevel][i] = 0.0
                else:
                    z1[zLevel][i] = sets.getZValue(zLevel-1)
                z2[zLevel][i] = sets.getZValue(zLevel)

        for zLevel in range(sets.getNumberOfSlices()):
            self.ax.plot3D(x,y1[zLevel],z1[zLevel],label=sets.getName()+"_upper",color= self.colorList[zLevel%10])
            self.ax.plot3D(x,y2[zLevel],z1[zLevel],label=sets.getName()+"_lower",color= self.colorList[zLevel%10])
            self.ax.plot3D(x,y1[zLevel],z2[zLevel],label=sets.getName()+"_upper",color= self.colorList[zLevel%10])
            self.ax.plot3D(x,y2[zLevel],z2[zLevel],label=sets.getName()+"_lower",color= self.colorList[zLevel%10])

    def turnOnInteraction(self):
        plt.ion()

    def closeAllFigures(self):
        plt.close('all')

    def plotMFasSurface(self,plotName: str,sets: GenT2MF_Interface,xAxisRange: Tuple,xDisc: int,addExtraPoints: bool):
        self.ax.set_xlabel("X-Axis")
        self.ax.set_ylabel("Z-Axis")
        self.ax.set_zlabel("Y-Axis")

        if isinstance(sets,GenT2MF_Triangular):
            for zLevel in range(sets.getNumberOfSlices()):
                xUpper = [sets.getZSlice(zLevel).getUMF().getStart(), sets.getZSlice(zLevel).getUMF().getPeak(),sets.getZSlice(zLevel).getUMF().getEnd()]
                zUpper = None
                yUpper = [[0 for i in range(3)] for j in range(2)]

                if zLevel == 0:
                    zUpper = [0.0,sets.getZValue(zLevel)]
                else:
                    zUpper = [sets.getZValue(zLevel-1),sets.getZValue(zLevel)]
                for xD in range(3):
                    yUpper[0][xD] = sets.getZSlice(zLevel).getFS(xUpper[xD]).getRight()
                    yUpper[1][xD] = yUpper[0][xD]

                xLower = [sets.getZSlice(zLevel).getLMF().getStart(), sets.getZSlice(zLevel).getLMF().getPeak(),sets.getZSlice(zLevel).getLMF().getEnd()]
                zLower = None
                yLower = [[0 for i in range(3)] for j in range(2)]

                if zLevel == 0:
                    zLower = [0.0,sets.getZValue(zLevel)]
                else:
                    zLower = [sets.getZValue(zLevel-1),sets.getZValue(zLevel)]
                for xD in range(3):
                    yLower[0][xD] = sets.getZSlice(zLevel).getFS(xLower[xD]).getLeft()
                    yLower[1][xD] = yLower[0][xD]
                x,y = np.meshgrid(xUpper,zUpper)
                self.ax.plot_surface(np.asarray(x), np.asarray(y),np.asarray(yUpper),alpha = 0.5,color=self.colorList[zLevel%10])
                x,y = np.meshgrid(xLower,zLower)
                self.ax.plot_surface(np.asarray(x), np.asarray(y),np.asarray(yLower),alpha = 0.5,color=self.colorList[zLevel%10])
        elif isinstance(sets,GenT2MF_Trapezoidal):
            for zLevel in range(sets.getNumberOfSlices()):
                xUpper = [sets.getZSlice(zLevel).getUMF().getA(), sets.getZSlice(zLevel).getUMF().getB(),sets.getZSlice(zLevel).getUMF().getC(),sets.getZSlice(zLevel).getUMF().getD()]
                zUpper = None
                yUpper = [[0 for i in range(4)] for j in range(2)]

                if zLevel == 0:
                    zUpper = [0.0,sets.getZValue(zLevel)]
                else:
                    zUpper = [sets.getZValue(zLevel-1),sets.getZValue(zLevel)]
                for xD in range(4):
                    yUpper[0][xD] = sets.getZSlice(zLevel).getFS(xUpper[xD]).getRight()
                    yUpper[1][xD] = yUpper[0][xD]

                xLower = [sets.getZSlice(zLevel).getLMF().getA(), sets.getZSlice(zLevel).getLMF().getB(),sets.getZSlice(zLevel).getLMF().getC(),sets.getZSlice(zLevel).getLMF().getD()]
                zLower = None
                yLower = [[0 for i in range(4)] for j in range(2)]

                if zLevel == 0:
                    zLower = [0.0,sets.getZValue(zLevel)]
                else:
                    zLower = [sets.getZValue(zLevel-1),sets.getZValue(zLevel)]
                for xD in range(4):
                    yLower[0][xD] = sets.getZSlice(zLevel).getFS(xLower[xD]).getLeft()
                    yLower[1][xD] = yLower[0][xD]
                x,y = np.meshgrid(xUpper,zUpper)
                self.ax.plot_surface(np.asarray(x), np.asarray(y),np.asarray(yUpper),alpha = 0.5,color=self.colorList[zLevel%10])
                x,y = np.meshgrid(xLower,zLower)
                self.ax.plot_surface(np.asarray(x), np.asarray(y),np.asarray(yLower),alpha = 0.5,color=self.colorList[zLevel%10])
        else:
            for zLevel in range(sets.getNumberOfSlices()):
                xUpper = self.discretize(xAxisRange,xDisc)
                zUpper = None
                yUpper = [[0 for i in range(xDisc)] for j in range(2)]

                if zLevel == 0:
                    zUpper = [0.0,sets.getZValue(zLevel)]
                else:
                    zUpper = [sets.getZValue(zLevel-1),sets.getZValue(zLevel)]
                for xD in range(xDisc):
                    yUpper[0][xD] = sets.getZSlice(zLevel).getFS(xUpper[xD]).getRight()
                    yUpper[1][xD] = yUpper[0][xD]

                xLower = self.discretize(xAxisRange,xDisc)
                zLower = None
                yLower = [[0 for i in range(xDisc)] for j in range(2)]

                if zLevel == 0:
                    zLower = [0.0,sets.getZValue(zLevel)]
                else:
                    zLower = [sets.getZValue(zLevel-1),sets.getZValue(zLevel)]
                for xD in range(xDisc):
                    yLower[0][xD] = sets.getZSlice(zLevel).getFS(xLower[xD]).getLeft()
                    yLower[1][xD] = yLower[0][xD]

                if addExtraPoints:
                    x_upper2 = [0.0] * (len(xUpper) + 2)
                    y_upper2 = [[0.0 for i in range(len(yUpper[0]) + 2)] for j in range(2)]
                    x_Lower2 = [0.0] * (len(xLower) + 2)
                    y_Lower2 = [[0.0 for i in range(len(yLower[0]) + 2)] for j in range(2)]

                    x_upper2[0] = sets.getSupport().getLeft()
                    x_upper2[-1] = sets.getSupport().getRight()
                    x_Lower2[0] = x_upper2[0]
                    x_Lower2[-1] = x_upper2[-1]

                    for i in range(len(xUpper)):
                        x_upper2[i + 1] = xUpper[i]
                        x_Lower2[i + 1] = xLower[i]
                        y_upper2[0][i + 1] = yUpper[0][i]
                        y_Lower2[0][i + 1] = yLower[0][i]
                        y_upper2[1][i + 1] = yUpper[1][i]
                        y_Lower2[1][i + 1] = yLower[1][i]

                    xUpper = x_upper2
                    xLower = x_Lower2
                    yUpper = y_upper2
                    yLower = y_Lower2
                x,y = np.meshgrid(xUpper,zUpper)
                self.ax.plot_surface(np.asarray(x), np.asarray(y),np.asarray(yUpper),alpha = 0.5,color=self.colorList[zLevel%10])
                x,y = np.meshgrid(xLower,zLower)
                self.ax.plot_surface(np.asarray(x), np.asarray(y),np.asarray(yLower),alpha = 0.5,color=self.colorList[zLevel%10])

    def discretize(self,support: Tuple,discLevel: int) -> List[float]:
        """Discretize the support values"""
        d = [0] * discLevel
        stepSize = (support.getSize())/(discLevel-1.0)
        d[0] = support.getLeft()
        d[-1] = support.getRight()
        for i in range(1,discLevel-1):
            d[i] = support.getLeft()+i*stepSize
        return d


import os, sys
from os.path import join as ospj
import torch
import numpy as np
from PIL import Image
import torch.utils.data as data
import kornia
import argparse
from logger import Logger

class PairedImageDataset(data.Dataset):
    def __init__(self, lr_img_path, lr_filelist_path, hr_img_path, hr_filelist_path, args):
        self.args=args
        self.lr_img_path = lr_img_path
        self.hr_img_path = hr_img_path
        self.lr_filelist_path = lr_filelist_path
        self.hr_filelist_path = hr_filelist_path

        self.lr_img_list = [x.strip() for x in open(self.lr_filelist_path).readlines()]
        self.hr_img_list = [x.strip() for x in open(self.hr_filelist_path).readlines()]

        # -85.61112_30.197733_28cm.tif -> -85.61112_30.197733_50cm.png
        self.paired_lr_img_list = [x.replace("28cm.tif", "50cm.png") for x in self.hr_img_list]

    def __getitem__(self, item):
        lr_img_name = self.paired_lr_img_list[item]
        hr_img_name = self.hr_img_list[item]

        lr_img = Image.open(ospj(self.lr_img_path, lr_img_name)).convert('RGB')
        hr_img = Image.open(ospj(self.hr_img_path, hr_img_name)).convert('RGB')

        lr_img = np.asarray(lr_img) / 255.0
        hr_img = np.asarray(hr_img) / 255.0

        lr_img = kornia.image_to_tensor(lr_img).squeeze()
        hr_img = kornia.image_to_tensor(hr_img).squeeze()

        return lr_img, hr_img

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

class TVDenoise(torch.nn.Module):
    def __init__(self, args):
        super(TVDenoise, self).__init__()
        self.l2_term = torch.nn.MSELoss(reduction='mean')
        self.l1_term = torch.nn.L1Loss(reduction='mean')
        self.psnr = kornia.losses.PSNRLoss(max_val=1.0)
        self.ssim=kornia.losses.SSIM(5, reduction='mean')
        self.regularization_term = kornia.losses.TotalVariation()
        self.args=args

        self.xyxy = torch.nn.Parameter(data=torch.tensor([[0.], [0.], [713], [713]]), requires_grad=True)
        self.mem = torch.nn.Parameter(data=torch.tensor(
            [[1., 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [1, 0, 0, 0],
             [0, 0, 0, 1]]), requires_grad=False)

    def forward(self, src_img, dst_img):

        new_image = self.get_new_image(src_img, dst_img)
        return new_image

    def get_new_image(self, src_img, dst_img):
        self.boxes=torch.matmul(self.mem, self.xyxy).reshape(1, 4, 2)
        return kornia.crop_and_resize((src_img), self.boxes, dst_img.shape[-2:])

def train(epoch_i, data_loader, network, optimizer, args):
    num_iters = len(data_loader)
    loss_list=[]
    l1loss_list=[]
    l2loss_list=[]
    for i, input_tuple in enumerate(data_loader):
        optimizer.zero_grad()
        lr_img, hr_img = input_tuple
        resized_img = network(hr_img, lr_img)

        l1loss = network.l1_term(resized_img, lr_img)
        l2loss = network.l2_term(resized_img, lr_img)

        if args.use_l2_loss:
            loss = l2loss
        else:
            loss = l1loss

        loss.backward()
        optimizer.step()

        loss_list.append(loss.detach().numpy())
        l1loss_list.append(l1loss.item())
        l2loss_list.append(l2loss.item())
        if i % 20 == 0:
            print("[{:2d}] [{:3d}/{:3d}]: loss {:.5f} l1:{:.5f} l2:{:.5f}".
                  format(epoch_i, i, num_iters, loss.item(), l1loss.item(), l2loss.item()),
                  "crop", network.xyxy.detach().numpy().flatten())

    print("Averge loss: %.5f\tl1: %.5f\tl2: %.5f"%(np.mean(loss_list), np.mean(l1loss_list), np.mean(l2loss_list)))

def main():
    parser = argparse.ArgumentParser(description="Learnable Cropping Images")
    parser.add_argument('--use_l2_loss', action='store_true')
    parser.add_argument('--batch_size', type=int, default=32)
    parser.add_argument('--shuffle', action='store_true')
    parser.add_argument('--workers', type=int, default=2)
    parser.add_argument('--not_pin_memory', action='store_true')
    parser.add_argument('--lr_img_path', type=str, default="../../dataset/satellite_images/")
    parser.add_argument('--lr_filelist_path', type=str, default="data/satellite_images_filelist.txt")
    parser.add_argument('--hr_img_path', type=str, default="../../dataset/aerial_images/")
    parser.add_argument('--hr_filelist_path', type=str, default="data/aerial_images_filelist.txt")
    parser.add_argument('--num_epochs', type=int, default=25)
    parser.add_argument('--learning_rate', type=float, default=100.0)
    parser.add_argument('--exp_name', type=str, default="learncrop")

    args = parser.parse_args()

    logger = Logger()
    exp_dir = ospj("exps", args.exp_name+logger._timestr)
    os.makedirs(exp_dir, exist_ok=True)

    logger.create_log(exp_dir)
    sys.stdout = logger
    if args.use_l2_loss:
        print("use l2 loss")
    else:
        print("use l1 loss")

    dataset = PairedImageDataset(args.lr_img_path, args.lr_filelist_path, args.hr_img_path, args.hr_filelist_path, args)
    data_loader = torch.utils.data.DataLoader(dataset, batch_size=args.batch_size, shuffle=args.shuffle,
                                              num_workers=args.workers, pin_memory=not args.not_pin_memory, sampler=None,
                                              drop_last=False)
    network = TVDenoise(args)
    optimizer = torch.optim.SGD(network.parameters(), lr=args.learning_rate, momentum=0.9)

    for epoch_i in range(args.num_epochs):
        train(epoch_i, data_loader, network, optimizer, args)

if __name__ == "__main__":
    main()