import random, ctypes
from tkinter import *

x = 900 #x value defines cat's initial x axis position
cycle = 0
check = 1
idle_num = [1, 2, 3, 4]
sleep_num = [10, 11, 12, 13, 15]
walk_left = [6, 7]
walk_right = [8, 9]
event_number = random.randrange(1, 3, 1)

#transfer random no. to event
def event(cycle, check, event_number, x):
    if event_number in idle_num:
        check = 0
        print('idle')
        window.after(400, update, cycle, check, event_number, x) #no. 1,2,3,4 = idle
    elif event_number == 5:
        check = 1
        print('from idle to sleep')
        window.after(100, update, cycle, check, event_number, x) #no. 5 = idle to sleep
    elif event_number in walk_left:
        check = 4
        print('walking towards left')
        window.after(100, update, cycle, check, event_number, x) #no. 6,7 = walk towards left
    elif event_number in walk_right:
        check = 5
        print('walking towards right')
        window.after(100, update, cycle, check, event_number, x) #no 8,9 = walk towards right
    elif event_number in sleep_num:
        check = 2
        print('sleep')
        window.after(1000, update, cycle, check, event_number, x) #no. 10,11,12,13,15 = sleep
    elif event_number == 14:
        check = 3
        print('from sleep to idle')
        window.after(100, update, cycle, check, event_number, x)#no. 15 = sleep to idle

#making gif work
def gif_work(cycle, frames, event_number, first_num, last_num):
    if cycle < len(frames) - 1:
        cycle += 1
    else:
        cycle = 0
        event_number = random.randrange(first_num, last_num + 1, 1)
    return cycle, event_number

#update animations
def update(cycle, check, event_number, x):
    #idle
    if check == 0:
        frame = idle[cycle]
        cycle, event_number = gif_work(cycle, idle, event_number, 1, 9)
    #idle to sleep
    elif check == 1:
        frame = idle_to_sleep[cycle]
        cycle, event_number = gif_work(cycle, idle_to_sleep, event_number, 10, 10)
    #sleep
    elif check == 2:
        frame = sleep[cycle]
        cycle, event_number = gif_work(cycle, sleep, event_number, 10, 15)
    #sleep to idle
    elif check == 3:
        frame = sleep_to_idle[cycle]
        cycle, event_number = gif_work(cycle, sleep_to_idle, event_number, 1, 1)
    #walk toward left
    elif check == 4:
        frame = walk_positive[cycle]
        cycle, event_number = gif_work(cycle, walk_positive, event_number, 1, 9)
        x -= 3
    #walk towards right
    elif check == 5:
        frame = walk_negative[cycle]
        cycle , event_number = gif_work(cycle, walk_negative, event_number, 1, 9)
        x -= -3

    #{window size} + {initial pos} + {y pos}
    window.geometry(f"100x100+{str(x)}+570")
    label.configure(image=frame)
    window.after(1, event, cycle, check, event_number, x)

window = Tk()

#call buddy's action gif
idle = [PhotoImage(file="./animations/idle.gif", format='gif -index %i' %(i)) for i in range(5)]
idle_to_sleep = [PhotoImage(file="./animations/idle_to_sleep.gif", format='gif -index %i' %(i)) for i in range(8)]
sleep = [PhotoImage(file="./animations/sleep.gif", format='gif -index %i' %(i)) for i in range(3)]
sleep_to_idle = [PhotoImage(file="./animations/sleep_to_idle.gif", format='gif -index %i' %(i)) for i in range(8)]
walk_positive = [PhotoImage(file="./animations/walking_positive.gif", format='gif -index %i' %(i)) for i in range(8)] #walk to left gif
walk_negative = [PhotoImage(file="./animations/walking_negative.gif", format='gif -index %i' %(i)) for i in range(8)] #walk to right gif

#window configuration
window.config(highlightbackground='black')
label = Label(window, bd=0, bg='black')
window.overrideredirect(True)
window.wm_attributes('-transparentcolor', 'black')
label.pack()

#loop the program
window.attributes('-topmost', True) #always visible
window.after(100, update, cycle, check, event_number, x)
window.mainloop()


"""
    job interview online assessment:

        parts assembly and required time

        problem:

        1. parts sizes are in an int array like [10,6,4, 20]
        2. assemble 2 parts at a time, a combined one will have the size = part1 size + part2 size
        3. time required = part1 size + part2 size
        4. return the minimum time required

"""

def get_requiredtime(parts):
    total_time = 0
    remaining_parts = parts
    while len(remaining_parts) > 1:
        x1 = remaining_parts[0]
        x2 = remaining_parts[1]
        new_parts = x1 + x2
        total_time += x1 + x2
        if len(remaining_parts) == 2:
            # you just finished
            break
        remaining_parts = [new_parts] + remaining_parts[2:]
    return total_time

if __name__ == '__main__':
    # test case 1
    test1 = [10,6,4,18]
    print(test1)
    print('required {} time unit'.format(get_requiredtime(test1)))


# decimal to binary w function

def binaryConverter(decimalNum):
	temp = 0
	list1 = []

	while decimalNum != 0:
		temp = decimalNum % 2
		list1.append(temp)
		decimalNum = decimalNum//2

	return list1[::-1]

print(binaryConverter(int(input('enter a decimal: '))))


import os
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
from flask import Flask, request, redirect, render_template

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///logs.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# Init SQLAlchemy
db = SQLAlchemy(app)

# Log data model
class Log(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    def __repr__(self):
        return f'<Log {self.id}>'

@app.route('/')
def index():
    return 'Welcome to the Work Log!'

@app.route('/create', methods=['GET', 'POST'])
def create():
    if request.method == 'POST':
        # 处理表单提交的数据
        title = request.form['title']
        content = request.form['content']
        # 将日志保存到数据库中
        log = Log(title=title, content=content)
        db.session.add(log)
        db.session.commit()
        return redirect('/')
    else:
        # 显示表单
        return '''
            <form method="post">
                Title
                <input type="text" name="title" id="title" />
                Content
                <textarea name="content" id="content"></textarea>
                <input type="submit" value="Create" />
            </form>
        '''

@app.route('/logs')
def logs():
    logs = Log.query.all()
    return render_template('logs.html', logs=logs)

@app.route('/logs/<int:id>')
def log(id):
    log = Log.query.get_or_404(id)
    return render_template('log.html', log=log)

if __name__ == '__main__':
    if not os.path.exists('logs.db'):
        open('logs.db', 'w').close()
        with app.app_context():
            model = Log
            db.create_all()

    app.run(debug=True)


from PIL import Image
from os import listdir

mypath = "static/img/merch/test"

onlyfiles = [f for f in listdir(mypath)]

for i in onlyfiles:
    foo = Image.open(f'{mypath}/{i}')
    print(foo.size)
    foo = foo.resize((500, 500))
    foo.save(f'static/img/merch/ready/{i}', optimize=True, quality=95)


import json
import os
import PIL
import PIL.Image
from IPython.display import display
from PIL import Image

import matplotlib.pyplot as plt

import pandas as pd
import numpy as np

from sklearn.model_selection import train_test_split

from collections import Counter

from keras_preprocessing.image import ImageDataGenerator
from keras.preprocessing import image
from keras.applications.vgg16 import VGG16, preprocess_input
from keras.models import Sequential, Model
from keras.layers import Conv2D, MaxPooling2D, GlobalAveragePooling2D
from keras_preprocessing.image import ImageDataGenerator
from keras.layers import Dense, Activation, Flatten, Dropout, Input

from keras import optimizers
from keras import metrics

from keras.models import load_model
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score

from keras.callbacks import ModelCheckpoint, ReduceLROnPlateau

model_name = "vgg16_majurca_2805_1130"

def getData():
    """ Function getData() uses the JSON info to select the dataset

    Parameters:
    None

    Returns:
    files : list
        List of the filenames of all the dataset
    labels : list
        List of the labels corrsponding to im_path

    """

    filename = "./dataset/majurca-ecoclassifier-assets.json"

    files = []
    labels = []

    # k will count the amount of ignored images
    k = 0
    v = 0
    # Read JSON data into the datastore variable
    if filename:
        with open(filename, "r") as f:
            list_info = json.load(f)

    str_filter = "192-168-0-31"

    for dico in list_info:
        # print(v)
        v += 1

        # Condition that keeps only images from top camera
        if str_filter in dico["path"]:

            # Condition that keeps only the concerned labels
            if dico["tag_slugs"] != [] and dico["tag_slugs"] in [
                ["godet-vide"],
                ["pet-fonce"],
                ["pet-clair"],
                ["pe-hd-opaque"],
            ]:

                if dico.get("thumbnail_320x200_path") != None:
                    str1 = dico["thumbnail_320x200_path"]
                    # Changes character in filename / Depending on OS
                    # str2 = str1.replace(":", "_")
                    files.append(str1)
                    labels.append(dico["tag_slugs"][0])

            else:
                # print('no label',dict['path'])
                k = k + 1
        else:
            # print('wrong camera',dict['path'])
            k = k + 1

    print("{} images were ignored".format(k))

    return files, labels

def splitData(X, y):
    """ Function splitData(X,y) creates a separated Test Set

    Parameters:
    X : list
        List of the filenames of the whole dataset
    y : list
        List of the labels of the whole dataset

    Returns:
    X_tv : list
        List of the filenames of the Training + Validation set (aka tv)
    y_tv : list
        List of the labels corresponding to the files in X_tv
    X_test : list
        List of the filenames that will be used to test/evaluate our model
    y_test : list
        List of the labels corresponding to the files in X_test

    """

    output_dir = "./dataset_split"

    # Splits the dataset in 2 with Stratify ( keeps classes repartition)
    X_tv, X_test, y_tv, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42, stratify=y
    )

    # Create folder for the test and training split
    if not os.path.exists(output_dir):
        os.mkdir(output_dir)
        os.mkdir(output_dir + "/test")
        os.mkdir(output_dir + "/train")

        # Copying files into test and train folder
        for file in X_test:
            os.system("cp ./dataset/" + file + " " + output_dir + "/test/" + file)
        for file in X_tv:
            os.system("cp ./dataset/" + file + " " + output_dir + "/train/" + file)

    # If the folder already exists, doesn't copy
    else:
        print("Warning: output dir {} already exists".format(output_dir))

    return X_tv, X_test, y_tv, y_test

def getModelVGG16():
    """ Function getModelVGG16() builds and compiles the model

    Parameters:
    None

    Returns:
    model :
        Model ready to be trained

    """

    # Loads the VGG16 pre-trained network
    base_model = VGG16(weights="imagenet", include_top=False, input_shape=(224, 224, 3))
    print("Model loaded.")

    # Builds a classifier model to put on top of the convolutional model
    top_model = Sequential()
    top_model.add(Flatten(input_shape=base_model.output_shape[1:]))
    top_model.add(Dense(256, activation="relu"))
    top_model.add(Dropout(0.5))
    top_model.add(Dense(4, activation="sigmoid"))

    # Adds the model on top of the convolutional base
    model = Model(inputs=base_model.input, outputs=top_model(base_model.output))

    # Sets the first 10 layers
    # to non-trainable (weights will not be updated)
    for layer in model.layers[:15]:
        layer.trainable = False

    model.summary()

    # Compiles the model with a Adam optimizer
    model.compile(
        loss="categorical_crossentropy",
        optimizer=optimizers.Adam(lr=3e-6),
        metrics=["accuracy"],
    )

    return model

def train(X, y, model):
    """ Function train(X,y) prepares the data and trains the model

    Parameters:
    X : list
        List of the filenames of the Training+Validation dataset
    y : list
        List of the labels of the Training+Validation dataset
    model :
        Model that will be trained

    Returns:
    model_trained:
        Trained model
    history:
        Keras history file that contains all the informations of the training

    """

    HEIGHT = 224  # 224 for resnet50 and VGG16 / 299 for InceptionV3
    WIDTH = 224  # 224 for resnet50 and VGG16 / 299 for inceptionV3
    BATCH_SIZE = 16
    EPOCHS = 40

    output_folder = "/artifacts/models/"

    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    CATEGORIES = ["godet-vide", "pet-clair", "pet-fonce", "pe-hd-opaque"]

    # Data augmentation parameters
    train_datagen = ImageDataGenerator(
        preprocessing_function=preprocess_input,
        featurewise_std_normalization=True,
        rotation_range=0,
        horizontal_flip=False,
        vertical_flip=False,
    )

    validation_datagen = ImageDataGenerator(preprocessing_function=preprocess_input)

    # DATA PREPARATION
    # With .flow_from_dataframe()
    n_train_samples = len(X) * 0.8
    n_val_samples = len(X) * 0.2
    target_labels = y

    datagen = ImageDataGenerator(
        preprocessing_function=preprocess_input, validation_split=0.2
    )
    # Conversion of the 2 lists in 1 pandas dataframe
    dataset_info = {"filename": X, "class": y}
    dataframe = pd.DataFrame(dataset_info)

    train_generator = datagen.flow_from_dataframe(
        dataframe,
        directory="./dataset_split/train/",
        subset="training",
        class_mode="categorical",
        classes=target_labels,
        target_size=(HEIGHT, WIDTH),
        batch_size=BATCH_SIZE,
    )

    validation_generator = datagen.flow_from_dataframe(
        dataframe,
        directory="./dataset_split/train/",
        subset="validation",
        class_mode="categorical",
        classes=target_labels,
        target_size=(HEIGHT, WIDTH),
        batch_size=BATCH_SIZE,
    )

    # Always saving the model with the best val_angle_error
    monitor = "val_loss"
    checkpointer = ModelCheckpoint(
        filepath=os.path.join(output_folder, model_name + ".hdf5"),
        monitor=monitor,
        verbose=2,
        mode="min",
        save_best_only=True,
    )
    reduce_lr = ReduceLROnPlateau(monitor=monitor, factor=0.3, patience=3)

    # Starts training
    history = model.fit_generator(
        train_generator,
        steps_per_epoch=n_train_samples // BATCH_SIZE,
        epochs=EPOCHS,
        validation_data=validation_generator,
        validation_steps=n_val_samples // BATCH_SIZE,
        verbose=2,
        callbacks=[checkpointer, reduce_lr],
    )

    return model, history

def plotHistory(history):
    """ Function plotHistory(history) plots the training loss/acc evolution

    Parameters:
    history: Keras history
        Contains all the info o the training

    Returns:
    None

    """

    # Summarize history for accuracy
    plt.plot(history.history["acc"])
    plt.plot(history.history["val_acc"])
    print(history.history["val_acc"][-1])
    plt.title("model accuracy")
    plt.ylabel("accuracy")
    plt.xlabel("epoch")
    plt.legend(["train", "validation"], loc="upper left")

    plt.savefig("/artifacts/logs/history_acc.png")

    # Summarize history for loss
    plt.plot(history.history["loss"])
    plt.plot(history.history["val_loss"])
    plt.title("model loss")
    plt.ylabel("loss")
    plt.xlabel("epoch")
    plt.legend(["train", "validation"], loc="upper left")

    plt.savefig("/artifacts/logs/history_loss.png")

def evaluate(X_test, y_test):
    """ Function evaluate(X_test, y_test) evaluattes the trained model on the holdout Test Set

    Parameters:
    X_test: List
        Contains all the filenames of the test set
    y_test: List
        Contains all the labels corresponding to the files in X_test

    Returns:
    None

    """

    TEST_DIR = "./dataset_split/test"
    HEIGHT = 224  # 224 for resnet50 and VGG16 / 299 for InceptionV3
    WIDTH = 224  # 224 for resnet50 and VGG16 / 299 for inceptionV3
    BATCH_SIZE = 16

    # DATA PREPARATION
    test_datagen = ImageDataGenerator(preprocessing_function=preprocess_input)

    # Conversion of the 2 lists in 1 pandas dataframe
    test_info = {"id": X_test, "label": y_test}
    testdf = pd.DataFrame(test_info)

    # Data preparation
    test_generator = test_datagen.flow_from_dataframe(
        dataframe=testdf,
        directory="./dataset_split/test/",
        x_col="id",
        y_col="label",
        batch_size=BATCH_SIZE,
        shuffle=False,
        class_mode="categorical",
        target_size=(HEIGHT, WIDTH),
    )

    model = load_model("/artifacts/models/vgg16_majurca_2805_1130.hdf5")

    probabilities = model.predict_generator(test_generator, len(test_generator))

    # The true labels
    y_true = test_generator.classes

    # The predicted labels
    y_pred = probabilities.argmax(axis=-1)

    # Plots confusion matrix
    print(confusion_matrix(y_true, y_pred))

    # Computes global accuracy
    acc = accuracy_score(y_true, y_test)
    print(acc)
    print("================================")
    print(y_true)
    print("================================")
    print(y_pred)
    print("================================")

def main():

    # Gives the whole dataset
    files, labels = getData()

    # Splits the dataset in two / Creates the holdout Test set
    X_tv, X_test, y_tv, y_test = splitData(files, labels)

    # Builds the model
    model = getModelVGG16()

    # Starts training
    model_trained, history = train(X_tv, y_tv, model)
    model_trained.save("/artifacts/models/vgg16_final.h5")

    # PLots Loss and Accuracy evolution
    plotHistory(history)

    # Evaluates the trained network on test set
    evaluate(X_test, y_test)

main()


"""This module contains methods for datatype conversions."""
from __future__ import annotations

import logging
import re
import sys
from abc import ABC
from base64 import b85decode, b85encode
from datetime import datetime
from decimal import Decimal
from distutils.version import StrictVersion
from fractions import Fraction
from typing import (
    TYPE_CHECKING,
    Any,
    List,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    Union,
)
from uuid import UUID, uuid4

import numpy as np
import rdflib
from rdflib import RDFS, XSD, BNode, Literal, URIRef, term
from rdflib.term import Identifier, Node

if TYPE_CHECKING:
    from simphony_osp.ontology.annotation import OntologyAnnotation
    from simphony_osp.ontology.attribute import OntologyAttribute
    from simphony_osp.ontology.individual import OntologyIndividual
    from simphony_osp.ontology.oclass import OntologyClass
    from simphony_osp.ontology.relationship import OntologyRelationship

# Some IDEs may not detect a type alias defined as Union[...] otherwise.
python_version_supports_type_alias = (
    StrictVersion("3.10")
    <= StrictVersion(f"{sys.version_info.major}.{sys.version_info.minor}")
    < StrictVersion("4.0")
)
if python_version_supports_type_alias:
    from typing import TypeAlias
else:
    from typing import Any as TypeAlias

__all__ = [
    "ATTRIBUTE_VALUE_TYPES",
    "AttributeValue",
    "AnnotationValue",
    "Identifier",
    "NestedList",
    "NestedMutableSequence",
    "NestedSequence",
    "NestedTuple",
    "OntologyPredicate",
    "Pattern",
    "PredicateValue",
    "RelationshipValue",
    "SimplePattern",
    "SimpleTriple",
    "Triple",
    "UID",
    "Vector",
    "rdf_to_python",
]

# Collection type hints
NestedSequence = Union[Sequence[Any], Sequence["NestedSequence"]]
NestedMutableSequence = Union[
    MutableSequence[Any], MutableSequence["NestedSequence"]
]
NestedTuple = Union[Tuple[Any, ...], Tuple["NestedTuple", ...]]
NestedList = Union[List[Any], List["NestedList"]]

# Ignore RDFLib binding warning.
class _BindingWarningFilter(logging.Filter):
    """Filters datatype rebinding warnings from RDFLib."""

    def filter(self, record):
        """Checks that the string is not contained in the message."""
        return " was already bound. Rebinding." not in record.getMessage()

rdflib_term_logger = logging.getLogger(rdflib.term.__name__)
rdflib_term_logger.addFilter(_BindingWarningFilter())

class CustomDataType(ABC):
    """Abstract class for the Python representation of custom data types.

    Such custom data types must also have an RDF representation (the IRI
    attribute and a string representation).

    Beware that any custom datatype will need to have a "lexicalizer" and a
    "constructor" for rdflib compatibility. See the piece of code
    immediately after this class for more details.
    """

    iri: URIRef

"""After the custom data type has been defined, the rdflib lexicalizer
and constructor have to be specified.

The rdflib lexicalizer creates a string representation of the Python object.
For example, for vectors it is just a string representation of their binary
representation. This is string representation is what the user will find in
exported json or rdf files, and also what literals print to the Python console
through the __repr__ method.

The rdflib constructor does the opposite, create a Python representation
from a literal constructed from a string. For example, for vectors,
it converts the string representation of their binary form into the Vector
python object.

Both the lexicalizer and the constructor might be optional. For more
information, please check  the documentation and source code for
[`rdflib.term.bind`]
(https://rdflib.readthedocs.io/en/stable/apidocs/rdflib.html#rdflib.term.bind).
"""

class Vector(CustomDataType):
    """Custom data type representing vectors.

    The data type is based on an underlying numpy array, or more
    specifically, the binary data for its shape, datatype and content.

    The reason behind not using directly a numpy array is that such objects
    are not hashable, but an RDF graph is a _set_ of triples, so having
    non-hashable objects leads to problems when it comes to handling and
    serializing the object.
    """

    # TODO: improve the interaction of the user with the object if possible:
    #   - If possible create the underlying numpy array when passing it as
    #     an argument to numpy's `array` function, instead of creating an array
    #     of vector objects.
    #   - Implement some operations with numpy arrays that return numpy arrays,
    #     just as it was done in the AttributeSet case.
    iri = URIRef("https://www.simphony-osp.eu/types#Vector")

    _ELEMENT_LEN: int = 3
    _DTYPE_LEN: int = 1
    _SHAPE_LEN: int = 1

    @property
    def data(self) -> np.ndarray:
        """Returns the underlying numpy array, constructed from static data."""
        return np.frombuffer(self.__bytes, dtype=self.__dtype).reshape(
            self.__shape
        )

    def __getitem__(self, item: int):
        """Slice the underlying numpy array."""
        return self.data[item]

    def __init__(
        self, value: Union[Literal, np.ndarray, NestedSequence, Vector]
    ):
        """Creates a new vector from `value`.

        The process of creating such vector can be divided in several steps:
            1. Construct a numpy array from `value`.
            2. Read its `dtype` and `shape` and store them as private
               attributes.
            3. Convert to bytes the data contained in the array and store
               them in the `__bytes` private attribute.
        """
        if isinstance(value, Vector):
            array = np.array(value.data)
        else:
            array = np.array(value)
        # TODO: restrict datatypes.
        self.__dtype = array.dtype
        self.__shape = array.shape
        self.__bytes = array.tobytes()

    def __str__(self) -> str:
        """Convert the vector to a string."""
        return self.data.__str__()

    def __repr__(self) -> str:
        """String representation for the vector (i.e. on Python console)."""
        array_repr = self.data.__repr__()
        array_repr = array_repr.replace("\n", " ")
        array_repr = re.sub(r"\s{2,}", " ", array_repr)
        return (
            f"<{self.__class__.__module__}.{self.__class__.__name__}:"
            f" {array_repr}>"
        )

    def __hash__(self):
        """Get the vector's hash."""
        return hash((self.__bytes, self.__dtype, self.__shape))

    def __eq__(
        self, other: Union[Literal, np.ndarray, NestedSequence, Vector]
    ):
        """Compare the vector to another object that can be vectorized."""
        return np.array_equal(self.data, Vector(other).data)

    def to_blob(self) -> bytes:
        """Convert the vector to a bytes representation.

        Use `from_blob` to convert back to a vector.
        """
        blob = bytes()
        # Attach data type.
        blob += len(bytes(self.__dtype.str, encoding="UTF-8")).to_bytes(
            self._DTYPE_LEN, "big"
        )
        blob += bytes(self.__dtype.str, encoding="UTF-8")
        # Attach shape.
        blob += len(self.__shape).to_bytes(self._SHAPE_LEN, "big")
        for integer in self.__shape:
            blob += integer.to_bytes(self._ELEMENT_LEN, "big")
        # Attach array.
        blob += self.__bytes
        return blob

    @classmethod
    def from_blob(cls, blob: bytes) -> Vector:
        """Convert a bytes representation of a vector into a Vector object."""
        # Get data type.
        start_dtype = 0
        len_dtype = int.from_bytes(blob[start_dtype : cls._DTYPE_LEN], "big")
        dtype = np.dtype(
            blob[
                start_dtype
                + cls._DTYPE_LEN : start_dtype
                + cls._DTYPE_LEN
                + len_dtype
            ].decode()
        )
        # Get shape.
        start_shape = start_dtype + cls._DTYPE_LEN + len_dtype
        len_shape = int.from_bytes(
            blob[start_shape : start_shape + cls._SHAPE_LEN], "big"
        )
        shape = tuple(
            int.from_bytes(
                blob[
                    start_shape
                    + cls._SHAPE_LEN
                    + i * cls._ELEMENT_LEN : start_shape
                    + cls._SHAPE_LEN
                    + (i + 1) * cls._ELEMENT_LEN
                ],
                "big",
            )
            for i in range(0, len_shape)
        )
        # Get byte array.
        start_array = (
            start_shape + cls._SHAPE_LEN + len_shape * cls._ELEMENT_LEN
        )
        byte_array = blob[start_array:]
        array = np.frombuffer(byte_array, dtype=dtype).reshape(shape)
        return Vector(array)

    def to_b85(self) -> str:
        """Convert the vector to a base85 representation.

        Use `from_b85` to convert back to a vector.
        """
        return b85encode(self.to_blob()).decode("UTF-8")

    @classmethod
    def from_b85(cls, string) -> Vector:
        """Convert a base 85 representation of a vector back into a Vector."""
        return cls.from_blob(b85decode(string.encode("UTF-8")))

    @classmethod
    def from_blob_or_b85(cls, value: Union[str, bytes]) -> Vector:
        """Restore a vector from a base 85 or bytes representation of it."""
        if isinstance(value, bytes):
            return cls.from_blob(value)
        elif isinstance(value, str):
            return cls.from_b85(value)
        else:
            raise TypeError("Only str and bytes are allowed.")

for datatype in (Literal, np.ndarray, Sequence, list, tuple, Vector):
    term.bind(
        Vector.iri,
        datatype,
        lexicalizer=lambda x: Vector(x).to_b85(),
        constructor=lambda x: Vector.from_blob_or_b85(x),
        datatype_specific=True,
    )

class UID(CustomDataType):
    """Custom data type representing the unique identifier of an entity.

    This data type is actually not used in the RDF graph itself (an RDF
    identifier is used instead), but rather within SimPhoNy to identify the
    ontology entities objects. It plays the same role as a semantic web
    identifier, and can always be converted to one. In fact, such conversion
    is always performed before it is used in a graph operation. The only
    difference is that it can be based not only on semantic web identifiers
    such as blank nodes or IRIs, but also on other datatypes that can be
    converted on the fly to semantic-web compatible identifiers.

    However, if you want to store a UID as a literal for whatever reason,
    you are welcome to use this custom data type.
    """

    iri = URIRef("https://www.simphony-osp.eu/types#UID")
    data: Union[Node, UUID]

    __slots__ = ("data",)

    def __init__(
        self,
        value: Optional[Union[UID, UUID, str, Node, int, bytes]] = None,
    ):
        """Initializes a new UID from `value`."""
        super().__init__()
        invalid = False
        if value is None:
            value = uuid4()
        elif isinstance(value, str):
            if value.startswith(ENTITY_IRI_PREFIX):
                value = value[len(ENTITY_IRI_PREFIX) :]
                try:
                    value = UUID(hex=value)
                except ValueError:
                    invalid = True
            elif not isinstance(value, Node):
                value = URIRef(value)
        elif isinstance(value, UUID):
            invalid = value.int == 0
        elif isinstance(value, int):
            value = UUID(int=value)
        elif isinstance(value, bytes):
            value = UUID(bytes=value)
        elif isinstance(value, UID):
            value = value.data
        if invalid or not isinstance(value, (UUID, Node)):
            raise ValueError(f"Invalid uid: {value}.")
        self.data = value

    def __hash__(self):
        """Get the uid's hash."""
        return hash(self.data)

    def __eq__(self, other):
        """Compare the uid to other uids.

        Returns false if `other` does not belong to the same class.
        """
        return isinstance(other, self.__class__) and self.data.__eq__(
            other.data
        )

    def __repr__(self):
        """String representation for the uid (i.e. on the Python console)."""
        return (
            f"<class '{self.__class__.__module__}"
            f".{self.__class__.__name__}':"
            f" {self.data.__repr__()}>"
        )

    def __str__(self):
        """Convert the uid to a string."""
        return self.data.__str__()

    def __format__(self, format_spec):
        """Formatter for UID objects."""
        return self.data.__format__(format_spec)

    def to_iri(self) -> URIRef:
        """Convert the UID to an IRI."""
        data = self.data
        if isinstance(data, UUID):
            return_iri = URIRef(ENTITY_IRI_PREFIX + str(data))
        elif isinstance(data, URIRef):
            return_iri = data
        elif isinstance(data, BNode):
            raise TypeError(
                f"The UID {self} cannot be converted to an IRI, "
                f"as it is a blank node."
            )
        else:
            raise RuntimeError(f"Illegal UID type {type(data)}.")
        return return_iri

    def to_uuid(self) -> UUID:
        """Convert the UID to a UUID.

        Will only work if the underlying data is a UUID.
        """
        data = self.data
        if not isinstance(data, UUID):
            raise Exception(
                f"Tried to get the UUID of the UID object "
                f"{self}, but this object is not a UUID."
            )
        return data

    def to_identifier(self) -> Identifier:
        """Convert the UID to a RDFLib Identifier."""
        data = self.data
        # logic in `to_iri` duplicated here in exchange for performance gains
        return (
            URIRef(ENTITY_IRI_PREFIX + str(data))
            if isinstance(data, UUID)
            else data
        )

for datatype in (UID, UUID, URIRef, str, int, bytes):
    if datatype in (int, bytes):

        def _lexicalizer(x):
            return str(UID(x))

    else:
        _lexicalizer = None
    term.bind(
        UID.iri,
        datatype,
        lexicalizer=_lexicalizer,
        constructor=None,
        datatype_specific=True,
    )

ENTITY_IRI_PREFIX = "https://www.simphony-osp.eu/entity#"

# --- RDF TO PYTHON --- #

def _get_all_python_subclasses(cls):
    return set(cls.__subclasses__()) | {
        sc
        for c in cls.__subclasses__()
        for sc in _get_all_python_subclasses(c)
    }

# OWL data types
#  See [Datatype Maps](https://www.w3.org/TR/owl2-syntax/#Datatype_Maps) on
#  the OWL specification.
#  OWL_TO_PYTHON gathers data types that are not bound already to RDFLib terms.
OWL_TO_PYTHON = {
    URIRef("http://www.w3.org/2002/07/owl#real"): float,
    # ↑ Not defined by RDFLib.
    URIRef("http://www.w3.org/2002/07/owl#rational"): Fraction,
    # ↑ Not defined by RDFLib.
    # XSD.Name: ?, (TODO: not supported)
    # ↑ Not defined by RDFLib.
    # XSD.NCName: ?, (TODO: not yet supported)
    # ↑ Not defined by RDFLib.
    # XSD.NMTOKEN: ?, (TODO: not yet supported)
    # ↑ Not defined by RDFLib.
    # XSD.base64Binary: ?, (TODO: not yet supported)
    # ↑ Not defined by RDFLib.
    # XSD.dateTimeStamp: ?, (TODO: not yet supported)
    # ↑ ¿Is it defined by RDFLib?
    # RDF.XMLLiteral: ?, (TODO: not yet supported)
    # ↑ ¿Is it defined by RDFLib?
}
#  OWL_TO_PYTHON_DEFINED_RDFLIB gathers data types that were bound to RDFLib
#  terms already by the RDFLib developers.
OWL_TO_PYTHON_DEFINED_RDFLIB = {
    XSD.decimal: Decimal,  # -> Defined already by RDFLib.
    XSD.integer: int,  # -> Defined already by RDFLib.
    XSD.nonNegativeInteger: int,  # -> Defined already by RDFLib.
    XSD.nonPositiveInteger: int,  # -> Defined already by RDFLib.
    XSD.positiveInteger: int,  # -> Defined already by RDFLib.
    XSD.negativeInteger: int,  # -> Defined already by RDFLib.
    XSD.long: int,  # -> Defined already by RDFLib.
    XSD.int: int,  # -> Defined already by RDFLib.
    XSD.short: int,  # -> Defined already by RDFLib.
    XSD.byte: int,  # -> Defined already by RDFLib.
    XSD.unsignedLong: int,  # -> Defined already by RDFLib.
    XSD.unsignedInt: int,  # -> Defined already by RDFLib.
    XSD.unsignedShort: int,  # -> Defined already by RDFLib.
    XSD.unsignedByte: int,  # -> Defined already by RDFLib.
    XSD.double: float,  # -> Defined already by RDFLib.
    XSD.float: float,  # -> Defined already by RDFLib.
    XSD.string: str,  # -> Defined already by RDFLib.
    XSD.normalizedString: str,  # -> Defined already by RDFLib.
    XSD.token: str,  # -> Defined already by RDFLib.
    XSD.boolean: bool,  # -> Defined already by RDFLib.
    XSD.hexBinary: bytes,  # -> Defined already by RDFLib.
    XSD.anyURI: str,  # -> Defined already by RDFLib.
    XSD.dateTime: datetime,  # -> Defined already by RDFLib.
}
OWL_TO_PYTHON.update(OWL_TO_PYTHON_DEFINED_RDFLIB)
OWL_COMPATIBLE_TYPES = tuple(OWL_TO_PYTHON.values())
OWLCompatibleType = Union[OWL_COMPATIBLE_TYPES]
# Custom STATIC data types (fixed URI).
CUSTOM_TO_PYTHON = {
    cls.iri: cls for cls in _get_all_python_subclasses(CustomDataType)
}
CUSTOM_COMPATIBLE_TYPES = tuple(CUSTOM_TO_PYTHON.values())
CustomCompatibleType = (
    Union[CUSTOM_COMPATIBLE_TYPES] if CUSTOM_COMPATIBLE_TYPES else None
)
# All RDF data types compatible with SimPhoNy, automatically generated from
#  the previous mappings.
RDF_TO_PYTHON = {RDFS.Literal: str}
RDF_TO_PYTHON.update(OWL_TO_PYTHON)
RDF_TO_PYTHON.update(CUSTOM_TO_PYTHON)
#  Bind all the types to RDFLib.
for iri, data_type in RDF_TO_PYTHON.items():
    # Skip custom data types, which where already bound under their definition.
    # Skip also data types already bound by RDFLib.
    if iri in set(CUSTOM_TO_PYTHON) | set(OWL_TO_PYTHON_DEFINED_RDFLIB):
        continue
    term.bind(iri, data_type)
# Define what are the Python types compatible with RDF.
ATTRIBUTE_VALUE_TYPES = tuple(x for x in RDF_TO_PYTHON.values())

def rdf_to_python(value: Any, rdf_data_type: URIRef) -> Any:
    """Changes the type of a Python object to match an RDF datatype.

    Converts a Python object into another Python object whose type is
    compatible with the specified data type

    Args:
        value: The Python object to be transformed.
        rdf_data_type: The RDF data type that the resulting object needs to
            be compatible with.

    Returns:
        A Python object compatible with `rdf_data_type`.
    """
    # Converting the literal to a string  or calling Literal twice seems
    #  redundant, but is intentional.
    #
    #  One would expect rdflib to convert the literal to the adequate
    #  datatype by calling 'value.toPython()'. However, this is not always
    #  true. Two cases have to be considered:
    #   - The literal has been created from an arbitrary Python object. Then
    #     calling `toPython` returns the original Python object, no matter
    #     what datatype was specified when creating it.
    #   - The literal has been created from a string. Then calling `toPython`
    #     invokes the RDFLib constructor, spawning a Python object of the
    #     adequate datatype.
    if isinstance(value, Literal):
        # Here we convert the received literal to a string so that RDFLib
        # first calls the lexicalizer on it. In such a way the new literal
        # is created from a string, and the constructor is invoked when
        # calling `toPython`.
        result = Literal(
            str(value), datatype=rdf_data_type, lang=value.language
        ).toPython()
    else:
        # When an arbitrary python object is received instead, first we
        # create a literal with the correct data type, and then perform the
        # same action as before: call the lexicalizer on such literal so
        # that the new one is created from a string.
        result = Literal(value, datatype=rdf_data_type)
        result = Literal(str(result), datatype=rdf_data_type).toPython()
    return result

# --- Various type hints and type hint aliases --- #

# RDF type hints
Pattern = Tuple[Optional[Node], Optional[Node], Optional[Node]]
SimplePattern = Tuple[
    Optional[Union[URIRef, Literal]],
    Optional[Union[URIRef, Literal]],
    Optional[Union[URIRef, Literal]],
]
Triple = Tuple[Node, Node, Node]
SimpleTriple = Tuple[
    Union[URIRef, Literal], Union[URIRef, Literal], Union[URIRef, Literal]
]

# Predicates
OntologyPredicate: TypeAlias = Union[
    "OntologyAnnotation",
    "OntologyAttribute",
    "OntologyRelationship",
]

# Predicate targets
AttributeValue = Union[tuple(value for value in RDF_TO_PYTHON.values())]
AnnotationValue: TypeAlias = Union[
    "OntologyAnnotation",
    "OntologyAttribute",
    "OntologyClass",
    "OntologyIndividual",
    "OntologyRelationship",
    AttributeValue,
    URIRef,
    Literal,
]
RelationshipValue: TypeAlias = "OntologyIndividual"
PredicateValue: TypeAlias = Union[
    "OntologyIndividual", AttributeValue, AnnotationValue
]


from dash import html
import dash_bootstrap_components as dbc

derecha = dbc.Container(
    [
        # html.H2('Datos del proyecto'),
        # html.Hr(),
        # html.Label('Nombre:'),
        # dbc.Input(value="Nombre"),
        # html.Label('Localización:'),
        # dbc.Input(value="Localización"),
        # html.Label('Fecha Inicio:'),
        # dbc.Input(value="Fecha", type="date"),
        # html.Label('Fecha Fin:'),
        # dbc.Input(value="Fecha", type="date"),

        html.H3('Suelo CH'),
        html.Hr(),
        html.H6('Este suelo tiene propiedades por qeu blablabla'),
        html.H3('Suelo MH'),
        html.Hr(),
        html.H6('Este suelo tiene propiedades por qeu blablabla'),
        html.H3('Suelo CL'),
        html.Hr(),
        html.H6('Este suelo tiene propiedades por qeu blablabla'),
        html.H3('Suelo ML'),
        html.Hr(),
        html.H6('Este suelo tiene propiedades por qeu blablabla'),
        html.H3('Suelo CL-ML'),
        html.Hr(),
        html.H6('Este suelo tiene propiedades por qeu blablabla'),
    ]
)


import argparse
import io
import json,os,shutil
import re as Regex

__all__ = [
    "main",
]

error_prefix = "\x1b[31mERROR:\x1b[0m"
warn_prefix = "\x1b[32mWARNING:\x1b[0m"

class Package:
    name:str
    url:str
    recSubModules:bool
    gitClone:bool
    gnBindingDest:str
    deps:"list[str]"
    def __init__(self, name:str,deps:"list[str]",url:str ,gitClone:bool, gnBindingDest:str,recSubModules:bool):
        self.name = name
        self.deps = deps
        self.gitClone = gitClone
        self.url = url
        self.gnBindingDest = gnBindingDest
        self.recSubModules = recSubModules
        return

def get_current_packages() -> "list[Package]":
    l:"list[Package]" = []
    j:dict = json.load(io.open(f"{os.path.dirname(os.path.abspath(__file__))}/pkgs.json","r"))
    assert(j.get("packages"))
    pkgs:"list[dict[str,str]]" = j.get("packages")
    for package in pkgs:
        clSubModules = False
        if package.get("cloneSubmodules") is None:
            clSubModules = False
        else:
            clSubModules = package.get("cloneSubmodules")
        l.append(Package(package.get("name"),package.get("deps"),package.get("url"),True,package.get("gn_binding"),recSubModules=clSubModules))
    return l

PkgList = "list[dict[str,str]]"

def package_is_installed(name:str,installedPkgList:PkgList) -> bool:
    for i in installedPkgList:
        if i.get("name") == name and os.path.exists(i.get("installedPath") + f"/BUILD.gn"):
            return True
    return False

pkgs:"list[Package]" = get_current_packages()

def get_package(name:str,home:str,homeConfig:dict,root:bool = True,install_if_root:bool = True,depContext:bool = False):
    if os.path.exists(f"{home}/Gnpkg.gni") == False:
        prev_dir = os.path.abspath(os.getcwd())
        os.chdir(os.path.abspath(os.path.dirname(__file__)))
        if os.path.exists(home) == False:
            os.makedirs(home)
        shutil.copy2("./Gnpkg.gni",f"{home}/Gnpkg.gni")
        os.chdir(prev_dir)

    if homeConfig.get("packages") == None:
        homeConfig["packages"] = []
    cfg:PkgList = homeConfig["packages"]

    global pkgs
    # Check if Package is Already Installed.
    if package_is_installed(name=name,installedPkgList=cfg):
        if not depContext:
            print(f"{error_prefix} Package {name} is already installed.",flush=True)
        return

    success:bool = False
    for p in pkgs:
        if p.name == name:
            success = True
            print(f"Installing Package {name}")
            if p.deps is not None:
                print("Dependencies:")
                for d in p.deps:
                    print(f"\t-> {d}")
                for d in p.deps:
                    get_package(d,home=home,homeConfig=homeConfig,root = False,depContext=True)
            if p.gitClone:

                if p.recSubModules:
                    git_cmd_line = "git clone --recurse-submodules " + p.url + f" {home}/{p.name}/code"
                else:
                    git_cmd_line = "git clone " + p.url + f" {home}/{p.name}/code"
                os.system(git_cmd_line)
                original_dir =  os.path.abspath(os.getcwd())
                os.chdir(os.path.dirname(os.path.abspath(__file__)))

                for iter in os.walk(p.gnBindingDest):
                    for f in iter[2]:
                        shutil.copy2(os.path.join(iter[0],f),f"{home}/{p.name}/{os.path.basename(f)}")

                os.chdir(original_dir)
                homeConfig["packages"].append({
                    "name":p.name,
                    "installedPath":f"{homeConfig.get('installDest')}/{p.name}"
                })

    if success == False:
        raise f"Could not find package {name}.\nExiting..."

    if root and install_if_root:
        json.dump(homeConfig,io.open("./GNPKG","w"),sort_keys=True,indent=2)

    return

def package_get_dependents(name:str) -> "list[str]":
    dependents:"list[str]" = []
    global pkgs
    for p in pkgs:
        if p.deps is not None:
            for d in p.deps:
                if d == name:
                    dependents.append(p.name)
                    break
    return dependents

def package_is_git_cloned(name:str):
    global pkgs
    for p in pkgs:
        if p.name == name:
            return p.gitClone
    return None

# For Removing .git folder in git cloned packages
def onerror(func, path, exc_info):

    import stat
    if not os.access(path, os.W_OK):
        os.chmod(path, stat.S_IWUSR)
        func(path)
    else:
        raise ""

def remove_package(name:str,homeConfig:dict):
    pkgs:PkgList = homeConfig.get("packages")
    success:bool = False
    for i in range(len(pkgs)):
        p = pkgs[i]
        assert(p.get('name'))
        if p.get("name") == name:
            success = True
            parents = package_get_dependents(name)
            if len(parents) > 0:
                cont = input(f"{warn_prefix} This package is a dependency of the following packages {parents}\nRemoving this package could result in build errors. Continue?[Y/N]:")
                if Regex.match(r"^N|n",cont):
                    exit(0)
                elif Regex.match(r"^Y|y",cont) == None:
                    print(f"Unknown Option:{cont}")
                    exit(1)
            # if package_is_git_cloned(name):
            #     shutil.rmtree(p.get("installedPath") + "/code/.git",onerror=onerror)
            shutil.rmtree(p.get("installedPath"))
            pkgs.pop(i)
            break

    if not success:
        print(f"{error_prefix} Package not previously installed.\nExiting...")
    else:
        json.dump(homeConfig,io.open("./GNPKG","w"),sort_keys=True,indent=2)

def link_utils():
    dest = os.path.abspath(os.getcwd())
    if os.path.exists(dest + "/gn-utils"):
        return
    else:
        os.chdir(os.path.dirname(os.path.abspath(__file__)))
        os.symlink(os.path.abspath("../../gn-utils"),dest + "/gn-utils",target_is_directory=True)
        os.chdir(dest)
def get_chromium_build_utils():
    os.system("git clone https://chromium.googlesource.com/chromium/src/build ./build")

def main(args):
    parser = argparse.ArgumentParser(prog="gnpkg")
    subparsers = parser.add_subparsers(dest="command")
    utils_parser = subparsers.add_parser("utils")
    utils_parser.add_argument("--get",action="store_const",const=True,default=False,help="Gets the Latest GN Utils and installs it in a `gn-utils` dir")
    utils_parser.add_argument("--getchrome",action="store_const",const=True,default=False,help="Fetches the Chromium GN Build Utils. (Used for chromium project repos)")
    get_parser = subparsers.add_parser("get")
    # parser.add_argument("--update",action="store_const",const=True,default=False)
    get_parser.add_argument("package",type=str,nargs="?")

    remove_parser = subparsers.add_parser("remove")
    remove_parser.add_argument("package",type=str)

    args = parser.parse_args(args=args)
    home:str
    homeConfig:dict
    if args.command != "utils":
        if os.path.exists("./GNPKG"):
                homeConfig = json.load(io.open("./GNPKG","r"))
                assert(homeConfig.get("installDest"))

                home = os.path.abspath(homeConfig.get("installDest"))
        else:
            raise FileNotFoundError("./GNPKG")

    if args.command == "utils":
        if args.get:
            link_utils()
        elif args.getchrome:
            get_chromium_build_utils()

    elif args.command == "get":
        if args.package is None:
           instPkgList:PkgList = homeConfig.get("packages")
           if instPkgList is None or len(instPkgList) == 0:
               print(f"{error_prefix} No Packages have been added to this config.Exiting...")
               exit(1)
           else:
               for p in instPkgList:
                   get_package(p.get("name"),home=home,homeConfig=homeConfig,install_if_root=False)
        else:
            get_package(args.package,home=home,homeConfig=homeConfig)
    elif args.command == "remove":
        remove_package(args.package,homeConfig=homeConfig)


"""
Program to perform various function related to Stacks.
"""

def push_stack(stack,item):
    '''
    To push an element into the Stack.

    Parameters
    ----------
    stack : LIST
        list of umeric characters.
    item : NUMERIC
        Element to be pushed in stack.

    '''
    stack.append(item)
    print(item,"pushed successfully into stack.")

def pop_stack(stack):
    '''
    To pop last element of the stack.

    Parameters
    ----------
    stack : LIST
        list of numeric characters.

    '''
    if len(stack)==0:
        print("Stack Underflow !!!")

    else:
        top=len(stack)-1
        top_element=stack.pop(top)
        print("Element",top_element," poped.")

def display_stack(stack):
    '''
    To view stack.

    Parameters
    ----------
    stack : LIST
        list of numeric characters.

    '''
    length=len(stack)
    if length==0:
        print("Stack is empty.")

    else:
        print("Stack is: ")
        for i in range(len(stack)-1, -1, -1):
            print("\t",stack[i])

def peek_stack(stack):
    '''
    To view topmost/last element of the stack.

    Parameters
    ----------
    stack : LIST
        list of numeric characters.

    '''
    length=len(stack)
    if length==0:
        print("Stack is Empty")
    else:
      print('Topmost element is:',stack[length-1])

#--------------------main--------------------------
stack=[]
menu='''
----------------------------------
| {:^30s} |
----------------------------------
| {:<30s} |
| {:<30s} |
| {:<30s} |
| {:<30s} |
| {:<30s} |
----------------------------------
'''.format("MENU",
            "1. Add element to stack.",
            "2. Remove element from stack.",
            "3. View stack contents.",
            "4. Peek stack.",
            "5. QUIT."
            )

while True:
    print(menu)
    act=input("Enter the serial number of the task you want to perform: ")

    if act=='1':
        item=int(input("Enter new element: "))
        push_stack(stack, item)

    elif act=='2':
        pop_stack(stack)

    elif act=='3':
        display_stack(stack)

    elif act=='4':
        peek_stack(stack)

    elif act=='5':
        print("Thank you for using this program.")
        break

    else:
        print("Please enter valid number !!!")

    print("*"*100)


import networkx as nx
import pandas as pd
from cobra.flux_analysis import find_blocked_reactions

class UmFinder:
    def __init__(self, cobra_model, cc_method='fva', report=True):
        self._model = cobra_model

        if report:
            print("===========================================================")
            print("Initializing UmFinder Builder using")
            print("Model: %s" % cobra_model.id)

            print("- Nº of reactions: %i" % len(self._model.reactions))
            print("- Nº of metabolites: %i" % len(self._model.metabolites))

            print("\nChecking network consistency (may take some minutes)")
            print("Finding blocked reaction using method: %s\n" % cc_method)

        self._blocked_reactions = find_blocked_reactions(self.model)
        self._gap_metabolites = UmFinder.find_gap_metabolites(self.model, self.blocked_reactions)
        self._gap_graph = UmFinder.create_gap_graph(self.model, self._gap_metabolites, self._blocked_reactions)

        unconnected_modules = nx.connected_components(self._gap_graph.to_undirected())
        self._unconnected_modules = sorted(unconnected_modules, key=lambda x: len(x), reverse=True)

        if report:
            print("- Nº of blocked reactions: %i" % len(self._blocked_reactions))
            print("- Nº of gap metabolites: %i" % len(self._gap_metabolites))
            print("- Nº of unconnected modules: %i" % len(self.unconnected_modules))

            if len(self.unconnected_modules):
                df_ums = self.unconnected_modules_frame()
                df_biggest_um = df_ums.node_type[df_ums.um_id == 1]
                rxns = df_biggest_um.index[df_biggest_um =='rxn']
                mets = df_biggest_um.index[df_biggest_um == 'met']
                print("- N of reactions in the biggest unconnected module: %i" % len(rxns))
                print("- N of metabolites in the biggest unconnected module: %i" % len(mets))

    @property
    def model(self):
        return self._model

    @property
    def gap_metabolites(self):
        return frozenset(self._gap_metabolites)

    @property
    def gap_graph(self):
        return self._gap_graph

    @property
    def blocked_reactions(self):
        return frozenset(self._blocked_reactions)

    @property
    def unconnected_modules(self):
        return self._unconnected_modules

    def update(self):
        self._blocked_reactions = find_blocked_reactions(self.model)
        self._gap_metabolites = UmFinder.find_gap_metabolites(self.model, self.blocked_reactions)
        self._gap_graph = UmFinder.create_gap_graph(self.model, self._gap_metabolites, self._blocked_reactions)
        self._unconnected_modules = nx.connected_component_subgraphs(self._gap_graph.to_undirected())

        self._unconnected_modules = sorted(self._unconnected_modules, key=lambda x: len(x), reverse=True)

    def unconnected_module_subgraphs(self):
        for um in self.unconnected_modules:
            yield self.gap_graph.subgraph(um)

    def unconnected_modules_frame(self):
        columns = ['node_id', 'node_type', 'um_id']
        data = {}
        counter = 0
        for i, um in enumerate(self.unconnected_modules):
            for e in um:
                if e in self.gap_metabolites:
                    e_type = 'met'
                elif e in self.blocked_reactions:
                    e_type = 'rxn'
                else:
                    e_type = None
                data[counter] = (e, e_type, i+1)
                counter += 1

        return pd.DataFrame.from_dict(data, orient='index', columns=columns)

    @staticmethod
    def find_gap_metabolites(model, blocked_reactions):
        gap_metabolites = []
        for m in model.metabolites:
            reactions = set([r.id for r in m.reactions])
            if reactions.issubset(blocked_reactions):
                gap_metabolites.append(m.id)

        return gap_metabolites

    @staticmethod
    def create_metabolic_graph(cobra_model, directed=True, reactions=None, rev_rxn_label='reversible'):

        graph = nx.DiGraph()
        if not directed:
            graph = nx.Graph()

        if not reactions:
            reactions = cobra_model.reactions

        if not hasattr(reactions[0], 'id'):
            reactions = [cobra_model.reactions.get_by_id(r) for r in reactions]

        for r in reactions:
            graph.add_node(r.id, label=r.id, text=r.id, node_class="rxn", node_id=r.id)
            for m in r.metabolites:
                if m.id not in graph.nodes():
                    graph.add_node(m.id, label=m.id, text=m.id, node_class="met", node_id=m.id)

                (tail, head) = (r.id, m.id)
                if r.get_coefficient(m) < 0:
                    (tail, head) = (m.id, r.id)

                graph.add_edge(tail, head)
                graph[tail][head][rev_rxn_label] = r.lower_bound < 0

        return graph

    @staticmethod
    def create_gap_graph(model, gap_metabolites, blocked_reactions):

        if hasattr(gap_metabolites[0], 'id'):
            gap_metabolites = [m.id for m in gap_metabolites]

        if hasattr(blocked_reactions[0], 'id'):
            blocked_reactions = [r.id for r in blocked_reactions]

        graph = UmFinder.create_metabolic_graph(model)
        gap_graph = graph.subgraph(gap_metabolites + blocked_reactions)

        return gap_graph

def main():
    model = cobra.test.create_test_model('ecoli')
    um_finder = UmFinder(model)


from inkplate6_PLUS import Inkplate
from image import *
import time

display = Inkplate(Inkplate.INKPLATE_1BIT)

#main function used by micropython
if __name__ == "__main__":
    display.begin()
    display.tsInit(1)
    display.drawRect(450, 350, 100, 100, display.BLACK)
    display.display()

    counter = 0

    while True:
        #touch the square
        if(display.touchInArea(450, 350, 100, 100)):
            counter += 1
            print(counter)


"""Return the nth power distance beetween the insersection of a and b."""
from typing import Dict

def _even_nth(a_val: float, b_val: float)->float:
    return a_val - b_val

def _odd_nth(a_val: float, b_val: float)->float:
    return abs(a_val - b_val)

def intersection_nth_variation(a: Dict, b: Dict, exp: float=2, overlap: bool=False)->float:
    """Return the nth power distance beetween the insersection of a and b."""
    total = 0
    n = 0
    bget = b.__getitem__

    if exp % 2 == 0:
        nth = _even_nth
    else:
        nth = _odd_nth

    for k, a_val in a.items():
        try:
            total += nth(a_val, bget(k))**exp
            n += 1
        except KeyError:
            pass
    result = total
    if overlap:
        return result, n
    return result


import os
from urllib.parse import urlparse

import boto3

import configs

s3_client = boto3.client("s3")

def get_object(key: str):
    try:
        obj = s3_client.get_object(Bucket=configs.S3_BUCKET_NAME, Key=key)
        return obj["Body"].read().decode()  # StreamingBody -> bytes -> str
    except Exception as e:
        return ""

def put_object(key, body):
    if type(body) == str:
        body = body.encode()

    try:
        return s3_client.put_object(
            ACL="private",
            Body=body,
            Bucket=configs.S3_BUCKET_NAME,
            Key=key,
        )
    except Exception as e:
        print(e)
        return None

def s3_key_gen(url):
    """Generate S3 object name
    "configs.KEY_MAP" is used to succeed to the histories of deprecated URLs

    https://.../cse/888/subview.do
    -> cse_888_subview.do.json
    """

    _url = configs.KEY_MAP.get(url, url)
    path = urlparse(_url).path
    return os.path.join(
        configs.S3_BOARD_FOLDER, path.strip("/").replace("/", "_") + ".json"
    )


from __future__ import division, unicode_literals

import copy
import logging
import math
import multiprocessing
import re
import shutil
import subprocess
import tempfile
import time
import xml.etree.cElementTree as ET

import psutil
from pathlib import Path

import spreads.util as util
from spreads.config import OptionTemplate
from spreads.plugin import HookPlugin, ProcessHooksMixin

IS_WIN = util.is_os('windows')
CLI_BIN = util.find_in_path('scantailor-cli')
GUI_BIN = util.find_in_path('scantailor')

if not CLI_BIN:
    raise util.MissingDependencyException(
        "Could not find executable `scantailor-cli`. Please" " install the"
        " appropriate package(s)!")

logger = logging.getLogger('spreadsplug.scantailor')

class ScanTailorPlugin(HookPlugin, ProcessHooksMixin):
    __name__ = 'scantailor'

    @classmethod
    def configuration_template(cls):
        conf = {
            'autopilot': OptionTemplate(value=True,
                                        docstring="Skip manual correction"),
            'rotate': OptionTemplate(value=False, docstring="Rotate pages"),
            'split_pages': OptionTemplate(value=True,
                                          docstring="Split pages"),
            'deskew': OptionTemplate(value=True, docstring="Deskew pages"),
            'content': OptionTemplate(value=True,
                                      docstring="Detect page content"),
            'auto_margins': OptionTemplate(value=True,
                                           docstring="Automatically detect"
                                                     " margins"),
            'detection': OptionTemplate(value=('content', 'page'),
                                        docstring="Content detection mode",
                                        selectable=True),
            'margins': OptionTemplate([2.5, 2.5, 2.5, 2.5])
        }
        return conf

    def __init__(self, config):
        super(ScanTailorPlugin, self).__init__(config)
        help_out = util.get_subprocess([CLI_BIN],
                                       stdout=subprocess.PIPE).communicate()[0]
        self._enhanced = bool(re.match(r".*<images\|directory\|->.*",
                              help_out.splitlines()[7]))

    def _generate_configuration(self, in_paths, projectfile, out_dir):
        """ Run images through ScanTailor pre-processing steps.

        :param in_paths:        Paths to images to be processed
        :type in_paths:         list of :py:class:`pathlib.Path`
        :param projectfile:     Path ScanTailor configuration file
        :type projectfile:      :py:class:`pathlib.Path`
        :param out_dir:         Output directory for processed files
        :type out_dir:          :py:class:`pathlib.Path`
        """
        # Filters are numbered from 1 to 6, with 6 being the 'create output
        # files' step.
        filterconf = [self.config[x].get(bool)
                      for x in ('rotate', 'split_pages', 'deskew', 'content',
                                'auto_margins')]
        start_filter = filterconf.index(True)+1
        end_filter = len(filterconf) - list(reversed(filterconf)).index(True)
        marginconf = self.config['margins'].as_str_seq()

        # Build initial command-line
        generation_cmd = [CLI_BIN,
                          '--start-filter={0}'.format(start_filter),
                          '--end-filter={0}'.format(end_filter),
                          '--layout=1.5',
                          '-o={0}'.format(projectfile)]

        # The 'enhanced' fork of ScanTailor has some additional features
        page_detection = self.config['detection'].get() == 'page'
        if self._enhanced and page_detection:
            generation_cmd.extend([
                '--enable-page-detection',
                '--disable-content-detection',
                '--enable-fine-tuning'
            ])
        else:
            generation_cmd.extend([
                '--margins-top={0}'.format(marginconf[0]),
                '--margins-right={0}'.format(marginconf[1]),
                '--margins-bottom={0}'.format(marginconf[2]),
                '--margins-left={0}'.format(marginconf[3]),
            ])
        if IS_WIN:
            # NOTE: Due to Window's commandline length limit of 8192 chars,
            #       we have to pipe in the list of files via stdin
            generation_cmd.append("-")
        else:
            generation_cmd.extend(in_paths)

        generation_cmd.append(unicode(out_dir))
        logger.debug(" ".join(generation_cmd))
        if IS_WIN:
            sp = util.get_subprocess(generation_cmd, stdin=subprocess.PIPE)
            sp.stdin.write(" ".join(in_paths))
            sp.stdin.close()
        else:
            sp = util.get_subprocess(generation_cmd)
        proc = psutil.Process(sp.pid)

        # Keep track of the progress by monitoring the files opened by the
        # ScanTailor process. Since it processes the files in order and we
        # know in advance how often a file will be opened (= number of steps)
        # we can reliably calculate how far a long we are and emit
#       # :py:attr:`on_progressed` events.
        num_images = len(in_paths)
        num_steps = (end_filter - start_filter)+1
        last_fileidx = 0
        recent_fileidx = 0
        finished_steps = 0
        while proc.is_running():
            try:
                recent_fileidx = next(in_paths.index(x.path)
                                      for x in proc.open_files()
                                      if x.path in in_paths)
            except StopIteration:
                pass
            except psutil.AccessDenied:
                # This means the process is no longer running
                break
            if recent_fileidx == last_fileidx:
                time.sleep(.01)
                continue
            if recent_fileidx < last_fileidx:
                finished_steps += 1
            last_fileidx = recent_fileidx
            progress = 0.5*((finished_steps*num_images+last_fileidx) /
                            float(num_steps*num_images))
            self.on_progressed.send(self, progress=progress)
        # TODO: Check exit status for errors

    def _split_configuration(self, projectfile, temp_dir):
        """ Split a single ScanTailor configuration file into as many
            individual files as the machine has CPU cores

        :param projectfile:     Path ScanTailor configuration file
        :type projectfile:      :py:class:`pathlib.Path`
        :param temp_dir:        Output directory for split files
        :type temp_dir:         :py:class:`pathlib.Path`
        :returns:               Paths to split files
        :rtype:                 list of :py:class:`pathlib.Path`
        """
        num_pieces = multiprocessing.cpu_count()
        whole_tree = ET.parse(unicode(projectfile))
        num_files = len(whole_tree.findall('./files/file'))
        splitfiles = []
        files_per_job = int(math.ceil(float(num_files)/num_pieces))
        for idx in xrange(num_pieces):
            subtree = copy.deepcopy(whole_tree)
            root = subtree.getroot()
            start = idx*files_per_job
            end = start + files_per_job
            if end > num_files:
                end = None
            for elem in ('files', 'images', 'pages',
                         'file-name-disambiguation'):
                elem_root = root.find(elem)
                to_keep = elem_root.getchildren()[start:end]
                to_remove = [x for x in elem_root.getchildren()
                             if x not in to_keep]
                for node in to_remove:
                    elem_root.remove(node)
            out_file = temp_dir / "{0}-{1}.ScanTailor".format(projectfile.stem,
                                                              idx)
            subtree.write(unicode(out_file))
            splitfiles.append(out_file)
        return splitfiles

    def _generate_output(self, projectfile, out_dir, num_pages):
        """ Run last step for the project file and keep track of the progress
            by emitting :py:attr:`on_progressed` signals.

        :param projectfile:     Path ScanTailor configuration file
        :type projectfile:      :py:class:`pathlib.Path`
        :param out_dir:         Output directory for processed files
        :type out_dir:          :py:class:`pathlib.Path`
        :param num_pages:       Total number of pages to process
        :type num_pages:        int
        """
        logger.debug("Generating output...")
        temp_dir = Path(tempfile.mkdtemp(prefix="spreads."))
        split_config = self._split_configuration(projectfile, temp_dir)
        logger.debug("Launching those subprocesses!")
        processes = [util.get_subprocess([CLI_BIN, '--start-filter=6',
                                          unicode(cfgfile), unicode(out_dir)])
                     for cfgfile in split_config]

        last_count = 0
        while processes:
            recent_count = sum(1 for x in out_dir.glob('*.tif'))
            if recent_count > last_count:
                progress = 0.5 + (float(recent_count)/num_pages)/2
                self.on_progressed.send(self, progress=progress)
                last_count = recent_count
            for p in processes[:]:
                if p.poll() is not None:
                    processes.remove(p)
            time.sleep(.01)
        shutil.rmtree(unicode(temp_dir))

    def process(self, pages, target_path):
        """ Run the most recent image of every page through ScanTailor.

        :param pages:       Pages to be processed
        :type pages:        list of :py:class:`spreads.workflow.Page`
        :param target_path: Base directory where rotated images are to be
                            stored
        :type target_path:  :py:class:`pathlib.Path`
        """
        autopilot = self.config['autopilot'].get(bool)
        if not autopilot and not util.find_in_path('scantailor'):
            raise util.MissingDependencyException(
                "Could not find executable `scantailor` in"
                " $PATH. Please install the appropriate"
                " package(s)!")

        # Create temporary files/directories
        projectfile = Path(tempfile.mkstemp(suffix='.ScanTailor')[1])
        out_dir = Path(tempfile.mkdtemp(prefix='st-out'))

        # Map input paths to their pages so we can more easily associate
        # the generated output files with their pages later on
        in_paths = {}
        for page in pages:
            fpath = page.get_latest_processed(image_only=True)
            if fpath is None:
                fpath = page.raw_image
            in_paths[unicode(fpath)] = page

        logger.info("Generating ScanTailor configuration")
        self._generate_configuration(sorted(in_paths.keys()),
                                     projectfile, out_dir)

        if not autopilot:
            logger.warn("If you are changing output settings (in the last "
                        "step, you *have* to run the last step from the GUI. "
                        "Due to a bug in ScanTailor, your settings would "
                        "otherwise be ignored.")
            time.sleep(5)
            logger.info("Opening ScanTailor GUI for manual adjustment")
            proc = util.get_subprocess([GUI_BIN, unicode(projectfile)])
            proc.wait()
        # Check if the user already generated output files from the GUI
        if not sum(1 for x in out_dir.glob('*.tif')) == len(pages):
            logger.info("Generating output images from ScanTailor "
                        "configuration.")
            self._generate_output(projectfile, out_dir, len(pages))

        # Associate generated output files with our pages
        for fname in out_dir.glob('*.tif'):
            out_stem = fname.stem
            for in_path, page in in_paths.iteritems():
                if Path(in_path).stem == out_stem:
                    target_fname = target_path/fname.name
                    shutil.copyfile(unicode(fname), unicode(target_fname))
                    page.processed_images[self.__name__] = target_fname
                    break
            else:
                logger.warn("Could not find page for output file {0}"
                            .format(fname))

        # Remove temporary files/directories
        shutil.rmtree(unicode(out_dir))
        # FIXME: This fails on Windows since there seems to be some non-gcable
        #        reference to the file around, but I currently cannot figure
        #        out where, so we just ignore the error...
        try:
            projectfile.unlink()
        except WindowsError as e:
            if e.errno == 32:
                pass