# -*- coding: utf-8 -*-

#importando as bibliotecas

from matplotlib.pyplot import text
import yfinance as yf
import pandas as pd
import numpy as np
import os.path
import telegram

pd.options.mode.chained_assignment = None

#escolher uma ação
wege = yf.Ticker('WEGE3.SA')

#escolher inteervalo de dados
wege_dia = wege.history(period='id', interval='5m')

#pegar preço de fechamento
wege_dia = wege_dia.Close

#transformando em dataframe
df_wege_dia = pd.DataFrame(wege_dia)

#reset index
df_wege_dia.reset_index(inplace=True)

#pegar o ultimo valor negociado
wege_dia_ultimo_preco = df_wege_dia.tail(1)

#renomear as colunas
wege_dia_ultimo_preco.rename(columns={'Datetime':'data_pregao', 'Close':'preco_fechamento'}, inplace=True)

#Ajustar a data
wege_dia_ultimo_preco['data_pregao']=pd.to_datetime(wege_dia_ultimo_preco['data_pregao'], format='%Y-%m-%d')

#Usar o data frame historico e pegar apenas o preço de fechamento e data pregão

if os.path.isfile('wege.csv'):
    df_wege = pd.read_csv('wege.csv', delimiter=';')
else:
    df = pd.read_csv('all_bovesta.csv', delimiter=';') #colocar aqui o seu arquivo do bovespa
    df_wege = df[df['silga_acao']=='WEGE3']
    df_wege = df_wege[['data_pregao', 'preco_fechamento']]

#Ajustar a data
df_wege['data_pregao']=pd.to_datetime(df_wege['data_pregao'], format='%Y-%m-%d')

#Retirar a ultima data que queremos calcular
df_remove = df_wege.loc[(df_wege['data_pregao'] == pd.to_datetime('today').normalize())]

df_wege = df_wege.drop(df_wege.index)

#append data atual
df_wege_total = df_wege.append(wege_dia_ultimo_preco)

#Ajuste data atual

df_wege_total['data_pregao']=pd.to_datetime(df_wege_total['data_pregao'], utc=True).dt.date

df_wege_total.to_csv('wege.csv', sep=';', index=False)

#Calcular MACD

rapidaMME=df_wege_total.preco_fechamento.ewm(span=12).mean()

lentaMME = df_wege_total.preco_fechamento.ewm(span=26).mean()

MACD= rapidaMME - lentaMME

sinal=MACD.ewm(span=9).mean()

df_wege_total['MACD'] = MACD
df_wege_total['sinal'] = sinal

#Ajuste de indx e retirar o campo data pregão

df_wege_total = df_wege_total.set_index(pd.DatetimeIndex(df_wege_total['data_pregao'].values))
df_wege_total = df_wege_total.drop('data_pregao',1)

# Criar codigo para verificar a compra ou a venda

df_wege_total['flag']=''
df_wege_total['preco_compra']=np.nan
df_wege_total['preco_venda']=np.nan

for i in range(1, len(df_wege_total.sinal)):
    if df_wege_total['MACD'][i] > df_wege_total['sinal'][i]:
        if df_wege_total['flag'][i-1] == 'c':
            df_wege_total['flag'][i]='C'
        else:
            df_wege_total['flag'][i]='C'
            df_wege_total['preco_compra'][i] = df_wege_total['preco_fechamento'][i]

    elif df_wege_total['MACD'][i] < df_wege_total['sinal'][i]:
        if df_wege_total['flag'][i-1] =='V':
            df_wege_total['flag'][i]='V'
        else:
            df_wege_total['flag'][i]='V'
            df_wege_total['preco_venda'][i] = df_wege_total['preco_fechamento'][i]

#Verifica os 2 ultimos dias
hoje = df_wege_total.flag[-1]
ontem = df_wege_total.flag[-2]

flag= hoje
preco_fechamento = round(df_wege_total.preco_fechamento.tail(1)[-1],2)

print(flag, preco_fechamento)

my_token = '1840232813:AAHxoVmcDWHK3jAxiTWsMqTsiw9vTHaICpY'
chat_id = '-476980685'

def envia_mensagem(msg, chat_id, token=my_token):
    bot=telegram.Bot(token = token)
    bot.SendMessage(chat_id = chat_id, text=msg)

msg = f'WEGE3 (WEGE), {flag} preço de fechamento: {preco_fechamento}'

if ontem == hoje:
    envia_mensagem(msg, chat_id, my_token)


import os
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.template.loader import get_template
from xhtml2pdf import pisa
from ..models import *
from django.contrib.auth.models import User
from django.contrib.staticfiles import finders

def link_callback(uri, rel):
    """
    Convert HTML URIs to absolute system paths so xhtml2pdf can access those
    resources
    """
    result = finders.find(uri)
    if result:
        if not isinstance(result, (list, tuple)):
            result = [result]
        result = list(os.path.realpath(path) for path in result)
        path = result[0]
    else:
        sUrl = settings.STATIC_URL  # Typically /static/
        sRoot = settings.STATIC_ROOT  # Typically /home/userX/project_static/
        mUrl = settings.MEDIA_URL  # Typically /media/
        mRoot = settings.MEDIA_ROOT  # Typically /home/userX/project_static/media/

        if uri.startswith(mUrl):
            path = os.path.join(mRoot, uri.replace(mUrl, ""))
        elif uri.startswith(sUrl):
            path = os.path.join(sRoot, uri.replace(sUrl, ""))
        else:
            return uri

    # make sure that file exists
    if not os.path.isfile(path):
        raise Exception(
            'media URI must start with %s or %s' % (sUrl, mUrl)
        )
    return path

def admission_letter(request):
    # HttpResponse("workng")
    # get_student = get_object_or_404(Student, user=request.user)
    # user = User.objects.get(id=request.user.id)

    signature = CollegeSettings.objects.first()

    entry = Registration.objects.all()

    template_path = 'KCHS/registration/admission_letter.html'
    context = {'logo': signature,
               'registration': entry}

    # Create a Django response object, and specify content_type as pdf
    response = HttpResponse(content_type='application/pdf')
    # if the file is dowloaded
    # response['Content-Disposition'] = 'attachment; filename="fieldApplicationForm.pdf"'
    # if display
    response['Content-Disposition'] = 'filename="FieldApplicationLetter.pdf"'

    # find the template and render it.
    template = get_template(template_path)
    html = template.render(context)

    # create a pdf
    pisa_status = pisa.CreatePDF(
        html, dest=response)
    # if error then show some funny view
    if pisa_status.err:
        return HttpResponse('We had some errors <pre>' + html + '</pre>')
    return response


# -*- coding: utf-8 -*-
"""
Created on Tue Apr 26 15:20:29 2016

@author: neo
"""

#cat1 = 'new_candidate2.cat'
#cat1 = 'icrf1.cat'
cat1 = '331_sou.cat'
cat2 = 'icrf2.cat'
#cat3 = 'MFV247.cat'
#cat4 = 'AMS260.cat'

#cat  = 'common_source.cat'
#fcat = open('../catalog/'+cat,'w')

#cat1  = 'common_source.cat'
f1 = open('../catalog/'+cat1,'r')
sou1 = f1.readlines()

#cat2 = '352_sou.cat'
f2 = open('../catalog/'+cat2,'r')
sou2 = f2.readlines()

#f3 = open('../catalog/'+cat3,'r')
#sou3 = f3.readlines()
#
#f4 = open('../catalog/'+cat4,'r')
#sou4 = f4.readlines()

f1.close()
f2.close()
#f3.close()
#f4.close()

com = [ sou1[i].strip('\n') for i in range(len(sou1)) if sou1[i] in sou2 \
#    and sou1[i] in sou3 and sou1[i] in sou4]
    ]
print(len(com))
#for i in range(len(com)):
#    print>>fcat, com[i]
#fcat.close()


from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QHeaderView
from db.models import *
from gui.widgets.custom_widgets import DialogWithDisablingOptions

class MainWidget(QtWidgets.QWidget):
    def __init__(self, parent, model):
        super().__init__(parent)
        self.layout = QtWidgets.QHBoxLayout(self)

        self.league_list = QtWidgets.QTableView(self)
        self.league_list.setModel(model)
        self.league_list.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
        self.league_list.clearSelection()
        self.league_list.resizeColumnsToContents()
        self.league_list.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.layout.addWidget(self.league_list, 2)

        self.button_layout = QtWidgets.QVBoxLayout(self)

        self.new_league_button = QtWidgets.QPushButton("New league", self)
        self.button_layout.addWidget(self.new_league_button)
        self.new_league_button.clicked.connect(self.on_new_league)

        self.new_round_button = QtWidgets.QPushButton("New round", self)
        self.button_layout.addWidget(self.new_round_button)
        self.new_round_button.clicked.connect(self.on_new_round)

        self.del_button = QtWidgets.QPushButton("Delete league", self)
        self.button_layout.addWidget(self.del_button)
        self.del_button.clicked.connect(self.on_delete)

        self.results_overview_button = QtWidgets.QPushButton("Results overview", self)
        self.button_layout.addWidget(self.results_overview_button)
        self.results_overview_button.clicked.connect(self.on_load)

        self.quit_button = QtWidgets.QPushButton("Quit", self)
        self.button_layout.addWidget(self.quit_button)
        self.quit_button.clicked.connect(self.on_quit)

        self.button_layout.addStretch()
        self.jsolutions_label = QtWidgets.QLabel("JSolutions")
        self.jsolutions_label.setAlignment(QtCore.Qt.AlignCenter)
        self.button_layout.addWidget(self.jsolutions_label)
        self.button_layout.setAlignment(QtCore.Qt.AlignTop)
        self.button_layout.setAlignment(self.jsolutions_label, QtCore.Qt.AlignBottom)

        self.layout.addLayout(self.button_layout, 1)
        self.setLayout(self.layout)

    def get_selected_league(self):
        selected_indexes = self.league_list.selectedIndexes()
        if len(selected_indexes) != 2 or (selected_indexes[0].row() != selected_indexes[1].row()):
            return None
        else:
            return self.league_list.model().get_league(selected_indexes[0])

    @pyqtSlot()
    def on_quit(self):
        QtCore.QCoreApplication.instance().quit()

    @pyqtSlot()
    def on_load(self):
        league = self.get_selected_league()
        if league is None:
            return
        if league.max_round == 0 or league.max_round is None:
            QtWidgets.QMessageBox.warning(self, "Zero rounds error", "Zero rounds have been played in this league. "
                                                                     "Unable to show the results overview.")
        else:
            from gui.windows import LeagueOverviewWindow
            win = LeagueOverviewWindow(league, parent=self)
            win.show()

    @pyqtSlot()
    def on_delete(self):
        league = self.get_selected_league()
        if league is None:
            return
        league = League.get_by_name(league.name)
        reply = QtWidgets.QMessageBox.question(self, "Message", f"Are you sure you want to delete {league.name} league?",
                                                      QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
        if reply == QtWidgets.QMessageBox.Yes:
            Result.delete_all_from_league(league)
            league.delete_instance()
            self.league_list.model().refresh()
        else:
            return

    @pyqtSlot()
    def on_new_league(self):
        league_names = list(map(lambda x: x.name, League.get_all()))
        dialog = DialogWithDisablingOptions("New league", "Please enter valid league name:", league_names)
        if dialog.exec_():
            league = League.create(name=dialog.ret_str)
            league.save()
            self.league_list.model().refresh()

    @pyqtSlot()
    def on_new_round(self):
        league = self.get_selected_league()
        if league is None:
            return
        from gui.windows import InputWindow
        new_round_win = InputWindow(self, league)
        new_round_win.show()

    def refresh_leagues_overview(self):
        self.league_list.model().refresh()


# ATRIBUTOS:
# dia: int
# mes: int
# anio: int

class Fecha:

	def __init__(self, x):
		(dia,mes,anio)=x.split("/")
		(self.dia,self.mes,self.anio)=(int(dia), int(mes), int(anio))

	def __str__(self):
			return str(self.dia)+"/"+str(self.mes)+"/"+str(self.anio)

	def __sub__(self,x):
		pass

	def siguiente(self):
		nueva = Fecha(str(self))
		nueva.dia += 1
		if nueva.dia > Fecha.dias_mes(nueva.mes, nueva.anio):
			nueva.dia = 1
			nueva.mes += 1
			if nueva.mes > 12:
				nueva.mes = 1
				nueva.anio += 1
		return nueva

	@staticmethod
	def dias_mes(mes, anio):
		dias = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
		if mes == 2 and Fecha.bisiesto(anio):
			return 29
		return dias[mes - 1]

	@staticmethod
	def bisiesto(anio):
		return (anio % 4 == 0 and anio % 100 != 0) or (anio % 400 == 0)

f = Fecha("01/05/2013")
print(f)
g = f.siguiente()
print(g)


#!/usr/bin/env python3
#
#  Add metadata from Apple Podcasts to cached mp3s
#  so they sync to Garmin Watches with appropriate
#  metadata
#  ---------------
#  Michael Oliver, 2022, MIT License
#
#  Standing on the shoulders of giants:
#  Modified prior art and inspiration by Douglas Watson
#  https://douglas-watson.github.io/post/2020-05_export_podcasts/
#
#  Intended for use as a cron job or to be run before Garmin Express
#
#  Queries the Apple Podcasts database for episodes that have been
#  downloaded, then updates the metadata embeded in those files
#  so that the mp3's have the correct metadata
#
#  https://mcoliver.com

import os
import urllib.parse
import sqlite3

SQL = """
SELECT p.ZAUTHOR, p.ZTITLE, e.ZTITLE, e.ZASSETURL, e.ZPUBDATE
from ZMTEPISODE e
join ZMTPODCAST p
    on e.ZPODCASTUUID = p.ZUUID
where ZASSETURL NOTNULL;
"""

def check_imports():
    ''' Prompts for password to install dependencies, if needed '''
    try:
        import mutagen
    except ImportError:
        os.system(
            """osascript -e 'do shell script "/usr/bin/pip3 install mutagen" with administrator privileges'""")

def get_downloaded_episodes(db_path):
    '''Run SQL Query'''
    return sqlite3.connect(db_path).execute(SQL).fetchall()

def main(db_path):
    '''Itterate through the database and re-encode the mp3s'''
    for author, podcast, title, path, zpubdate \
            in get_downloaded_episodes(db_path):

        src_path = urllib.parse.unquote(path[len('file://'):])
        print(f"Updating: {src_path}")
        if os.path.exists(src_path):
            try:
                mp3 = MP3(src_path, ID3=EasyID3)
                if mp3.tags is None:
                    mp3.add_tags()
                mp3.tags['artist'] = author
                mp3.tags['album'] = podcast
                mp3.tags['title'] = title
                mp3.save()
            except HeaderNotFoundError:
                print(f"Corrupted file: {podcast} - {title}")
                continue
            except IsADirectoryError:
                print(
                    f"Failed to export {podcast} - {title}, media file is a movie")
                continue
            except FileNotFoundError:
                print("File does not exist. skipping")
                continue
        else:
            print (f"File does not Exist {src_path}")

if __name__ == "__main__":
    db_path = os.path.expanduser(
        "~/Library/Group Containers/243LU875E5.groups.com.apple.podcasts/Documents/MTLibrary.sqlite")

    check_imports()
    from mutagen.mp3 import MP3, HeaderNotFoundError
    from mutagen.easyid3 import EasyID3

    main(db_path)


from moduleBaseClass import ModuleBaseClass
from StringIO import StringIO
from PIL import Image

class Module(ModuleBaseClass):
    def __init__(self):
        self.header = 'x42\x4d'
        self.name = 'bmp'

    def final_check(self, raw):
        try:
            Image.open(StringIO(raw))
            return True
        except:
            return False


import pandas as pd
import lxml.html
import requests
import shelve
import os, sys
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger()

if not os.path.exists('database'):
    os.mkdir('database')
elif not os.path.isdir('database'):
    os.remove('database')
    os.mkdir('database')

xlsx = 'database/Free+English+textbooks.xlsx'
xfile = pd.ExcelFile(xlsx)
df = xfile.parse()

books = shelve.open('database/serial')

class Book:

    PARENT = 'https://link.springer.com'

    def __init__(self, idx, title, edition, subject, url):

        self.title      = title.replace('/', '_')
        self.idx        = idx

        self.name       = f'{self.title}, {edition}'
        self.subject    = self._process(subject)
        self._image_url = None
        self._url       = url

        self.pdf        = None
        self.epub       = None

    def __repr__(self):
        return f'{self.idx}: {self.name}'

    def __eq__(self, other):
        return self.idx == other.idx

    def _process(self, subject):

        subject = subject.split(';')[0]

        book = self

        try:
            books[subject].append(book)
        except (KeyError, AttributeError):
            books[subject] = []
            books[subject].append(book)

        self._make_template(book, subject)

        return subject

    def _make_template(self, book, subject):

        path = os.path.join('templates', subject)

        if os.path.exists(path):
            return
        else:
            html = '''{% extends "base.html" %}
{% block content %}

<< BACK TO INDEX

{{ subject }} Books

    {% for book in books[subject] %}

{{ book.name }}

DOWNLOAD:

        {% if book.pdf %}
            PDF
        {% endif %}

        {% if book.epub %}
            EPUB
        {% endif %}

        {% if not book.pdf and not book.epub %}
            unavailable.
        {% endif %}

    {% endfor %}

{% endblock %}'''

            with open(path + '.html', 'w') as fhand:
                fhand.write(html)

    def _scrape(self):

        name = self.name.replace(' ', '_') + '.html'
        path = os.path.join('static', 'cache', name)

        if os.path.exists(path):
            with open(path, 'rb') as fhand:
                html = fhand.read()
                html = lxml.html.fromstring(html)
        else:
            response = requests.get(self._url)
            content  = response.content
            with open(path, 'wb') as fhand:
                fhand.write(content)
            html = lxml.html.fromstring(content)

        try:
            xpath, epub = self.__get_xpaths(html)
        except IndexError:
            print(f'Error: {self.idx} {self.name}'
                  ' server access point missing')
            return False

        else:
            self.__make_links(xpath, epub)

        finally:
            self.image = self.name.replace(' ', '_').replace('\\', '_') + '.jpeg'
            path       = os.path.join('static', 'images', self.image)
            if not os.path.exists(self.image):
                self.__set_image_url(html)
                try:
                    image = requests.get(self._image_url).content
                except:
                    image = ""
                finally:
                    with open(path, 'wb') as fhand:
                        fhand.write(image)

    def __get_xpaths(self, html):
        epub  = None
        xpath = html.xpath('//*[@id="main-content"]/article[1]/'
                           'div/div/div[2]/div/div/a')
        if not bool(xpath):
            xpath = html.xpath(
                '//*[@id="main-content"]/article[1]/div/div/div[2]/div[1]/a'
                )
            epub  = html.xpath(
                '//*[@id="main-content"]/article[1]/div/div/div[2]/div[2]/a'
                )
            epub = epub[0]
        xpath = xpath[0]
        return xpath, epub

    def __make_links(self, xpath, epub):
        stub     = xpath.get('href')
        self.pdf = __class__.PARENT + stub
        if epub is not None:
            stub      = epub.get('href')
            self.epub = __class__.PARENT + stub

    def __set_image_url(self, html):
        if self.pdf or self.epub:
            img_xpath = html.xpath(
                '//*[@id="main-content"]/article[1]/div/aside[1]/'
                'div/div/div/img'
            )
            img_xpath = None if not len(img_xpath) else img_xpath[0]
            self._image_url = img_xpath.get('src')
        else:
            self._image_url = ""

def load_data():

    for idx, row in df.iterrows():

        book = Book(idx,
                df['Book Title'].iloc[idx],
                df['Edition'].iloc[idx],
                df['Subject Classification'].iloc[idx],
                df['OpenURL'].iloc[idx])

        try:

            assert book in books[book.subject]
            logger.info(f' SKIPPING : {book.name}')
            continue

        except (KeyError, AssertionError) as init:

            subject = books[book.subject]
            book._scrape()
            subject.append(book)
            books[book.subject] = subject
            logger.info(f' SERIALIZED : {book.name}')

    else: books.close()


from app import create_celery_app
from app.lib.flask_mailplus import send_templated_msg

celery = create_celery_app()

@celery.task()
def test_mail_func(email,message):

    ctx = {'email':email, 'message':message}

    send_templated_msg(subject='[Snake Eyes], contact',
                       sender = email,
                       recipients= "",
                       reply_to = email,
                       template = "contact/mail/index",ctx=ctx)
    return None


from heapq import heappush, heappop
class Solution:
    def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:
        visited = set()
        heap = []
        output = []

        heappush(heap, (nums1[0]+nums2[0], 0, 0))
        visited.add((0, 0))

        while len(output) < k and heap:
            _, i, j = heappop(heap)
            output.append((nums1[i], nums2[j]))
            if i+1 < len(nums1) and (i+1, j) not in visited:
                heappush(heap, (nums1[i+1]+nums2[j], i+1, j))
                visited.add((i+1, j))
            if j+1 < len(nums2) and (i, j+1) not in visited:
                heappush(heap, (nums1[i]+nums2[j+1], i, j+1))
                visited.add((i, j+1))

        return output


import datetime
import sys
import uuid

import pandas as pd
import pytest
from dagster_gcp import (
    bigquery_resource,
    bq_create_dataset,
    bq_delete_dataset,
    bq_solid_for_queries,
    import_df_to_bq,
    import_gcs_paths_to_bq,
)
from dagster_pandas import DataFrame
from google.cloud import bigquery
from google.cloud.exceptions import NotFound

from dagster import (
    DagsterExecutionStepExecutionError,
    InputDefinition,
    List,
    ModeDefinition,
    Nothing,
    OutputDefinition,
    Path,
    execute_pipeline,
    pipeline,
    solid,
)
from dagster.config.validate import validate_config
from dagster.core.definitions import create_environment_type
from dagster.seven import mock

def dataset_exists(name):
    '''Check if dataset exists - ensures we have properly cleaned up after tests and haven't leaked
    any datasets'''
    client = bigquery.Client()
    dataset_ref = client.dataset(name)

    try:
        client.get_dataset(dataset_ref)
        return True
    except NotFound:
        return False

def get_dataset():
    '''Creates unique dataset names of the form: test_ds_83791a53
    '''
    return 'test_ds_' + str(uuid.uuid4()).replace('-', '_')

def bq_modes():
    return [ModeDefinition(resource_defs={'bigquery': bigquery_resource})]

def test_simple_queries():
    @pipeline(mode_defs=bq_modes())
    def bq_pipeline():
        bq_solid_for_queries(
            [
                # Toy example query
                'SELECT 1 AS field1, 2 AS field2;',
                # Test access of public BQ historical dataset (only processes ~2MB here)
                # pylint: disable=line-too-long
                '''SELECT *
            FROM `weathersource-com.pub_weather_data_samples.sample_weather_history_anomaly_us_zipcode_daily`
            ORDER BY postal_code ASC, date_valid_std ASC
            LIMIT 1''',
            ]
        ).alias('bq_query_solid')()

    res = execute_pipeline(bq_pipeline).result_for_solid('bq_query_solid')
    assert res.success

    values = res.output_value()
    for df in values:
        assert isinstance(df, pd.DataFrame)
    assert values[0].to_dict('list') == {'field1': [1], 'field2': [2]}
    assert values[1].to_dict('list') == {
        'postal_code': ['02101'],
        'country': ['US'],
        'date_valid_std': [datetime.date(2014, 1, 1)],
        'doy_std': [1],
        'avg_temperature_air_2m_f': [25.05],
        'avg_temperature_anomaly_air_2m_f': [-7.81],
        'tot_precipitation_in': [0.0],
        'tot_precipitation_anomaly_in': [-0.28],
        'tot_snowfall_in': [0.0],
        'tot_snowfall_anomaly_in': [-1.36],
        'avg_wind_speed_10m_mph': [7.91],
        'avg_wind_speed_10m_anomaly_mph': [-1.85],
    }

# pylint: disable=line-too-long
def test_bad_config():
    configs_and_expected_errors = [
        (
            # Create disposition must match enum values
            {'create_disposition': 'this is not a valid create disposition'},
            'Value not in enum type BQCreateDisposition',
        ),
        (
            # Dataset must be of form project_name.dataset_name
            {'default_dataset': 'this is not a valid dataset'},
            'Value at path root:solids:test:config:query_job_config:default_dataset is not valid. Expected "_Dataset"',
        ),
        (
            # Table must be of form project_name.dataset_name.table_name
            {'destination': 'this is not a valid table'},
            'Value at path root:solids:test:config:query_job_config:destination is not valid. Expected "_Table"',
        ),
        (
            # Priority must match enum values
            {'priority': 'this is not a valid priority'},
            'Value not in enum type BQPriority',
        ),
        (
            # Schema update options must be a list
            {'schema_update_options': 'this is not valid schema update options'},
            'Value at path root:solids:test:config:query_job_config:schema_update_options must be list. Expected: [BQSchemaUpdateOption]',
        ),
        (
            {'schema_update_options': ['this is not valid schema update options']},
            'Value not in enum type BQSchemaUpdateOption',
        ),
        (
            {'write_disposition': 'this is not a valid write disposition'},
            'Value not in enum type BQWriteDisposition',
        ),
    ]

    @pipeline(mode_defs=bq_modes())
    def test_config_pipeline():
        bq_solid_for_queries(['SELECT 1']).alias('test')()

    env_type = create_environment_type(test_config_pipeline)
    for config_fragment, error_message in configs_and_expected_errors:
        config = {'solids': {'test': {'config': {'query_job_config': config_fragment}}}}
        result = validate_config(env_type, config)
        assert result.errors[0].message == error_message

def test_create_delete_dataset():
    dataset = get_dataset()

    @pipeline(mode_defs=bq_modes())
    def create_pipeline():
        bq_create_dataset.alias('create_solid')()

    config = {'solids': {'create_solid': {'config': {'dataset': dataset, 'exists_ok': True}}}}

    assert execute_pipeline(create_pipeline, config).result_for_solid('create_solid').success

    config = {'solids': {'create_solid': {'config': {'dataset': dataset, 'exists_ok': False}}}}
    with pytest.raises(DagsterExecutionStepExecutionError) as exc_info:
        execute_pipeline(create_pipeline, config)
    assert 'Dataset "%s" already exists and exists_ok is false' % dataset in str(
        exc_info.value.user_exception
    )

    @pipeline(mode_defs=bq_modes())
    def delete_pipeline():
        bq_delete_dataset.alias('delete_solid')()

    # Delete should succeed
    config = {'solids': {'delete_solid': {'config': {'dataset': dataset}}}}
    assert execute_pipeline(delete_pipeline, config).result_for_solid('delete_solid').success

    # Delete non-existent with "not_found_ok" should succeed
    config = {'solids': {'delete_solid': {'config': {'dataset': dataset, 'not_found_ok': True}}}}
    assert execute_pipeline(delete_pipeline, config).result_for_solid('delete_solid').success

    # Delete non-existent with "not_found_ok" False should fail
    config = {'solids': {'delete_solid': {'config': {'dataset': dataset, 'not_found_ok': False}}}}
    with pytest.raises(DagsterExecutionStepExecutionError) as exc_info:
        execute_pipeline(delete_pipeline, config)
    assert 'Dataset "%s" does not exist and not_found_ok is false' % dataset in str(
        exc_info.value.user_exception
    )

    assert not dataset_exists(dataset)

# See: https://github.com/dagster-io/dagster/issues/1711
@pytest.mark.skip
def test_pd_df_load():
    dataset = get_dataset()
    table = '%s.%s' % (dataset, 'df')

    test_df = pd.DataFrame({'num1': [1, 3], 'num2': [2, 4]})

    create_solid = bq_create_dataset.alias('create_solid')
    load_solid = import_df_to_bq.alias('load_solid')
    query_solid = bq_solid_for_queries(['SELECT num1, num2 FROM %s' % table]).alias('query_solid')
    delete_solid = bq_delete_dataset.alias('delete_solid')

    @solid(
        input_defs=[InputDefinition('success', Nothing)], output_defs=[OutputDefinition(DataFrame)]
    )
    def return_df(_context):  # pylint: disable=unused-argument
        return test_df

    config = {
        'solids': {
            'create_solid': {'config': {'dataset': dataset, 'exists_ok': True}},
            'load_solid': {'config': {'destination': table}},
            'delete_solid': {'config': {'dataset': dataset, 'delete_contents': True}},
        }
    }

    @pipeline(mode_defs=bq_modes())
    def bq_pipeline():
        delete_solid(query_solid(load_solid(return_df(create_solid()))))

    result = execute_pipeline(bq_pipeline, config)
    assert result.success

    values = result.result_for_solid('query_solid').output_value()
    assert values[0].to_dict() == test_df.to_dict()

    # BQ loads should throw an exception if pyarrow and fastparquet aren't available
    with mock.patch.dict(sys.modules, {'pyarrow': None, 'fastparquet': None}):
        with pytest.raises(DagsterExecutionStepExecutionError) as exc_info:
            result = execute_pipeline(bq_pipeline, config)
        assert (
            'loading data to BigQuery from pandas DataFrames requires either pyarrow or fastparquet'
            ' to be installed' in str(exc_info.value.user_exception)
        )
        cleanup_config = {
            'solids': {'delete_solid': {'config': {'dataset': dataset, 'delete_contents': True}}}
        }

        @pipeline(mode_defs=bq_modes())
        def cleanup():
            delete_solid()

        assert execute_pipeline(cleanup, cleanup_config).success

    assert not dataset_exists(dataset)

# See: https://github.com/dagster-io/dagster/issues/1711
@pytest.mark.skip
def test_gcs_load():
    dataset = get_dataset()
    table = '%s.%s' % (dataset, 'df')

    create_solid = bq_create_dataset.alias('create_solid')
    query_solid = bq_solid_for_queries(
        [
            'SELECT string_field_0, string_field_1 FROM %s ORDER BY string_field_0 ASC LIMIT 1'
            % table
        ]
    ).alias('query_solid')
    delete_solid = bq_delete_dataset.alias('delete_solid')

    @solid(
        input_defs=[InputDefinition('success', Nothing)], output_defs=[OutputDefinition(List[Path])]
    )
    def return_gcs_uri(_context):  # pylint: disable=unused-argument
        return ["gs://cloud-samples-data/bigquery/us-states/us-states.csv"]

    config = {
        'solids': {
            'create_solid': {'config': {'dataset': dataset, 'exists_ok': True}},
            'import_gcs_paths_to_bq': {
                'config': {
                    'destination': table,
                    'load_job_config': {
                        'autodetect': True,
                        'skip_leading_rows': 1,
                        'source_format': 'CSV',
                        'write_disposition': 'WRITE_TRUNCATE',
                    },
                }
            },
            'delete_solid': {'config': {'dataset': dataset, 'delete_contents': True}},
        }
    }

    @pipeline(mode_defs=bq_modes())
    def bq_pipeline():
        delete_solid(query_solid(import_gcs_paths_to_bq(return_gcs_uri(create_solid()))))

    result = execute_pipeline(bq_pipeline, config)
    assert result.success

    values = result.result_for_solid('query_solid').output_value()
    assert values[0].to_dict() == {'string_field_0': {0: 'Alabama'}, 'string_field_1': {0: 'AL'}}

    assert not dataset_exists(dataset)


from pathlib import Path
import numpy as np
import torch
from torch.utils.data import Dataset
from torchvision import transforms
import torchvision.transforms.functional as TF
from PIL import Image

from src.draw_utils import save_img_with_kps
from src.readers.image_reader import ImageReader
from typing import Dict
from torch import Tensor
from typing import Tuple

class Phase0PointsDataset(Dataset):
    MEAN = [0.5, 0.5, 0.5]
    STD = [0.2, 0.2, 0.2]
    IMG_SIZE = 768
    TRANSFORMS = transforms.Compose(
        [
            transforms.Resize(IMG_SIZE),
            transforms.ToTensor(),
            transforms.Normalize(mean=MEAN, std=STD),
        ]
    )

    def __init__(self, reader: ImageReader, augment: bool = False):
        assert isinstance(reader, ImageReader)
        self.reader = reader
        self.augment = augment

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

    def __getitem__(self, idx: int) -> Dict[str, Tensor]:
        sample = self.reader[idx]

        img = sample.phase_0_image
        kps = torch.tensor(sample.phase_0_points).to(dtype=torch.float32)

        if self.augment:
            if np.random.rand() < 0.5:
                img, kps = Phase0PointsDataset.color_augment(img, kps)

            if np.random.rand() < 0.5:
                img, kps = Phase0PointsDataset.rotate(img, kps)

            if np.random.rand() < 0.5:
                img, kps = Phase0PointsDataset.perspective_augment(img, kps)

            if np.random.rand() < 0.5:
                img, kps = Phase0PointsDataset.crop_augment(img, kps)

        kps = kps / torch.tensor([img.width, img.height])
        kps = kps.flatten()

        img_tensor = Phase0PointsDataset.TRANSFORMS(img)

        sample_t = {
            "img": img_tensor,
            "kps": kps,
        }

        return sample_t

    @staticmethod
    def color_augment(img: Image.Image, kps: Tensor) -> Tuple[Image.Image, Tensor]:
        img = TF.adjust_brightness(img, 0.7 + np.random.rand() * 1.5)
        img = TF.adjust_contrast(img, 0.5 + np.random.rand() * 1.5)
        img = TF.adjust_gamma(img, gamma=0.5 + np.random.rand(), gain = 0.5 + np.random.rand())
        img = TF.adjust_hue(img, -0.5 + np.random.rand())
        img = TF.adjust_saturation(img, np.random.rand() * 1.5)
        return img, kps

    @staticmethod
    def rotate(img: Image.Image, kps: Tensor) -> Tuple[Image.Image, Tensor]:
        rotation_angle_deg = np.random.rand() * 30 - 15
        rotation_angle_rad = np.deg2rad(rotation_angle_deg)
        rotation_matrix = np.array(
            [
                [np.cos(rotation_angle_rad), -np.sin(rotation_angle_rad)],
                [np.sin(rotation_angle_rad), np.cos(rotation_angle_rad)],
            ]
        )
        rot_torch = torch.from_numpy(rotation_matrix.astype(np.float32))
        img = TF.rotate(img, np.rad2deg(rotation_angle_rad))

        center = torch.tensor([img.width, img.height]) / 2
        kps = kps - center
        kps = torch.matmul(kps, rot_torch)
        kps = kps + center
        return img, kps

    @staticmethod
    def perspective_augment(img: Image.Image, kps: Tensor) -> Tuple[Image.Image, Tensor]:
        topleft = kps[0]
        topright = kps[1]
        bottomleft = kps[2]
        bottomright = kps[3]

        startpoints = [
            topleft.to(dtype=torch.int32).tolist(),
            topright.to(dtype=torch.int32).tolist(),
            bottomright.to(dtype=torch.int32).tolist(),
            bottomleft.to(dtype=torch.int32).tolist(),
        ]

        a = min([torch.linalg.norm(topleft - topright) * 0.1, torch.linalg.norm(topleft - bottomleft) * 0.1])
        new_topleft = topleft + (-a + np.random.rand() * 2*a)
        new_topleft = torch.clip(new_topleft, torch.tensor([0, 0]), torch.tensor([img.width, img.height]))
        new_topright = topright + (-a + np.random.rand() * 2*a)
        new_topright = torch.clip(new_topright, torch.tensor([0, 0]), torch.tensor([img.width, img.height]))
        new_bottomleft = bottomleft + (-a + np.random.rand() * 2*a)
        new_bottomleft = torch.clip(new_bottomleft, torch.tensor([0, 0]), torch.tensor([img.width, img.height]))
        new_bottomright = bottomright + (-a + np.random.rand() * 2*a)
        new_bottomright = torch.clip(new_bottomright, torch.tensor([0, 0]), torch.tensor([img.width, img.height]))

        endpoints = [
            new_topleft.to(dtype=torch.int32).tolist(),
            new_topright.to(dtype=torch.int32).tolist(),
            new_bottomright.to(dtype=torch.int32).tolist(),
            new_bottomleft.to(dtype=torch.int32).tolist(),
        ]
        img = transforms.functional.perspective(img, startpoints, endpoints)
        kps = torch.stack([new_topleft, new_topright, new_bottomleft, new_bottomright])

        return img, kps

    @staticmethod
    def crop_augment(img: Image.Image, kps: Tensor) -> Tuple[Image.Image, Tensor]:
        kps_x0 = kps[:, 0].min().item()
        kps_x1 = kps[:, 0].max().item()
        kps_y0 = kps[:, 1].min().item()
        kps_y1 = kps[:, 1].max().item()

        crop_x0 = int(kps_x0 * np.random.rand())
        crop_x1 = int(kps_x1 + np.random.rand() * (img.width - kps_x1))
        crop_y0 = int(kps_y0 * np.random.rand())
        crop_y1 = int(kps_y1 + np.random.rand() * (img.height - kps_y1))

        # make square
        crop_1 = max(crop_x1 - crop_x0, crop_y1 - crop_y0)
        crop_y1 = crop_y0 + crop_1
        crop_x1 = crop_x0 + crop_1

        img = img.crop((crop_x0, crop_y0, crop_x1, crop_y1))
        kps = kps - torch.tensor([crop_x0, crop_y0])

        return img, kps

    @staticmethod
    def img_from_tensor(img_tensor: Tensor) -> Image.Image:
        img: np.ndarray = img_tensor.permute(1, 2, 0).numpy()
        img = (
            img * np.array(Phase0PointsDataset.STD) + np.array(Phase0PointsDataset.MEAN)
        ) * 255
        img = img.astype(np.uint8)
        img = Image.fromarray(img)
        return img

    def show(self, idx: int, out_folder: Path, repeat_idx=0, verbose: bool = False):
        sample_t = self[idx]
        img_tensor = sample_t["img"]
        kps_tensor = sample_t["kps"]

        img = Phase0PointsDataset.img_from_tensor(img_tensor)
        kps = kps_tensor.reshape(-1, 2).numpy() * Phase0PointsDataset.IMG_SIZE
        filename = out_folder / f"sample_{idx}_{repeat_idx}.jpg"

        save_img_with_kps(img, kps, filename, circle_radius=10, verbose=verbose)


#Reference Files
#https://lotr.fandom.com/wiki/Quest_of_the_Ring
#https://www.asciiart.eu/books/lord-of-the-rings

import random
import time

#Title page/main menu
def main():
    print(" _____         _ _          __   _   _           ______ _")
    print("|_   _|       (_) |        / _| | | | |          | ___ (_)")
    print("  | |_ __ __ _ _| |   ___ | |_  | |_| |__   ___  | |_/ /_ _ __   __ _ ___ ")
    print("  | | '__/ _` | | |  / _ \|  _| | __| '_ \ / _ \ |    /| | '_ \ / _` / __|")
    print("  | | | | (_| | | | | (_) | |   | |_| | | |  __/ | |\ \| | | | | (_| \__ \ ")
    print("  \_/_|  \__,_|_|_|  \___/|_|    \__|_| |_|\___| \_| \_|_|_| |_|\__, |___/")
    print("                                                                 __/ |    ")
    print("                                                                |___/")
    print("\n")
    print("Welcome to the Hobbiton Trail")
    print("Travel from the Shire to Mordor to deliver the one ring!")
    print("1.) Start")
    print("2.) Exit")
    option = input("-->")
    if option == "1" or option == "start" or option == "Start":
        start()
    elif option == "2" or option == "Exit" or option == "exit":
        quit()
    else:
        main()

#asking name of main player "Ring-bearer"
def start():
    print("\n")
    global player_name
    player_name = input("What is your name, young hobbit?: ")
    while len(player_name)>0:
        if len(player_name)>1:
            print(str(player_name)+"? A fine hobbit name.")
            companion()
        elif len(player_name)==1:
            player_name_choice = input(str(player_name)+"? Truly? Only one letter? Must be Dwarvish. Are you certain that's your name? (y/n):")
            if player_name_choice == "y" or player_name_choice == "Y":
                print("\n")
                print("Very well..."+(str(player_name))+"... *grumble*... I'm still going to treat you like any other hobbit!")
                companion()
            elif player_name_choice == "n" or player_name_choice == "N":
                player_name = input("What is your name: ")
                companion()
        else:
            print("Did you write anything? My eyes must be going. Could you write that again for me?")
            player_name = input("What is your name: ")
            companion()

#asking name of companion "gardener"
def companion():
    print("\n")
    global companion_name
    companion_name = input("This is a dangerous journey to undertake alone, who will be joining you?: ")
    while len(companion_name)>0:
        if len(companion_name)>1:
            print (str(companion_name)+"? A worthy companion.")
            f.companions.append(companion_name)
            mode()
        elif len(companion_name)==1:
            companion_name_choice = input(str(companion_name)+"? *sigh* Another dwarf? There's sure to be too much singing on this journey. Are you sure that's who you wish to travel with? (y/n):")
            if companion_name_choice == "y" or player_name_choice =="Y":
                print("Very well..."+(str(companion_name))+"... *sigh* ... Save me from the stubbornness of Dwarves.")
                f.companions.append(companion_name)
                mode()
            elif companion_name_choice == "n" or player_name_choice == "N":
                companion_name = input("Who will be joining you?: ")
                f.companions.append(companion_name)
                mode()

 #starting variables to refer back to/modify
class party():
    def fellowship(object):
        disbanded = False
        def ofthering(self):
            if self.disbanded:
                continue_choice = input("the fellowship is disbanded, but not yet broken. Try again? (y/n): ")
                if continue_choice == "y" or continue_choice =="Y":
                    start()
                else:
                    main()

f = party()
f.companions = []
f.weapons = 2
f.water = 0
f.ponies = 2
f.miles = 1535
f.day = 0
f.food = 0
f.daysleft = 1

def party():
    disbanded == False
    if disbanded == True:
        continue_choice = input("The fellowship is broken, but not yet disbanded. Try again? (y/n): ")
        if continue_choice == "y" or continue_choice == "Y":
            start()
        elif continue_choice == "n" or continue_choice == "N":
            main()

#main and starting stats for main player. Death statement.
def ringbearer(r):
    r.alive == True
    r.fleshwound = False
    r.RingMad = False
    r.LazyHobbit = False
    if r.alive ==  False:
        print("\n")
        continue_choice = input("The ring-bearer has fallen. But not all hope is yet lost. Try again? (y/n?): ")
        if continue_choice == "y" or continue_choice == "Y":
            start()
        elif continue_choice == "n" or continue_choice == "N":
            main()

#main and starting stats for companion character. Death statement.
def gardener(g):
    g.alive == True
    g.fleshwound = False
    g.LazyHobbit = False
    if g.alive == False:
        print("\n")
        print("Your companion has fallen... their final words are whispered out.")
        time.sleep(3)
        print("\n")
        print("'I made a promise, player_name. A promise.")
        time.sleep(1)
        print("Don't you leave them, companion_name.")
        time.sleep(1)
        print("And I don't mean to.")
        time.sleep(2)
        print("I don't mean to.'")
        time.sleep(3)
        continue_choice = input ("The journey cannot continue without your dearest friend. Do you attempt the journey again? (y/n?): ")
        if continue_choice == "y" or continue_choice == "Y":
            start()
        elif continue_choice == "n" or continue_choice == "N":
            main()

#choose difficulty level
def mode():
    print("\n")
    print("The road ahead is long and arduous, though perhaps I can ease your way.")
    mode_choice = input("How difficult of a journey would you like to undertake? (easy, normal, hard, impossible): ")
    if mode_choice == "easy":
        f.food = 1000
        f.water = 1000
        intro()
    elif mode_choice == "normal":
        f.food = 500
        f.water = 500
        intro()
    elif mode_choice == "hard":
        f.food = 300
        f.water = 300
        intro()
    elif mode_choice == "impossible":
        f.food = 100
        f.water = 100
        intro()
    else:
        print("please check your spelling and try again")
        mode()

#Intro text
def intro():
    print("\n")
    print("You have a difficult road ahead of you my young hobbit.")
    time.sleep(1)
    print("You carry with you one of the great rings of power.")
    time.sleep(1)
    print("THE great ring of power.")
    time.sleep(1)
    print("                                             _______________________")
    print("   _______________________-------------------                       `|")
    print(" /:--__                                                              |")
    print("||< > |                                   ___________________________/")
    print("| \__/_________________-------------------                         |")
    print("|                                                                  |")
    print(" |                       THE LORD OF THE RINGS                      |")
    print(" |                                                                  |")
    print(" |       Three Rings for the Elven-kings under the sky,             |")
    print("  |        Seven for the Dwarf-lords in their halls of stone,        |")
    print("  |      Nine for Mortal Men doomed to die,                          |")
    print("  |        One for the Dark Lord on his dark throne                  |")
    print("  |      In the Land of Mordor where the Shadows lie.                 |")
    print("   |       One Ring to rule them all, One Ring to find them,          |")
    print("   |       One Ring to bring them all and in the darkness bind them   |")
    print("   |     In the Land of Mordor where the Shadows lie.                |")
    print("   |                                              ____________________|_")
    print("  |  ___________________-------------------------                      `|")
    print("  |/`--_                                                                 |")
    print("  ||[ ]||                                            ___________________/")
    print("   \===/___________________--------------------------")
    print("\n")
    print("\n")
    print("\n")
    print("\n")
    time.sleep(2)
    proceed = input("To proceed on your journey, select any key and hit enter: ")
    if proceed !=" ":
        chp1()
    else: #easter egg because I'm a dork
        print("\n")
        print("Any key except that one! Fool of a Took! Hold on, I'll fix this.")
        time.sleep(2)
        print("...")
        time.sleep(1)
        print("...")
        time.sleep(1)
        print("*grunt*")
        time.sleep(1)
        print("...")
        time.sleep(.5)
        print("Ahh, there we go.")
        time.sleep(2)
        chp1()

#Chapter 1 text
def chp1():
    print("\n")
    print("It's a dangerous business, "+str(player_name)+", going out your door.")
    time.sleep(1)
    print("You step onto the road, and if you don't keep your feet,")
    time.sleep(1)
    print("there's no knowing where you might be swept off to")
    time.sleep(1)
    print("\n")
    print("                    . .:.:.:.:. .:\     /:. .:.:.:.:. ,")
    print("               .-._  `..:.:. . .:.:`- -':.:. . .:.:.,'  _.-.")
    print("              .:.:.`-._`-._..-''_...---..._``-.._.-'_.-'.:.:.")
    print("           .:.:. . .:_.`' _..-''._________,``-.._ `.._:. . .:.:.")
    print("        .:.:. . . ,-'_.-''      ||_-(O)-_||      ``-._`-. . . .:.:.")
    print("       .:. . . .,'_.'           '---------'           `._`.. . . .:.:")
    print("     :.:. . . ,','               _________               `.`. . . .:.:")
    print("    `.:.:. .,','            _.-''_________``-._            `._.     _.':")
    print("  -._  `._./ /            ,'_.-'' ,       ``-._`.          ,' '`:..'  _.-:")
    print(" .:.:`-.._' /           ,','                   `.`.       /'  '  \\.-':.:.:")
    print(" :.:. . ./ /          ,','               ,       `.`.    / '  '  '\\. .:.::")
    print(":.:. . ./ /          / /    ,                      \ \  :  '  '  ' \\. .:.:")
    print(".:. . ./ /          / /            ,          ,     \ \ :  '  '  ' '::. .:.")
    print(":. . .: :    o     / /                               \ ;'  '  '  ' ':: . .:")
    print(".:. . | |   /_\   : :     ,                      ,    : '  '  '  ' ' :: .:.")
    print(":. . .| |  ((<))  | |,          ,       ,             |\'__',-._.' ' ||. .:")
    print(".:.:. | |   `-'   | |---....____                      | ,---\/--/  ' ||:.:.")
    print("------| |         : :    ,.     ```--..._   ,         |''  '  '  ' ' ||----")
    print("_...--. |  ,       \ \             ,.    `-._     ,  /: '  '  '  ' ' ;;..._")
    print(":.:. .| | -O-       \ \    ,.                `._    / /:'  '  '  ' ':: .:.:")
    print(".:. . | |_(`__       \ \                        `. / / :'  '  '  ' ';;. .:.")
    print(":. . .<' (_)  `>      `.`.          ,.    ,.     ,','   \  '  '  ' ;;. . .:")
    print(".:. . |):-.--'(         `.`-._  ,.           _,-','      \ '  '  '//| . .:.")
    print(":. . .;)()(__)(___________`-._`-.._______..-'_.-'_________\'  '  //_:. . .:")
    print(".:.:,' \/\/--\/--------------------------------------------`._',;'`. `.:.::")
    print(":.,' ,' ,'  ,'  /   /   /   ,-------------------.   \   \   \  `. `.`. `..:")
    print(",' ,'  '   /   /   /   /   //                   \\   \   \   \   \  ` `...:")
    print("\n")
    time.sleep(1)
    print("And so "+(str(player_name))+" set forth on their journey,")
    print("accompanied by their loyal friend and gardener "+(str(companion_name))+",")
    print("to the city of Bree, to meet with Gandalf.")
    print("\n")
    time.sleep(2)
    trav1()

#stats to display during travel/in future GUI
def init_stat():
    print("CURRENT STATUS")
    print("Days on the road: "+(str(f.day)))
    print("Miles to Mordor: "+(str(f.miles)))
    print("Companions: "+ ", ".join(f.companions))
    print("Food remaining: "+(str(f.food)))
    print("Water remaining: "+(str(f.water)))
    print("Ponies: "+(str(f.ponies)))
    print("Weapons: "+(str(f.weapons)))
    print("\n")

#Actions that players can potentially take
def progress():
    travelevents()
    deathcheck()
    miles_traveled = random.randint(30,60)
    f.day +=1
    f.miles = f.miles - miles_traveled + f.ponies*5
    f.food = f.food - 1 - len(f.companions) - f.ponies
    f.water = f.water - 1 - len(f.companions) - f.ponies

##random travel events that can occur during progress
def travelevents():
    event = random.randint(1,20)
    if event == 1:
        print("\n")
        print("The Nazgul are on your trail. You must hide to avoid them, this costs you one day of travel and supplies.")
        f.day +=1
        f.food = f.food - 1 - len(f.companions) - f.ponies
        f.water = f.water - 1 - len(f.companions) - f.ponies
    elif event == 2:
        print("\n")
        print("An Uruk-Hai war party is traveling nearby. You run for cover, dropping some food.")
        f.food = f.food - 10
    elif event == 3:
        print("\n")
        print("The eye of Sauron has noticed you. Lie low for a while until his gaze passes. This costs you one day of travel and supplies.")
        f.day +=1
        f.food = f.food - 1 - len(f.companions) - f.ponies
        f.water = f.water - 1 - len(f.companions) - f.ponies
    elif event ==4:
        print("\n")
        print("A member of your party has been bitten by a gigantic spider. You use water to cleanse the wound.")
        f.water = f.water - 10
    elif event ==5:
        print("\n")
        print("The Nazgul have spotted you! Do you fight or do you run?")
        fight_flight = input("(fight/run): ")
        if fight_flight == "fight":
            while f.weapons <= 0:
                print("You have no weapons! The Nazgul overpower you and kill the party in their quest for the ring.")
                r.alive == False
            while f.weapons >0:
                brawl = random.randint(1,3)
                if brawl ==1:
                    print("\n")
                    print("You successfully fight off the Nazgul")
                if brawl ==2:
                    print("\n")
                    print("You succsessfully fend off the Nazgul for now, but one of your weapons is broken in the fight")
                    f.weapons -= 1
                else:
                    print("\n")
                    print("One of the Nazgul has managed to stab you. "+(str(player_name))+" has an injury.")
                    r.fleshwound = True
                    ringbearer()
        else:
            runchance = random.randint(1,3)
            if runchance ==1:
                print("\n")
                print("You drop some of your food and water in your escape.")
                f.food -= 15
                f.water -= 15
            else:
                print("\n")
                print("You got away safely.")

def forage():
    print("\n")
    f.day += 1
    eventf = random.randint(1,5)
    foraged = random.randint(10,21)
    if eventf == 1:
        print("You've found mushrooms!")
        f.food = f.food + foraged
        f.water = f.water + foraged
    elif eventf == 2:
        print("You've found potatoes!")
        f.food = f.food + foraged
        f.water = f.water + foraged
    elif eventf == 3:
        print("You've found... something. It appears to be edible.")
        f.food = f.food + foraged - 5
        f.water = f.water + foraged
    elif eventf ==4:
        print("The land here appears barren, there's no food to be found.")
    else:
        print("You've found mushrooms! In your haste you eat them before identifying them and realize they are poisoned. You waste 5 water rinsing your mouth.")
        f.water = f.water - 5

def hunt():
    print("\n")
    f.day +=1
    eventh = random.randint(1,6)
    caught = random.randint(20,40)
    if eventh ==1:
        print("You've caught a deer!")
        f.food = f.food + caught
    if eventh ==2:
        print("You've caught some rabbits!")
        f.food = f.food + caught
    if eventh ==3:
        print("You caught a troll... better him than us... and better than starving... I guess.")
        f.food = f.food + caught
    if eventh ==4:
        print("The Uruk-Hai marched through here recently. There is no game to be found.")
    if eventh ==5:
        injury = random.randint(1,3)
        if injury ==1:
            print("You were injured trying to bring down an animal! You now have a fleshwound!")
            r.fleshwound = True
            ringbearer()
        else:
            print("Your companion, "+(str(companion_name))+", was injured trying to bring down an animal! They now have a fleshwound!")
            g.fleshwound = True
            gardener()

def rest():
    print("\n")
    f.day +=1
    f.food = f.food - 1 - len(f.companions) - f.ponies
    f.water = f.water - 1 - len(f.companions) - f.ponies
    eventr = random.randint(1,3)
    if eventr ==1 or eventr ==2:
        if r.fleshwound == True or g.fleshwound == True:
            print("You've rested and tended to your injuries. None of your party have fleshwounds.")
            r.fleshwound = False
            g.fleshwound = False
            if r.RingMad == True :
                print("During your rest, your companions were able to talk some sense into you. The corruption of the ring seems to have passed for now.")
                r.RingMad = False
            if r.LazyHobbit == True or g.LazyHobbit == True:
                print("Given a day to laze around, the hobbits appear to be cured of their laziness.")
                r.LazyHobbit = False
                g.LazyHobbit = False
            else:
                print("Your party is fully rested")
    else:
            print("Your party takes the day to rest, but the elements prevent you from feeling re-energized.")

def helpchoices():
    print("\n")
    print("Travel: make progress on your journey. Consuming food and water based on the size of your party. Ponies can help you travel faster.")
    print("Forage: gather food and water to sustain your party.")
    print("Hunt: hunt nearby creatures for increased food. Provides more food than foraging, but does not provide water.")
    print("Rest: take one day to rest your party, with the potential to heal injuries and detrimental status effects.")
    print("\n")

def quit_check():
    check = input("are you sure you want to quit? (y/n): ")
    if check =="y" or check =="Y":
        print("...One does not simply walk into Mordor...")
        quit()
    else:
        print("And so we continue on our journey.")

def deathcheck():
    if f.food == 0 or f.water == 0:
        print("You have run out of resources. You cannot possibly go on.")
        r.alive == False
        ringbearer()
    elif f.daysleft == 0:
        print("You couldn't find help in time. You didn't make it.")
        r.alive == False
        ringbearer()

#Travel marker for chapter 1
def trav1():
    print("\n")
    print("The Shire ---------> Bree")
    init_stat()
    while  1415 < f.miles <=1535:
        options1()
    else:
        chap2()

#Travel options for chapter 1
def options1():
    print("what would you like to do?")
    select = input("travel, forage, hunt, rest, help, quit: ")
    if select == "travel":
        progress()
        trav1()
    elif select == "forage":
        forage()
        trav1()
    elif select == "hunt":
        hunt()
        trav1()
    elif select == "rest":
        print("\n")
        print("Lazy hobbits, we've barely gotten started!")
        rest()
        trav1()
    elif select =="help":
        helpchoices()
    elif select=="quit":
        quit_check()
    else:
        print("Please check your spelling and try again.")
        select = input("travel, forage, hunt, rest, help, quit: ")

#Chapter 2 text/story
def chap2():
    print("\n")
    print("On the road to Bree you find your friends Pippin and Merry stealing vegetables from Farmer Maggot's fields.")
    time.sleep(1)
    print("They join you on your journey to Bree, bringing the vegetables they have found with them.")
    f.food += 50
    f.companions.append("Pippin")
    f.companions.append("Merry")
    time.sleep(2)
    print("\n")
    print("Pippin and Merry and joined the party.")
    print("\n")
    init_stat()
    print("\n")
    time.sleep(2)
    print("As you continue toward Bree your party is pursued by mysterious black riders.")
    time.sleep(1)
    print("\n")
    time.sleep(1)
    print("Fortunately you are able to hide from these dangerous riders and make it to the Inn of the Prancing Pony to meet Gandalf.")
    time.sleep(5)
    print("\n")
    print("\n")
    print("   _____,,;;;`;   The Inn     ;';;;,,_____")
    print(",~(  )  , )~~\|    of the     |/~~( ,  (  )~;")
    print("' / / --`--,      Prancing       .--'-- \ \ `")
    print(" /  \    | '        Pony         ` |    /  |")
    print("\n")
    time.sleep(3)
    print("Unfortunately, Gandalf is unable to meet you in Bree,")
    print("however, you do encounter a ranger by the name of Strider.")
    print("Your part leaves with Strider to make their way to Rivendell")
    print("\n")
    print("Strider joins the party. Bringing his own weapons and rations.")
    f.companions.append("Strider")
    f.food += 30
    f.weapons += 3
    f.ponies += 1
    trav2()

#travel for chapter 2
def trav2():
    print("\n")
    print("Bree ----------> Rivendell")
    init_stat()
    while 1320 < f.miles <=1535:
        options2()
    else:
        chap3()

    #Slightly modified text and options for chapter 2
def options2():
    print("\n")
    print("what would you like to do?")
    select = input("travel, forage, hunt, rest, help, quit: ")
    if select == "travel":
        progress()
        trav2()
    if select == "forage":
        forage()
        trav2()
    if select == "hunt":
        print("\n")
        print("Strider grabs his bow and leaves the camp to hunt.")
        hunt()
        trav2()
    if select == "rest":
        print("\n")
        print("As the hobbits sit down to second breakfast, Strider stairs at you in disbelief.")
        print("'What are you doing??'")
        print("\n")
        time.sleep(2)
        print("We'd better get back on the road.")
        rest()
        trav2()
    if select=="help":
        helpchoices()
    if select =="quit":
        quit_check()

daysleft = 4
#morgal blade death timer
def mbtimer():
    if daysleft == 0:
        r.alive == FALSE
        ringbearer()

#Chapter 3 text/story
def chap3():
    print("\n")
    print("Strider successfully leads your party through the Midgewater Marshes.")
    print("As you make your journey to Rivendell, you rest at the ruin of Weathertop.")
    print("There, you are waylayed by five of the Nazgul.")
    time.sleep(2)
    print("\n")
    print("In an attempt to hide from them, "+(str(player_name))+" put on the ring.")
    print("Oh no! "+(str(player_name))+" is stabbed with a poisoned Morgul blade by their leader.")
    print("\n")
    print("       .---.")
    print("       |---|")
    print("       |---|")
    print("       |---|")
    print("   .---^ - ^---.")
    print("   :___________:")
    print("      |  |//")
    print("      |  |//|")
    print("      |  |//|")
    print("      |  |//|")
    print("      |  |//|")
    print("      |  |//|")
    print("      |  |.-|")
    print("      |.-'**|")
    print("       \***/")
    print("        \*/")
    print("         V")
    print("\n")
    time.sleep(2)
    print("Strider managed to fight away the Nazgul with fire and blade.")
    time.sleep(1)
    print("\n")
    print("Strider is unable to heal "+(str(player_name))+"'s wound.")
    print("A poisoned tip of the Ringwraith's blade remains in their flesh, moving toweard their heart.")
    time.sleep(1)
    print("\n")
    print((str(player_name))+" doesn't have much time left.")
    print("\n")
    print("The elf, Arwen, appears. She offers aid, and hurries the party on to Rivendell.")
    print("\n")
    print("Arwen has joined your party. She brings her horse, Asfaloth, weapons, and supplies.")
    f.companions.append("Arwen")
    f.food += 30
    f.water += 30
    f.ponies += 1
    init_stat()
    print("\n")
    print("Arwen urges that there isn't much time left for "+(str(player_name)))
    print("'I'll carry them, I can move faster than the group,' she urges")
    time.sleep(1)
    print("\n")
    print("Should "+(str(player_name))+" travel with Arwen? Or remain with the larger group?")
    print("Note, if you don't make it to Rivendell in 4 days, "+(str(player_name))+" will surely die.")
    print("1.) Travel with Arwen")
    print("2.) Travel with the full group")
    option = input("-->")
    if option == "1" or option == "Arwen":
        f.companions.remove(companion_name)
        f.companions.remove("Strider")
        f.companions.remove("Pippin")
        f.companions.remove("Merry")
        f.ponies = 1
        f.daysleft = 5
        trav3a()
    elif option == "2" or option == "group" or option == "party":
        trav3b()
    else:
        print("please check your input and try again")
        option = input("-->")

#traveling with Arwen
def trav3a():
    print("\n")
    print("Weathertop ----------> Rivendell")
    init_stat()
    while 1067 < f.miles <=1320:
        options3a()
    else:
        chap4()

#traveling with the party
def trav3b():
    print("\n")
    print("Weathertop ----------> Rivendell")
    init_stat()
    while 1067 < f.miles <=1320:
        options3b()
    else:
        chap4()

 #swift travel with Arwen, no stopping for supplies
def options3a():
    print("\n")
    print("We must make haste. Do not tarry.")
    select = input("travel, help, quit: ")
    if select == "travel":
        progress_swift()
        trav3a()
    if select=="help":
        helpchoices()
    if select =="quit":
        quit_check()

#travel options with the party.
def options3b():
    print("\n")
    print("what would you like to do?")
    select = input("travel, forage, hunt, rest, help, quit: ")
    if select == "travel":
        progress_slow()
        trav2()
    if select == "forage":
        print("\n")
        print("Arwen and Strider urge you to hurry. Though if you can find the Kingsfoil plant, it might help prevent the poison from spreading.")
        forage()
        print((str(companion_name))+" manages to find Kingsfoil. Perhaps "+(str(player_name))+" might make it another day.")
        daysleft +=1
        trav2()
    if select == "hunt":
        print("\n")
        print("Strider grabs his bow and leaves the camp to hunt.")
        daysleft -=1
        hunt()
        trav2()
    if select == "rest":
        print("\n")
        print("The hobbits are exhausted and stop to rest.")
        print("\n")
        daysleft -=1
        rest()
        trav2()
    if select=="help":
        helpchoices()
    if select =="quit":
        quit_check()

#Arwen speed
def progress_swift():
    f.daysleft -=1
    mbtimer()
    deathcheck()
    miles_traveled = random.randint(50,80)
    f.day +=1
    f.miles = f.miles - miles_traveled + f.ponies*5
    f.food = f.food - 1 - len(f.companions) - f.ponies
    f.water = f.water - 1 - len(f.companions) - f.ponies

#party speed - slightly increased for fairness and a possibility to make it
def progress_slow():
    f.daysleft -=1
    mbtimer()
    deathcheck()
    miles_traveled = random.randint(40,70)
    f.day +=1
    f.miles = f.miles - miles_traveled + f.ponies*5
    f.food = f.food - 1 - len(f.companions) - f.ponies
    f.water = f.water - 1 - len(f.companions) - f.ponies

def chap4():
    print("placeholder")
    quit()

main()


"""
Module to handle a local InfoKinds with unique name.

NOTE: this is taken from python-common in nomad-lab-base.
It is copied here to remove the dependency from nomad-lab-base.
For more info on python-common visit:
https://gitlab.mpcdf.mpg.de/nomad-lab/python-common

The author of this code is: Dr. Fawzi Roberto Mohamed
E-mail: mohamed@fhi-berlin.mpg.de

"""
from past.builtins import cmp
from future import standard_library
standard_library.install_aliases()

from builtins import str
from builtins import range
from builtins import object
import logging
from ai4materials.external import compact_sha
import json
import os, re
from ai4materials.external.json_support import jsonCompactS, jsonCompactD, jsonIndentD
from io import open

class InfoKindEl(object):
    """Info kind (tipically from a file, without shas but with locally unique names)"""
    __slots__ = ["name","description","kindStr","units","superNames","dtypeStr", "repeats", "shape", "extra_args"]
    IGNORE_EXTRA_ARGS = 1
    ADD_EXTRA_ARGS = 2
    RAISE_IF_EXTRA_ARGS = 3

    def __init__(self, name, description, kindStr = "type_document_content", units = None, superNames = None,
            dtypeStr = None, shape = None, extraArgsHandling = ADD_EXTRA_ARGS, repeats = None, **extra_args):
        if superNames is None:
            superNames = []
        self.name = name
        self.description = description
        self.kindStr = kindStr
        self.superNames = superNames
        self.units = units
        self.dtypeStr = dtypeStr
        if dtypeStr in ["None", "null"]:
            self.dtypeStr = None
        self.shape = shape
        self.repeats = repeats
        if extraArgsHandling == self.ADD_EXTRA_ARGS:
            self.extra_args = extra_args
        elif extraArgsHandling == self.IGNORE_EXTRA_ARGS:
            self.extra_args = {}
        else:
            raise Exception("extra arguments to InfoKindEl:" + str(extra_args))

    def __eq__(o1, o2):
        try:
            if not (o1.name == o2.name and o1.description == o2.description and o1.kindStr == o2.kindStr and
                o1.units == o2.units and o1.shape == o2.shape):
                return False
            if o1.dtypeStr != o2.dtypeStr:
                return False
            if o1.repeats != o2.repeats:
                return False
            if o1.extra_args != o2.extra_args:
                return False
            if o1.superNames == o2.superNames:
                return True
            if len(o1.superNames) != len(o2.superNames):
                return False
            if o1.superNames[0] != o2.superNames[0]:
                return False
            a1 = o1.superNames[1:]
            a2 = o2.superNames[1:]
            a1.sort()
            a2.sort()
            for i in range(len(a1)):
                if a1[i] != a2[i]:
                    return False
            return True
        except:
            raise
            return False

    def __cmp__(k1, k2):
        c = cmp(k1.name, k2.name)
        if c != 0: return c
        c = cmp(k1.kindStr, k2.kindStr)
        if c != 0: return c
        c = cmp(k1.description, k2.description)
        if c != 0: return c
        if len(k1.superNames) > 0:
            if len(k2.superNames) > 0:
                c = cmp(k1.superNames[0], k2.superNames[0])
                if c != 0: return c
                s1 = k1.superNames[1:]
                s2 = k2.superNames[1:]
                c = cmp(s1, s2)
                if c != 0: return c
            else:
                return 1
        elif len(k2.superNames) > 0:
            return -1
        if c != 0: return c
        c = cmp(k1.units, k2.units)
        if c != 0: return c
        c = cmp(k1.dtypeStr, k2.dtypeStr)
        if c != 0: return c
        c = cmp(k1.repeats, k2.repeats)
        if c != 0: return c
        c = cmp(k1.shape, k2.shape)
        if c != 0: return c
        if k1.extra_args == k2.extra_args:
            return 0
        if k1.extra_args is None:
            return 1
        if k2.extra_args is None:
            return -1
        extraK1 = list(k1.extra_args.keys())
        extraK1.sort()
        extraK2 = list(k2.extra_args.keys())
        extraK2.sort()
        i = 0
        while (i < len(extraK1) and i < len(extraK2)):
            kk1 = extraK1[i]
            kk2 = extraK2[i]
            c = cmp(kk1, kk2)
            if c != 0: return c # use -c ?
            c = cmp(k1.extra_args[kk1], k2.extra_args[kk2])
            if c != 0: return c
        c = cmp(len(extraK1), len(extraK2))
        return c

    def __ne__(o1, o2):
        return not o1.__eq__(o2)

    def prepare(self, env):
        if len(self.superNames) > 1:
            a = self.superNames[1:]
            a.sort(lambda x, y: cmp(env.gidOf(x, precalculatedGid = True), env.gidOf(y, precalculatedGid = True)))
            self.superNames[1:] = a

    def evalGid(self, env):
        self.prepare(env)
        sha = env.newSha()
        self.serialize(env,sha.update, precalculatedGid = True, selfGid = False)
        return 'p' + sha.b64digest()[:28]

    def serialize(self, env, writeOut, subGids = True, addExtraArgs = True, precalculatedGid = False, selfGid = True):
        d = self.toDict(env, subGids = subGids, addExtraArgs = addExtraArgs, precalculatedGid = precalculatedGid, selfGid = selfGid)
        jsonCompactD(d, writeOut)

    def toDict(self, env = None, addExtraArgs = True, inlineExtraArgs = True , selfGid = False, subGids = False, precalculatedGid = False):
        res = {
            "description": self.description,
            "name": self.name,
            "superNames": self.superNames,
        }
        try:
            if self.kindStr != "type_document_content":
                if self.kindStr is None or self.kindStr == "":
                    res["kindStr"] = "MetaType"
                else:
                    res["kindStr"] = self.kindStr
            if env:
                if selfGid:
                    res["gid"] = env.gidOf(self.name, precalculatedGid = precalculatedGid)
                if subGids:
                    res["superGids"] = [ env.gidOf(sName, precalculatedGid = precalculatedGid) for sName in self.superNames ]
            elif subGids or selfGid:
                raise Exception("env required in toDict for subGids or selfGid")
            if self.units is not None:
                res["units"] = self.units
            if self.dtypeStr is not None:
                res["dtypeStr"] = self.dtypeStr
            if self.repeats is not None:
                res["repeats"] = self.repeats
            if self.shape is not None:
                res["shape"] = self.shape
            if addExtraArgs:
                if inlineExtraArgs:
                    res.update(self.extra_args)
                else:
                    res["extraArgs"] = self.extra_args
        except:
            logging.exception("error in InfoKindEl.toDict, partial dict is %s", res)
        return res

    def __unicode__(self):
        s = StringIO.StringIO()
        self.serialize(s)
        return s.string

class RelativeDependencySolver(object):
    def __init__(self):
        self.deps = {}

    def __call__(self, infoKindEnv, source, dep):
        if "relativePath" not in dep:
            raise Exception('Invalid dependency for relativeDependencySolver there must be a relativePath')
        basePath = source.get('path')
        if basePath:
            baseDir = os.path.dirname(os.path.abspath(basePath))
        else:
            baseDir = os.getcwd()
        dPath = os.path.realpath(os.path.join(baseDir, dep['relativePath']))
        if dPath in self.deps:
            return self.deps[dPath]
        depInfo = None
        depIKEnv = InfoKindEnv(path = dPath, dependencyLoader=infoKindEnv.dependencyLoader)
        self.deps[dPath] = depIKEnv
        with open(dPath, encoding="utf-8") as f:
            try:
                depInfo = json.load(f)
            except:
                logging.exception("Error while loading dependency %s" % f)
                raise
        if depInfo:
            depIKEnv.fromJsonList(depInfo, name = os.path.basename(dPath), source = { 'path': dPath }, dependencyLoad = False)
        return depIKEnv

class InfoKindEnv(object):
    """An environment keeping locally unique InfoKinds and their gids"""
    def __init__(self, infoKinds = None, name = None, description = None, newSha = compact_sha.sha512, gids = None,
            dependencyLoader = None, path = None, uri = None, deps = None):
        self.newSha = newSha
        self.clear()
        self.name = name
        self.description = description
        self.dependencyLoader = dependencyLoader
        if dependencyLoader is None:
            self.dependencyLoader = RelativeDependencySolver()
        self.path = path
        self.uri  = uri
        if not infoKinds is None:
            for ik in infoKinds:
                self.addInfoKindEl(ik)
        if not gids is None:
            self.gids = gids
        if deps:
            self.deps = deps

    def __str__(self):
        if self.path:
            return "InfoKindEnv loaded from {}".format(self.path)

    def clear(self):
        self.gids = {}
        self.infoKinds = {}
        self.deps = []

    def depNames(self):
        res = set()
        for dep in self.deps:
            for name in dep.infoKinds.keys():
                res.add(name)
        return res

    def noDepNames(self):
        return set(self.infoKinds.keys()).difference(self.depNames())

    def embedDeps(self):
        hidden = []
        duplicate = set()
        for dep in self.deps:
            for name, ikEl in dep.infoKinds.items():
                oldVal=self.infoKinds.get(name, None)
                if oldVal is None:
                    self.infoKinds[name] = ikEl
                elif ikEl != oldVal:
                    hidden.append(ikEl)
                else:
                    duplicate.add(name)
        return { "hidden": hidden, "duplicate": duplicate }

    def addInfoKindEl(self, infoKind):
        if infoKind.name in self.infoKinds and infoKind != self.infoKinds[infoKind.name]:
            raise Exception('InfoKindEnv has collision for name {0}: {1} vs {2}'
                    .format(infoKind.name, infoKind, self.infoKinds[infoKind.name]))
        self.infoKinds[infoKind.name] = infoKind

    def addDependenciesFrom(self, infoKindEnv):
        toAdd = set(self.infoKinds.keys())
        missing = set()
        while len(toAdd):
            ikName = toAdd.pop()
            ik = self.infoKinds.get(ikName,None)
            if ik is None:
                depInfoKindEl = infoKindEnv.infoKinds.get(ikName, None)
                if depInfoKindEl:
                    self.infoKinds[ikName] = depInfoKindEl
                    toAdd.add(ikName)
                else:
                    missing.add(ikName)
            else:
                for dep in ik.superNames:
                    if not dep in self.infoKinds:
                        toAdd.add(dep)
        return missing

    def gidOf(self, name, precalculatedGid=False):
        res = self.gids.get(name,None)
        if res is None:
            if precalculatedGid:
                raise Exception("non precalculated gid for %s" % name)
            res = self.calcGid(name)
        return res

    def calcGid(self, name):
        inProgress = []
        toDo = [name]
        hasPending = False
        for i in range(2):
            while len(toDo) > 0:
                if not hasPending and inProgress:
                    now = inProgress.pop()
                else:
                    now = toDo.pop()
                if now in self.gids and now in inProgress:
                    inProgress.remove(now)
                hasPending = False
                nowVal = self.infoKinds.get(now, None)
                if nowVal is None:
                    raise Exception("while calculating gid of %r found unknown key %r" % (name, now))
                for subName in nowVal.superNames:
                    if subName in self.gids:
                        continue
                    hasPending = True
                    if subName in toDo:
                        toDo.remove(subName)
                    if subName in inProgress:
                        raise Exception('found loop to %s evaluating %s, currently in progress: %s' % (subName, now, inProgress))
                    toDo.append(subName)
                if not hasPending:
                    self.gids[now] = nowVal.evalGid(self)
                    if now in inProgress:
                        inProgress.remove(now)
                else:
                    if now in inProgress:
                        raise Exception('found loop to %s, currently in progress: %s' % (now, inProgress))
                    inProgress.append(now)
            toDo = list(inProgress)
        return self.gids[name]

    def keyDependsOnKey(self, k1Name, k2Name):
        """partial ordering given by the dependencies
        1: k1Name depends on k2Name
        0: k1Name == k2Name
        -1: k2Name depends on k1Name
        None: no dependency"""
        if k1Name == k2Name: return 0
        k1 = self.infoKinds[k1Name]
        k2 = self.infoKinds[k2Name]
        if k1.superNames != k2.superNames:
            allSuperK1 = set()
            toDoK1 = list(k1.superNames)
            allSuperK2 = set()
            toDoK2 = list(k2.superNames)
            while (len(toDoK1) > 0 or len(toDoK2) > 0):
                if len(toDoK1) > 0:
                    el1Name = toDoK1.pop()
                    if k2Name == el1Name:
                        return 1
                    el1 = self.infoKinds[el1Name]
                    if el1.kindStr in self and not el1.kindStr in allSuperK1:
                        toDoK1.append(el1.kindStr)
                    for subEl in el1.superNames:
                        if not subEl in allSuperK1:
                            toDoK1.append(subEl)
                    allSuperK1.update(el1.superNames)
                if len(toDoK2) > 0:
                    el2Name = toDoK2.pop()
                    if k1Name == el2Name:
                        return -1
                    el2 = self.infoKinds[el2Name]
                    if el2.kindStr in self and not el2.kindStr in allSuperK2:
                        toDoK2.append(el2.kindStr)
                    for subEl in el2.superNames:
                        if not subEl in allSuperK2:
                            toDoK2.append(subEl)
                    allSuperK2.update(el2.superNames)
        return None

    def __contains__(self, name):
        "if an item with the given name is contained in this environment"
        return name in self.infoKinds

    def __len__(self):
        """returns the number of InfoKindEl stored in this environment"""
        return len(self.infoKinds)

    def __getitem__(self, name):
        """returns a dictionary representing the entry with the given name, or None if it does not exist"""
        ikEl = self.infoKinds.get(name, None)
        if ikEl:
            return ikEl.toDict(self)
        return None

    def infoKindEls(self):
        return list(self.infoKinds.values())

    def infoKindEl(self, name):
        """returns the InfoKindEl with the given name, or None if it does not exist"""
        return self.infoKinds.get(name, None)

    def calcGids(self):
        for k in self.infoKinds.keys():
            if not k in self.gids:
                self.gids[k]=self.calcGid(k)

    def serialize(self, writeOut, subGids = True, selfGid = True):
        infoKinds = self.sortedIKs()
        writeOut("""{
  "type": "nomad_meta_info_1_0",
  "description": """)
        if self.description:
            jsonIndentD(self.description, writeOut, extraIndent = 4)
        else:
            writeOut('""')
        writeOut(',\n')
        if not self.path:
            baseDir = os.getcwd()
        else:
            baseDir = os.path.normpath(os.path.dirname(os.path.abspath(self.path)))
        depKeys = set()
        if self.deps:
            writeOut('  "dependencies": [ ')
            depColon = False
            for d in self.deps:
                path = d.path
                uri = d.uri
                depKeys.update(d.infoKinds.keys())
                if path:
                    path = os.path.normpath(os.path.abspath(path))
                    if path.startswith(baseDir) or not uri:
                        if depColon:
                            writeOut(", ")
                        else:
                            depColon = True
                        jsonIndentD({"relativePath": os.path.relpath(path, baseDir)}, writeOut, extraIndent = 4)
                        continue
                if uri:
                    if depColon:
                        writeOut(", ")
                    else:
                        depColon = True
                    jsonIndentD({"uri": uri}, writeOut, extraIndent = 4)
                    continue
                raise Exception("Dependency on serializable %s" % d)
            writeOut('],\n')
        addColon = False
        writeOut('  "metaInfos": [ ')
        for ik in infoKinds:
            if ik.name in depKeys:
                continue
            if addColon:
                writeOut(", ")
            else:
                addColon = True
            jsonIndentD(ik.toDict(env = self, subGids = subGids, selfGid = selfGid), writeOut, extraIndent = 4, check_circular = True)
        writeOut("]\n}\n")

    def sortedIKs(self):
        infoKinds = list(self.infoKinds.values())
        infoKinds.sort(lambda x, y: cmp(x.name.lower()+x.name, y.name.lower()+y.name))
        return infoKinds # self.sortAndComplete(infoKinds, ignoreMissing = True)

    def toJsonList(self, withGids):
        infoKinds = list(self.infoKinds.keys())
        infoKinds.sort(lambda x, y: self.compareKeys(x.name, y.name))
        return [self.infoKinds[x].toDict(self,
                self if withGids else None) for x in infoKinds]

    def verifyGids(self, preserveAbsent=False):
        changes = {}
        oldGids = self.gids
        self.gids = {}
        self.calcGids()
        for k,v in oldGids.items():
            newVal = self.gids.get(k, None)
            if newVal is None:
                if preserveAbsent:
                    self.gids[k] = v
                else:
                    changes[k] = (v, None)
            elif v != newVal:
                changes[k] = (v, newVal)
        return changes

    def fromJsonList(self, jsonDict, name,  source, extraArgsHandling = InfoKindEl.ADD_EXTRA_ARGS, dependencyLoad=False):
        typeStr = jsonDict.get("type","nomad_meta_info_1_0")
        typeRe = re.compile(r"nomad_meta_info_(?P<major>[0-9]+)_(?P<minor>[0-9]+)$")
        self.name = name
        m = typeRe.match(typeStr)
        if not m:
            raise Exception("unexpected type '%s', expected nomad_meta_info_1_0" % typeStr)
        if int(m.group("major")) != 1:
            raise Exception("Unsupported major version %s, expeced 1")
        dependencies = jsonDict.get("dependencies",[])
        jsonList = jsonDict.get("metaInfos",[])
        self.description = jsonDict.get("description","")
        overwritten = []
        gidToCheck = {}
        deps = []
        for d in dependencies:
            if self.dependencyLoader is None:
                raise Exception("no dependencyLoader while loading local_in")
            dep = self.dependencyLoader(self, source, d)
            if dep:
                self.deps.append(dep)
        index = -1
        for ii in jsonList:
            index += 1
            val = dict(ii)
            if not "name" in ii:
                raise Exception("InfoKind at %d is without name: %s" % (index, ii) )
            oldVal=self.infoKinds.get(ii['name'],None)
            gid=None
            if "gid" in ii:
                gid = ii['gid']
                del val['gid']
            if "superGids" in ii:
                if not "superNames" in ii:
                    raise Exception("superGids without superNames in fromJsonList")
                superNames = ii["superNames"]
                superGids = ii["superGids"]
                if len(superNames) != len(superGids):
                    raise Exception("superGids incompatible with superNames in fromJsonList: %s vs %s" % (ii["superGids"], ii["superNames"]))
                toCheck = {}
                for i in range(len(superNames)):
                    assert not superNames[i] in toCheck.keys(), "duplicate superName %r in %r" % (superNames[i], ii["name"])
                    toCheck[superNames[i]] = superGids[i]
                gidToCheck[ii["name"]] = toCheck
                del val['superGids']
            val['extraArgsHandling'] = extraArgsHandling
            ikEl = InfoKindEl(**val)
            if not oldVal is None and ikEl != oldVal:
                overwritten.append((oldVal, ikEl))
            if gid:
                self.gids[ii['name']] = gid
            self.infoKinds[ikEl.name] = ikEl
        res = { "overwritten": overwritten }
        if not dependencyLoad:
            res.update(self.embedDeps())
            for childName, gids in gidToCheck.items():
                for name, gid in gids.items():
                    if self.gidOf(name) != gid:
                        raise Exception("incompatible superGid for superName %s of %s (%s vs %s)" % (name, ii["name"], gid, self.gidOf(name)))
        if res.get("overwritten", False) or res.get("duplicate", False) or res.get("hidden", False):
            res["hasWarnings"] = True
        else:
            res["hasWarnings"] = res.get("hasWarnings", False)
        return res

    def sortAndComplete(self, propsToSort, ignoreMissing = False):
        """builds a list of properties in propsToSort, so that all the dependecies of each
        property are present before them"""
        toDo = list(propsToSort)
        done = set()
        deps = []
        res = []
        while len(toDo)>0:
            pAtt = toDo.pop()
            nameAtt = pAtt.name
            if nameAtt in done:
                continue
            deps = [nameAtt]
            while len(deps)>0:
                nameAtt = deps[-1]
                pAtt = self.infoKinds.get(nameAtt, None)
                if pAtt is None:
                    if ignoreMissing:
                        deps.pop()
                        done.add(nameAtt)
                        continue
                    else:
                        raise Exception("missing dependent InfoKindEl {0} following chain {1}".format(nameAtt, pAtt))
                hasDepsToDo = False
                kindStr = pAtt.kindStr
                kindType = self.infoKindEl(kindStr)
                for superName in pAtt.superNames:
                    if not superName in done:
                        if superName in deps:
                            raise Exception("circular dependency {0}, {1}".format(deps,superName))
                        deps.append(superName)
                        hasDepsToDo = True
                if kindType and not kindStr in done:
                    if kindStr in deps:
                        raise Exception("circular dependency in kindStr {0}, {1}".format(deps,kindStr))
                    deps.append(kindStr)
                    hasDepsToDo = True
                if not hasDepsToDo:
                    deps.pop()
                    res.append(pAtt)
                    done.add(nameAtt)
        return res

    def metaInfoNameWithAllSuper(self, name):
        """returns the meta info names of name and all its dependencies"""
        toAdd = set([name])
        res = set([name])
        while toAdd:
            e = toAdd.pop()
            for superName in self.infoKinds[e].superNames:
                if not superName in res:
                    res.add(superName)
                    toAdd.add(superName)
        return res

    def firstAncestorsByType(self, name):
        """Returns the first acestors of each type separated in roots and children.
        (scala conversion, could be improved a bit)"""
        metaInfoNames = self.metaInfoNameWithAllSuper(name)
        metaInfoNames.remove(name)
        mInfo = list(metaInfoNames)
        edges = {}
        for i, metaName in enumerate(mInfo):
            metaInfo = self.infoKinds[metaName]
            edges[i] = [mInfo.index(x) for x in metaInfo.superNames]
        typeGroups = {}
        for mIdx, metaName in enumerate(mInfo):
            kindStr = self.infoKinds[metaName].kindStr
            tNow = typeGroups.get(kindStr, None)
            if tNow is None:
                typeGroups[kindStr] = [mIdx]
            else:
                tNow.append(mIdx)
        childsByType = {}
        toDo  = set(range(len(mInfo)))
        while (toDo):
            now = toDo.pop()
            kindNow = self.infoKinds[mInfo[now]].kindStr
            toDo2 = set(edges[now])
            known2 = set(edges[now])
            while (toDo2):
                now2 = toDo2.pop()
                if (self.infoKinds[mInfo[now2]].kindStr == kindNow):
                    childs = childsByType.get(kindNow, None)
                    if childs:
                        childs.add(now2)
                    else:
                        childsByType[kindNow] = set([now2])
                    if now2 in toDo:
                        toDo.remove(now2)
                for el in edges[now2]:
                    if not el in known2:
                        toDo2.add(el)
                        known2.add(el)
        res = {}
        for typeName, allChilds in typeGroups.items():
            childs = childsByType.get(typeName, set())
            allForKind = set(allChilds)
            rootNames = [mInfo[x] for x in (allForKind - childs)]
            childNames = [mInfo[x] for x in childs]
            res[typeName] = (rootNames, childNames)
        return res

def loadJsonFile(filePath, dependencyLoader = None, extraArgsHandling = InfoKindEl.ADD_EXTRA_ARGS, uri = None):
    env = InfoKindEnv(dependencyLoader = dependencyLoader, path = filePath, uri = uri)
    try:
        with open(filePath, encoding="utf-8") as f:
            o = json.load(f)
            warnings = env.fromJsonList(o, name = os.path.basename(filePath), source = {'path': filePath}, extraArgsHandling = extraArgsHandling)
    except:
        logging.exception("Error while loading file %s" % filePath)
        raise
    return env, warnings

def load_metainfo(filename, dependencyLoader=None, extraArgsHandling=InfoKindEl.ADD_EXTRA_ARGS, uri=None):
    """Loads a metainfo environment for a filename. The filename should not
    contain the full path, as the full path is resolved here and not by the
    caller.

    Args:
        filename: filename as a string.

    Returns:
        Tuple containing the metainfo environment, and any possible warnings
        that were encountered in the loading.
    """
    path = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../../nomad-meta-info/meta_info/nomad_meta_info/{}".format(filename)))
    return loadJsonFile(path, dependencyLoader, extraArgsHandling, uri)

def loadJsonStream(fileStream, name = None, dependencyLoader = None, extraArgsHandling = InfoKindEl.ADD_EXTRA_ARGS, filePath = None, uri = None):
    if filePath is None:
        try:
            filePath = fileStream.name
        except:
            filePath = None
    if name is None and not filePath is None:
        name = os.path.basename(filePath)
    env = InfoKindEnv(dependencyLoader = dependencyLoader, name = name, path = filePath, uri = uri)
    try:
        o = json.load(fileStream)
        warnings = env.fromJsonList(o, source = {'path': filePath}, extraArgsHandling = extraArgsHandling)
    except:
        logging.exception("Error while loading file %s" % filePath)
        raise
    return env, warnings


from functools import cmp_to_key

def custom_split(s):
    if s == '': return []
    cnt = 0
    res = []
    last_comma = -1
    brackets = 0
    while (cnt < len(s)):
        if s[cnt] == '[': brackets += 1
        if s[cnt] == ']': brackets -= 1
        if s[cnt] == ',':
            if brackets == 0:
                res.append(s[last_comma+1:cnt])
                last_comma = cnt
        cnt += 1
    res.append(s[last_comma+1:])
    return res

def compare(l1, l2):
    len1 = len(l1)
    len2 = len(l2)
    cnt = 0
    while cnt < len1 and cnt < len2:
        len1 = len(l1)
        len2 = len(l2)
        if l1[cnt][0] != '[' and l2[cnt][0] != '[': # both values are integers
            if int(l1[cnt]) < int(l2[cnt]): return 1
            if int(l1[cnt]) > int(l2[cnt]): return -1
        else:
            if l1[cnt][0] == '[':
                l1_new = custom_split(l1[cnt][1:-1])
            else:
                temp = l1[cnt]
                l1_new = []
                l1_new.append(temp)
            if l2[cnt][0] == '[':
                l2_new = custom_split(l2[cnt][1:-1])
            else:
                temp = l2[cnt]
                l2_new = []
                l2_new.append(temp)
            compare_res = compare(l1_new,l2_new)
            if compare_res == 1: return 1
            if compare_res == -1: return -1
        cnt += 1
    if not cnt < len1 and cnt < len2: return 1
    if cnt < len1 and not cnt < len2: return -1
    return 0

def custom_sort(a,b):
    return True if compare(a,b) == 1 else False

packets = []

DIVIDER_PACKET_1 = ["[[2]]"]
DIVIDER_PACKET_2 = ["[[6]]"]

with open('input.txt') as f:
    lines = [line.rstrip('\n') for line in f]
    cnt = 1
    sum = 0
    while 3*(cnt-1) < len(lines):
        packet_one = lines[3*(cnt-1)]
        packet_two = lines[3*(cnt-1)+1]
        list_one = custom_split(packet_one[1:-1])
        list_two = custom_split(packet_two[1:-1])
        packets.append(list_one)
        packets.append(list_two)
        cnt += 1
    packets.append(DIVIDER_PACKET_1)
    packets.append(DIVIDER_PACKET_2)

packets = sorted(packets, key=cmp_to_key(compare), reverse=True)
cnt = 1
decoder_key = 1
for packet in packets:
    print(packet)
    if packet == DIVIDER_PACKET_1: decoder_key *= cnt
    if packet == DIVIDER_PACKET_2: decoder_key *= cnt
    cnt += 1
print("decoder_key: ", decoder_key)