from corpus.datasources.webscrape import WebScrape
from corpus.event import EventStorage,EventManager, EventSeriesManager
import datetime
from enum import Enum
import glob
import re
import os
import sys
import threading
import time
from argparse import ArgumentParser
from argparse import RawDescriptionHelpFormatter
#from lodstorage.jsonpicklemixin import JsonPickleMixin
from lodstorage.storageconfig import StorageConfig
import corpus.datasources.wikicfp as wcfp
#import jsonpickle

class CrawlType(Enum):
    '''
    possible supported storage modes
    '''
    EVENT = "Event"
    SERIES = "Series"

    @property
    def urlPrefix(self):
        baseUrl="http://www.wikicfp.com/cfp"
        if self.value is CrawlType.EVENT.value:
            url= f"{baseUrl}/servlet/event.showcfp?eventid="
        elif self.value is CrawlType.SERIES.value:
            url= f"{baseUrl}/program?id="
        return url

    @classmethod
    def isValid(cls,value:str)->bool:
        '''
        check whether the given value is valid

        Args:
            value(str): the value to check

        Return:
            bool: True if the value is a valid value of this enum
        '''
        return value in ["Event","Series"]

    @classmethod
    def ofValue(cls,value:str):
        if value=="Event":
            return CrawlType.EVENT
        elif value=="Series":
            return CrawlType.SERIES
        else:
            return None

class CrawlBatch(object):
    '''
    describe a batch of pages to fetch metadata from
    '''

    def __init__(self,threads:int,startId:int,stopId:int,crawlTypeValue:str,threadIndex=None,):
        '''
        construct me with the given number of threads, startId and stopId

        Args:
           threads(int): number of threads to use
           startId(int): id of the event to start crawling with
           stopId(int): id of the event to stop crawling
           crawlTypeValue(str): the type of crawling (Event or Series)
           threadIndex(int): the index of this batch
        '''
        self.threads=threads
        self.threadIndex=threadIndex
        self.startId=startId
        self.stopId=stopId
        if startId <= stopId:
            self.step = +1
            self.total = stopId-startId+1
        else:
            self.step = -1
            self.total = startId-stopId+1
        self.batchSize = self.total // threads
        if not CrawlType.isValid(crawlTypeValue):
            raise Exception(f"Invalid crawlType {crawlTypeValue}")
        self.crawlType=CrawlType.ofValue(crawlTypeValue)

    def split(self)->list:
        '''
        split me for my threads
        '''
        crawlBatches=[]
        for threadIndex in range(self.threads):
            s = self.startId + threadIndex * self.batchSize
            e = s + self.batchSize-1
            splitBatch=CrawlBatch(1,s, e,self.crawlType.value,threadIndex)
            crawlBatches.append(splitBatch)
        return crawlBatches

    def __str__(self):
        '''
        get my string representation
        Return
            str: my string representation
        '''
        text=f"WikiCFP {self.crawlType.value} IDs {self.startId} - {self.stopId} ({self.threads} threads of {self.batchSize} IDs each)"
        return text

class WikiCfpScrape(object):
    '''
    support events from http://www.wikicfp.com/cfp/
    '''

    def __init__(self,jsonEventManager,jsonEventSeriesManager,profile:bool=True,debug:bool=False,jsondir:str=None,limit=200000,batchSize=1000,showProgress=True):
        '''
        Constructor

        Args:
            config(StorageConfig): the storage configuration to use
            debug(bool): if True debug for crawling is on
            limit(int): maximum number of entries to be crawled
            batchSize(int): default size of batches
            showProgress(bool): if True show Progress
        '''
        self.debug=debug
        self.limit=limit
        self.batchSize=batchSize
        self.showProgress=showProgress
        self.jsonEventManager=jsonEventManager
        self.jsonEventSeriesManager=jsonEventSeriesManager
        self.jsonManagers={
            "Event": jsonEventManager,
            "Series": jsonEventSeriesManager
        }
        self.profile=profile
        if jsondir is not None:
            self.jsondir=jsondir
        else:
            cachePath=self.jsonEventManager.config.getCachePath()
            self.jsondir=f"{cachePath}/wikicfp"
            if not os.path.exists(self.jsondir):
                    os.makedirs(self.jsondir)

    def getManager(self,crawlType:CrawlType):
        '''
        get the manager for the given crawlType

        Args:
           crawlType(CrawlType) the manager for the given CrawlType
        '''
        manager=self.jsonManagers[crawlType.value]
        return manager

    def cacheToJsonManager(self,crawlType:CrawlType):
        '''
        cache the crawlType entities to my json manager for that crawlType

        Args:
            crawlType(CrawlType): the CrawlType to work with

        Return:
            EntityBasemanager: the entityManager

        '''
        jsonEm=self.getManager(crawlType)
        if jsonEm.isCached():
            jsonEm.fromStore()
        else:
            self.crawlFilesToJson(crawlType=crawlType,withStore=True)
        return jsonEm

    def crawlFilesToJson(self,crawlType:CrawlType,withStore=True):
        '''
        convert the crawlFiles to Json

        Args:
            crawlType(CrawlType): the CrawlType to work with
            withStore(bool): True if the data should be stored after collecting

        Return:
            jsonEm(EventBaseManager): the JSON - storage based EventBaseManager to use to collect the results
        '''
        # crawling is not done on startup but need to be done
        # in command line mode ... we just collect the json crawl result files here
        #self.crawl(startId=startId,stopId=stopId)
        jsonEm=self.getManager(crawlType)
        entityList=jsonEm.getList()
        startTime=time.time()
        jsonFiles=self.jsonFiles(crawlType)
        if len(jsonFiles)==0:
            if self.profile or self.debug:
                print(f"No wikiCFP crawl json backups for {crawlType.value} available")
        else:
            # loop over all crawled files
            for jsonFilePath in jsonFiles:
                config=StorageConfig.getJSON(debug=self.debug)
                config.cacheFile=jsonFilePath
                if crawlType.value is CrawlType.EVENT.value:
                    batchEm=EventManager(name=jsonFilePath,clazz=jsonEm.clazz,config=config)
                elif crawlType.value is CrawlType.SERIES.value:
                    batchEm=EventSeriesManager(name=jsonFilePath,clazz=jsonEm.clazz,config=config)
                # legacy mode -file were created before ORM Mode
                # was available - TODO: make new files available in ORM mode with jsonable
                #jsonPickleEm=JsonPickleMixin.readJsonPickle(jsonFileName=jsonFilePath,extension='.json')
                #jsonPickleEvents=jsonPickleEm['events']
                #if self.showProgress:
                #    print("%4d: %s" % (len(jsonPickleEvents),jsonFilePath))
                batchEm.fromStore(cacheFile=jsonFilePath)
                for entity in batchEm.getList():
                    if hasattr(entity, "startDate"):
                        if entity.startDate is not None:
                            entity.year=entity.startDate.year
                    doAdd=not(entity.deleted)
                    # SPAM Filter
                    if hasattr(entity, "locality"):
                        if entity.locality.startswith("1"):
                            doAdd=False
                    # Series Filter
                    if hasattr(entity, "title"):
                        if entity.title.startswith("WikiCFP : Call For Papers of Conferences, Workshops and Journals"):
                            doAdd=False

                    if doAdd:
                        entityList.append(entity)
            if self.profile:
                elapsed=time.time()-startTime
                print (f"read {len(entityList)} {crawlType.value} records in {elapsed:5.1f} s")
            if withStore:
                jsonEm.store(limit=self.limit,batchSize=self.batchSize)
        return jsonEm

    def jsonFiles(self,crawlType:CrawlType)->list:
        '''
        get the list of the json files that have my data

        Args:
            crawlType(CrawlType): the tpe of the files

        Return:
            list: a list of json file names
        '''
        jsonFiles=sorted(glob.glob(f"{self.jsondir}/wikicfp_{crawlType.value}*.json"),key=lambda path:int(re.findall(r'\d+',path)[0]))
        return jsonFiles

    def getJsonFileName(self,crawlBatch):
        '''
        get my the JsonFileName

        Args:
            crawlBatch(CrawlBatch): the batch to crawl):

        Return:
            str: the json file name for this batch
        '''
        jsonFilePath=f"{self.jsondir}/wikicfp_{crawlBatch.crawlType.value}{crawlBatch.startId:06d}-{crawlBatch.stopId:06d}.json"
        return jsonFilePath

    def getBatchEntityManager(self,crawlBatch:CrawlBatch):
        '''
        get the batch Entity Manager for the given crawlBatch

        Args:
            crawlBatch(CrawlBatch): the batch to crawl):

        Return:
            EntityManager: either a Event or a Series Manager
        '''
        jsonFilepath=self.getJsonFileName(crawlBatch)
        config=EventStorage.getStorageConfig(debug=self.debug, mode="json")
        config.cacheFile=jsonFilepath
        crawlType=crawlBatch.crawlType
        print(f"CrawlBatch has crawlType {type(crawlType)}{crawlType}/{crawlType.value}")
        if crawlType.value is CrawlType.EVENT.value:
            batchEm=wcfp.WikiCfpEventManager(config=config)
        elif crawlType.value is CrawlType.SERIES.value:
            batchEm=wcfp.WikiCfpEventSeriesManager(config=config)
        else:
            raise Exception(f"Invalid crawlType {crawlType}")
        return batchEm

    def crawl(self,crawlBatch:CrawlBatch):
        '''
        see https://github.com/TIBHannover/confIDent-dataScraping/blob/master/wikicfp.py

        Args:
            crawlBatch(CrawlBatch): the batch to crawl
        '''

        print(f'crawling {crawlBatch}')
        batchEm=self.getBatchEntityManager(crawlBatch)

        # get all ids
        crawlType=crawlBatch.crawlType
        for eventId in range(int(crawlBatch.startId), int(crawlBatch.stopId+1), crawlBatch.step):
            wEvent=WikiCfpEventFetcher(crawlType=crawlType)
            retry=1
            maxRetries=3
            retrievedResult=False
            while not retrievedResult:
                try:
                    rawEvent=wEvent.fromEventId(eventId)
                    if crawlType.value is CrawlType.EVENT.value:
                        event=wcfp.WikiCfpEvent()
                        event.fromDict(rawEvent)
                        title="? deleted: %r" %event.deleted if not 'title' in rawEvent else event.title
                        batchEm.getList().append(event)
                    elif crawlType.value is CrawlType.SERIES.value:
                        eventSeries=wcfp.WikiCfpEventSeries()
                        eventSeries.fromDict(rawEvent)
                        title="?" if not 'title' in rawEvent else eventSeries.title
                        batchEm.getList().append(eventSeries)
                    retrievedResult=True
                except Exception as ex:
                    if "HTTP Error 500" in str(ex):
                        print(f"{eventId} inaccessible due to HTTP Error 500")
                        retrievedResult=True
                    elif "timed out" in str(ex):
                        print(f"{eventId} access timed Out on retry attempt {retry}")
                        retry+=1
                        if retry>maxRetries:
                            raise ex
                    else:
                        raise ex
                    pass

            print(f"{eventId:06d}: {title}")

        batchEm.store()
        return batchEm

    def threadedCrawl(self,crawlBatch:CrawlBatch):
        '''
        crawl with the given number of threads, startId and stopId

        Args:
            crawlBatch(CrawlBatch): the batch to crawl
        '''
        # determine the eventId range for each threaded job
        startTime=time.time()

        msg=f'Crawling {crawlBatch}'
        print(msg)

        # this list will contain all threads -> we can wait for all to finish at the end
        jobs = []

        # now start each thread with its id range and own filename
        for crawlBatch in crawlBatch.split():

            thread = threading.Thread(target = self.crawl, args=(crawlBatch,))
            jobs.append(thread)

        for job in jobs:
            job.start()

        # wait till all threads have finished before print the last output
        for job in jobs:
            job.join()

        if self.debug:
            elapsed=time.time()-startTime
            print(f'crawling done after {elapsed:5.1f} s')

class WikiCfpEventFetcher(object):
    '''
    a single WikiCfpEentFetcher to fetch and event or series
    '''
    def __init__(self,crawlType=CrawlType.EVENT,debug=False,showProgress:bool=True,timeout=20):
        '''
        construct me

        Args:
            showProgress(bool): if True show progress
            timeout(float): the default timeout

        '''
        self.debug=debug
        self.crawlType=crawlType
        self.showProgress=showProgress
        self.progressCount=0
        self.timeout=timeout

    def fromTriples(self,rawEvent,triples):
        '''
        get the rawEvent dict from the given triple e.g.:

        v:Event(v:summary)=IDC 2009
        v:Event(v:eventType)=Conference
        v:Event(v:startDate)=2009-06-03T00:00:00
        v:Event(v:endDate)=2009-06-05T23:59:59
        v:Event(v:locality)=Milano, Como, Italy
        v:Event(v:description)= IDC  2009 : The 8th International Conference on Interaction Design and Children
        v:Address(v:locality)=Milano, Como, Italy
        v:Event(v:summary)=Submission Deadline
        v:Event(v:startDate)=2009-01-19T00:00:00
        v:Event(v:summary)=Notification Due
        v:Event(v:startDate)=2009-02-20T00:00:00
        v:Event(v:summary)=Final Version Due
        v:Event(v:startDate)=2009-03-16T00:00:00
        '''
        recentSummary=None
        for s,p,o in triples:
            s=s.replace("v:","")
            p=p.replace("v:","")
            if self.debug:
                print ("%s(%s)=%s" % (s,p,o))
            if recentSummary in ['Submission Deadline','Notification Due','Final Version Due']:
                key=recentSummary.replace(" ","_")
            else:
                key=p
            if p.endswith('Date'):
                dateStr=o
                if dateStr=="TBD":
                    o=None
                else:
                    o=datetime.datetime.strptime(
                        dateStr, "%Y-%m-%dT%H:%M:%S").date()
            if not key in rawEvent:
                rawEvent[key]=o
            if p=="summary":
                recentSummary=o
            else:
                recentSummary=None

    @staticmethod
    def getUrl(cfpid,crawlType:CrawlType=CrawlType.EVENT)->str:
        '''
        Args:
            cfpid(int): the WikiCFP id of the event or series
            crawlType(CrawlType): Event or Series

        Returns:
            the WikiCfP url
        '''
        url=f"{crawlType.urlPrefix}{cfpid}"
        return url

    @classmethod
    def getLatestEvent(cls,debug=False,showProgress=True):
        '''
        get the latest Event doing a binary search
        '''
        wikicfp=WikiCfpEventFetcher(debug,showProgress=showProgress)
        wikicfp.progressCount=0
        wikicfp.getLatesEvetFromPair()

    def getHighestNonDeletedIdInRange(self,fromId:int,toId:int)->int:
        '''
        get the event with the highest id int the range fromId to toId that is not deleted

        Args:
            fromId(int): minimum id to search from
            toId(int): maximum id to search to

        Returns:
            int: maxium id of an event that is not deleted or None if there is none in this range
        '''
        maxId=None
        for eventId in range(fromId,toId+1):
            if self.showProgress:
                print(".",end='',flush=True)
                self.progressCount+=1
                if self.progressCount % 50 == 0:
                    print(flush=True)
            rawEvent=self.fromEventId(eventId)
            if not rawEvent['deleted']:
                maxId=eventId
        return maxId

    def getLatesEvetFromPair(self,low=5000,high=300000,margin=40):
        '''
        get the latest Event doing a binary search

        Args:
            low(int): lower index to search from
            hight(int): upper index boundary
        '''
        if high-low>margin+1:
            mid=(high+low)//2
            midId=(self.getHighestNonDeletedIdInRange(mid-margin, mid))
            if midId:
                return self.getLatesEvetFromPair(mid+1,high)
            else:
                return self.getLatesEvetFromPair(low, mid-1)
        else:
            return mid
        pass

    def fromEventId(self,cfpid:int):
        '''
        see e.g. https://github.com/andreeaiana/graph_confrec/blob/master/src/data/WikiCFPCrawler.py

        Args:
            cfpid(int): the wikicfp id to use
        '''
        url=WikiCfpEventFetcher.getUrl(cfpid,self.crawlType)
        return self.fromUrl(url)

    def rawEventFromWebScrape(self,rawEvent:dict,triples:list,scrape:WebScrape):
        '''
        fill the given rawEvent with the data derived from the scrape

        Args:
            rawEvent(dict): the event dictionary
            triples(list): the triples found
            scrape(WebScrape): the webscrape object to be used for parsing
        '''
        if len(triples)==0:
            #scrape.printPrettyHtml(scrape.soup)
            firstH3=scrape.fromTag(scrape.soup, 'h3')
            if "This item has been deleted" in firstH3:
                rawEvent['deleted']=True
        else:
            self.fromTriples(rawEvent,triples)
            # add series information
            # Tag: International Semantic Web Conference
            m,seriesText=scrape.findLinkForRegexp(r'/cfp/program\?id=([0-9]+).*')
            if m:
                seriesId=m.group(1)
                rawEvent['seriesId']=seriesId
                rawEvent['series']=seriesText
                pass

            if 'summary' in rawEvent:
                rawEvent['acronym']=rawEvent.pop('summary').strip()
            if 'description' in rawEvent:
                rawEvent['title']=rawEvent.pop('description').strip()

    def rawEventSeriesFromWebScrape(self,rawEvent:dict,scrape:WebScrape):
        '''
        fill the given rawEventSeries with the data derived from the scrape

        Args:
            rawEvent(dict): the event dictionary
            scrape(WebScrape): the webscrape object to be used for parsing
        '''
        title=scrape.soup.find("title")
        rawEvent["title"]=title.text.strip()
        dblpM,_text=scrape.findLinkForRegexp(r'http://dblp.uni-trier.de/db/([a-zA-Z0-9/-]+)/index.html')
        if dblpM:
            dblpSeriesId=dblpM.group(1)
            rawEvent['dblpSeriesId']=dblpSeriesId
        pass

    def fromUrl(self,url:str)->dict:
        '''
        get the event form the given url

        Args:
            url(str): the url to get the event from

        Returns:
            dict: a raw event dict or None if an error occured

        '''
        regexp=r"^"+self.crawlType.urlPrefix.replace("?","\?")+"(\d+)$"
        m=re.match(regexp,url)
        if not m:
            raise Exception("Invalid URL %s" % (url))
        else:
            cfpId=int(m.group(1))
        rawEvent={}
        if self.crawlType.value is CrawlType.EVENT.value:
            rawEvent['eventId']=f"{cfpId}"
        elif self.crawlType.value is CrawlType.SERIES.value:
            rawEvent['seriesId']=f"{cfpId}"
        rawEvent['url']=url
        rawEvent['wikiCfpId']=cfpId
        rawEvent['deleted']=False
        scrape=WebScrape(debug=self.debug,timeout=self.timeout)
        triples=scrape.parseRDFa(url)
        if scrape.err:
            raise Exception(f"fromUrl {url} failed {scrape.err}")
        if self.crawlType.value is CrawlType.EVENT.value:
            self.rawEventFromWebScrape(rawEvent, triples, scrape)
        else:
            self.rawEventSeriesFromWebScrape(rawEvent,scrape)
        return rawEvent

__version__ = 0.4
__date__ = '2020-06-22'
__updated__ = '2021-08-09'

DEBUG = 1

def main(argv=None): # IGNORE:C0111
    '''main program.'''

    if argv is None:
        argv=sys.argv[1:]

    program_name = os.path.basename(__file__)
    program_version = "v%s" % __version__
    program_build_date = str(__updated__)
    program_version_message = '%%(prog)s %s (%s)' % (program_version, program_build_date)
    program_shortdesc = "script to read event metadata from http://wikicfp.com"
    user_name="Wolfgang Fahl"
    program_license = '''%s

  Created by %s on %s.
  Copyright 2020-2021 TIB Hannover, Wolfgang Fahl. All rights reserved.

  Licensed under the Apache License 2.0
  http://www.apache.org/licenses/LICENSE-2.0

  Distributed on an "AS IS" basis without warranties
  or conditions of any kind, either express or implied.

USAGE
''' % (program_shortdesc,user_name, str(__date__))

    try:
        # Setup argument parser
        parser = ArgumentParser(description=program_license, formatter_class=RawDescriptionHelpFormatter)
        parser.add_argument("-d", "--debug", dest="debug", action="count", help="set debug level [default: %(default)s]")
        parser.add_argument('-V', '--version', action='version', version=program_version_message)
        parser.add_argument('--startId', type=int, help='eventId to start crawling from', required=True)
        parser.add_argument('--stopId', type=int, help='eventId to stop crawling at', required=True)
        parser.add_argument('--crawlType',type=str,default="Event",help="The crawlType - Event or Series")
        parser.add_argument('-p','--targetPath',type=str,help="targetPath (JSON directory) for crawl results")
        parser.add_argument('-t','--threads', type=int, help='number of threads to start', default=10)

        # Process arguments
        args = parser.parse_args(argv)
        wikiCfp=wcfp.WikiCfp()
        wikiCfpScrape=wikiCfp.wikiCfpScrape
        wikiCfpScrape.jsondir=args.targetPath
        wikiCfpScrape.debug=args.debug
        crawlBatch=CrawlBatch(args.threads, args.startId, args.stopId,args.crawlType,None)
        wikiCfpScrape.threadedCrawl(crawlBatch)

    except KeyboardInterrupt:
        ### handle keyboard interrupt ###
        return 1
    except Exception as e:
        if DEBUG:
            raise(e)
        indent = len(program_name) * " "
        sys.stderr.write(program_name + ": " + repr(e) + "\n")
        sys.stderr.write(indent + "  for help use --help")
        return 2

if __name__ == "__main__":
    if DEBUG:
        sys.argv.append("-d")
    sys.exit(main())


import time
import os.path

def calc_neighberhood(alist, gene_idx, idx_from, idx_to):
    """
    this is a help function to pre_calc_neighborhoods
    :param alist: a synteny list.
    :param gene_idx: index of current gene to get neighborhood for
    :param idx_from: start from which index to include genes inside the neighborhood
    :param idx_to: final index to include genes for neighborhood
    :return: returns a list with whole neighborhood for a specific gene.
    """
    neighberhood = []

    try:
        for idx in range(idx_from, idx_to):
            if alist[idx][0] == alist[gene_idx][0]:
                # make sure only genes from same chromosome are added
                neighberhood.append(int(alist[idx][1]))
    except:
        print("Some error occured in calc_neighberhood.")

    return neighberhood

def pre_calc_neighborhoods(synteny1, synteny2, k):
    """
    This function calculates the neighborhood of size k for all genes in synteny1 and synteny2.
    :param synteny1: synteny list for first genome
    :param synteny2: synteny list for second genome
    :param k: size of neighborhood(#genes up and down from a certain gene in respective synteny file)
    :return: the neighborhoods for all genes in both synteny files
    """
    gene_neighborhoods1 = {}
    gene_neighborhoods2 = {}

    for idx, gene in enumerate(synteny1):
        idx_from1 = max(idx - k, 0)
        idx_to1 = min(idx + k + 1, len(synteny1))

        neighborhood = calc_neighberhood(synteny1, idx, idx_from1, idx_to1)
        gene_neighborhoods1[gene[1]] = neighborhood

    for idx, gene in enumerate(synteny2):
        idx_from2 = max(idx - k, 0)
        idx_to2 = min(idx + k + 1, len(synteny2))

        neighborhood = calc_neighberhood(synteny2, idx, idx_from2, idx_to2)
        gene_neighborhoods2[gene[1]] = neighborhood

    return gene_neighborhoods1, gene_neighborhoods2

def data_preprocessing(querySyntenyFile1, querySyntenyFile2):
    """
    This function makes sure no duplicates genes exist within a genome, and also gives indexes the remaining genes for future usage.
    :param querySyntenyFile1: input Synteny file for first genome.
    :param querySyntenyFile2: input Synteny file for second genome.
    :return: returns a dictionary with an index for a certain gene to be able to convert string gene identifier to an integer for efficiency(both memory and speed).
    """

    indexing = {}

    t = time.process_time()
    f = open('./Data/genome1_genes.txt', 'w')
    g = open('./Data/genome2_genes.txt', 'w')

    genome1 = set()
    genome2 = set()
    count = 1

    # indexing first genome
    with open('./Data/' + querySyntenyFile1, 'r') as file:
        for lines in file:
            lines = lines.split()
            if len(lines) == 3:
                if lines[2] in genome1:
                    pass
                else:
                    f.write(lines[0] + "\t" + str(count) + "\t" + lines[2] + "\n")
                    indexing[lines[2]] = count
                    count += 1
                    genome1.add(lines[2])
    f.close()

    # indexing second genome
    with open('./Data/' + querySyntenyFile2, 'r') as file:
        for lines in file:
            lines = lines.split()
            if len(lines) == 3:
                if lines[2] in genome2:
                    pass
                else:
                    g.write(lines[0] + "\t" + str(count) + "\t" + lines[2] + "\n")
                    indexing[lines[2]] = count
                    count += 1
                    genome2.add(lines[2])
    g.close()
    elapsed_time = time.process_time() - t
    print("time to preprocess query files: ", elapsed_time, " seconds.")
    return indexing

def synteny_score(querySyntenyFile1, querySyntenyFile2, NC_scores, k):
    """
    This is the main function for calculating SyS scores.
    :param querySyntenyFile1: synteny file for first genome
    :param querySyntenyFile2: synteny file for reference genome
    :param NC_scores: dictionary consisting of nc scores for genes
    :param k: size of neighborhood for a gene
    :return: returns a dictionary to convert string gene identifier to respective integer gene identifier.
    """
    sys_file_exist = os.path.isfile('./Data/sys.txt')

    indexing_dict = data_preprocessing(querySyntenyFile1, querySyntenyFile2)

    # if this has been calculated already, then just load that file.
    if sys_file_exist:
        # sys file already exist.
        print("sys file already exist. Either move or delete the existing sys.txt in th Data folder. Moving on to next module.")
        return indexing_dict
    else:
        # no sys file exist so create and calculate sys values and write them to file.
        Synteny1 = []

        # load a list of genes from first genome
        with open('./Data/genome1_genes.txt', 'r') as file:
            for lines in file:
                lines = lines.split()
                del lines[2]
                lines[1] = int(lines[1])
                Synteny1.append(lines)

        print("SyntenyFile1 finished loading.")
        print("Synteny1 has: ", len(Synteny1), " genes.\n")

        Synteny2 = []

        # load a list of genes from first genome
        with open('./Data/genome2_genes.txt', 'r') as file:
            for lines in file:
                lines = lines.split()
                del lines[2]
                lines[1] = int(lines[1])
                Synteny2.append(lines)

        print("SyntenyFile2 finished loading.")
        print("Synteny2 has: ", len(Synteny2), " genes.\n")

        # load NC file into a dict.
        print("start loading NC_pairs...")
        t1 = time.process_time()
        nc_dict = {}
        with open('./Data/' + NC_scores, 'r') as file:
            for lines in file:
                lines = lines.split()

                try:
                    nc_dict[(indexing_dict[lines[0]], indexing_dict[lines[1]])] = float(lines[2])
                except:
                    # data mismatch between nc and rest of program(most likely genes in nc not found in synteny) so ignore those entries.
                    pass

        print("finished loading # of NC_pairs: ", len(nc_dict))

        elapsed_time = time.process_time() - t1
        print("seconds to load NC-data: ", elapsed_time)

        # pre-calc neighborhoods to reduce runtime
        print("Starting pre-calc for SyS...")
        t = time.process_time()
        synteny1_neighbors, synteny2_neighbors = pre_calc_neighborhoods(Synteny1, Synteny2, k)

        elapsed_time = time.process_time() - t
        print("Finished pre-calc SyS, took: ", elapsed_time)

        print("Starting SyS calculation...")
        f = open('./Data/sys.txt', 'a')
        t1 = time.process_time()

        # calculate genome1 * genome2 genepairs
        for gene1 in Synteny1:
            for gene2 in Synteny2:

                neighborhood1 = synteny1_neighbors[gene1[1]]
                neighborhood2 = synteny2_neighbors[gene2[1]]

                nc_max = 0

                for a in neighborhood1:
                    for b in neighborhood2:
                        if (a, b) in nc_dict:
                            nc_max = max(nc_dict[(a, b)], nc_max)

                if nc_max > 0:
                    f.write(str(gene1[1]) + "\t" + str(gene2[1]) + "\t" + str(nc_max) + "\n")
                    #SyS_values[(gene1[1], gene2[1])] = nc_max

        elapsed_time = time.process_time() - t1
        print("genepairs synteny1*synteny2 took: ", elapsed_time)

        t2 = time.process_time()
        # calculate genome1 * genome1 genepairs.
        for idx, el in enumerate(Synteny1):
            neighborhood1 = synteny1_neighbors[el[1]]
            for idxx in range(idx + 1, len(Synteny1)):
                neighborhood2 = synteny1_neighbors[Synteny1[idxx][1]]

                nc_max = 0

                for a in neighborhood1:
                    for b in neighborhood2:
                        if (a, b) in nc_dict:
                            nc_max = max(nc_dict[(a, b)], nc_max)

                if nc_max > 0:
                    f.write(str(el[1]) + "\t" + str(Synteny1[idxx][1]) + "\t" + str(nc_max) + "\n")
                    #SyS_values[(el[1], Synteny1[idxx][1])] = nc_max
        elapsed_time = time.process_time() - t2
        print("genepairs synteny1*synteny1 took: ", elapsed_time)

        t3 = time.process_time()
        # calculate genome2 * genome2 genepairs
        for idx, el in enumerate(Synteny2):
            neighborhood1 = synteny2_neighbors[el[1]]
            for idxx in range(idx + 1, len(Synteny2)):
                neighborhood2 = synteny2_neighbors[Synteny2[idxx][1]]

                nc_max = 0

                for a in neighborhood1:
                    for b in neighborhood2:
                        if (a, b) in nc_dict:
                            nc_max = max(nc_dict[(a, b)], nc_max)

                if nc_max > 0:
                    f.write(str(el[1]) + "\t" + str(Synteny2[idxx][1]) + "\t" + str(nc_max) + "\n")
                    #SyS_values[(el[1], Synteny2[idxx][1])] = nc_max
        f.close()
        elapsed_time = time.process_time() - t3
        print("genepairs synteny2*synteny2 took: ", elapsed_time)

        elapsed_time = time.process_time() - t
        print("SyS calculation complete. Whole module took: ", elapsed_time)

    return indexing_dict


import logging
import json
from datetime import datetime
import time
from resilient_circuits import ResilientComponent, function, handler, StatusMessage, FunctionResult, FunctionError
from fn_cisco_amp4ep.lib.amp_client import Ampclient
from fn_cisco_amp4ep.lib.helpers import validate_opts, validate_params
from fn_cisco_amp4ep.lib.amp_ratelimit import AmpRateLimit

RATE_LIMITER = AmpRateLimit()

class FunctionComponent(ResilientComponent):
    """Component that implements Resilient function 'fn_amp_get_groups' of package fn_cisco_amp4ep.
    The function can be used to query to get information on groups or individual group by group_guid.

    The Function takes the following parameter:
        amp_group_guid, amp_group_name, amp_limit

        An example of a set of query parameter might look like the following:
                amp_group_guid = None
                amp_group_name = None
                amp_limit      = None

    The function will execute a REST api get request against a Cisco AMP for endpoints server and returns a result in
    JSON format similar to the following.

    {
      "input_params": {"group_guid": null, "limit": null, "name": null},
      "response": {
        "version": "v1.2.0",
        "data": [
          {
            "source": null,
            "guid": "abcd1234-a123-b456-c769-abcdef123456",
            "name": "Audit",
            "links": {
              "group": "https://api.amp.cisco.com/v1/groups/abcd1234-a123-b456-c769-abcdef123456"
            },
            "description": "Test Audit Group 1"
          },
          {
            "source": null,
            "guid": "abcd1234-a123-b456-c769-abcdef123457",
            "name": "Domain Controller",
            "links": {
              "group": "https://api.amp.cisco.com/v1/groups/abcd1234-a123-b456-c769-abcdef123457"
            },
            "description": "Test Domain Controller Group 2"
          },
          {
            "source": null,
            "guid": "abcd1234-a123-b456-c769-abcdef123458",
            "name": "Protect",
            "links": {
              "group": "https://api.amp.cisco.com/v1/groups/abcd1234-a123-b456-c769-abcdef123458"
            },
            "description": "Test Protect Group 3"
          },
          ...
        ],
        "metadata": {
          "results": {
            "index": 0,
            "total": 5,
            "items_per_page": 500,
            "current_item_count": 5
          },
          "links": {
            "self": "https://api.amp.cisco.com/v1/groups"
          }
        }
      },
      "query_execution_time": "2018-10-08 12:49:38"
    }
    """
    def __init__(self, opts):
        """constructor provides access to the configuration options"""
        super(FunctionComponent, self).__init__(opts)
        self.options = opts.get("fn_cisco_amp4ep", {})
        validate_opts(self)

    @handler("reload")
    def _reload(self, event, opts):
        """Configuration options have changed, save new values"""
        self.options = opts.get("fn_cisco_amp4ep", {})
        validate_opts(self)

    @function("fn_amp_get_groups")
    def _fn_amp_get_groups_function(self, event, *args, **kwargs):
        """Function: Returns information on group or groups."""
        try:
            # Get the function parameters:
            amp_group_guid = kwargs.get("amp_group_guid")  # text
            amp_group_name = kwargs.get("amp_group_name")  # text
            amp_limit = kwargs.get("amp_limit")  # number

            log = logging.getLogger(__name__)
            log.info("amp_group_guid: %s", amp_group_guid)
            log.info("amp_group_name: %s", amp_group_name)
            log.info("amp_limit: %s", amp_limit)

            yield StatusMessage("Running Cisco AMP get groups query...")

            params = {"group_guid": amp_group_guid, "name": amp_group_name, "limit": amp_limit}

            validate_params(params)

            amp = Ampclient(self.options, RATE_LIMITER)

            rtn = amp.get_paginated_total(amp.get_groups, **params)
            query_execution_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            # Add in "query_execution_time" and "ip_address" to result to facilitate post-processing.
            results = {"response": rtn, "query_execution_time": query_execution_time, "input_params": params}
            param_name = None if params["name"] is None else params["name"].encode('utf-8')
            yield StatusMessage("Returning 'group' or 'groups' results for group guid '{}', group name '{}' and limit '{}'"
                                .format(params["group_guid"], param_name, params["limit"]))

            log.debug(json.dumps(results))
            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            log.exception("Exception in Resilient Function for Cisco AMP for endpoints.")
            yield FunctionError()


#!/usr/bin/env python3
"""
Autor : Patrick Ion <10758912@iespenyagolosa.com>
Fecha   : 31/10/22
Propósito: Calculo de areas
"""

print('Calculo de áreas - Elige una figura geométrica: ')
print('a) Triangulo')
print('b) Circulo')
Figura=input('Que figura quieres calcular (escribe T o C)?').upper()
if Figura=='T':
    base=float(input('Escribe la base: '))
    altura=float(input('Escribe la altura:'))
    area=base*altura/2
    print(f'Un triangulo de base {base} y altura {altura} tiene una área de {area}')
elif Figura=='C':
    radio=float(input('Escribe el radio: '))
    area=3.14*radio**2
    print(f'Un circulo de radio {radio} tiene un área de {area}')


import random
import copy

#conventions
UP = 'w'
FRONT = 'b'
DOWN = 'y'
RIGHT = 'o'
LEFT = 'r'
BACK = 'g'

#Indexes for edges
EDGETOP, EDGERIGHT, EDGEDOWN, EDGELEFT = 1, 5, 7, 3

#Indexes for corners
CORNERTOPLEFT, CORNERTOPRIGHT, CORNERDOWNRIGHT, CORNERDOWNLEFT = 0, 2, 8, 6

#Solved cube dict
SOLVEDCUBEDICT = {'w': {'up': 'g', 'right': 'o', 'down': 'b', 'left': 'r', 'colors': ['w', 'w', 'w', 'w', 'w', 'w', 'w', 'w', 'w']}, 'y': {'up': 'b', 'right': 'o', 'down': 'g', 'left': 'r', 'colors': ['y', 'y', 'y', 'y', 'y', 'y', 'y', 'y', 'y']}, 'b': {'up': 'w', 'right': 'o', 'down': 'y', 'left': 'r', 'colors': ['b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b']}, 'g': {'up': 'w', 'right': 'r', 'down': 'y', 'left': 'o', 'colors': ['g', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g']}, 'o': {'up': 'w', 'right': 'g', 'down': 'y', 'left': 'b', 'colors': ['o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o']}, 'r': {'up': 'w', 'right': 'b', 'down': 'y', 'left': 'g', 'colors': ['r', 'r', 'r', 'r', 'r', 'r', 'r', 'r', 'r']}}

class Cube:

    def __init__(self, cubeDict=SOLVEDCUBEDICT):
        """
        Constructor
        Parameter: cubeDict (solved cube by default)
        cubeDict should be in the following format
        {'color of face 1': {'up': 'color up', 'right': 'color right', 'down': 'color down',
        'left': 'color left', 'colors': ['color0', 'color2', ..., 'color8']}, ...}

        The folors in the 'colors field are in the following order'\n
                up \n
                012 \n
        left    345     right \n
                678 \n
                down \n

        All faces adjacent to UP, must have UP at its up direction
        """
        self.cubeDict = copy.deepcopy(cubeDict) #dict representing cube
        self.movesDone = []      #moves done to this cube

        self.up, self.down, self.right, self.left, self.front, self.back = UP, DOWN, RIGHT, LEFT, FRONT, BACK

    def rotate(self, faceColor, mode=""):
        """
            Rotate specified face clock wise (mode = ""), counter clockwise (mode = p) or twice (mode=2)

            Example (clockwise)\n
             u            l  \n
            012          630 \n
          l 345 r  ->  d 741 u\n
            678          852 \n
             d            r  \n
        """
        face = self.cubeDict[faceColor]
        curColors = face['colors']

        #Rotate faces
        newColors = [None]*9
        if mode == "":
            newColors[0] = curColors[6]
            newColors[1] = curColors[3]
            newColors[2] = curColors[0]
            newColors[3] = curColors[7]
            newColors[4] = curColors[4]
            newColors[5] = curColors[1]
            newColors[6] = curColors[8]
            newColors[7] = curColors[5]
            newColors[8] = curColors[2]
        elif mode == "p":
            newColors[0]= curColors[2]
            newColors[1] = curColors[5]
            newColors[2] = curColors[8]
            newColors[3] = curColors[1]
            newColors[4] = curColors[4]
            newColors[5] = curColors[7]
            newColors[6] = curColors[0]
            newColors[7] = curColors[3]
            newColors[8] = curColors[6]
        elif mode == "2":
            newColors[0]= curColors[8]
            newColors[1] = curColors[7]
            newColors[2] = curColors[6]
            newColors[3] = curColors[5]
            newColors[4] = curColors[4]
            newColors[5] = curColors[3]
            newColors[6] = curColors[2]
            newColors[7] = curColors[1]
            newColors[8] = curColors[0]

        face['colors'] = newColors

        #rotate pieces in adjacent faces
        #indexes of pieces that need to be rotated, in clockwise order (from rotating face's perspective)
        adjIndexes = {}
        #colors of adj pieces
        adjPieces = {}
        for direction in ['up', 'right', 'down', 'left']:
            adjColor = face[direction]
            adjFace = self.cubeDict[adjColor]

            if adjFace['up'] == faceColor: adjIndexes[direction] = [2, 1, 0]
            elif adjFace['right'] == faceColor: adjIndexes[direction] = [8, 5, 2]
            elif adjFace['down'] == faceColor: adjIndexes[direction] = [6, 7, 8]
            elif adjFace['left'] == faceColor: adjIndexes[direction] = [0, 3, 6]

            adjPieces[direction] = [adjFace['colors'][adjIndexes[direction][0]], adjFace['colors'][adjIndexes[direction][1]], adjFace['colors'][adjIndexes[direction][2]]]

        if mode == "":
            adjPieces['up'], adjPieces['right'], adjPieces['down'], adjPieces['left'] = adjPieces['left'], adjPieces['up'], adjPieces['right'], adjPieces['down']
        elif mode == "p":
            adjPieces['up'], adjPieces['right'], adjPieces['down'], adjPieces['left'] = adjPieces['right'], adjPieces['down'], adjPieces['left'], adjPieces['up']
        elif mode == "2":
            adjPieces['up'], adjPieces['right'], adjPieces['down'], adjPieces['left'] = adjPieces['down'], adjPieces['left'], adjPieces['up'], adjPieces['right']

        for direction in ['up', 'right', 'down', 'left']:
            adjColor = face[direction]
            adjFaceColors = self.cubeDict[adjColor]['colors']
            indexes = adjIndexes[direction]

            adjFaceColors[indexes[0]], adjFaceColors[indexes[1]], adjFaceColors[indexes[2]] = adjPieces[direction][0], adjPieces[direction][1], adjPieces[direction][2]

        self.movesDone.append(faceColor+mode) #add cur move to moves done

    def __str__(self):
        """
        String representation of the cube
        """
        s = ""
        for face in ['w', 'y', 'b', 'g', 'r', 'o']:
            leftSpacing = 10
            left = self.cubeDict[face]['left']
            right = self.cubeDict[face]['right']
            s += face + "--------\n"
            s += "                     " + self.cubeDict[face]['up'] + "\n"
            s += " "*leftSpacing + str(self.cubeDict[face]['colors'][0:3]) + "\n"
            s += left + " "*(leftSpacing - len(left))+ str(self.cubeDict[face]['colors'][3:6]) + "    " + right +"\n"
            s += " "*leftSpacing + str(self.cubeDict[face]['colors'][6:9]) + "\n"
            s += "                     " + self.cubeDict[face]['down'] + "\n"*2
        return s

    def getState(self):
        """
        Short way for representing cube state
        """
        s = "".join(self.cubeDict["w"]["colors"])
        s += "".join(self.cubeDict["y"]["colors"])
        s += "".join(self.cubeDict["b"]["colors"])
        s += "".join(self.cubeDict["g"]["colors"])
        s += "".join(self.cubeDict["r"]["colors"])
        s += "".join(self.cubeDict["o"]["colors"])
        return s

    def changePerspective(self, up=UP, down= DOWN, left= LEFT, right=RIGHT, front = FRONT, back= BACK):
        """
        Change perspective from which cube is seen
        """
        self.up = up
        self.down = down
        self.left = left
        self.right = right
        self.front = front
        self.back = back

    def f(self, mode=""):
        """
        Rotate front face
        mode="" (default): clockwise, mode="2": twice, mode="p": counterclockwise
        """
        self.rotate(self.front, mode=mode)

    def u(self, mode=""):
        self.rotate(self.up, mode=mode)

    def r(self, mode=""):
        self.rotate(self.right, mode=mode)

    def d(self, mode=""):
        self.rotate(self.down, mode=mode)

    def l(self, mode=""):
        self.rotate(self.left, mode=mode)

    def b(self, mode=""):
        self.rotate(self.back, mode=mode)

    def clearMoves(self):
        """
        Reset array with moves
        """
        self.movesDone = []

    def undoMoves(self, n):
        """
        undo the last n moves
        if n > moves done, it will simply stop
        """
        counter = 0
        while len(self.movesDone) > 0 and counter < n:
            move = self.movesDone.pop()
            face, mode = move[0], ""
            if move[-1] == '2': mode = '2'
            elif move[-1] == 'p': pass
            else: mode = 'p'

            self.rotate(face, mode)
            self.movesDone.pop()
            counter += 1

    def isSolved(self):
        """
        Returns True if cube is solved
        """
        return self.getState() == "wwwwwwwwwyyyyyyyyybbbbbbbbbgggggggggrrrrrrrrrooooooooo"

    def scramble(self, moves='random', clear=True, seed=None):
        """
        Scramble cube.
        moves = number of moves to use, if it is 'random', then it chooses a random nuumber
        between 100 and 200
        clear = Boolean, whether you want to clear all moves done to cube from movesDone list
        """
        if seed != None: random.seed(seed)
        if moves == 'random': moves = random.randint(100, 200)
        for i in range(moves):
            face = random.choice(['w', 'y', 'b', 'g', 'r', 'o'])
            mode = random.choice(["", "p", "2"])
            self.rotate(face, mode)

        if clear: self.movesDone = []

    def getAdjFaceColor(self, faceColor, index):

        """
        Return color(s) of adjacent face to given face and index at counter clockwise order
        """

        if index == EDGERIGHT: return self.cubeDict[faceColor]['right']
        elif index == EDGETOP: return self.cubeDict[faceColor]['up']
        elif index == EDGELEFT: return self.cubeDict[faceColor]['left']
        elif index == EDGEDOWN: return self.cubeDict[faceColor]['down']
        elif index == CORNERTOPLEFT: return self.cubeDict[faceColor]['up'], self.cubeDict[faceColor]['left']
        elif index == CORNERDOWNLEFT: return self.cubeDict[faceColor]['left'], self.cubeDict[faceColor]['down']
        elif index == CORNERDOWNRIGHT: return self.cubeDict[faceColor]['down'], self.cubeDict[faceColor]['right']
        elif index == CORNERTOPRIGHT : return self.cubeDict[faceColor]['right'], self.cubeDict[faceColor]['up']
        else: raise Exception

    def getEdgeOther(self, faceColor, index):
        """
        Gets the other color of the small edge cube with seleceted face index
        """
        if index > 7 or index < 1 or index % 2 == 0: raise Exception

        adjFaceColor = None
        if index == EDGERIGHT: adjFaceColor = self.cubeDict[faceColor]['right']
        elif index == EDGEDOWN: adjFaceColor = self.cubeDict[faceColor]['down']
        elif index == EDGELEFT: adjFaceColor = self.cubeDict[faceColor]['left']
        elif index == EDGETOP: adjFaceColor = self.cubeDict[faceColor]['up']
        else: raise Exception

        adjFace = self.cubeDict[adjFaceColor]
        if adjFace['right'] == faceColor: return adjFace['colors'][EDGERIGHT]
        elif adjFace['up'] == faceColor: return adjFace['colors'][EDGETOP]
        elif adjFace['left'] == faceColor: return adjFace['colors'][EDGELEFT]
        elif adjFace['down'] == faceColor: return adjFace['colors'][EDGEDOWN]
        else: raise Exception

    def getCornerOther(self, faceColor, index):
        """
        Gets the other colors of small corner cube with selected index.
        Returns colors in counter clockwise direction from face perspective
        """
        if index > 8 or index < 0 or index % 2 == 1: raise Exception

        adjFaceColor1, adjFaceColor2 = None, None
        if index == CORNERTOPLEFT: adjFaceColor1, adjFaceColor2 = self.cubeDict[faceColor]['up'], self.cubeDict[faceColor]['left']
        elif index == CORNERDOWNLEFT: adjFaceColor1, adjFaceColor2 = self.cubeDict[faceColor]['left'], self.cubeDict[faceColor]['down']
        elif index == CORNERDOWNRIGHT: adjFaceColor1, adjFaceColor2 = self.cubeDict[faceColor]['down'], self.cubeDict[faceColor]['right']
        elif index == CORNERTOPRIGHT: adjFaceColor1, adjFaceColor2 = self.cubeDict[faceColor]['right'], self.cubeDict[faceColor]['up']
        else: raise Exception

        ###
        color1, color2 = None, None
        adjFace1, adjFace2 = self.cubeDict[adjFaceColor1], self.cubeDict[adjFaceColor2]
        if adjFace1['left'] == faceColor: color1 = adjFace1['colors'][CORNERTOPLEFT]
        elif adjFace1['up'] == faceColor: color1 = adjFace1['colors'][CORNERTOPRIGHT]
        elif adjFace1['right'] == faceColor: color1 = adjFace1['colors'][CORNERDOWNRIGHT]
        elif adjFace1['down'] == faceColor: color1 = adjFace1['colors'][CORNERDOWNLEFT]
        else: raise Exception

        if adjFace2['left'] == faceColor: color2 = adjFace2['colors'][CORNERDOWNLEFT]
        elif adjFace2['up'] == faceColor: color2 = adjFace2['colors'][CORNERTOPLEFT]
        elif adjFace2['right'] == faceColor: color2 = adjFace2['colors'][CORNERTOPRIGHT]
        elif adjFace2['down'] == faceColor: color2 = adjFace2['colors'][CORNERDOWNRIGHT]
        else: raise Exception

        return color1, color2

    def isLayerCorrect(self, faceColor, index):
        """
        Returns True if piece is in the correct layer
        """
        if index in [EDGETOP, EDGERIGHT, EDGEDOWN, EDGELEFT]:
            return self.cubeDict[faceColor]['colors'][index] == faceColor or self.getEdgeOther(faceColor, index) == faceColor
        elif index in [CORNERTOPLEFT, CORNERTOPRIGHT, CORNERDOWNRIGHT, CORNERDOWNLEFT]:
            otherColor1, otherColor2 = self.getCornerOther(faceColor, index)
            return faceColor in [self.cubeDict[faceColor]['colors'][index], otherColor1, otherColor2]
        else: return False

    def isPositionCorrect(self, faceColor, index):
        """
        Returns True if position of piece is correct
        """
        if not self.isLayerCorrect(faceColor, index): return False

        ####
        if index in [EDGETOP, EDGERIGHT, EDGEDOWN, EDGELEFT]:
            otherColor = self.getAdjFaceColor(faceColor, index)
            return self.cubeDict[faceColor]['colors'][index] == otherColor or self.getEdgeOther(faceColor, index) == otherColor

        elif index in [CORNERTOPLEFT, CORNERTOPRIGHT, CORNERDOWNRIGHT, CORNERDOWNLEFT]:
            color1, color2 = self.getCornerOther(faceColor, index)
            adjColor1, adjColor2 = self.getAdjFaceColor(faceColor, index)
            return color1 in [faceColor, adjColor1, adjColor2] and color2 in [faceColor, adjColor1, adjColor2] and self.cubeDict[faceColor]['colors'][index] in [faceColor, adjColor1, adjColor2]
        else: raise Exception

    def isPieceCorrect(self, faceColor, index):
        """
        Returns True if the piece corresponding to given color and index is in the correct position and orientation
        """
        return self.isPositionCorrect(faceColor, index) and self.isOrientationCorrect(faceColor, index)

    def isCornerPieceCorrect(self, color1, color2, color3):
        """
        Returns True if corner piece with given colors is correct
        """
        faceColor, index = self.findCorner(color1, color2, color3)
        return self.isPieceCorrect(faceColor, index)

    def isEdgePieceCorrect(self, color1, color2):
        """
        Returns True if edge with specified colors is in correct postion and orientation
        """
        faceColor, index = self.findEdge(color1, color2)
        return self.isPieceCorrect(faceColor, index)

    def isOrientationCorrect(self, faceColor, index):
        """
        Returns True if orientation of piece is correct
        """
        return self.cubeDict[faceColor]['colors'][index] == faceColor

    def findEdge(self, color1, color2):
        """
        Returns face color and index of color 1 of edge piece with color1 and color2
        """
        for color in [UP, DOWN, FRONT, BACK, RIGHT, LEFT]:
            for  index in [EDGEDOWN, EDGETOP, EDGELEFT, EDGERIGHT]:
                if self.cubeDict[color]['colors'][index] == color1:
                    if self.getEdgeOther(color, index) == color2:
                        return color, index
        raise Exception

    def findCorner(self, color1, color2, color3):
        """
        Returns face color and index of color 1 of corner piece with color1, color2 and color3
        """
        for color in [UP, DOWN, FRONT, BACK, RIGHT, LEFT]:
            for  index in [CORNERDOWNLEFT, CORNERDOWNRIGHT, CORNERTOPLEFT, CORNERTOPRIGHT]:
                if self.cubeDict[color]['colors'][index] == color1:
                    otherColor1, otherColor2 = self.getCornerOther(color, index)
                    if color2 in [otherColor1, otherColor2] and color3 in [otherColor1, otherColor2]:
                        return color, index
        raise Exception


from os import system

def book(*files):
    for file in files:
        with open(file, 'r') as f:
            for line in f:
                yield line

def read_chapter(next_line, book_lines):
    while next_line[0] != '#':
        print(next_line.strip())
        next_line = next(book_lines)
    system("""bash -c 'read -s -n 1 -p "Press space to continue..."'""")
    print()
    print(next_line)
    read_chapter(next(book_lines), book_lines)

def book_reader(*files):
    book_lines = book(*files)
    print(next(book_lines))
    next_line = next(book_lines)
    try:
        read_chapter(next_line, book_lines)
    except StopIteration:
        pass


import clr
clr.AddReference("RevitAPI")
from Autodesk.Revit import DB

from abc import ABCMeta
from exceptions import TypeValidationException

_app = __revit__.Application  # noqa

class RevitVersion(object):
    _rvt_version = _app.VersionNumber
    _as_integer = int(_rvt_version)

    @classmethod
    def as_integer(cls):
        return cls._as_integer

class AbstractUnitType(object):
    """
    Abstract unit types across Revit versions.
    The type of physical quantity to be measured, for example length or force.

    since 2014: `DB.UnitType`\n
    since 2021: `DB.SpecTypeId`
    """
    pass

class AbstractUnitOfMeasure(object):
    """
    Abstract units of measure across Revit versions.

    since 2014: `DB.DisplayUnitType`\n
    since 2021: `DB.UnitTypeId`
    """

class _CrossVersionRevitObject(object):
    """
    Base class for utils that help to bypass changes in methods
    across different versions of Revit API.
    """

    __metaclass__ = ABCMeta
    _rvt_ver_num = RevitVersion.as_integer()

    def _validate_type(self, obj, expected_type, err_msg=None):
        # type: (object, type, str) -> None
        if not isinstance(obj, expected_type):
            raise TypeValidationException(
                message=err_msg,
                expected_type=expected_type,
                provided_type=type(obj)
            )

class _Definition(_CrossVersionRevitObject):
    def __init__(self, definition):
        # type: (DB.Definition) -> None
        self._validate_type(definition, DB.Definition)
        self._definition = definition

    def get_unit_type(self):
        # type: () -> AbstractUnitType | DB.UnitType | DB.SpecTypeId
        if self._rvt_ver_num < 2021:
            return self._definition.UnitType

        if self._rvt_ver_num < 2022:
            return self._definition.GetSpecTypeId()

        return self._definition.GetDataType()

class _Parameter(_CrossVersionRevitObject):
    def __init__(self, parameter):
        # type: (DB.Parameter) -> None
        self._validate_type(parameter, DB.Parameter)
        self._parameter = parameter

    def get_units_of_measure(self):
        # type: () -> AbstractUnitOfMeasure
        if self._rvt_ver_num < 2021:
            return self._parameter.DisplayUnitType
        return self._parameter.GetUnitTypeId()

class _FormatOptions(_CrossVersionRevitObject):
    def __init__(self, format_opts):
        # type: (DB.FormatOptions) -> None
        self._validate_type(format_opts, DB.FormatOptions)
        self._format_opts = format_opts

    def get_units_of_measure(self):
        # type: (DB.FormatOptions) -> AbstractUnitOfMeasure
        if self._rvt_ver_num < 2021:
            return self._format_opts.DisplayUnits
        return self._format_opts.GetUnitTypeId()

class _UnitUtils(_CrossVersionRevitObject):
    def __init__(self):
        self._unit_utils = DB.UnitUtils

    def is_unit_of_measure(self, unit_of_measure):
        # type: (AbstractUnitOfMeasure) -> bool
        if self._rvt_ver_num < 2021:
            return self._unit_utils.IsValidDisplayUnit(unit_of_measure)
        return self._unit_utils.IsUnit(unit_of_measure)

    def is_valid_unit_of_measure(self, unit_type, unit_of_measure):
        # type: (AbstractUnitType, AbstractUnitOfMeasure) -> bool
        if self._rvt_ver_num < 2021:
            return self._unit_utils.IsValidDisplayUnit(
                unit_type,
                unit_of_measure
            )
        return self._unit_utils.IsValidUnit(unit_type, unit_of_measure)

def definition(definition):
    # type: (DB.Definition) -> _Definition
    return _Definition(definition)

def format_options(format_opts):
    # type: (DB.FormatOptions) -> _FormatOptions
    return _FormatOptions(format_opts)

def parameter(parameter):
    # type: (DB.Parameter) -> _Parameter
    return _Parameter(parameter)

unit_utils = _UnitUtils()


import numpy as np
import pandas as pd
from scipy.special import comb
#from tslearn import metrics

# Given a mixture and a sorted list of genes, check if expression levels are in the same order.
# Provide a score for each gene.
def spearman_rank(set_1, set_2):
    # order the sets
    set_1_ord = sorted(set_1)
    set_2_ord = sorted(set_2)

    # append relevant rank to each value in set
    set_1_ranked = []
    set_2_ranked = []
    d = []

    for i in range(len(set_1)):
        set_1_ranked.append([set_1[i], set_1_ord.index(set_1[i]) + 1])
        set_2_ranked.append([set_2[i], set_2_ord.index(set_2[i]) + 1])
        d.append(set_1_ranked[i][1] - set_2_ranked[i][1])

    d_sq = [i**2 for i in d]
    # Indices of smallest elements.
    indices_small = np.argsort(d_sq)[0:round(0.9 * len(set_2))]
    sum_d_sq = sum(d_sq)
    # calculate n^3 - n
    n_cu_min_n = len(set_1)**3 - len(set_1)
    r = 1 - ((6.0 * sum_d_sq) / n_cu_min_n)  # Spearman rank.
    return indices_small, round(r, 2)

# Set_1 should be the pure expression values.
def kendall_rank(set_1, set_2):
    dist = np.zeros(len(set_1))
    for i in range(len(set_1)):
        for j in range(i, len(set_1)):
            if ((set_1[i] <= set_1[j]) and (set_2[i] > set_2[j])) or ((set_1[i] > set_1[j]) and (set_2[i] <= set_2[j])):
                dist[j] += 1
                dist[i] += 1
    indices_small = np.argsort(dist)[0:round(0.8 * len(set_2))]
    rank = dist.sum() / comb(len(set_1), 2, exact=True)
    return indices_small, round(rank, 2)

def dtw_rank(set_1, set_2):
    dist = np.zeros(len(set_1))
    for i in range(len(set_1)):
        set_1_temp = np.delete(set_1.values, i)
        set_2_temp = np.delete(set_2, i)
        P = np.array([set_1_temp, np.arange(0, len(set_1_temp))])
        Q = np.array([set_1_temp, np.arange(0, len(set_2_temp))])
        dist[i] = metrics.dtw(P, Q)
    indices_large = np.argsort(dist)[len(dist):-round(0.9 * len(dist)) - 1:-1]
    return indices_large, 0

# This function does a gene differentiation analysis. It finds for each cell types its majorodizing genes.
# We also get a factor of how much (strongly majordizing) each gene is.
def gene_diff(pure, mix):
    num_genes_per_cell = 80
    gene_list_per_cell = {}
    # Loop on all cells
    for cell_type in pure:
        gene_list = []
        data = pure.copy()
        for factor in range(100, 1, -1):  # 2
            data2 = data.copy()
            data2['max_val'] = data.drop(cell_type, axis=1).max(axis=1)
            data2['med_val'] = data.drop(cell_type, axis=1).median(axis=1)
            data2 = data2.loc[(data2[cell_type] > 0.5 * factor * data2.max_val) & (data2.max_val > 0)]  # Ignore cases where all other mixes are zeros.
            # data2 = data2.loc[(data2[cell_type] > 0.5 * factor * data2.max_val) & (data2.med_val > 0)]  # Ignore cases where all other mixes are zeros.
            if (len(data2) == 0):
                continue
            data2.sort_values(cell_type, inplace=True, ascending=False)
            #gene_list += list(zip(data2.index, [factor for i in range(len(data2.index))]))
            gene_list += list(data2.index)
            data.drop(data2.index, inplace=True)
            if (len(gene_list) > num_genes_per_cell):
                gene_list = gene_list[0:num_genes_per_cell]
                break

        # Select maximum correlated genes (max(mix columns) agrees with pure).
        #max_col = mix.loc[gene_list].sum().idxmax()
        #mix_column = mix[max_col].loc[gene_list]
        #indices_small, score = kendall_rank(pure[cell_type].loc[gene_list], mix_column.values)
        #gene_list = [gene_list[i] for i in indices_small]

        #print(f'{cell_type} : {gene_list}')
        if (len(gene_list) < 8):
            print(f'WARNING: {cell_type} short differential gene list, length {len(gene_list)}.')
        gene_list_per_cell[cell_type] = gene_list

    gene_list_df = pd.DataFrame(dict([(k, pd.Series([i for i in v])) for k, v in gene_list_per_cell.items()]), columns=pure.columns)
    #factor_list = pd.DataFrame(dict([(k, pd.Series([j * 0.5 for (i, j) in v])) for k, v in gene_list_per_cell.items()]), columns=pure.columns)

    #import IPython
    # IPython.embed()
    # for cell in cells:
    #  print(cell, ',', *[x for x in gene_list_df[cell].values.tolist() if str(x) != 'nan'])
    return gene_list_df


import typing as ty
from functools import reduce

def build_grid_from_list_map(list_map: ty.List[str]) -> ty.List[ty.List]:
    return [list(grid_row) for grid_row in list_map]

def encounters_multipled(encounters: ty.List):
    if len(encounters) == 1:
        return encounters[0]

    return reduce(lambda x, y: x * y, encounters)

def get_encountered_trees(
    tree_map: ty.List, move_x: int, move_y: int) -> int:
    encountered_trees = 0
    current_x = 0
    current_y = 0

    grid = build_grid_from_list_map(tree_map)
    grid_rows = len(grid) - 1
    grid_cols = len(grid[0]) - 1

    out_bound = False

    while True:
        if current_y + move_y > grid_rows:
            break

        current_y += move_y

        if current_x + move_x > grid_cols:
            current_x = (current_x + move_x - grid_cols) - 1
        else:
            current_x += move_x

        encounter = grid[current_y][current_x]

        if encounter == '#':
            encountered_trees += 1
            marker = 'X'
        else:
            marker = 'O'

        grid[current_y][current_x] = marker

    return encountered_trees

def move_by_patterns(tree_map: ty.List, movements: ty.List) -> int:
    encounters = list()
    for move in movements:
        encounters.append(
            get_encountered_trees(tree_map, *move)
        )
    return encounters_multipled(encounters)


from setuptools import setup, find_packages

install_requires = [
    'numpy',
]

tests_require = [
    'pytest',
]

setup(name='dlutils',
      version='0.1',
      description='Deep Learning Utilities',
      author="Bruno Di Giorgi",
      author_email='bruno@brunodigiorgi.it',
      url='https://github.com/brunodigiorgi/dlutils',
      license="GPLv2",
      packages=find_packages(),
      package_data={'': ['*.html_template']},
      zip_safe=False,
      install_requires=install_requires,
      extras_require={
          'testing': tests_require,
      },
      )


# Artificial Neural Network - Predicting probabilities a customer leaves the bank

# Part 1 - Data Preprocessing

# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset
dataset = pd.read_csv('Churn_Modelling.csv')
X = dataset.iloc[:, 3:13].values
y = dataset.iloc[:, 13].values

# Encoding categorical data
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import LabelEncoder, OneHotEncoder

# Label Encode the 2 categorical features: Country and Gender
le_X1 = LabelEncoder()
X[:, 1] = le_X1.fit_transform(X[:, 1])
le_X2 = LabelEncoder()
X[:, 2] = le_X2.fit_transform(X[:, 2])

# One Hot Encode Country; Gender not needed since binary
# zz = onehotencoder.fit_transform(X[:, 1].reshape(-1, 1)).toarray() # reshape converts the column '1' into a 2D np.array:
# [1.0, 0.0, ...] reshaped into [[1.0], [0.0], ...]
# Outputs the OHEd 2D array; need to put it back into X
ohe_cat = [1] # List of categories to OneHotEncode: actually just the column '1'
ct = ColumnTransformer([('encoder', OneHotEncoder(), ohe_cat)], remainder='passthrough')
X = np.array(ct.fit_transform(X), dtype=np.float) # Not sure why must enforce np.float...

# Avoid dummy variable trap
X = X[:, 1:]

# Splitting the dataset into the Training set and Test set
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

# Feature Scaling
from sklearn.preprocessing import StandardScaler

sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)

# Part 2 - Evaluating, Improving and Tuning the ANN

# Evaluating the ANN
from tensorflow.keras.wrappers.scikit_learn import KerasClassifier
from sklearn.model_selection import cross_val_score
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.layers import Dense

def build_classifier(): # Function to build the ANN
    classifier = Sequential()
    classifier.add(Dense(units=6, kernel_initializer='uniform', activation='relu', input_dim=11))
    classifier.add(Dense(units=6, kernel_initializer='uniform', activation='relu'))
    classifier.add(Dense(units=1, kernel_initializer='uniform', activation='sigmoid'))
    classifier.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

    return classifier

classifier = KerasClassifier(build_fn=build_classifier, batch_size=10, epochs=100)

accuracies = cross_val_score(estimator=classifier, X=X_train, y=y_train, cv=10, n_jobs=-1) # change this if code crashes... n_jobs=1 or 2 is a safe (slow) choice
mean = accuracies.mean()
variance = accuracies.std()

# Improving the ANN
# Dropout Regularization to reduce overfitting if needed

# Tuning the ANN
from sklearn.model_selection import GridSearchCV

def build_classifier2(optimizer):
    classifier = Sequential()
    classifier.add(Dense(units=6, kernel_initializer='uniform', activation='relu', input_dim=11))
    classifier.add(Dense(units=6, kernel_initializer='uniform', activation='relu'))
    classifier.add(Dense(units=1, kernel_initializer='uniform', activation='sigmoid'))
    classifier.compile(optimizer=optimizer, loss='binary_crossentropy', metrics=['accuracy'])

    return classifier

classifier2 = KerasClassifier(build_fn=build_classifier2)

parameters = {
    'batch_size': [25, 32],
    'epochs': [100, 500],
    'optimizer': ['adam', 'rmsprop'],
}

grid_search = GridSearchCV(estimator=classifier2,
                           param_grid=parameters,
                           scoring='accuracy',
                           cv=10, n_jobs=-1) # change this if code crashes... n_jobs=1 or 2 is a safe (slow) choice

grid_search = grid_search.fit(X_train, y_train)

best_parameters = grid_search.best_params_
best_accuracy = grid_search.best_score_


# author: zong
# date: 2022/5/17 16:57
import os

filename = 'student.txt'

def main():
    while True:
        menu()
        choice = int(input('请选择：'))
        if choice in [0, 1, 2, 3, 4, 5, 6, 7]:
            if choice == 0:
                answer = input('您确定要退出系统吗？(y/n)')
                if answer == 'y' or answer == 'Y':
                    print('谢谢您的使用！')
                    break  # 退出系统
                else:
                    continue  # 返回主菜单
            elif choice == 1:
                insert()
            elif choice == 2:
                search()
            elif choice == 3:
                delete()
            elif choice == 4:
                modify()
            elif choice == 5:
                sort()
            elif choice == 6:
                total()
            elif choice == 7:
                show()

def menu():
    print('====================================学生信息管理系统====================================')
    print('---------------------------------------功能菜单----------------------------------------')
    print('\t\t\t\t\t\t1.录入学生信息')
    print('\t\t\t\t\t\t2.查找学生信息')
    print('\t\t\t\t\t\t3.删除学生信息')
    print('\t\t\t\t\t\t4.修改学生信息')
    print('\t\t\t\t\t\t5.排序')
    print('\t\t\t\t\t\t6.统计学生总人数')
    print('\t\t\t\t\t\t7.显示所有学生信息')
    print('\t\t\t\t\t\t0.退出系统')
    print('--------------------------------------------------------------------------------------')

def insert():
    student_list = []
    while True:
        id = input('请输入ID（如1001）：')
        if not id:
            break
        name = input('请输入姓名：')
        if not name:
            break
        try:
            english = int(input('请输入英语成绩：'))
            python = int(input('请输入python成绩：'))
            java = int(input('请输入java成绩：'))
        except:
            print('输入无效，不是整数类型，请重新输入！')
            continue
        # 将录入的学生信息保存到字典中
        student = {'id': id, 'name': name, 'english': english, 'python': python, 'java': java}
        # 将学生信息添加到列表中
        student_list.append(student)
        answer = input('是否继续录入？(y/n)\n')
        if answer == 'y' or answer == 'Y':
            continue
        else:
            break
    # 将学生信息保存到文件中
    save(student_list)
    print('学生信息录入完毕！')

def save(lst):
    try:
        stu_txt = open(filename, 'a', encoding='utf-8')
    except:
        stu_txt = open(filename, 'w', encoding='utf-8')
    for item in lst:
        stu_txt.write(str(item) + '\n')
    stu_txt.close()

def search():
    student_query = []
    while True:
        id = ''
        name = ''
        if os.path.exists(filename):
            mode = input('请输入查询方式：1.按ID查询 2.按姓名查询\n')
            if mode == '1':
                id = input('请输入要查询的ID：')
            elif mode == '2':
                name = input('请输入要查询的姓名：')
            else:
                print('输入无效，请重新输入！')
                search()
            with open(filename, 'r', encoding='utf-8') as f:
                student = f.readlines()
                for item in student:
                    d = dict(eval(item))
                    if id != '':
                        if d['id'] == id:
                            student_query.append(d)
                    elif name != '':
                        if d['name'] == name:
                            student_query.append(d)
            # 显示查询结果
            show_student(student_query)
            # 清空列表
            student_query.clear()
            answer = input('是否继续查询？(y/n)\n')
            if answer == 'y' or answer == 'Y':
                continue
            else:
                break
        else:
            print('暂未保存学生信息！')
            return

def show_student(lst):
    if len(lst) == 0:
        print('没有查询到学生相关信息！')
        return
    # 定义标题显示格式
    format_title = '{:^6}\t{:^12}\t{:^8}\t{:^10}\t{:^10}\t{:^8}'
    print(format_title.format('ID', '姓名', '英语成绩', 'python成绩', 'java成绩', '总成绩'))
    # 定义内容显示格式
    format_data = '{:^6}\t{:^12}\t{:^8}\t{:^10}\t{:^10}\t{:^8}'
    for item in lst:
        print(format_data.format(item['id'], item['name'], item['english'], item['python'], item['java'],
                                 int(item['english']) + int(item['python']) + int(item['java'])))

def delete():
    while True:
        student_id = input('请输入要删除的学生ID：')
        if student_id != '':
            if os.path.exists(filename):
                with open(filename, 'r', encoding='utf-8') as f:
                    student_old = f.readlines()
            else:
                student_old = []
            flag = False  # 标记是否删除
            if student_old:
                with open(filename, 'w', encoding='utf-8') as f:
                    d = {}
                    for item in student_old:
                        d = dict(eval(item))  # 将字符串转换为字典
                        if d['id'] != student_id:
                            f.write(str(d) + '\n')
                        else:
                            flag = True
                    if flag:
                        print(f'id为{student_id}的学生信息已被删除！')
                    else:
                        print(f'id为{student_id}的学生信息不存在！')
            else:
                print('没有学生信息！')
                break
            show()  # 删除之后重新显示所有学生信息
            answer = input('是否继续删除？(y/n)\n')
            if answer == 'y' or answer == 'Y':
                continue
            else:
                break

def modify():
    show()
    if os.path.exists(filename):
        with open(filename, 'r', encoding='utf-8') as f:
            student_old = f.readlines()
    else:
        return
    student_id = input('请输入要修改的学生ID：')
    with open(filename, 'w', encoding='utf-8') as f:
        for item in student_old:
            d = dict(eval(item))
            if d['id'] == student_id:
                print(f'id为{student_id}的学生信息：')
                while True:
                    try:
                        d['name'] = input('请输入姓名：')
                        d['english'] = int(input('请输入英语成绩：'))
                        d['python'] = int(input('请输入python成绩：'))
                        d['java'] = int(input('请输入java成绩：'))
                    except:
                        print('输入有误，请重新输入！')
                    else:
                        break
                f.write(str(d) + '\n')
                print('修改成功！')
            else:
                f.write(str(d) + '\n')
        answer = input('是否继续修改其他学生信息？(y/n)\n')
        if answer == 'y' or answer == 'Y':
            modify()

def sort():
    show()
    if os.path.exists(filename):
        with open(filename, 'r', encoding='utf-8') as f:
            student_list = f.readlines()
        student_new = []
        for item in student_list:
            d = dict(eval(item))
            student_new.append(d)
    else:
        return
    asc_or_desc = input('请选择（0，升序 1，降序）：\n')
    if asc_or_desc == '0':
        asc_or_desc = False
    elif asc_or_desc == '1':
        asc_or_desc = True
    else:
        print('输入有误，请重新输入！')
        sort()
    mode = input('请选择排序方式（1，按英语成绩排序 2，按Python成绩排序 3，按Java成绩排序 0，按总成绩排序）：\n')
    if mode == '1':
        student_new.sort(key=lambda x: int(x['english']), reverse=asc_or_desc)
    elif mode == '2':
        student_new.sort(key=lambda x: int(x['python']), reverse=asc_or_desc)
    elif mode == '3':
        student_new.sort(key=lambda x: int(x['java']), reverse=asc_or_desc)
    elif mode == '0':
        student_new.sort(key=lambda x: int(x['english']) + int(x['python']) + int(x['java']), reverse=asc_or_desc)
    else:
        print('输入有误，请重新输入！')
        sort()
    show_student(student_new)

def total():
    if os.path.exists(filename):
        with open(filename, 'r', encoding='utf-8') as f:
            students = f.readlines()
            if students:
                print('学生总人数：', len(students))
            else:
                print('没有学生信息！')
    else:
        print('暂未保存数据信息！')

def show():
    student_lst = []
    if os.path.exists(filename):
        with open(filename, 'r', encoding='utf-8') as f:
            students = f.readlines()
            for item in students:
                student_lst.append(eval(item))
            if student_lst:
                show_student(student_lst)
    else:
        print('暂未保存数据信息！')

if __name__ == '__main__':
    main()


import typing as t

from dataclasses import dataclass

G = t.TypeVar("G")
A = t.TypeVar("A")
P = t.TypeVar("P")

@dataclass
class Rules(t.Generic[G, A, P]):
    init_game: t.Callable[[], G]
    take_action_mut: t.Callable[[G, A], G]
    is_over: t.Callable[[G], t.Optional[P]]
    get_final_score: t.Callable[[G], t.Dict[str, float]]
    encode_action: t.Callable[[A], str]
    decode_action: t.Callable[[str], A]
    get_random_action: t.Callable[[G], t.Optional[A]]
    get_all_actions: t.Callable[[G], t.List[A]]
    get_players: t.Callable[[G], t.List[P]]

    format_gamestate: t.Callable[[G], str]

    encode_gamestate: t.Callable[[G], bytes]
    decode_gamestate: t.Callable[[bytes], G]

def load_rules(game_type: str):
    if game_type == "2048":
        import t2048.rules as rules
        import t2048.fmt as fmt
    elif game_type == "connect4":
        import connect4.rules as rules
        import connect4.fmt as fmt
    else:
        raise Exception(f"Unknown game type {game_type}")

    return Rules(
        init_game=rules.init_game,
        take_action_mut=rules.take_action_mut,
        is_over=rules.is_over,
        get_final_score=rules.get_final_score,
        encode_action=rules.encode_action,
        decode_action=rules.decode_action,
        get_random_action=rules.get_random_action,
        get_all_actions=rules.get_all_actions,
        get_players=rules.get_players,
        format_gamestate=fmt.format_gamestate,
        encode_gamestate=rules.encode_gamestate,
        decode_gamestate=rules.decode_gamestate,
    )


import transliterate

#Обрабатывает прежде полученые данные, и формирует из их выходной результат для передачи в AMS систему
class Dump_all:
    def __init__(self,agents,agents_numb,atribute,atribute_numb,verb,verb_numb,point,money,money_numb,data):
        self.agents = agents
        self.ag_numb = agents_numb
        self.atribute = atribute
        self.attr_numb = atribute_numb
        self.action = verb
        self.action_numb = verb_numb
        self.points = point
        self.money = money
        self.money_numb = money_numb
        self.data = data
    def dump_to_file(self):
        for i in self.ag_numb:
            tmp = '\n'+ transliterate.translit(
                str(self.agents[self.ag_numb.index(i)]).replace(
                    ' ', '_'),
                language_code='uk',
                reversed=True)+ ' - '

            for j in self.attr_numb:
                if (i<j):
                    tmp+= transliterate.translit(
                        str(self.atribute[self.attr_numb.index(j)]).replace(
                            ' ', '_'),
                        language_code='uk',
                        reversed=True) +', '
                self.atribute.pop(self.attr_numb.index(j))
                self.attr_numb.pop(self.attr_numb.index(j))
            with open('Dump.txt','a',encoding='utf-8') as f:
                f.write(str(tmp)+"\n")

        with open('Dump.txt', 'a', encoding='utf-8') as f:
            f.write(str(transliterate.translit(
                        str(self.action['faiding']).replace(
                            ' ', '_'),
                        language_code='uk',
                        reversed=True)+", "+transliterate.translit(
                        str(self.action['NonFaiding']).replace(
                            ' ', '_'),
                        language_code='uk',
                        reversed=True)+'\n' ))

        self.atribute.reverse()
        self.attr_numb.reverse()
        self.money.reverse()
        self.money_numb.reverse()
        tmp_string = ''

        tmp_string += transliterate.translit(
            str("судового збору").replace(
                 ' ', '_'),
             language_code='uk',
            reversed=True) +" = "+ transliterate.translit(
                            str(self.money[0]).replace(
                                ' ', '_'),
                            language_code='uk',
                            reversed=True) + "\n"
        self.money.pop(0)
        self.money_numb.pop(0)

        for i in self.money_numb:
            index_i = self.money_numb.index(i)
            for j in self.attr_numb:
                index_j = self.attr_numb.index(j)
                if j<i:
                    with open('Dump.txt', 'a', encoding='utf-8') as f:
                        f.write(str(transliterate.translit(
                            str(self.atribute[self.attr_numb.index(j)]).replace(
                                ' ', '_'),
                            language_code='uk',
                            reversed=True) +" = "+transliterate.translit(
                            str( self.money[self.money_numb.index(i)]).replace(
                                ' ', '_'),
                            language_code='uk',
                            reversed=True) +'\n'))
                self.atribute.pop(self.attr_numb.index(j))
                self.attr_numb.pop(self.attr_numb.index(j))
            self.money.pop(self.money_numb.index(i))
            self.money_numb.pop(self.money_numb.index(i))

        self.money.reverse()

        tmp_string +=   transliterate.translit(
                            str("штрафні санкції").replace(
                                ' ', '_'),
                            language_code='uk',
                            reversed=True)+" = "+transliterate.translit(
                            str(self.money[0]).replace(
                                ' ', '_'),
                            language_code='uk',
                            reversed=True) + "\n"
        tmp_string += transliterate.translit(
            str("штрафні санкції").replace(
                ' ', '_'),
            language_code='uk',
            reversed=True) +" = "+ transliterate.translit(
                            str(self.money[1]).replace(
                                ' ', '_'),
                            language_code='uk',
                            reversed=True) + "\n"
        #tmp_string += transliterate.translit(
          #  str("судового збору").replace(
           #     ' ', '_'),
           # language_code='uk',
            #reversed=True) + self.money[2] + "\n"

        with open('Dump.txt', 'a', encoding='utf-8') as f:
            f.write(str(tmp_string)+'\n'+transliterate.translit(
                            str(self.points).replace(
                                ' ', '_'),
                            language_code='uk',
                            reversed=True)+'\n'+transliterate.translit(
                            str(self.data).replace(
                                ' ', '_'),
                            language_code='uk',
                            reversed=True))


"""
This module handles:
- Getting individual loan
- Deleting and Updating Loan
- By Extension, Updating parent Loan

"""
from fastapi import APIRouter, Depends, HTTPException, status
from ..utils import get_db
from models.loan import Loan
from models.loan_profile import LoanProfile
from models.loan_out import LoanOut
from sqlalchemy.orm import Session
from schemas.loan import LoanOutUpdate

router = APIRouter(tags=['LoanOut'], prefix='/loan_out')

@router.get('/')
def get_loans(storage: Session = Depends(get_db)) -> list[dict | None]:
    """Return all [individual] loan instances as a list of dictionary from the database"""
    loans: list = []
    all_loans: dict = storage.all(LoanOut)
    if not all_loans:
        return loans
    loan = [all_loans[key].to_dict() for key in all_loans.keys()]
    return loan

@router.get('/{id}')
def get_loan(id: str, storage: Session = Depends(get_db)) -> dict:
    """Return a single loan instances as a dictionary from the database"""
    loan: dict = storage.get(LoanOut, id)
    if loan is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Loan instance not found")
    loan = loan.to_dict()
    return loan

@router.delete('/{id}', status_code=status.HTTP_204_NO_CONTENT)
def delete_loan(id: str, loan_id: dict, storage: Session = Depends(get_db)) -> None:
    """Delete a loan instance and update the aggregate (parent) loan"""
    loan_id = loan_id.get('loan_id')
    loan = storage.get(Loan, loan_id)
    loan_out = storage.get(LoanOut, id)
    if loan is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                            detail="Aggregate Loan instance not found")
    if loan_out is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                            detail="Loan instance not found")

    loan_dict: dict = loan.to_dict()
    loanout_dict: dict = loan_out.to_dict()

    if loan_id != loanout_dict['loan_id']:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                            detail="Couldn't link loan instance to aggregate loan")
    total_amount = loan_dict['total_amount'] - loanout_dict['amount']
    loan_dict['total_amount'] = total_amount
    loan_out.delete()

    # if parent instance of loan less or equals - we delete the loan
    # because it implies that the aggregate loan doesn't have a child
    # instance else we update
    if loan_dict['total_amount'] <= 0:
        loan.delete()
    else:
        loan.update(loan_dict)

@router.put('/{id}', status_code=status.HTTP_204_NO_CONTENT)
def update_loanout(id: str, data: dict, storage: Session = Depends(get_db)):
    """Update a loan instance"""
    if len(data) < 1:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail='request data is empty'
        )

    # data = data.model_dump()

    loanout_data: dict = data['loanout_data']
    loan_out = storage.get(LoanOut, id)

    if loan_out is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                            detail="Loan instance not found")
    loanout_dict: dict = loan_out.to_dict()

    # I only want to update the Parent Loan if it is "amount" that's
    # been updated
    if 'amount' in loanout_data:
        parent_ids: dict = data['parent_ids']
        loan_id = parent_ids.get('loan_id')
        loan_profile_id = parent_ids.get('loan_profile_id')
        loan = storage.get(Loan, loan_id)

        if loan is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                                detail="Aggregate Loan instance not found")
        loan_dict: dict = loan.to_dict()

    if 'amount' in loanout_data:
        if loan_id != loanout_dict['loan_id']:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                                detail="Couldn't link loan instance to aggregate loan")
        total_amount = loan_dict['total_amount'] - loanout_dict['amount']
        total_amount += loanout_data['amount']
        loan_dict['total_amount'] = total_amount
        loan.update(loan_dict)

        # Get exiting loan_profile and update it
        loan_profile = storage.get(LoanProfile, loan_profile_id)
        if loan_profile is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                                detail="Loan Profile instance not found")

        if loan_dict['is_member']:  # Interest rate on loans
            RATE = 0.05
        else:
            RATE = 0.1

        loan_profile_data = {}
        loan_profile_data['principal'] = loan_dict['total_amount']
        loan_profile_data['interest'] = loan_dict['total_amount'] * RATE
        loan_profile_data['total'] = loan_dict['total_amount'] + \
            loan_profile_data['interest']

        print(loan_profile.to_dict()['loan_id'])
        print(loan_dict['id'])
        if loan_profile.to_dict()['loan_id'] != loan_dict['id']:
            raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                                detail="Cannot attach <Loan Profile Instance> to <Loan Instance>")

        loan_profile.update(loan_profile_data)
        loan_profile.save()

    loan_out.update(loanout_data)

    return {}


import subprocess
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

def scan_ports(target: str) -> dict:
    # Placeholder using Nmap as an example; adjust as necessary
    command = ["nmap", "-sV", "-T4", target]
    try:
        result = subprocess.check_output(command, text=True)

        # Here we are simply returning the raw result. In reality, you'd parse this for port and service details.
        # The dictionary structure can be {port: service}. This can be further enhanced to capture more info.
        return {'raw_output': result}
    except subprocess.CalledProcessError as e:
        print(f"Error during port scan: {e}")
        return {}

class PortScanGUI(Gtk.Box):

    def __init__(self, subdomains: list):
        super().__init__(spacing=10)

        # Dropdown to select subdomain/IP
        self.subdomain_selector = Gtk.ComboBoxText()
        for subdomain in subdomains:
            self.subdomain_selector.append_text(subdomain)
        self.subdomain_selector.set_active(0)
        self.pack_start(self.subdomain_selector, True, True, 0)

        # Button
        self.button = Gtk.Button(label="Start Port Scan")
        self.button.connect("clicked", self.on_button_clicked)
        self.pack_start(self.button, True, True, 0)

        # Display
        self.text_view = Gtk.TextView()
        self.text_view.set_editable(False)
        self.pack_start(self.text_view, True, True, 0)

    def on_button_clicked(self, widget):
        target = self.subdomain_selector.get_active_text()
        if target:
            result = scan_ports(target)

            # Display raw output in the text view. You can further parse the result to show structured data.
            text_buffer = self.text_view.get_buffer()
            text_buffer.set_text(result.get('raw_output', ''))


# -*- coding: utf-8 -*-
#
# author: Amanul Haque
#
# File Description: This code contained methods fot supervised classification algorithms

from word_to_vec_vectorizer import vectorization
from sklearn import svm
from sklearn.svm import SVC
from sklearn.svm import LinearSVC
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import MultinomialNB
from sklearn import metrics
from Semi_EM_NB import Semi_EM_MultinomialNB
from scipy import sparse
from sklearn.semi_supervised import LabelPropagation
from compare_labels_probabilities import compare_labels_probabilities
from sklearn.tree import DecisionTreeClassifier
import numpy as np
from sklearn.ensemble import RandomForestClassifier
import sys
from scipy import sparse
from sklearn.semi_supervised import LabelSpreading
from transductive_SVM import transductive_SVM

class classification:

    def __init__(self):
        self.a = 0

    def classifier_1(self, X_train, y_train, X_test, labelled_set, unlabelled_set):
        clf = svm.SVC(kernel = 'linear', C=1.0, probability = True)
        clf.fit(X_train, y_train)
        predicted_labels = clf.predict(X_test)
        predicted_probabilities = clf.predict_proba(X_test)
        return predicted_labels, predicted_probabilities

    def linear_svc(self, X_train, y_train, X_test):
        clf = LinearSVC(random_state=0, tol=1e-5)
        clf.fit(X_train, y_train)
        predicted_labels = clf.predict(X_test)
        prediction_confidence = clf.decision_function(X_test)
        return predicted_labels, prediction_confidence, clf

    def radial_svc(self, X_train, y_train, X_test):
        clf = SVC(gamma='auto', kernel='rbf')
        clf.fit(X_train, y_train)
        predicted_labels = clf.predict(X_test)
        prediction_confidence = clf.decision_function(X_test)
        return predicted_labels, prediction_confidence, clf

    def logistic_regression(self, X_train, y_train, X_test):
        clf = LogisticRegression(random_state=0, solver='lbfgs', multi_class='multinomial').fit(X_train, y_train)
        predicted_labels = clf.predict(X_test)
        prediction_confidence = clf.decision_function(X_test)
        final_labels = clf.predict(X_test)
        label_prob = clf.predict_proba(X_test)
        print(compare_labels_probabilities().compare(label_prob, final_labels))
        return predicted_labels, prediction_confidence, clf

    #def calibration_isotonic(self, X_train, y_train, X_test, clf)

    def expectation_maximization(self,  X_train, y_train, X_test):

        nb_clf = MultinomialNB(alpha=1e-2)
        #print("XX ", X_train.shape, "\tYY ", y_train.shape)
        nb_clf.fit(X_train, y_train)
        #print("XXXXXX ", X_test.shape)
        #print(X_test)

        # Train Naive Bayes classifier (imported)
        # using both labeled and unlabeled data set
        em_nb_clf = Semi_EM_MultinomialNB(alpha=1e-2) # semi supervised EM based Naive Bayes classifier
        #X_test = np.asmatrix(X_test)
        #X_test = sparse.csr_matrix(X_test)
        #print("TYPE : ", type(X_test))
        em_nb_clf.fit(X_train, y_train, X_test)
        # em_nb_clf.fit_with_clustering(X_l, y_l, X_u)
        # em_nb_clf.partial_fit(X_l, y_l, X_u)

        '''
        # Evaluate original NB classifier using test data set
        pred = nb_clf.predict(X_test)
        print("Normal Naive Bayes")
        print(pred)
        print(metrics.classification_report(test_Xy.target, pred, target_names=['1','2']))
        # pprint(metrics.confusion_matrix(test_Xy.target, pred))
        print(metrics.accuracy_score(test_Xy.target, pred))
        '''

        # Evaluate semi-supervised EM NB classifier using test data set
        pred = em_nb_clf.predict(X_test)
        print("EM Semi supervised")
        #print(pred)
        #unique, counts = np.unique(pred, return_counts=True)
        #print(dict(zip(unique, counts)))
        return pred, em_nb_clf
        #sys.exit()
        #print(metrics.classification_report(test_Xy.target, pred, target_names=['1','2']))
        # pprint(metrics.confusion_matrix(test_Xy.target, pred))
        #print(metrics.accuracy_score(test_Xy.target, pred))

        # find the most informative features
        #import numpy as np
        '''
        def show_topK(classifier, vectorizer, categories, K=10):
            feature_names = np.asarray(vectorizer.get_feature_names())
            for i, category in enumerate(categories):
                topK = np.argsort(classifier.coef_[i])[-K:]
                print("%s: %s" % (category, " ".join(feature_names[topK])))

        #show_topK(nb_clf, vectorizer, ['1','2'], K=10) # keywords for each class by original NB classifier

        #show_topK(em_nb_clf, vectorizer, ['1','2'], K=10) # keywords for each class by semisupervised EM NB classifier
        '''
        #print(nb_clf.class_log_prior_, em_nb_clf.clf.class_log_prior_)

    def label_propagation(self, X_train, y, X_test):

        clf = LabelPropagation()
        print("X_train Shape :", X_train.shape, type(X_train))
        print("X_test shape : ", X_test.shape, type(X_test))
        print("y shape : ", y.shape )

        X = np.concatenate((X_train.todense(), X_test.todense()),axis=0)
        print("X shape now ", X.shape)
        print("Y shape now ", y.shape)
        clf.fit(X, y)
        final_labels = clf.predict(X_test)
        label_prob = clf.predict_proba(X_test)
        print(compare_labels_probabilities().compare(label_prob, final_labels))
        return final_labels, clf

    def decision_tree(self, X_train, y_train, X_test):
        clf = DecisionTreeClassifier(random_state=0)
        clf.fit(X_train, y_train)
        final_labels = clf.predict(X_test)
        return final_labels, clf

    def random_forest(self, X_train, y_train, X_test):
        clf = RandomForestClassifier()
        clf.fit(X_train, y_train)
        final_labels = clf.predict(X_test)
        return final_labels, clf

    def label_spreading(self, X_train, y, X_test):
        clf = LabelSpreading()
        X = np.concatenate((X_train.todense(), X_test.todense()),axis=0)
        print("X shape now ", X.shape)
        print("Y shape now ", y.shape)
        clf.fit(X, y)
        final_labels = clf.predict(X_test)
        label_prob = clf.predict_proba(X_test)
        print(compare_labels_probabilities().compare(label_prob, final_labels))
        return final_labels, clf

    def transductive_svm(self, X_train, y, X_test):
        clf = transductive_SVM()
        X = np.concatenate((X_train.todense(), X_test.todense()),axis=0)
        print("X shape now ", X.shape)
        print("Y shape now ", y.shape)
        y = y.astype(int)
        print(y)
        clf.fit(X, y)
        final_labels = clf.predict(X_test)
        #label_prob = clf.predict_proba(X_test)
        #print(compare_labels_probabilities().compare(label_prob, final_labels))
        return final_labels, clf

    def one_class_svm(self, X_train, y_train, X_test):
        clf = svm.OneClassSVM(nu = 0.5, kernel = 'rbf', gamma = 0.1)
        clf.fit(X_train, y_train)
        y_pred = clf.predict(X_test)
        unique, counts = np.unique(y_pred, return_counts=True)
        print(dict(zip(unique, counts)))
        return y_pred, clf

# =============================================================================
# cl = classification()
# X_train, y_train, X_test, labelled_set, unlabelled_set = cl.get_vectorized_data()
# predicted_labels, prediction_confidence = cl.classifier_2(X_train, y_train, X_test, labelled_set, unlabelled_set)
# print(predicted_labels, predicted_labels.shape)
# print(prediction_confidence, prediction_confidence.shape)
# =============================================================================


"""
Basic example of sending a request to the udCloud server using udServerAPI. Returned is a json response from the server
"""
import udSDK
from udSDKServerAPI import udServerAPI
import sampleLogin
if __name__ == "__main__":
  udSDK.LoadUdSDK("")
  context = udSDK.udContext()
  sampleLogin.log_in_sample(context)
  # test basic whoami request:
  s = udServerAPI(context)
  j = s.query("_user/whoami", None)
  print(j)