import logging
from fuzzywuzzy import fuzz
from src.constants import special_char_replacement, text_numbers, extra_info
logger = logging.getLogger("PATH_REPORT_LOGGER")
HEADING = 'Heading'
REGION = 'Region'
EXTRA = 'Extra_Info'
ATTRIBUTES = ['Pos_Cores','Total_Cores','Gleason_Primary','Gleason_Secondary','Gleason_Total','Cancer_Dx']
BEGIN = 'B'
INNER = 'I'
Outter = 'O'
Data_Dir = "Data/NLP/PreProcessed/scispacy/"

class PathologyReport:

    def __init__(self, filename, vector_reps, data_dir=Data_Dir, raw_text=None):
        self.filename = filename
        self.vecs = vector_reps
        self.num_sents = len(self.vecs)
        self.predictions = None
        self.SECTIONS = {'START':[]}    # keeps track of every non-O tag found in a section
        self.REGIONS = {'START':{}}     # keeps track of ATTRIBUTE-REGION links per section
        self.FLOATING = {'START':{}}    # tags that have no region
        self.EXTRA = []
        self.LABEL_DICT = {}
        if filename:
            self.fulltext = open(data_dir + filename,'r').read()
        else:
            self.fulltext = raw_text
        self.resolved_regions = None
        self.grade = None
        self.extra_info_counts = None
        self.gleason_score = None

    def add_region(self,section,region_name):
        self.REGIONS[section].setdefault(region_name, {})
        for attribute in ATTRIBUTES:
            self.REGIONS[section][region_name].setdefault(attribute,[])
        return True

    def get_text(self,bounds_list,line):
        return self.fulltext[bounds_list[0][0]:bounds_list[-1][1]]

    def get_tag_info(self,tag):
        type = tag.split('-')[1] if tag != Outter else Outter
        is_begin = BEGIN in tag
        is_inner = INNER in tag
        return type, is_begin, is_inner

    def extract_tag(self, current_bounds_list, line,prev_tag_type, curr_section):
        new_section = False
        text = self.get_text(current_bounds_list, line).strip()
        # record this text in tag dictionary
        self.LABEL_DICT.setdefault(prev_tag_type, [])
        self.LABEL_DICT[prev_tag_type].append(text)

        if prev_tag_type == HEADING:
            self.SECTIONS.setdefault(text, [])  # add a new section
            curr_section = text
            new_section = True
        else:
            if prev_tag_type != Outter:
                self.SECTIONS[curr_section].append((prev_tag_type, text))
        return text, new_section

    def realize_predictions(self):
        curr_section = 'START'
        prev_b = False
        prev_i = False
        prev_tag_type = None
        current_bounds_list = []
        global_offset = 0
        for pred_id, prediction_vector in enumerate(self.predictions):
            for i, current_tag in enumerate(prediction_vector):
                current_tag_type, is_begin, is_inner = self.get_tag_info(current_tag)
                if not prev_tag_type:
                    prev_tag_type = current_tag_type
                    prev_b = is_begin
                    prev_i = is_inner

                else:
                    if not (prev_tag_type == current_tag_type) or is_begin:
                        # we need to extract the text for this tag and add it to section array
                        l = self.vecs[pred_id]['RAW']
                        text, new_sect = self.extract_tag(current_bounds_list,self.vecs[pred_id]['RAW'],prev_tag_type,curr_section)
                        if new_sect:
                            curr_section = text
                        current_bounds_list = []

                current_bounds_list.append(self.vecs[pred_id]['TOKEN_BOUNDS'][i])
                prev_tag_type = current_tag_type
                prev_b = is_begin
                prev_i = is_inner

            text, new_sect = self.extract_tag(current_bounds_list, self.vecs[pred_id]['RAW'], prev_tag_type, curr_section) # handle final tokens in line
            if new_sect:
                curr_section = text
            current_bounds_list = []
            prev_b = False
            prev_i = False
            prev_tag_type = None

        return None

    def populate_regions(self):
        for section in self.SECTIONS:
            self.FLOATING.setdefault(section,{})
            self.REGIONS.setdefault(section,{})
            current_region = None
            for tupl in self.SECTIONS[section]:
                tag = tupl[0]
                text = tupl[1]
                if tag == REGION:
                    current_region = text
                    self.add_region(section,current_region)
                elif tag in ATTRIBUTES:
                    if current_region:
                        """
                        if self.REGIONS[section][region_id][tag]:
                            logger.warning("In file {}, overwriting attribute {}, later tag found...\n\t{}\t->\t{}".format(
                                self.filename,tag,self.REGIONS[section][region_id][tag],text
                            ))
                        """
                        if not self.REGIONS[section][current_region][tag]:   # only allow one of each attribute per region PER section
                            self.REGIONS[section][current_region][tag].append(text)
                    else:
                        # have a region attribute with no prior region labeled, put in floating
                        self.FLOATING[section].setdefault(tag,[])
                        self.FLOATING[section][tag].append(text)
                elif tag == EXTRA:
                    self.EXTRA.append(text)
                    continue
            # Move onto the next section

        return None

    def get_region_similarity(self,string1,string2):
        return fuzz.token_sort_ratio(string1,string2)

    def match_region_set(self,agreggated_set,new_set):
        # we'll need a matrix of scores
        scores = {}
        for r1 in agreggated_set:
            for r2 in new_set:
                scores[(r1,r2)] = self.get_region_similarity(r1,r2)
        debug = True

        sorted_scores = sorted([tupl for tupl in scores.items()],key=lambda x:x[1],reverse=True) # form: [ ( (aggregate_region, new_regoin), score ) , ...]
        # match each region from the new set to the aggregated set.
        choices = {}
        average_score = sum(list(scores.values()))/len(scores)
        for score in sorted_scores:
            r1, r2, sim = score[0][0], score[0][1], score[1]

            if r1 not in choices and r2 not in choices.values() and sim > average_score: # if we haven't mapped this region yet AND its similarity is greater than half of the average similarity, match
                choices[r1] = r2

        # now we need to figure out the unatched regions, we'll be treating these as new
        new_regions = [r for r in new_set if r not in list(choices.values()) + list(choices.keys())]

        return choices, new_regions

    def region_resolution(self):
        # aggregate all matching region information across sections
        aggregated_regions = {}
        # Do we find regions in different sections? If so, are there a different number of regions?
        for section in self.REGIONS:
            if not self.REGIONS[section]:
                # if no regions in this section, skip
                do_nothing = True
            else:
                if not aggregated_regions:
                    # if we haven't added any regions to our aggregated regions map, then just use the current section's regions
                    aggregated_regions = self.REGIONS[section]
                    debug = True
                else:
                    # we also have regions in this section, we need to find the best match for each region
                    current_regions = self.REGIONS[section]
                    choices, new_regions = self.match_region_set(aggregated_regions,current_regions)
                    debug = True

                    # combine all attributes of matched regions in aggregated regions
                    for r1, r2 in choices.items():
                        for attribute in current_regions[r2]:
                            for val in current_regions[r2][attribute]:
                                aggregated_regions[r1][attribute].append(val)
                    #  lastly, add new regions to aggregate if any were found

                    for region in new_regions:
                        aggregated_regions[region] = current_regions[region]
        self.resolved_regions = aggregated_regions
        return None

    def resolve_gleason_score(self,item):
        # the gleason score we tagged isn't only an integer figure it out here
        if item.isdigit():
            num = int(item)
            if num > 10:
                return int(item[0]) # just return the first digit, its most likely a double number or misread equation
            else:
                return num
        lower = item.lower()
        if lower in text_numbers:
            return text_numbers[lower]
        elif lower in special_char_replacement:
            return special_char_replacement[lower]
        debug = True
        return 0

    def resolve_core_classification(self, items):
        resolved = []
        for item in items:
            if item.isdigit():
                resolved.append(int(item))
            lower = item.lower()
            if lower in text_numbers:
                resolved.append(text_numbers[lower])
            if lower in special_char_replacement:
                resolved.append(special_char_replacement[lower])

        return resolved

    def calculate_grade(self,primary, secondary, total, simple=True):
        """
        Simple stratification is from the PCPR, not most recent prostate cancer schema
        """
        if simple:
            if total <= 6:
                return 1
            elif total > 7:
                return 3
            else:
                return 2
        if total == 0:
            if primary + secondary == 0:
                return 0
            else:
                total = primary + secondary

        # calculate grade
        if total <= 6:
            return 1
        elif total == 7:
            if primary == 3 and secondary == 4:
                return 2
            elif primary == 4 and secondary == 3:
                return 3
            else:
                # return worst case
                debug = True
                return 3
        elif total == 8:
            return 4
        elif total >= 9:
            return 5
        return None

    def count_extra_info(self):
        counts = {k:0 for k in extra_info}
        for info in self.EXTRA: # for every string we flagged as extra info
            categorized = False
            for category in extra_info:
                keywords = extra_info[category][0:-1]
                token_distance_words = extra_info[category][-1].split('|||')
                if any([keyword in info for keyword in keywords]) or any([fuzz.token_sort_ratio(info,w) > 50 for w in token_distance_words]):
                    counts[category] +=1
        return counts

    def _max_candidate(self,candidates):
        full_support_candidates = [c for c in candidates if c[1] == 3]
        two_support_candidates = [c for c in candidates if c[1] == 2]
        total_only_candidates = [c for c in candidates if c[1] == 1]

        # in order of support priority, sort by highest total and then highest primary
        if full_support_candidates:
            return sorted(full_support_candidates, key=lambda x: (x[0][0],x[0][2]), reverse=True)[
                0]  # get candidate with highest primary gleason)
        elif two_support_candidates:
            return sorted(two_support_candidates, key=lambda x: (x[0][0],x[0][2]), reverse=True)[0]
        elif total_only_candidates:
            return sorted(total_only_candidates, key=lambda x: (x[0][0],x[0][2]), reverse=True)[0]
        return None

    def drop_scores(self, scores, total=False):
        admissable = []

        for i, s in enumerate(scores):
            if not total:
                if s > 2 and s < 6:
                    admissable.append(s)
            else:
                if s>=6 and s<11:
                    admissable.append(s)
        return admissable

    def find_best_region_candidate(self,primaries,secondaries,totals):
        primaries = self.drop_scores(primaries)
        secondaries = self.drop_scores(secondaries)
        totals = self.drop_scores(totals, total=True)
        candidates = []
        total_cands = [t for t in totals if t <=10]
        for total in total_cands:
            # for each gleason_total we have, check if we have support primary and secondary vals
            sums = []
            if not primaries:
                if not secondaries:
                    candidates.append(((total - 3, 3,total)  ,   1 )  )

                for s in secondaries:
                    candidates.append(((total - s, s ,total)  ,   2 ) )
            elif not secondaries:
                for s in primaries:
                    candidates.append(((s, total - s, total), 2))
            else:
                # we have both primary and secondary vals
                sums = [(p, s) for p in primaries for s in secondaries]
                max_pair = ((-1000,None,None),-1000)
                for pair in sums:
                    if pair[0] + pair[1] == total:
                        if pair[0] > max_pair[0][0]:
                            max_pair = (   (pair[0],pair[1],total), 3  )
                    else:
                        if pair[0] > max_pair[0][0]:
                            max_pair = (   (pair[0],pair[1],pair[0] + pair[1]), 2  ) # 2 support because didn't add up to total
                if max_pair[0][2]:
                    candidates.append(max_pair)
        if not candidates and (primaries and secondaries):
            sums = [(p, s) for p in primaries for s in secondaries]
            max_pair = ((-1000, None, -1000), -1000)
            for pair in sums:
                    if pair[0] > max_pair[0][0] and pair[0] + pair[1] > max_pair[0][2]: # if the primary of this is highest so far and has a bigger total...
                        max_pair = (
                        (pair[0], pair[1], pair[0] + pair[1]), 2)  # 2 support because didn't have total
            if max_pair[0][2]:
                candidates.append(max_pair)
            return self._max_candidate([c for c in candidates if c[0][2] <=10])
        else:
            return self._max_candidate([c for c in candidates if c[0][2] <=10])
        return None

    def extract_gleason_from_section(self,section_tags):
        primaries, secondaries, totals = [], [] , []
        debug = True
        if 'Gleason_Primary' in section_tags:
            primaries.extend([self.resolve_gleason_score(score) for score in section_tags['Gleason_Primary']])
        if 'Gleason_Secondary' in section_tags:
            secondaries.extend([self.resolve_gleason_score(score) for score in section_tags['Gleason_Secondary']])
        if 'Gleason_Total' in section_tags:
            totals.extend([self.resolve_gleason_score(score) for score in section_tags['Gleason_Total']])
        if primaries.__len__() >=8 and secondaries.__len__() >=8 and totals.__len__() >= 8: # this is probably a WHO section
            return [], [] ,[]
        return primaries, secondaries ,totals

    def extract_gleason(self):
        gleason_scores = [
            [self.resolved_regions[r]['Gleason_Primary'],
             self.resolved_regions[r]['Gleason_Secondary'],
             self.resolved_regions[r]['Gleason_Total']

             ] for r in self.resolved_regions]

        for i, triple in enumerate(gleason_scores):
            for j, score_arr in enumerate(triple):
                for k, score in enumerate(score_arr):
                    x = gleason_scores[i][j][k] = self.resolve_gleason_score(score)

        candidates = []

        for i, region in enumerate(gleason_scores):
            totals = []
            primaries = []
            secondaries = []

            if region[0]:
                primaries.extend(region[0])
            if region[1]:
                secondaries.extend(region[1])
            if region[2]:
                totals.extend(region[2])
            totals = [x for x in totals if x != 0]
            primaries = [x for x in primaries if x != 0]
            secondaries = [x for x in secondaries if x != 0]

            best_candidate = self.find_best_region_candidate(primaries, secondaries, totals)
            if best_candidate:
                candidates.append(best_candidate)

        max_candidate = self._max_candidate(candidates)

        debug = True

        """
        If we didn't get a candidate from above, lets check the floating sections
        """

        if not max_candidate:
            floating_primaries, floating_secondaries, floating_totals = [], [], []
            for section in self.FLOATING:
                p, s, t = self.extract_gleason_from_section(self.FLOATING[section])
                floating_primaries.extend(p)
                floating_secondaries.extend(s)
                floating_totals.extend(t)
            max_candidate = self.find_best_region_candidate(floating_primaries, floating_secondaries, floating_totals)

        primary = max_candidate[0][0] if max_candidate else 0
        secondary = max_candidate[0][1] if max_candidate else 0
        total = max_candidate[0][2] if max_candidate else 0
        return primary, secondary, total

    def is_malignant(self,info):
        """
        If don't have positive cores, but have evidence of malignancy, we'll want to know
        """
        if any([info['Gleason_Primary'],
                info['Gleason_Secondary'],
                info['Gleason_Total'],
                info['Cancer_Dx']
                ]):
            return True
        return False

    def extract_cores(self):
        # 4-tuples of (region name, [pos cores], [total cores], malignancy)
        regions = [ (r,self.resolve_core_classification(self.resolved_regions[r]['Pos_Cores']),
                     self.resolve_core_classification(self.resolved_regions[r]['Total_Cores']),
                    self.is_malignant(self.resolved_regions[r]))
                    for r in self.resolved_regions]

        pos, total = 0, 0
        for region, pos_cores, total_cores, status in regions:
            _pos, _total = None, None
            if total_cores:
                _total = max(total_cores)
            else:
                _total = 1
            if pos_cores:
                _pos = max(pos_cores)
            else:
                if status:
                    _pos = _total #TODO assume all cores are malignant if no positive given?
                else:
                    _pos =  0
            pos   +=  _pos
            total +=  _total
        debug = True
        return pos, total

    def stratify(self):

        # Extract relevent gleason scores
        primary, secondary, total = self.extract_gleason()
        self.gleason_score = [primary, secondary, total]
        self.grade = self.calculate_grade(primary, secondary, total)

        # get pos cores / total cores
        self.pos_cores, self.total_cores = self.extract_cores()

        # get extra info
        self.extra_info_counts = self.count_extra_info()
        return None


# Based off of code from here: https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html
import torch
from torch import nn
from torchvision import models, transforms, datasets
import torch.optim as optim
from torch.optim import lr_scheduler
from torch.utils.mobile_optimizer import optimize_for_mobile

from time import time
from tempfile import TemporaryDirectory
import os
from matplotlib import pyplot as plt
import numpy as np
from PIL import Image

def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
    since = time()

    # Create a temporary directory to save training checkpoints
    with TemporaryDirectory() as tempdir:
        best_model_params_path = os.path.join(tempdir, 'best_model_params.pt')

        torch.save(model.state_dict(), best_model_params_path)
        best_acc = 0.0

        for epoch in range(num_epochs):
            print(f'Epoch {epoch}/{num_epochs - 1}')
            print('-' * 10)

            # Each epoch has a training and validation phase
            for phase in ['train', 'validate']:
                if phase == 'train':
                    model.train()  # Set model to training mode
                else:
                    model.eval()   # Set model to evaluate mode

                running_loss = 0.0
                running_corrects = 0

                # Iterate over data.
                for inputs, labels in dataloaders[phase]:
                    inputs = inputs.to(device)
                    labels = labels.to(device)

                    # zero the parameter gradients
                    optimizer.zero_grad()

                    # forward
                    # track history if only in train
                    with torch.set_grad_enabled(phase == 'train'):
                        outputs = model(inputs)
                        _, preds = torch.max(outputs, 1)
                        loss = criterion(outputs, labels)

                        # backward + optimize only if in training phase
                        if phase == 'train':
                            loss.backward()
                            optimizer.step()

                    # statistics
                    running_loss += loss.item() * inputs.size(0)
                    running_corrects += torch.sum(preds == labels.data)
                if phase == 'train':
                    scheduler.step()

                epoch_loss = running_loss / dataset_sizes[phase]
                epoch_acc = running_corrects.double() / dataset_sizes[phase]

                print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')

                # deep copy the model
                if phase == 'validate' and epoch_acc > best_acc:
                    best_acc = epoch_acc
                    torch.save(model.state_dict(), best_model_params_path)

            print()

        time_elapsed = time() - since
        print(f'Training complete in {time_elapsed // 60:.0f}m {time_elapsed % 60:.0f}s')
        print(f'Best val Acc: {best_acc:4f}')

        # load best model weights
        model.load_state_dict(torch.load(best_model_params_path))
    return model

def imshow(inp, title=None):
    """Display image for Tensor."""
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)
    if title is not None:
        plt.title(title)
    plt.pause(0.001)  # pause a bit so that plots are updated

def visualize_model_predictions(model,img_path):
    was_training = model.training
    model.eval()

    img = Image.open(img_path).convert('RGB')
    img = data_transforms['validate'](img)
    img = img.unsqueeze(0)
    img = img.to(device)

    with torch.no_grad():
        outputs = model(img)
        _, preds = torch.max(outputs, 1)

        ax = plt.subplot(2,2,1)
        ax.axis('off')
        ax.set_title(f'Predicted: {class_names[preds[0]]}')
        imshow(img.cpu().data[0])

        model.train(mode=was_training)
        plt.show()

if __name__ == '__main__':
    # Set device to GPU if available, else CPU
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    # Set up transforms that crop images, normalize them, and performs some flipping for training images
    data_transforms = {
        'train': transforms.Compose([
            transforms.RandomResizedCrop(224),
            transforms.RandomHorizontalFlip(),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ]),
        'validate': transforms.Compose([
            transforms.Resize(256),
            transforms.CenterCrop(224),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ]),
    }

    # Create datasets with labels
    image_datasets = {x: datasets.ImageFolder(x, data_transforms[x]) for x in ['train', 'validate']} # Training and validation for all items
    dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4, shuffle=True, num_workers=4) for x in ['train', 'validate']}
    dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'validate']}
    class_names = image_datasets['train'].classes
    print(class_names)

    # Get pre-built model
    model_conv = models.resnet18(weights='IMAGENET1K_V1')

    # Prep model for training by freezing layers
    for param in model_conv.parameters():
        param.requires_grad = False

    # Parameters of newly constructed modules have requires_grad=True by default
    num_ftrs = model_conv.fc.in_features
    model_conv.fc = nn.Linear(num_ftrs, len(class_names))

    model_conv = model_conv.to(device) # Put the model on CPU/ GPU for training

    criterion = nn.CrossEntropyLoss() # Loss function for training

    # Observe that only parameters of final layer are being optimized as opposed to before.
    optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.001, momentum=0.9)

    # Decay LR by a factor of 0.1 every 7 epochs
    exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv, step_size=7, gamma=0.1)

    # Train the model
    model_conv = train_model(model_conv, criterion, optimizer_conv, exp_lr_scheduler, num_epochs=25)

    # Test the model with images from the internet
    visualize_model_predictions(model_conv, "pasta-test.jpg")
    visualize_model_predictions(model_conv, "can-test.jpg")

    # Save model for Raspberry Pi
    scripted_model = torch.jit.script(model_conv)
    mobile_model = optimize_for_mobile(scripted_model)
    mobile_model._save_for_lite_interpreter("mobile_model.ptl")

    # Save model for computer
    torch.save(model_conv, "full_model.pth")


from backend import logger, constants
import requests
import json

enabled = False
api = ""
host = ""

def initialize():
    try:
        payload = {'ApiKey': api}
        request = requests.get(host+constants.OMBI_SYSTEM_STATUS, params=payload)
        if(request.status_code is not 200):
            raise Exception()
    except:
        logger.error(__name__, "Could not create a connection to Ombi: Please check config.ini")
        exit()


import logging

import hou
from marshmallow import Schema, ValidationError, fields

from .util import calcHash

class Hypnotic:
    def __init__(self, hipFile, schema=None):
        self.hipFile = hipFile
        self.hipFileHash = None
        self.controls = None
        self.out = None

        self._loadHip(self.hipFile)

        if schema:
            self.controlSchema = schema
        else:
            self._generateSchema()

    def _generateSchema(self):
        """
        Scan the control node to generate a validation schema for incoming data.
        """
        if self.controls:
            parms = self.controls.parms()
        else:
            logging.error("No control node in object. Is the hip file initialized?")
            return

        schemaDict = {}
        for parm in parms:
            value = parm.eval()
            if isinstance(value, str):
                schemaDict[parm.name()] = fields.String(required=False)
            elif isinstance(value, int):
                schemaDict[parm.name()] = fields.Integer(required=False)
            elif isinstance(value, float):
                schemaDict[parm.name()] = fields.Float(required=False)
            else:
                logging.info(f"Skipping parameter {parm.name()} of unrecognized type")

        self.controlSchema = Schema.from_dict(schemaDict)

    def _loadHip(self, file=None, hash=None):
        """
        Reload the hip file definition, or switch to a new one.
        Veify changes with hash if provided.
        """

        logging.info("Reloading the hip file")
        # Calculate the new files hash
        if file:
            newHash = calcHash(file)
        else:
            newHash = calcHash(self.hipFile)

        # if a hash has been provided to verify the new file, make sure they match
        if hash:
            if hash != newHash:
                logging.error("New file hash does not match provided one")
                return

        # If we are loading a new file and not reloading one in the same place update
        # our file name
        if file:
            self.hipFile = file

        # Actually load the file and save the hash to the object
        hou.hipFile.load(self.hipFile)
        self.controls = hou.node("/obj/CONTROLS")
        self.out = hou.node("/out/OUT")
        self.out_gl = hou.node("/out/OUT_GL")
        self.out_uv = hou.node("/out/OUT_UV")
        self.hipFileHash = newHash

    def _detectChanges(self):
        """
        Detects changes in the hip file during runtime.
        """
        md5_hash = calcHash(self.hipFile)

        return not md5_hash == self.hipFileHash

    def _reloadIfChanged(self):
        """
        Re-imports the hip file if it has been changed externally since it was loaded.
        """
        if self._detectChanges():
            self._loadHip()

    def _validateControls(self, controls):
        """
        Ensure passed parameters will fit in the control node
        """

        try:
            self.controlSchema.load(controls)
            return True
        except ValidationError as err:
            logging.error(f"Controls did not pass Hypnotic validation.\n{err.messages}")
            return False

    def _writeControls(self, controls):
        """
        Write the parameters from a json request into the control node.
        """
        self._reloadIfChanged()
        controlsAreValid = self._validateControls(controls)
        if controlsAreValid:
            self.controls.setParms(controls)
        else:
            logging.error("Could not write controls. Reason: Invalid controls")

    def exportPrintFile(self, controls):
        """
        Export an obj file from the provided parameters.
        """
        self._writeControls(controls)

        outPath = f"$HIP/out/print/{self.hipFile.split('/')[-1].split('.')[0]}/out.obj"
        self.out.setParms({"sopoutput": outPath})

        self.out.render()

    def exportPreviewFile(self, controls):
        """
        Export a .glb file from the provided parameters.
        """
        self._writeControls(controls)

        outPath = f"$HIP/out/gl/{self.hipFile.split('/')[-1].split('.')[0]}/out.glb"
        self.out.setParms({"file": outPath})

        self.out_gl.render()


from setuptools import setup, find_packages

version = '0.0.1'

setup(name="django-tools",
      version=version,
      description='Various tools for dealing with SQL in django apps',
      classifiers=['Development Status :: 4 - Beta',
                   'Environment :: Web Environment',
                   'Intended Audience :: Developers',
                   'License :: OSI Approved :: MIT License',
                   'Operating System :: OS Independent',
                   'Programming Language :: Python',
                   'Framework :: Django',
                   'Topic :: Software Development :: Libraries :: Python Modules'],
      keywords='django sql orm',
      author='Shaun Duncan',
      author_email='shaun.duncan@gmail.com',
      url='https://github.com/shaunduncan/django-sqltools',
      license='MIT',
      packages=find_packages(),
)


from dataclasses import dataclass
from datetime import datetime
from typing import List

from interfaces import IObservable, IObserver

class RocketShip(IObservable):

    def __init__(self, name) -> None:
        self.name: str = name
        self.__has_launched : bool = False
        self.__observers : List[IObserver] = []

    @property
    def has_launched(self) -> bool:
        return self.__has_launched

    @has_launched.setter
    def has_launched(self, value : bool) -> None:
        self.__has_launched = value
        self.notify_observers()

    def add_observer(self, observer: IObserver) -> None:

        if observer not in self.__observers:
            self.__observers.append(observer)

    def remove_observer(self, observer: IObserver) -> None:

        if observer in self.observers:
            self.__observers.remove(observer)

    def notify_observers(self) -> None:
        for observer in self.__observers:
            observer.update()

@dataclass
class NasaScientist(IObserver):

    name: str
    rocket : RocketShip

    def update(self) -> None:
        if(self.rocket.has_launched == True):
            print(f"This is {self.name}. Inform that {self.rocket.name} has been launched.")

@dataclass
class NewsReporter(IObserver):

    name: str
    journal_name: str
    rocket : RocketShip

    def update(self) -> None:
        if(self.rocket.has_launched == True):
            print(
                f"This is {self.name} from the {self.journal_name} journal.",
                f"We passed the last night on the Nasa station and we are finally able that the {self.rocket.name} rocketship",
                f"has been launched at {datetime.now()}. \n The launching was a success!."
            )

if __name__ == '__main__':

    rocket = RocketShip('kepler')

    scientist = NasaScientist('Steven Shulz', rocket)
    reporter = NewsReporter('Emmily Gray', 'The Washington Post', rocket)

    rocket.add_observer(scientist)
    rocket.add_observer(reporter)

    rocket.has_launched = True


#!/usr/bin/env python
import os
import sys
import textwrap

import matplotlib.pyplot as plt
import pandas as pd
import scipy.cluster.hierarchy as shc
from Bio import SeqIO, Phylo
from Bio.SeqUtils.ProtParam import ProteinAnalysis
from ete3 import Tree, NodeStyle, TreeStyle, TextFace, CircleFace, faces, AttrFace

from phylofisher import help_formatter

def distance_matrix2tree(Z, names):
    """Return tree representation for distance matrix"""
    n = Z.shape[0] + 1
    i2n = [0] * (2 * n - 1)
    t = Tree()
    for i, (idx1, idx2, dist, sample_count) in enumerate(Z):
        idx1, idx2 = int(idx1), int(idx2)
        # create Tree object for tips / leaves
        if idx1 < n:
            i2n[idx1] = Tree(name=names[idx1])
        if idx2 < n:
            i2n[idx2] = Tree(name=names[idx2])
        # create new node
        t = Tree()
        # normalise distance
        dist1 = dist - i2n[idx1].get_farthest_leaf()[1]
        dist2 = dist - i2n[idx2].get_farthest_leaf()[1]
        # add children
        t.add_child(i2n[idx1], dist=dist1)
        t.add_child(i2n[idx2], dist=dist2)
        # store
        i2n[n + i] = t
    return t

def aa_comp_calc():
    peptides = ['A', 'G', 'P', 'S', 'T', 'C', 'F', 'W', 'Y', 'H', 'R', 'K', 'M', 'I', 'L', 'V', 'N', 'D', 'E', 'Q']
    if not os.path.isdir(args.output):
        os.mkdir(args.output)
    with open(args.input, 'r') as infile, open(f'{args.output}/aa_comp.tsv', 'w') as outfile:
        outfile.write('Taxon\t' + '\t'.join(peptides) + '\n')

        # Reads in input file
        for record in SeqIO.parse(infile, format=args.in_format):
            outfile.write(f'{record.id}\t')
            analysed_seq = ProteinAnalysis(str(record.seq))
            count_dict = analysed_seq.count_amino_acids()
            length = len(str(record.seq).replace("-", "").replace("X", "").replace("*", ""))
            out_str = ''

            # Loops through peptides and checks to see if it is in count_dict
            for pep in peptides:
                if pep in count_dict.keys():
                    out_str += f'{float(count_dict[pep]) / length}\t'
                else:
                    out_str += '0\t'

            outfile.write(out_str.strip() + '\n')

def parse_tree_colors():
    color_dict = {}
    tree_color_dict = {}
    meta_dict = {}

    with open(tree_colors, 'r') as infile:
        infile.readline()
        for line in infile:
            s_line = line.strip().split('\t')
            tree_color_dict[s_line[0]] = s_line[1]

    with open(metadata, 'r') as infile:
        infile.readline()
        for line in infile:
            s_line = line.strip().split('\t')
            meta_dict[s_line[0]] = s_line[2:4]

        if args.chimeras:
            with open(args.chimeras, 'r') as infile:
                for line in infile:
                    line = line.strip()
                    split_line = line.split('\t')
                    meta_dict[split_line[0]] = split_line[1:3]

    for k, v in meta_dict.items():
        if k in tree_color_dict.keys():
            color_dict[k] = tree_color_dict[k]
        elif v[0] in tree_color_dict.keys():
            color_dict[k] = tree_color_dict[v[0]]
        elif v[1] in tree_color_dict.keys():
            color_dict[k] = tree_color_dict[v[1]]
        else:
            sys.exit(f'{k} is not in the tree_color.tsv')

    return color_dict, tree_color_dict, meta_dict

def mylayout(node):
    # Internal Node formatting
    nstyle = NodeStyle()
    nstyle["size"] = 0
    nstyle["vt_line_width"] = 2
    nstyle["hz_line_width"] = 2

    if node.is_leaf():
        circle_face = CircleFace(10, color_dict[node.name], style='circle')
        faces.add_face_to_node(circle_face, node, position="branch-right", column=0)

        text_face = TextFace(meta_dict[node.name][0])
        text_face.fgcolor = color_dict[node.name]
        faces.add_face_to_node(text_face, node, position="branch-right", column=1)

        node.set_style(nstyle)

    elif node.is_root():
        node.dist = 0
        node.set_style(nstyle)

    else:
        node.set_style(nstyle)

def make_plot():
    df = pd.read_csv(f'{args.output}/aa_comp.tsv', sep="\t")
    df = df.set_index('Taxon')

    z = shc.linkage(df, method='ward')
    t = distance_matrix2tree(z, df.index.values)
    t.write(outfile=f'{args.output}/distance_matrix.tre')

    # Basic tree style
    ts = TreeStyle()
    ts.show_leaf_name = False
    ts.mode = "c"
    ts.show_scale = False
    ts.layout_fn = mylayout
    # PDF rendering
    t.render(f'{args.output}/aa_comp_calculator.pdf', w=183, units="mm", tree_style=ts)

if __name__ == '__main__':
    description = 'Calculates amino acid composition of the supplied super matrix'
    parser, optional, required = help_formatter.initialize_argparse(name='aa_comp_calculator.py',
                                                                    desc=description,
                                                                    usage='aa_comp_calculator.py '
                                                                          '[OPTIONS] -i <matrix>')

    required.add_argument('-i', '--input', required=True, type=str, metavar='matrix',
                          help=textwrap.dedent("""\
                          Path to input matrix for analysis.
                          """))
    required.add_argument('-d', '--database', type=str, metavar='<db_dir>', required=True,
                          help=textwrap.dedent("""\
                          Path to database directory.
                          """))

    optional.add_argument('--chimeras', type=str, metavar='chimera_info.tsv',
                          help=textwrap.dedent("""\
                          A .tsv containing a Unique ID, higher taxonomy, and lower taxonomy for each chimera within the input matrix.

                          Example:
                            Chimera_ID 1\tHigher_Tax\tLower_Tax
                            Chimera_ID 2\tHigher_Tax\tLower_Tax
                          """))
    optional.add_argument('-if', '--in_format', metavar='<format>', type=str, default='fasta',
                          help=textwrap.dedent("""\
                          Format of input matrix.
                          Options: fasta, nexus, phylip (names truncated at 10 characters),
                          Default: fasta
                          """))

    args = help_formatter.get_args(parser, optional, required, pre_suf=False, inp_dir=False)

    dfo = os.path.abspath(args.database)

    tree_colors = f'{dfo}/tree_colors.tsv'
    metadata = f'{dfo}/metadata.tsv'

    color_dict, tree_color_dict, meta_dict = parse_tree_colors()

    aa_comp_calc()
    make_plot()


import os
import sys
import pytest

from context import differ

class TestDiffer():
  base = "/tmp/differ/"

  @pytest.fixture(scope='function', autouse=True)
  def setup(self):
    """This function will be run before every test function in this class"""
    print("Running setup function")
    if os.path.exists(self.base):
      os.system("rm -rf {}".format(self.base))

  def test_utils_differ(self):
    """Test the basic functionality of differ.utils.Differ"""
    differ.utils.Differ(None, None)
    assert not differ.utils.Differ("FAIL", "FAIL")._valid
    obj = differ.utils.Differ(".", "FAIL")
    assert not obj._valid
    assert "NOT VALID" in repr(obj)
    assert not obj.extract_path(None, None)
    assert not obj.extract_path("Something", None)

    # Test with the same tar files
    obj = differ.utils.Differ(
      "tests/files/before.tgz",
      "tests/files/before.tgz",
      base=self.base)
    assert obj._valid
    assert "NOT VALID" not in repr(obj)
    obj.start()
    print(obj.path1_obj)
    assert not obj.changes.get_added()
    assert not obj.changes.get_removed()
    assert not obj.changes.get_removed()

    # Test with the different files
    before_items = [
      # TAR
      'before.tar',
      # TAR GZIP
      'before.tar.gz',
      'before.tgz',
      # TAR BZIP2
      'before.tar.bz2',
      # TAR LZMA
      'before.tar.xz',
      # ZIP
      'before.zip',
      # Directory
      'before',
    ]
    for f_name in before_items:
      obj = differ.utils.Differ(
        "tests/files/{}".format(f_name),
        "tests/files/after.tgz",
        base=self.base)
      assert obj._valid
      obj.start()
      assert len(obj.changes.get_added()) == 1
      assert len(obj.changes.get_removed()) == 1
      assert len(obj.changes.get_changed()) == 2
      assert os.path.exists(obj.summary_path)
      with open(obj.summary_path) as fp:
        summary = fp.read()
        assert "added 1" in summary
        assert "removed 1" in summary
        assert "changed 2" in summary

  def test_differ_stat_changes(self):
    """Test differ where there are mode changes"""
    obj = differ.utils.Differ(
      "tests/files/stat_changes/before",
      "tests/files/stat_changes/after",
      base=self.base)
    assert obj._valid
    obj.start()
    assert len(obj.changes.get_added()) == 0
    assert len(obj.changes.get_removed()) == 0
    assert len(obj.changes.get_changed()) == 1
    assert len(obj.changes.get_changed_stat()) == 2
    assert os.path.exists(obj.summary_path)
    with open(obj.summary_path) as fp:
      summary = fp.read()
      assert "added 0" in summary
      assert "removed 0" in summary
      assert "changed 1" in summary
      assert "changed stat 2" in summary

  def test_differ_fifo(self):
    """Test differ where there are fifo files"""

    # Create 2 directories
    before = os.path.join(self.base, "before_tmp")
    after = os.path.join(self.base, "after_tmp")
    os.makedirs(before)
    os.makedirs(after)

    os.system("mkfifo {}/test_file".format(before))
    os.system("touch {}/test_file".format(after))

    obj = differ.utils.Differ(
      before,
      after,
      base=self.base)
    assert obj._valid
    obj.start()
    assert len(obj.changes.get_added()) == 0
    assert len(obj.changes.get_removed()) == 0
    assert len(obj.changes.get_changed()) == 0
    assert len(obj.changes.get_changed_stat()) == 1
    assert os.path.exists(obj.summary_path)
    with open(obj.summary_path) as fp:
      summary = fp.read()
      assert "added 0" in summary
      assert "removed 0" in summary
      assert "changed 0" in summary
      assert "changed stat 1" in summary

    changed_path = obj.changes.get_changed_stat()[0]
    change = obj.changes.changes.get(changed_path)
    assert change.related
    with open(change.related[0], 'r') as fp:
      mode = fp.read()
      assert 'fifo => regular' in mode

  def test_plugin_iptables_strip(self):
    """Test the plugin iptables stripper"""
    obj = differ.utils.Differ(
      "tests/files/plugins/iptables/before.tgz",
      "tests/files/plugins/iptables/after.tgz",
      base=self.base)
    assert obj._valid
    obj.start()
    assert len(obj.changes.get_added()) == 0
    assert len(obj.changes.get_removed()) == 0
    assert len(obj.changes.get_changed()) == 1
    assert os.path.exists(obj.diff_dir)

    changed_path = os.path.join(
      obj.changed_dir,
      "my_iptables_mangle.diff")
    with open(changed_path, "r") as fp:
      changed_content = fp.read()
    assert "-DIFFER-TEST-3" in changed_content
    assert "+DIFFER-TEST-4" in changed_content


import numpy as np, pandas as pd
import streamlit as st

df=pd.read_csv("bid.csv")
#find the highest bidder for each commodity
df1=df.groupby("Commodity")["Bidding Price"].max()
print(df1)
print("------------------------------------------------------")
#extract the userid, commodity and bid price for the highest bidder
df2=df[df["Bidding Price"].isin(df1)]
print(df2)

for i in range(0,len(df2['Userid'])):
    if df2['Userid'].iloc[i] == userid:
        st.write("You are the highest bidder for", df2['Commodity'].iloc[i])
        continue


import time

import cairocffi as cairo

from cairotft import linuxfb
from cairotft import transitions

class Marquee():

    """A text marquee widget."""

    def __init__(self, display_object, text,
                 font_face, font_size,
                 text_color,
                 pos_x, pos_y, width, height,
                 background_color=(1, 1, 1, 1),
                 step=1,
                 interval_time=0.05,
                 transition=transitions.LinearTransition.ease_in,
                 smooth=False):
        """Initialisation of the Marquee.

        :param display_object: the Display class instanciation.
        :type display_object: :class:`cairotft.tft.TftDisplay`
        :param str text: the text to display.
        :param font_face: the font face used for the text
        :type font_face: :class:`cairocffi.ToyFontFace`
        :param tuple text_color: a tuple of 4 float representing the rgba
            value of the text color.
        :param int font_size: the size of the font.
        :param int pos_x: x coordinates of the text box (top left corner)
        :param int pos_y: y coordinates of the text box (top left corner)
        :param int width: the width of the text box
        :param int height: the height of the text box
        :param tuple background_color: a tuple of 4 float representing the
            rgba value of the background color to repaint the icon.
        :param int step: number of unit we skip at each frame. (X chars if
            smooth is False, X pixels if smooth is True)
        :param float interval_time: the time in s between two frames

        TODO: instead of repainting with a color, save the background or paint
              in order: from bottom to top.
        """
        self._stop = False
        self._showing = False
        self._pos = 0
        self.display_object = display_object
        self.text = text
        self.full_text = self.text + "   " + self.text
        self._shrinked_text = text
        self.font_face = font_face
        self.font_size = font_size
        self.text_color = text_color
        self.background_color = background_color
        self._old_text_color = text_color
        self._old_background_color = background_color
        self.pos_x = pos_x
        self.pos_y = pos_y
        self.width = width
        self.height = height
        self.step = step
        self.smooth = smooth
        self.max_offset = len(self.text) + 3
        self.interval_time = interval_time
        self.transition = transition
        self._transition_time = None
        self._first_time = None
        self._should_scroll = False  # if False, text does not scroll

    def _smooth_init_buffer(self, ctx):
        """Initialise the buffer used in smooth text."""
        # in smooth case, we need to instanciate specific buffers
        # at first, determine the width and height we need:
        ctx.set_font_size(self.font_size)
        ctx.set_font_face(self.font_face)
        (_, _, small_text_width, _,
         x_advance_text, _) = ctx.text_extents(self.text + "   ")
        self.smooth_text_width = int(x_advance_text)
        self.max_offset = self.smooth_text_width

        (_, y_bearing,
         width, height,
         x_advance, _) = ctx.text_extents(self.full_text)
        self.smooth_full_height = int(height)
        self.smooth_full_width = int(max(x_advance, width))
        self.smooth_pos_y = int(-y_bearing)
        stride = cairo.ImageSurface.format_stride_for_width(
            self.display_object.cairo_format, int(x_advance))
        textbuffer = linuxfb.memory_buffer(
            int(stride * self.smooth_full_height))
        self.smooth_textsurf = cairo.ImageSurface(
            format=self.display_object.cairo_format,
            width=self.smooth_full_width,
            height=self.smooth_full_height,
            data=textbuffer,
            stride=stride)
        # if the text does not fit inside the target box, we will scrool it
        if small_text_width > self.width:
            self._should_scroll = True
            self._transition_time = (self.interval_time *
                                     self.smooth_text_width / self.step)

        # creates the context for this memory surface and draw the text
        self.smooth_text_ctx = cairo.Context(self.smooth_textsurf)
        self._smooth_draw_text()

    def _smooth_draw_text(self):
        """draw the text in the smooth text buffer.

        This is not display, only drawed in the smooth buffer.
        """
        # background
        self.smooth_text_ctx.set_source_rgba(*self.background_color)
        self.smooth_text_ctx.rectangle(0,
                                       0,
                                       self.smooth_full_width,
                                       self.smooth_full_height)
        self.smooth_text_ctx.fill()
        # text
        self.smooth_text_ctx.set_font_size(self.font_size)
        self.smooth_text_ctx.set_font_face(self.font_face)
        self.smooth_text_ctx.set_source_rgba(*self.text_color)
        self.smooth_text_ctx.move_to(0, self.smooth_pos_y)
        if self._should_scroll:
            self.smooth_text_ctx.show_text(self.full_text)
        else:
            self.smooth_text_ctx.show_text(self.text)

    def _shrink_text(self, ctx):
        """based on the font size, calculate the width of the text.

        And eventually shrink the name if too long.
        """
        # same font face here than the one displayed.
        if self._pos > 0:
            new_text = (self.full_text)[self._pos:]
        else:
            new_text = self.text[self._pos:]
        while True:
            (_, _, _, _, x_advance, _) = ctx.text_extents(new_text)
            if x_advance > self.width:
                new_text = new_text[:-1]  # + '…'
            else:
                break
        self._shrinked_text = new_text
        if self._shrinked_text != self.text:
            self._should_scroll = True
            self._transition_time = (self.interval_time *
                                     len(self.text + "   ") / self.step)

    def change_color(self, color):
        """Change the text color.

        :param tuple color: a tuple of 4 float representing the
            rgba value of the text color.
        """
        self._old_text_color = self.text_color
        self.text_color = color
        if self.smooth and self._showing:
            self._smooth_draw_text()

    def change_background(self, background_color):
        """Change the background color.

        :param tuple background_color: a tuple of 4 float representing the
            rgba value of the background color to repaint the icon.
        """
        self._old_background_color = self.background_color
        self.background_color = background_color
        if self.smooth and self._showing:
            self._smooth_draw_text()

    def color_changed(self):
        """Return True is either text_color or background_color has changed.

        (since last paint)
        """
        if (self._old_text_color != self.text_color or
                self._old_background_color != self.background_color):
            return True

    def show(self, ctx, no_loop=False):
        """Show the text."""
        if not self._stop and self._showing:
            # here at each frame, move the text and display it.
            if (self.color_changed or self._should_scroll) and not self.smooth:
                # erase the text box
                ctx.set_source_rgba(*self.background_color)
                ctx.rectangle(self.pos_x, self.pos_y, self.width, self.height)
                ctx.fill()

                # display text
                ctx.set_source_rgba(*self.text_color)
                ctx.set_font_size(self.font_size)
                ctx.set_font_face(self.font_face)

                self._shrink_text(ctx)
                ctx.move_to(
                    self.pos_x,
                    (self.pos_y +
                     (self.height - self.font_size) / 2 +
                     self.font_size) - 2)
                ctx.show_text(self._shrinked_text)
                self.display_object.blit()
            elif (self.color_changed or self._should_scroll) and self.smooth:
                # erase the text box
                ctx.set_source_rgba(*self.background_color)
                ctx.rectangle(self.pos_x, self.pos_y, self.width, self.height)
                ctx.fill()
                ctx.set_source_surface(
                    self.smooth_textsurf.create_for_rectangle(
                        self._pos, 0, self.width, self.smooth_full_height),
                    self.pos_x,
                    (self.pos_y +
                     (self.height - self.smooth_full_height) / 2) + 1)
                ctx.paint()
                self.display_object.blit()

            if self._should_scroll:  # only cycle when text is too long.
                now = time.time()
                if self._first_time is not None:
                    transition_offset = self.transition(
                        (now - self._first_time) /
                        self._transition_time)
                else:
                    transition_offset = self.transition(0)
                    self._first_time = now
                self._pos = int(self.max_offset * transition_offset)
                if (now - self._first_time) > self._transition_time:
                    self._first_time = now  # recycle
                    self._pos = int(self.max_offset * self.transition(0))

            self._old_text_color = self.text_color
            self._old_background_color = self.background_color

            if not no_loop:
                if self.display_object.fps is not None:
                    interval_time = max(self.interval_time,
                                        1 / self.display_object.fps)
                else:
                    interval_time = self.interval_time
                self.display_object.loop.call_later(
                    interval_time, self.show, ctx)

    def start(self, ctx):
        """Start showing the marquee."""
        if not self._showing:
            self._showing = True
            self._stop = False
            self._first_time = None
            if self.smooth:
                self._smooth_init_buffer(ctx)
            else:
                self._shrink_text(ctx)

            self.display_object.loop.call_soon(
                self.show, ctx)

    def stop(self):
        """stop showing the marquee."""
        self._stop = True
        self._showing = False
        self._first_time = None


from django.contrib import admin
from django.urls import path, include, re_path
from django.conf import settings
from django.views.static import serve
from django.contrib.auth import views as auth_views
# from django.contrib.auth.views import LoginView
# from django.contrib.auth.views import login, logout_then_login, password_reset, password_reset_done, password_reset_confirm, password_reset_complete

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('Aplicaciones.Usuario.urls')),
    path('', include('Aplicaciones.report.urls')),
    # path('accounts/login/', auth_views.LoginView.as_view()),
    # path('accounts/login/', auth_views.LoginView.as_view(template_name='Aplicaciones/Usuario/templates/login.html')),
    path("accounts/login/", auth_views.LoginView.as_view(template_name = "login.html"), name='login'),
    path("^logout/$", auth_views.LogoutView.as_view(next_page = 'login'), name= 'logout'),
    # path('login', LoginView.as_view(),{'template_name':'login.html'}, name = 'login')
]

urlpatterns += [
    re_path(r'^media/(?P<path>.*)$', serve, {
        'document_root': settings.MEDIA_ROOT,
    })
]


# coding=utf-8

__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'

import logging
import os
import threading
import time
import fnmatch
import datetime
import sys
import shutil
import Queue
import requests

import octoprint.util as util

from octoprint.settings import settings
from octoprint.events import eventManager, Events
import sarge

# currently configured timelapse
current = None

def getFinishedTimelapses():
	files = []
	basedir = settings().getBaseFolder("timelapse")
	for osFile in os.listdir(basedir):
		if not fnmatch.fnmatch(osFile, "*.mpg"):
			continue
		statResult = os.stat(os.path.join(basedir, osFile))
		files.append({
			"name": osFile,
			"size": util.get_formatted_size(statResult.st_size),
			"bytes": statResult.st_size,
			"date": util.get_formatted_datetime(datetime.datetime.fromtimestamp(statResult.st_ctime))
		})
	return files

validTimelapseTypes = ["off", "timed", "zchange"]

updateCallbacks = []

def registerCallback(callback):
	if not callback in updateCallbacks:
		updateCallbacks.append(callback)

def unregisterCallback(callback):
	if callback in updateCallbacks:
		updateCallbacks.remove(callback)

def notifyCallbacks(timelapse):
	if timelapse is None:
		config = None
	else:
		config = timelapse.config_data()
	for callback in updateCallbacks:
		try: callback.sendTimelapseConfig(config)
		except: logging.getLogger(__name__).exception("Exception while pushing timelapse configuration")

def configureTimelapse(config=None, persist=False):
	global current

	if config is None:
		config = settings().get(["webcam", "timelapse"])

	if current is not None:
		current.unload()

	type = config["type"]

	postRoll = 0
	if "postRoll" in config and config["postRoll"] >= 0:
		postRoll = config["postRoll"]

	fps = 25
	if "fps" in config and config["fps"] > 0:
		fps = config["fps"]

	if type is None or "off" == type:
		current = None
	elif "zchange" == type:
		current = ZTimelapse(post_roll=postRoll, fps=fps)
	elif "timed" == type:
		interval = 10
		if "options" in config and "interval" in config["options"] and config["options"]["interval"] > 0:
			interval = config["options"]["interval"]
		current = TimedTimelapse(post_roll=postRoll, interval=interval, fps=fps)

	notifyCallbacks(current)

	if persist:
		settings().set(["webcam", "timelapse"], config)
		settings().save()

class Timelapse(object):
	QUEUE_ENTRY_TYPE_CAPTURE = "capture"
	QUEUE_ENTRY_TYPE_CALLBACK = "callback"

	def __init__(self, post_roll=0, fps=25):
		self._logger = logging.getLogger(__name__)
		self._image_number = None
		self._in_timelapse = False
		self._gcode_file = None

		self._post_roll = post_roll
		self._on_post_roll_done = None

		self._capture_dir = settings().getBaseFolder("timelapse_tmp")
		self._movie_dir = settings().getBaseFolder("timelapse")
		self._snapshot_url = settings().get(["webcam", "snapshot"])
		self._ffmpeg_threads = settings().get(["webcam", "ffmpegThreads"])

		self._fps = fps

		self._render_thread = None

		self._capture_mutex = threading.Lock()
		self._capture_queue = Queue.Queue()
		self._capture_queue_active = True

		self._capture_queue_thread = threading.Thread(target=self._capture_queue_worker)
		self._capture_queue_thread.daemon = True
		self._capture_queue_thread.start()

		# subscribe events
		eventManager().subscribe(Events.PRINT_STARTED, self.on_print_started)
		eventManager().subscribe(Events.PRINT_FAILED, self.on_print_done)
		eventManager().subscribe(Events.PRINT_DONE, self.on_print_done)
		eventManager().subscribe(Events.PRINT_RESUMED, self.on_print_resumed)
		for (event, callback) in self.event_subscriptions():
			eventManager().subscribe(event, callback)

	@property
	def post_roll(self):
		return self._post_roll

	@property
	def fps(self):
		return self._fps

	def unload(self):
		if self._in_timelapse:
			self.stop_timelapse(doCreateMovie=False)

		# unsubscribe events
		eventManager().unsubscribe(Events.PRINT_STARTED, self.on_print_started)
		eventManager().unsubscribe(Events.PRINT_FAILED, self.on_print_done)
		eventManager().unsubscribe(Events.PRINT_DONE, self.on_print_done)
		eventManager().unsubscribe(Events.PRINT_RESUMED, self.on_print_resumed)
		for (event, callback) in self.event_subscriptions():
			eventManager().unsubscribe(event, callback)

	def on_print_started(self, event, payload):
		"""
		Override this to perform additional actions upon start of a print job.
		"""
		self.start_timelapse(payload["file"])

	def on_print_done(self, event, payload):
		"""
		Override this to perform additional actions upon the stop of a print job.
		"""
		self.stop_timelapse(success=(event==Events.PRINT_DONE))

	def on_print_resumed(self, event, payload):
		"""
		Override this to perform additional actions upon the pausing of a print job.
		"""
		if not self._in_timelapse:
			self.start_timelapse(payload["file"])

	def event_subscriptions(self):
		"""
		Override this method to subscribe to additional events by returning an array of (event, callback) tuples.

		Events that are already subscribed:
		  * PrintStarted - self.onPrintStarted
		  * PrintResumed - self.onPrintResumed
		  * PrintFailed - self.onPrintDone
		  * PrintDone - self.onPrintDone
		"""
		return []

	def config_data(self):
		"""
		Override this method to return the current timelapse configuration data. The data should have the following
		form:

		    type: "<type of timelapse>",
		    options: { <additional options> }
		"""
		return None

	def start_timelapse(self, gcodeFile):
		self._logger.debug("Starting timelapse for %s" % gcodeFile)
		self.clean_capture_dir()

		self._image_number = 0
		self._in_timelapse = True
		self._gcode_file = os.path.basename(gcodeFile)

	def stop_timelapse(self, doCreateMovie=True, success=True):
		self._logger.debug("Stopping timelapse")

		self._in_timelapse = False

		def resetImageNumber():
			self._image_number = None

		def createMovie():
			self._render_thread = threading.Thread(target=self._create_movie, kwargs={"success": success})
			self._render_thread.daemon = True
			self._render_thread.start()

		def resetAndCreate():
			resetImageNumber()
			createMovie()

		def waitForCaptures(callback):
			self._capture_queue.put(dict(type=self.__class__.QUEUE_ENTRY_TYPE_CALLBACK, callback=callback))

		def getWaitForCaptures(callback):
			def f():
				waitForCaptures(callback)
			return f

		if self._post_roll > 0:
			eventManager().fire(Events.POSTROLL_START, dict(postroll_duration=self.calculate_post_roll(), postroll_length=self.post_roll, postroll_fps=self.fps))
			self._post_roll_start = time.time()
			if doCreateMovie:
				self._on_post_roll_done = getWaitForCaptures(resetAndCreate)
			else:
				self._on_post_roll_done = resetImageNumber
			self.process_post_roll()
		else:
			self._post_roll_start = None
			if doCreateMovie:
				waitForCaptures(resetAndCreate)
			else:
				resetImageNumber()

	def calculate_post_roll(self):
		return None

	def process_post_roll(self):
		self.post_roll_finished()

	def post_roll_finished(self):
		if self.post_roll:
			eventManager().fire(Events.POSTROLL_END)
			if self._on_post_roll_done is not None:
				self._on_post_roll_done()

	def captureImage(self):
		if self._capture_dir is None:
			self._logger.warn("Cannot capture image, capture directory is unset")
			return

		with self._capture_mutex:
			if self._image_number is None:
				self._logger.warn("Cannot capture image, image number is unset")
				return

			filename = os.path.join(self._capture_dir, "tmp_%05d.jpg" % self._image_number)
			self._image_number += 1

		self._logger.debug("Capturing image to %s" % filename)
		entry = dict(type=self.__class__.QUEUE_ENTRY_TYPE_CAPTURE,
		             filename=filename,
		             onerror=self._on_capture_error)
		self._capture_queue.put(entry)
		return filename

	def _on_capture_error(self):
		with self._capture_mutex:
			if self._image_number is not None and self._image_number > 0:
				self._image_number -= 1

	def _capture_queue_worker(self):
		while self._capture_queue_active:
			entry = self._capture_queue.get(block=True)

			if entry["type"] == self.__class__.QUEUE_ENTRY_TYPE_CAPTURE and "filename" in entry:
				filename = entry["filename"]
				onerror = entry.pop("onerror", None)
				self._perform_capture(filename, onerror=onerror)

			elif entry["type"] == self.__class__.QUEUE_ENTRY_TYPE_CALLBACK and "callback" in entry:
				args = entry.pop("args", [])
				kwargs = entry.pop("kwargs", dict())
				entry["callback"](*args, **kwargs)

	def _perform_capture(self, filename, onerror=None):
		eventManager().fire(Events.CAPTURE_START, {"file": filename})
		try:
			self._logger.debug("Going to capture %s from %s" % (filename, self._snapshot_url))
			r = requests.get(self._snapshot_url, stream=True)
			with open (filename, "wb") as f:
				for chunk in r.iter_content(chunk_size=1024):
					if chunk:
						f.write(chunk)
						f.flush()
			self._logger.debug("Image %s captured from %s" % (filename, self._snapshot_url))
		except:
			self._logger.exception("Could not capture image %s from %s" % (filename, self._snapshot_url))
			if callable(onerror):
				onerror()
			eventManager().fire(Events.CAPTURE_FAILED, {"file": filename})
			return False
		else:
			eventManager().fire(Events.CAPTURE_DONE, {"file": filename})
			return True

	def _create_movie(self, success=True):
		ffmpeg = settings().get(["webcam", "ffmpeg"])
		bitrate = settings().get(["webcam", "bitrate"])
		if ffmpeg is None or bitrate is None:
			self._logger.warn("Cannot create movie, path to ffmpeg or desired bitrate is unset")
			return

		input = os.path.join(self._capture_dir, "tmp_%05d.jpg")
		if success:
			output = os.path.join(self._movie_dir, "%s_%s.mpg" % (os.path.splitext(self._gcode_file)[0], time.strftime("%Y%m%d%H%M%S")))
		else:
			output = os.path.join(self._movie_dir, "%s_%s-failed.mpg" % (os.path.splitext(self._gcode_file)[0], time.strftime("%Y%m%d%H%M%S")))

		# prepare ffmpeg command
		command = [
			ffmpeg, '-framerate', str(self._fps), '-loglevel', 'error', '-i', input, '-vcodec', 'mpeg2video', '-threads', str(self._ffmpeg_threads), '-pix_fmt', 'yuv420p', '-r', str(self._fps), '-y', '-b', bitrate,
			'-f', 'vob']

		filters = []

		# flip video if configured
		if settings().getBoolean(["webcam", "flipH"]):
			filters.append('hflip')
		if settings().getBoolean(["webcam", "flipV"]):
			filters.append('vflip')
		if settings().getBoolean(["webcam", "rotate90"]):
			filters.append('transpose=2')

		# add watermark if configured
		watermarkFilter = None
		if settings().getBoolean(["webcam", "watermark"]):
			watermark = os.path.join(os.path.dirname(__file__), "static", "img", "watermark.png")
			if sys.platform == "win32":
				# Because ffmpeg hiccups on windows' drive letters and backslashes we have to give the watermark
				# path a special treatment. Yeah, I couldn't believe it either...
				watermark = watermark.replace("\\", "/").replace(":", "\\\\:")

			watermarkFilter = "movie=%s [wm]; [%%(inputName)s][wm] overlay=10:main_h-overlay_h-10" % watermark

		filterstring = None
		if len(filters) > 0:
			if watermarkFilter is not None:
				filterstring = "[in] %s [postprocessed]; %s [out]" % (",".join(filters), watermarkFilter % {"inputName": "postprocessed"})
			else:
				filterstring = "[in] %s [out]" % ",".join(filters)
		elif watermarkFilter is not None:
			filterstring = watermarkFilter % {"inputName": "in"} + " [out]"

		if filterstring is not None:
			self._logger.debug("Applying videofilter chain: %s" % filterstring)
			command.extend(["-vf", sarge.shell_quote(filterstring)])

		# finalize command with output file
		self._logger.debug("Rendering movie to %s" % output)
		command.append("\"" + output + "\"")
		eventManager().fire(Events.MOVIE_RENDERING, {"gcode": self._gcode_file, "movie": output, "movie_basename": os.path.basename(output)})

		command_str = " ".join(command)
		self._logger.debug("Executing command: %s" % command_str)

		try:
			p = sarge.run(command_str, stderr=sarge.Capture())
			if p.returncode == 0:
				eventManager().fire(Events.MOVIE_DONE, {"gcode": self._gcode_file, "movie": output, "movie_basename": os.path.basename(output)})
			else:
				returncode = p.returncode
				stderr_text = p.stderr.text
				self._logger.warn("Could not render movie, got return code %r: %s" % (returncode, stderr_text))
				eventManager().fire(Events.MOVIE_FAILED, {"gcode": self._gcode_file, "movie": output, "movie_basename": os.path.basename(output), "returncode": returncode, "error": stderr_text})
		except:
			self._logger.exception("Could not render movie due to unknown error")
			eventManager().fire(Events.MOVIE_FAILED, {"gcode": self._gcode_file, "movie": output, "movie_basename": os.path.basename(output), "returncode": 255, "error": "Unknown error"})

	def clean_capture_dir(self):
		if not os.path.isdir(self._capture_dir):
			self._logger.warn("Cannot clean capture directory, it is unset")
			return

		for filename in os.listdir(self._capture_dir):
			if not fnmatch.fnmatch(filename, "*.jpg"):
				continue
			os.remove(os.path.join(self._capture_dir, filename))

class ZTimelapse(Timelapse):
	def __init__(self, post_roll=0, fps=25):
		Timelapse.__init__(self, post_roll=post_roll, fps=fps)
		self._logger.debug("ZTimelapse initialized")

	def event_subscriptions(self):
		return [
			(Events.Z_CHANGE, self._on_z_change)
		]

	def config_data(self):
		return {
			"type": "zchange"
		}

	def process_post_roll(self):
		with self._capture_mutex:
			filename = os.path.join(self._capture_dir, "tmp_%05d.jpg" % self._image_number)
			self._image_number += 1

		if self._perform_capture(filename):
			for _ in range(self._post_roll * self._fps):
				newFile = os.path.join(self._capture_dir, "tmp_%05d.jpg" % self._image_number)
				self._image_number += 1
				shutil.copyfile(filename, newFile)

		Timelapse.process_post_roll(self)

	def _on_z_change(self, event, payload):
		self.captureImage()

class TimedTimelapse(Timelapse):
	def __init__(self, post_roll=0, interval=1, fps=25):
		Timelapse.__init__(self, post_roll=post_roll, fps=fps)
		self._interval = interval
		if self._interval < 1:
			self._interval = 1 # force minimum interval of 1s
		self._postroll_captures = 0
		self._timer = None
		self._logger.debug("TimedTimelapse initialized")

	@property
	def interval(self):
		return self._interval

	def config_data(self):
		return {
			"type": "timed",
			"options": {
				"interval": self._interval
			}
		}

	def on_print_started(self, event, payload):
		Timelapse.on_print_started(self, event, payload)
		if self._timer is not None:
			return

		self._logger.debug("Starting timer for interval based timelapse")
		from octoprint.util import RepeatedTimer
		self._timer = RepeatedTimer(self._interval, self._timer_task,
		                            run_first=True, condition=self._timer_active,
		                            on_finish=self._on_timer_finished)
		self._timer.start()

	def on_print_done(self, event, payload):
		self._postroll_captures = self.post_roll * self.fps
		Timelapse.on_print_done(self, event, payload)

	def calculate_post_roll(self):
		return self.post_roll * self.fps * self.interval

	def process_post_roll(self):
		pass

	def post_roll_finished(self):
		Timelapse.post_roll_finished(self)
		self._timer = None

	def _timer_active(self):
		return self._in_timelapse or self._postroll_captures > 0

	def _timer_task(self):
		self.captureImage()
		if self._postroll_captures > 0:
			self._postroll_captures -= 1

	def _on_timer_finished(self):
		self.post_roll_finished()


import cv2
import mediapipe as mp
import tensorflow as tf
import numpy as np
import math
import os
import csv
import matplotlib.pyplot as plt

class ApplicationTool():

    def __init__(self, list=[]):

        current_dir = os.path.dirname(os.path.abspath(__file__))
        self.parent_dir = os.path.dirname(current_dir)

        self.csv_path = self.parent_dir + '/result.csv'

        with open(self.csv_path, 'w', newline='') as csvfile:

            writer = csv.writer(csvfile)
            writer.writerow(list)

        self.counter1 = 0
        self.counter2 = 0

    def save_data(self, data):

        with open(self.csv_path, 'a', newline='') as csvfile:

            writer = csv.writer(csvfile)
            writer.writerow(data)

    def save_bar(self, title='Bar Chart', xlabel='gender', x=['man', 'woman'], ylabel="attract times", y=[6, 3]):

        plt.bar(x, y)

        plt.title(title)
        plt.xlabel(xlabel)
        plt.ylabel(ylabel)

        plt.savefig(self.parent_dir +"/result.png")


from __future__ import absolute_import, division, print_function

import time
import uuid

from vivarium.actor.control import ActorControl, AgentCommand

DEFAULT_BOOT = 'vivarium.environment.boot'

class ShepherdControl(ActorControl):
    """Send messages to agents in the system to control execution."""

    def __init__(self, actor_config):
        super(ShepherdControl, self).__init__(str(uuid.uuid1()), actor_config)

    def add_cell(self, agent_type, actor_config):
        self.add_agent(
            agent_type + '-' + str(uuid.uuid1()),
            agent_type,
            actor_config)

    def init_experiment(self, args, exp_config):
        default_experiment_id = exp_config.get('default_experiment_id', 'experiment')
        lattice_config = exp_config.get('lattice_config')
        environment_type = exp_config.get('environment_type')
        actor_config = exp_config.get('actor_config')

        actor_config['boot_config'].update(lattice_config)
        agents = exp_config['agents']

        # get from args
        experiment_id = args.get('experiment_id')
        if not experiment_id:
            experiment_id = self.get_experiment_id(default_experiment_id)

        print('Creating experiment id {}: {} environment, with agents {}\n'.format(
            experiment_id, environment_type, agents))

        # boot environment
        self.add_agent(experiment_id, environment_type, actor_config)
        time.sleep(10) # wait for the environment to boot

        actor_config['outer_id'] = experiment_id
        actor_config['boot'] = actor_config.get('boot', DEFAULT_BOOT)
        actor_config['working_dir'] = args['working_dir']

        # boot agents
        index = 0
        for agent_type, number in agents.items():
            for agent in range(number):
                self.add_cell(agent_type, dict(actor_config, **{
                    'seed': index}))
                index += 1

    def lattice_experiment(self, args, actor_config):
        # define experiment: environment type and agent type
        experiment_id = 'lattice_experiment'
        environment_type = 'lattice'
        agents = {
            'growth_division': 1}

        # overwrite default environment config
        lattice_config = {
            'name': 'lattice_experiment',
            'description': (
                'minimal growth_division agents are placed '
                'in a lattice environment')}

        exp_config = {
            'default_experiment_id': experiment_id,
            'lattice_config': lattice_config,
            'environment_type': environment_type,
            'actor_config': actor_config,
            'agents': agents}

        self.init_experiment(args, exp_config)

    def growth_division_experiment(self, args, actor_config):

        # define experimental environment and agents
        experiment_id = 'growth_division'
        environment_type = 'ecoli_core_glc'
        agents = {
            'growth_division': 1}

        # overwrite default environment config
        lattice_config = {
            'name': 'growth_division_experiment',
            'description': (
                'minimal growth_division agents are placed '
                'in a lattice environment')}

        exp_config = {
            'default_experiment_id': experiment_id,
            'lattice_config': lattice_config,
            'environment_type': environment_type,
            'actor_config': actor_config,
            'agents': agents}

        self.init_experiment(args, exp_config)

    def antibiotic_experiment(self, args, actor_config):
        experiment_id = 'antibiotic'
        environment_type = 'antibiotic_environment'
        agents = {'antibiotic_composite': 3}

        # overwrite default environment config
        lattice_config = {
            'name': 'antibiotic_experiment',
            'description': (
                'antibiotic_composite agents are placed in '
                'an environment with antibiotics'
            ),
        }

        exp_config = {
            'default_experiment_id': experiment_id,
            'lattice_config': lattice_config,
            'environment_type': environment_type,
            'actor_config': actor_config,
            'agents': agents,
        }

        self.init_experiment(args, exp_config)

    def ecoli_core_experiment(self, args, actor_config):

        # define experiment: environment type and agent type
        experiment_id = 'gluc-lact'
        environment_type = 'ecoli_core_glc'
        agents = {
            'shifter': 2}

        # overwrite default environment config
        lattice_config = {
            'name': 'ecoli_core_glc_lct',
            'description': (
                'glucose-lactose diauxic shifters are placed in '
                'a shallow environment with glucose and lactose. '
                'They start off with no LacY, and so uptake only '
                'glucose, but LacY is expressed upon depletion of '
                'glucose and they begin to uptake lactose. Cells '
                'have an e_coli_core BiGG metabolism, kinetic '
                'transport of glucose and lactose, and ode-based '
                'gene expression of LacY')}

        exp_config = {
            'default_experiment_id': experiment_id,
            'lattice_config': lattice_config,
            'environment_type': environment_type,
            'actor_config': actor_config,
            'agents': agents}

        self.init_experiment(args, exp_config)

    def chemotaxis_square(self, args, actor_config):
        # define experiment: environment type and agent type
        experiment_id = 'chemotaxis'
        environment_type = 'measp'
        agents = {
            'minimal_chemotaxis': 4}

        # overwrite default environment config
        lattice_config = {
            'name': 'chemotaxis square experiment',
            'description': (
                'a square environment with a static gradient of '
                'glucose and a-methyl-DL-aspartic acid (MeAsp) '
                'for observing chemotactic cells in action. Optimal '
                'chemotaxis is observed in a narrow range of CheA '
                'activity, where concentration of CheY-P falls into '
                'the operating range of flagellar motors.')}

        exp_config = {
            'default_experiment_id': experiment_id,
            'lattice_config': lattice_config,
            'environment_type': environment_type,
            'actor_config': actor_config,
            'agents': agents}

        self.init_experiment(args, exp_config)

    def chemotaxis_experiment(self, args, actor_config):
        # define experiment: environment type and agent type
        experiment_id = 'chemotaxis'
        environment_type = 'measp_long'
        agents = {
            'minimal_chemotaxis': 1,
            'motor': 1}

        # overwrite default environment config
        lattice_config = {
            'name': 'chemotaxis_experiment',
            'description': (
                'a long environment with a static gradient of glucose '
                'and a-methyl-DL-aspartic acid (MeAsp) for observing '
                'chemotactic cells in action. Optimal chemotaxis is '
                'observed in a narrow range of CheA activity, where '
                'concentration of CheY-P falls into the operating range '
                'of flagellar motors.')}

        exp_config = {
            'default_experiment_id': experiment_id,
            'lattice_config': lattice_config,
            'environment_type': environment_type,
            'actor_config': actor_config,
            'agents': agents}

        self.init_experiment(args, exp_config)

    def swarm_experiment(self, args, actor_config):
        # define experiment: environment type and agent type
        experiment_id = 'swarm'
        environment_type = 'measp_large'
        agents = {
            'minimal_chemotaxis': 1}

        # overwrite default environment config
        lattice_config = {
            'name': 'swarm_experiment',
            'description': (
                'a large experiment for running swarms of chemotactic cells')}

        exp_config = {
            'default_experiment_id': experiment_id,
            'lattice_config': lattice_config,
            'environment_type': environment_type,
            'actor_config': actor_config,
            'agents': agents}

        self.init_experiment(args, exp_config)

class EnvironmentCommand(AgentCommand):
    """
    Extend `AgentCommand` with new commands related to the experiments
    """

    def __init__(self, choices=[], description=''):
        full_description = '''
    Run an agent for the environmental context simulation.

    The commands are:
    `add --id OUTER_ID [--type T] [--config C]` ask the Shepherd to add an agent of
        type T with JSON configuration C to the environment OUTER_ID,
    `remove --id AGENT_ID` ask all Shepherds to remove agent AGENT_ID,
    `remove --prefix ID` ask all Shepherds to remove agents "ID*",
    `run [--id OUTER_ID]` start or resume one or all simulations,
    `pause [--id OUTER_ID]` pause one or all simulations,
    `divide --id AGENT_ID` ask a cell agent to divide,
    `shutdown [--id OUTER_ID]` shut down one or all environment agents and their
         connected agents,
    `experiment [--number N] [--type T] [--working-dir D]` ask the Shepherd to run
        a lattice environment with N agents of type T,
    'glc-g6p-experiment [--number N] [--type T]` ask the Shepherd to run a
        chemotaxis environment with N agents of type T
    'chemotaxis-experiment [--number N] [--type T]` ask the Shepherd to run a
        chemotaxis environment with N agents of type T
    ''' + description

        full_choices = [
            'growth-division-experiment',
            'ecoli-core-experiment',
            'chemotaxis-square',
            'chemotaxis-experiment',
            'swarm-experiment',
            'antibiotic_experiment',
            ] + choices

        super(EnvironmentCommand, self).__init__(
            full_choices,
            {}, # no additional args
            full_description)

    def experiment(self, args):
        self.require(args, 'number', 'working_dir')
        control = ShepherdControl({'kafka_config': self.get_kafka_config()})
        control.lattice_experiment(args, self.actor_config)
        control.shutdown()

    def growth_division_experiment(self, args):
        self.require(args, 'number')
        control = ShepherdControl({'kafka_config': self.get_kafka_config()})
        control.growth_division_experiment(args, self.actor_config)
        control.shutdown()

    def ecoli_core_experiment(self, args):
        self.require(args, 'number')
        control = ShepherdControl({'kafka_config': self.get_kafka_config()})
        control.ecoli_core_experiment(args, self.actor_config)
        control.shutdown()

    def chemotaxis_square(self, args):
        self.require(args, 'number')
        control = ShepherdControl({'kafka_config': self.get_kafka_config()})
        control.chemotaxis_square(args, self.actor_config)
        control.shutdown()

    def chemotaxis_experiment(self, args):
        self.require(args, 'number')
        control = ShepherdControl({'kafka_config': self.get_kafka_config()})
        control.chemotaxis_experiment(args, self.actor_config)
        control.shutdown()

    def swarm_experiment(self, args):
        self.require(args, 'number')
        control = ShepherdControl({'kafka_config': self.get_kafka_config()})
        control.swarm_experiment(args, self.actor_config)
        control.shutdown()

    def antibiotic_experiment(self, args):
        self.require(args, 'number')
        control = ShepherdControl({'kafka_config': self.get_kafka_config()})
        control.antibiotic_experiment(args, self.actor_config)
        control.shutdown()

    def add_arguments(self, parser):
        parser = super(EnvironmentCommand, self).add_arguments(parser)

        parser.add_argument(
            '-e', '--experiment_id',
            type=str,
            help='The experiment id')

        parser.add_argument(
            '-f', '--emit_field',
            type=str,
            default='minimal',
            help='emitted media field')

        parser.add_argument(
            '-m', '--media',
            type=str,
            default='minimal',
            help='The environment media')

        parser.add_argument(
            '-t', '--timeline',
            type=str,
            default=None,
            help='The timeline')

        return parser

def run():
    command = EnvironmentCommand()
    command.execute()

if __name__ == '__main__':
    run()


# coding:utf-8
from django.conf.urls import url
from django.contrib import admin
from . import paperViews

urlpatterns = [

    #  定义添加论文的url地址响应方法，跳转到 paperView的tianjiapaper方法
    url(r'^tianjiapaper/$', paperViews.tianjiapaper, name='tianjiapaper'),

    #  定义处理添加论文的url地址响应方法，跳转到 paperView的tianjiapaperact方法
    url(r'^tianjiapaperact/$', paperViews.tianjiapaperact, name='tianjiapaperact'),

    #  定义跳转修改论文的url地址响应方法，跳转到 paperView的xiugaipaper方法
    url(r'^xiugaipaper/(?P<id>[0-9]+)$', paperViews.xiugaipaper, name='xiugaipaper'),

    #  定义处理修改论文的url地址响应方法，跳转到 paperView的xiugaipaperat方法
    url(r'^xiugaipaperact/$', paperViews.xiugaipaperact, name='xiugaipaperact'),

    #  定义跳转管理论文的url地址响应方法，跳转到 paperView的paperguanli方法
    url(r'^paperguanli/$', paperViews.paperguanli, name='paperguanli'),

    #  定义跳转查看论文的url地址响应方法，跳转到 paperView的paperchakan方法
    url(r'^paperchakan/$', paperViews.paperchakan, name='paperchakan'),
    url(r'^papertuijian/$', paperViews.papertuijian, name='papertuijian'),

    #  定义处理删除论文的url地址响应方法，跳转到 paperView的shanchupaper方法
    url(r'^shanchupaperact/(?P<id>[0-9]+)$', paperViews.shanchupaperact, name='shanchupaperact'),

    #  定义跳转搜索论文的url地址响应方法，跳转到 paperView的sousuopaper方法
    url(r'^sousuopaper/$', paperViews.sousuopaper, name='sousuopaper'),

    #  定义论文详情的url地址响应方法，跳转到 paperView的paperxiangqing方法
    url(r'^paperxiangqing/(?P<id>[0-9]+)$', paperViews.paperxiangqing, name='paperxiangqing'),

    #  定义添加论文方法，响应页面请求，跳转到paperViews的usertianjiapaper方法
    url(r'^usertianjiapaper/$', paperViews.usertianjiapaper, name='usertianjiapaper'),

    #  定义处理添加论文方法，响应页面请求，跳转到paperViews的usertianjiapaperact方法
    url(r'^usertianjiapaperact/$', paperViews.usertianjiapaperact, name='usertianjiapaperact'),

    #  定义跳转修改论文方法，响应页面请求，跳转到paperViews的userxiugaipaper方法
    url(r'^userxiugaipaper/(?P<id>[0-9]+)$', paperViews.userxiugaipaper, name='userxiugaipaper'),

    #  定义处理修改论文方法，响应页面请求，跳转到paperViews的userxiugaipaperact方法
    url(r'^userxiugaipaperact/$', paperViews.userxiugaipaperact, name='userxiugaipaperact'),

    #  定义跳转论文管理方法，响应页面请求，跳转到paperViews的userpaperguanli方法
    url(r'^userpaperguanli/$', paperViews.userpaperguanli, name='userpaperguanli'),

    #  定义处理删除论文方法，响应页面请求，跳转到paperViews的usershanchupaper方法
    url(r'^usershanchupaperact/(?P<id>[0-9]+)$', paperViews.usershanchupaperact, name='usershanchupaperact'),
]


import csv
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score

TRAIN_TEST_COUNT = 650
TRAIN_PARTITION_FACTOR = 0.8

# Get data
all_predictors = []
all_responses = []
with open("diabetes.csv") as file:
    objects = csv.DictReader(file)
    for object in objects:
        all_predictors.append([float(object[predictor]) for predictor in object if predictor != "Outcome"])
        all_responses.append(int(object["Outcome"]))

predictors = all_predictors[:TRAIN_TEST_COUNT]
responses = all_responses[:TRAIN_TEST_COUNT]

train_predictors = predictors[:int(len(predictors) * TRAIN_PARTITION_FACTOR)]
test_predictors = predictors[int(len(predictors) * TRAIN_PARTITION_FACTOR):]
train_responses = responses[:int(len(responses) * TRAIN_PARTITION_FACTOR)]
test_responses = responses[int(len(responses) * TRAIN_PARTITION_FACTOR):]

print(f"Number of objects labeled '0' = {responses.count(0)}")

# Train
classifier = DecisionTreeClassifier(criterion="entropy", max_leaf_nodes=30, min_samples_leaf=20, random_state=2020)
classifier.fit(train_predictors, train_responses)
print(f"Tree depth = {classifier.tree_.max_depth}")

# Score
test_predictions = classifier.predict(test_predictors)
print(f"Accuracy = {accuracy_score(test_responses, test_predictions)}")
print(f"F1 score = {f1_score(test_responses, test_predictions, average='macro')}")

# Predict
for id in (743, 715, 740, 741):
    prediction = classifier.predict([all_predictors[id]])
    print(f"Prediction for id={id}: {prediction}")


import argparse
import json
import string

from collections import defaultdict
from nltk.tokenize import word_tokenize
from nltk.stem import SnowballStemmer

DEFAULT_DOCUMENTS_JSON = r"./documents.json"
DEFAULT_INDEX_JSON = r"./index.json"

def main():
    parser = argparse.ArgumentParser(description="builds an index file for documents")
    parser.add_argument("--input",
                        help='path to json with documents, default="' + DEFAULT_DOCUMENTS_JSON + '"',
                        default=DEFAULT_DOCUMENTS_JSON)
    parser.add_argument("--output",
                        help='where to store json with index, default="' + DEFAULT_INDEX_JSON + '"',
                        default=DEFAULT_INDEX_JSON)
    args = parser.parse_args()

    with open(args.input, "r") as json_file:
        documents = json.load(json_file)

    stemmer = SnowballStemmer("english")
    index = dict()

    for document in documents:
        text = document["title"] + " " + document["body"]

        # without punctuation tokens
        raw_tokens = [token for token in word_tokenize(text) if token not in string.punctuation]

        terms = [stemmer.stem(token) for token in raw_tokens]

        # from term to term frequency for the current document
        from_term_to_term_frequency = defaultdict(int)

        for term in terms:
            from_term_to_term_frequency[term] += 1

        for term, term_frequency in from_term_to_term_frequency.items():
            if term not in index:
                index[term] = {
                    "document_frequency": 0,
                    "term_frequency": {}
                }

            term_info = index[term]
            term_info["document_frequency"] += 1
            term_info["term_frequency"][document["id"]] = term_frequency
        pass

    with open(args.output, "wt") as index_json_file:
        json.dump(index, index_json_file)
    exit(0)

if __name__ == '__main__':
    main()


import kivy
import pandas as pd
kivy.require("1.9.0")
import theProgramAppFunctions as tpf
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivy.uix.listview import ListItemButton

"""
The Program App
Ryan Dunning
Version 0.1.0
"""

class TeamListButton(ListItemButton):
    pass

class TeamDB(BoxLayout):
    first_team_text_input = ObjectProperty()
    second_team_text_input = ObjectProperty()
    win_team= ObjectProperty()
    win_chance= ObjectProperty()
    first_score= ObjectProperty()
    second_score= ObjectProperty()
    team_search= ObjectProperty()
    team_list= ObjectProperty()

        #Make sure team is selected
        #Put Team Name in Text Box

    def choose_first_team(self):
        if self.team_list.adapter.selection:
            selection = self.team_list.adapter.selection[0].text
            self.first_team_text_input.text= selection
            #self.first_team_scores.text= selection + " Score: "

    def choose_second_team(self):
        if self.team_list.adapter.selection:
            selection = self.team_list.adapter.selection[0].text
            self.second_team_text_input.text= selection
            #self.second_team_scores.text= selection + " Score: "

    def run_program(self):
        all_teams= tpf.GetNCAABTeams()
        first_current_team= tpf.NCAABTeam()
        second_current_team= tpf.NCAABTeam()

        first_current_team.filldata(self.first_team_text_input.text, all_teams)
        second_current_team.filldata(self.second_team_text_input.text, all_teams)
        results= tpf.NCAABSimMatchup(first_current_team, second_current_team, all_teams)
        self.win_team.text= results[0]
        self.win_chance.text= str(results[1]) + "%"
        self.first_score.text= str(results[2])
        self.second_score.text= str(results[3])

    def search_for_teams(self):
        for x in range(len(self.team_list.adapter.data)):
            self.team_list.adapter.data.remove(self.team_list.adapter.data[0])
        all_teams= tpf.GetNCAABTeams()
        for x in range(len(all_teams)):
            team_name= all_teams[x]
            if self.team_search.text.lower() in team_name.lower():
                self.team_list.adapter.data.extend([team_name])
        self.team_search.text = ""
        self.team_list._trigger_reset_populate()

class ProgramApp(App):
    def build(self):
        return TeamDB()

dbApp = ProgramApp()
dbApp.run()