#! /usr/bin/python

from __future__ import division
from __future__ import print_function
from math import log, exp

def days_cost(day):
	return int(3 + 0.3 * day)

def get_glasses(day):
	if day < 9:
		return 12
	return 0

def get_price(day):
	return 8

def get_signs(day, assets, sign_cost):
	return 1
def base_demand(price):
	return max(108 - 0.8 * price * price, 0)

def advertising_multiplier(signs):
	return 1 - exp(-signs / 2) / 2

def days_temperature(day):
	return int(10 + 8.8 * (day % 4))

def temperature_multiplier(temperature):
	if temperature >= 15:
		return log(temperature / 15)
	return 0

def days_chance_of_precipitation(day):
	return int(15 * (day % 5)) / 100

def precipitation_multiplier(chance_of_precipitation):
	if chance_of_precipitation > 0.5:
		return 0
	return 1 - chance_of_precipitation / 2

def days_street_work(day):
	if day == 2:
		return 1
	if day == 8:
		return 0.5
	return 0

def street_work_multiplier(street_work):
	exponent = street_work
	if street_work < 1:
		exponent = -exponent
	return exp(exponent)

print('')
print('deterministic-lemonade.py: A Deterministic Ten-Day Lemonade Stand Simulation')
print('(loosely based on the 1979 game "Lemonade Stand" by the Minnesota Educational Computing Consortium)')
print('')
initial_assets = 200
assets = initial_assets
intitial_signs = 1
sign = intitial_signs
sign_cost = 15
days = 10
for day in range(days):
	if day > 0:
		print('')
	print('Day {day}:'.format(day = day + 1))
	print('')
	print('  You have {assets} cent(s) in assets.'.format(assets = assets))
	cost = days_cost(day)
	print('  At the current going rate for ingredients, a glass of lemonade will cost {cost} cent(s) to make.'.format(cost = cost))
	temperature = days_temperature(day)
	chance_of_precipitation = days_chance_of_precipitation(day)
	print('  The forecast calls for a temperature of {temperature} degrees Celsius ({converted} degrees Farenheit) with a(n) {percent}% chance of precipitation.'.format(temperature = temperature, converted = int(round(32 + 9 * temperature / 5)), percent = int(chance_of_precipitation * 100)))
	street_work = days_street_work(day)
	if street_work > 0.5:
		print('  There is ongoing street work, reducing traffic significantly.  But perhaps the work crew will get thirsty?')
	elif street_work > 0:
		print('  There is ongoing street work, slightly reducing traffic.  But perhaps the work crew will get thirsty?')
	print('')
	original_assets = assets
	glasses = 2 * (day % 4) # this limits the randomness as the day is the deciding factor on how many glasses to make.
	lemonade_cost = glasses * cost
	assets -= lemonade_cost
	price = 10 - (day % 4) # this limits you choice into choosing the price. Now each day is at a set price, no matter the weather.
	signs = (sign + 1) % 2
	advertising_cost = signs * sign_cost
	assets -= advertising_cost
	print('')
	advertising_factor = advertising_multiplier(signs)
	temperature_factor = temperature_multiplier(temperature)
	precipitation_factor = precipitation_multiplier(chance_of_precipitation)
	if precipitation_factor <= 0:
		if temperature < 5:
			if glasses > 0:
				print('It snows!  Your sales are ruined.')
			else:
				print('It snows!  You stay warm inside.')
		else:
			if glasses > 0:
				print('It rains!  Your sales are ruined.')
			else:
				print('It rains!  You stay dry inside.')
	street_work_factor = street_work_multiplier(street_work)
	if precipitation_factor > 0 and street_work_factor > 1 and glasses > 3:
		print('Over lunch, the work crew stops by, and everyone gets lemonade!')
	sales = min(int(base_demand(price) * advertising_factor * temperature_factor * precipitation_factor * street_work_factor), glasses)
	income = sales * price
	assets += income
	print('You began the day with {original_assets} cent(s) in assets.'.format(original_assets = original_assets))
	print('You prepared {glasses} glass(es) of lemonade for a total cost of {lemonade_cost} cent(s).'.format(glasses = glasses, lemonade_cost = lemonade_cost))
	print('You prepared {signs} sign(s) for a total cost of {advertising_cost} cent(s).'.format(signs = signs, advertising_cost = advertising_cost))
	print('You sold {sales} glass(es) of lemonade for a total income of {income} cent(s).'.format(sales = sales, income = income))
	print('Your profit for the day was {profit} cent(s).'.format(profit = income - lemonade_cost - advertising_cost))
	print('You ended the day with {assets} cent(s) in assets.'.format(assets = assets))
print('')
print('You finished the simulation with {assets} cent(s) in assets.'.format(assets = assets))
if assets > 0:
	print('Your business growth rate: {percent}% per day.'.format(percent = int(100 * ((assets / initial_assets) ** (1 / days) - 1))))
print('Your business rating:', end = ' ')
if assets < 50:
	print('Dismal!')
elif assets < 150:
	print('Disappointing')
elif assets < 250:
	print('Okay')
elif assets < 300:
	print('Good')
elif assets < 400:
	print('Great!')
elif assets < 500:
	print('Excellent!')
else:
	print('Incredible!')
print('')


from django.urls import path
from .import views

urlpatterns = [
    path('index', views.index, name='index'),
    path('create', views.create, name='create'),
    path('vote/<str:pk>/', views.vote, name='vote'),
    path('result/<str:pk>/', views.result, name='result'),
    path('viewresult/<str:pk>/', views.viewresult, name='viewresult'),
    path('deletepoll/<str:pk>/', views.delete, name='delete'),
    path('', views.intro, name='intro'),
]


import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s: %(message)s")

import os, enum, json, time
from requests import get
from bs4 import BeautifulSoup as BS4

CURRENT_YEAR = int(time.strftime("%Y"))

class LinksEnum(enum.Enum):
    base = "https://www.auto-data.net"
    entry = "https://www.auto-data.net/en/allbrands"

class AD_NET:
    def __init__(
            self,
            output_file: str = 'output.json',
            skip_existing: bool = False,
            only_include_makes: list = [],
        ):

        self._current_make = None
        self._current_model = None
        self._current_generation = None
        self._current_variant = None

        self.skip_existing = skip_existing
        self._only_include_makes = only_include_makes
        self._output_file = output_file
        self._break = None

        if self._output_file in os.listdir():
            with open(self._output_file, 'r') as f:
                self.indexed = json.load(f)
        else:
            self.indexed = {}

    def scrape(self, ):
        soup = BS4(get(LinksEnum.entry.value).text, 'html.parser')

        _el_cars = soup.find_all(class_='marki_blok')
        cars = [(
                    c.find('strong').text,
                    c.get('href'),
                ) for c in _el_cars]

        for i, car in enumerate(cars):
            self._current_make = car[0]

            if self.skip_existing and self._current_make in self.indexed:
                continue
            elif self._only_include_makes and self._current_make not in self._only_include_makes:
                continue
            else:
                logging.info(f'Scraping [{i+1}/{len(cars)}] {self._current_make}')
                self.indexed[self._current_make] = {}

                self._scrape_make(LinksEnum.base.value+car[1])

                self._write_json()

    def _scrape_make(self, link: str):
        soup = BS4(get(link).text, 'html.parser')

        _el_models = soup.find_all(class_='modeli')
        models = [(
                    m.find('strong').text,
                    m.get('href'),
                ) for m in _el_models]

        for i, model in enumerate(models):
            self._current_model = model[0]
            logging.info(f'   [{i+1}/{len(models)}] {self._current_model}')

            self.indexed[self._current_make][self._current_model] = {}

            self._scrape_model(LinksEnum.base.value+model[1])

    def _scrape_model(self, link: str):
        soup = BS4(get(link).text, 'html.parser')

        table = soup.find(class_='generr')
        _el_generations = table.find_all(class_=lambda c: c and any(x in c for x in ['lgreen', 'lred']))
        _el_generations = [g for g in _el_generations if g.find(class_=lambda c: c and any(x in c for x in ['cur', 'end'])) is not None]
        generations = [(
                        g.find('strong').text,
                        g.find(class_=lambda c: c and any(x in c for x in ['cur', 'end'])).text,
                        g.find('a').get('href'),
                    ) for g in _el_generations]

        for i, generation in enumerate(generations):
            self._current_generation = generation[0]
            logging.info(f'      [{i+1}/{len(generations)}] {self._current_generation}')

            self.indexed[self._current_make][self._current_model][self._current_generation] = {}

            fromto = generation[1].split(' - ')
            self.indexed[self._current_make][self._current_model][self._current_generation]['from'] = int(fromto[0])
            self.indexed[self._current_make][self._current_model][self._current_generation]['to'] = int(fromto[1]) if fromto[1] else CURRENT_YEAR

            self._scrape_generation(LinksEnum.base.value+generation[2])

    def _scrape_generation(self, link: str):
        soup = BS4(get(link).text, 'html.parser')

        table = soup.find(class_='carlist')
        _el_variants = table.find_all(class_=lambda c: c and any(x in c for x in ['lgreen', 'lred']))
        variants = [(
                        g.find(class_='tit').text,
                        g.find(class_=lambda c: c and any(x in c for x in ['cur', 'end'])).text,
                        g.find('a').get('href'),
                    ) for g in _el_variants]

        for i, variant in enumerate(variants):
            self._current_variant = variant[0]

            self.indexed[self._current_make][self._current_model][self._current_generation][self._current_variant] = {}

            fromto = variant[1].split(' - ')
            self.indexed[self._current_make][self._current_model][self._current_generation][self._current_variant]['from'] = int(fromto[0])
            self.indexed[self._current_make][self._current_model][self._current_generation][self._current_variant]['to'] = int(fromto[1]) if fromto[1] else CURRENT_YEAR
            self.indexed[self._current_make][self._current_model][self._current_generation][self._current_variant]['specs'] = {}

            logging.info(f'         [{i+1}/{len(variants)}] {self._current_variant}')

            self._scrape_variant(LinksEnum.base.value+variant[2])

    def _scrape_variant(self, link: str):
        soup = BS4(get(link).text, 'html.parser')

        # phase 1
        table = soup.find(class_='cardetailsout')
        specs = {}
        power_no_system = 0
        for row in table.find_all('tr'):
            key = row.find('th').text.lower()
            value = row.find('td', recursive=False)
            if value:
                value = value.text
            else:
                continue

            if 'body type' in key:
                specs['body_type'] = value

            elif 'seats' in key:
                specs['seats_count'] = int(value.split('-')[0].split('/')[0].strip(' '))

            elif 'doors' in key:
                specs['doors_count'] = int(value.split('-')[0].split('/')[0].strip('<').strip('>'))

            elif 'powertrain architecture' in key:
                specs['powertrain_architecture'] = value

            elif 'fuel type' in key:
                if 'gasoline' in value.lower() or 'petrol' in value.lower():
                    specs['fuel_type'] = 'gasoline'
                elif 'diesel' in value.lower():
                    specs['fuel_type'] = 'diesel'

            elif 'fuel consumption' in key:
                if not 'fuel_consumption' in specs:
                    specs['fuel_consumption'] = {}

                if not 'kg/100 km' in value:
                    consumption = value.split(' l/100')[0].split('-')[0].replace('..', '.')
                    if len(consumption) >= 3:
                        consumption = consumption[:3]
                    if 'combined' in key:
                        specs['fuel_consumption']['combined'] = float(consumption)
                    elif 'extra urban' in key:
                        specs['fuel_consumption']['extra_urban'] = float(consumption)
                    elif 'urban' in key:
                        specs['fuel_consumption']['urban'] = float(consumption)

            elif 'acceleration 0 - 100 km/h' in key:
                acceleration = value[:-5].strip('<').strip('>').split('-')[0].replace('..', '.').split(',')[0]
                if acceleration[-1] == '.':
                    acceleration = acceleration[:-1]
                if len(acceleration) >= 3:
                    acceleration = acceleration[:3]
                specs['acceleration'] = float(acceleration)

            elif 'fuel tank capacity' in key:
                fuel_tank_capacity = value.split(' l')[0].split(' (optional')[0].split(' ')[0].split('-')[0]
                if '+' in fuel_tank_capacity:
                    fuel_tank_capacity = sum([float(f) for f in fuel_tank_capacity.split('+')])
                specs['fuel_tank_capacity'] = float(fuel_tank_capacity)

            elif 'gross battery capacity' in key:
                specs['battery_capacity'] = float(value[:-4].split('-')[0])

            elif 'all-electric range' in key:
                specs['electric_range'] = float(value.split(' km')[0].split('-')[0].strip('<').strip('>'))

            elif 'system power' in key:
                power = value.split(' Hp')[0]
                if '+' in power:
                    power = sum([int(p) for p in power.split('+')])
                specs['power'] = int(power)

            elif 'emission standard' in key:
                specs['emission_standard'] = value[:-1]

            elif 'engine displacement' in key:
                specs['displacement'] = int(value.split(' cm3')[0])

            elif 'power' == key.strip(' '):
                power_no_system = int(value.split(' Hp ')[0])

            elif 'number of gears' in key:
                if 'automatic' in key:
                    specs['transmission'] = 'automatic'
                elif 'manual' in key:
                    specs['transmission'] = 'manual'

            elif 'drive wheel' in key:
                if 'Front' in value:
                    specs['traction'] = 'front'
                elif 'Rear' in value:
                    specs['traction'] = 'rear'
                elif 'All wheel drive' in value:
                    specs['traction'] = '4wd'

        # phase 2
        if not 'power' in specs or not specs['power']:
            specs['power'] = power_no_system

        if 'powertrain_architecture' in specs and specs['powertrain_architecture']:
            if 'PHEV' in specs['powertrain_architecture']:
                specs['powertrain_architecture'] = 'PHEV'
                specs['fuel_type'] = 'plug-in hybrid'
            elif 'BEV' in specs['powertrain_architecture']:
                specs['powertrain_architecture'] = 'BEV'
                specs['fuel_type'] = 'electric'
            elif 'FHEV' in specs['powertrain_architecture']:
                specs['powertrain_architecture'] = 'FHEV'
                if specs['fuel_type'] == 'gasoline':
                    specs['fuel_type'] = 'hybrid gasoline'
                elif specs['fuel_type'] == 'diesel':
                    specs['fuel_type'] = 'hybrid diesel'
            elif 'internal combustion engine' in specs['powertrain_architecture'].lower():
                specs['powertrain_architecture'] = 'ICE'
            elif 'FCEV' in specs['powertrain_architecture']:
                specs['powertrain_architecture'] = 'FCEV'

        if 'body_type' in specs and specs['body_type']:
            body_type = specs['body_type'].lower()
            if 'crossover' in body_type or 'cuv' in body_type:
                specs['body_type'] = 'crossover'
            elif 'suv' in body_type or 'mpv' in body_type or 'sav' in body_type or 'sac' in body_type or 'off-road' in body_type:
                specs['body_type'] = 'suv'
            elif 'coupe' in body_type:
                specs['body_type'] = 'coupe'
            elif 'hatchback' in body_type:
                specs['body_type'] = 'hatchback'
            elif 'sedan' in body_type:
                specs['body_type'] = 'sedan'
            elif 'liftback' in body_type:
                specs['body_type'] = 'liftback'
            elif 'targa' in body_type:
                specs['body_type'] = 'targa'
            elif 'minivan' in body_type:
                specs['body_type'] = 'minivan'
            elif 'pickup' in body_type or 'pick-up' in body_type:
                specs['body_type'] = 'pickup'
            elif 'cabriolet' in body_type or 'cabrio' in body_type:
                specs['body_type'] = 'cabriolet'
            elif 'roadster' in body_type:
                specs['body_type'] = 'roadster'
            elif 'fastback' in body_type:
                specs['body_type'] = 'fastback'
            elif 'grand tourer' in body_type:
                specs['body_type'] = 'grand tourer'
            elif 'estate' in body_type or 'wagon' in body_type:
                specs['body_type'] = 'wagon'
            else:
                print(specs['body_type'])
                specs['body_type'] = 'other'

        for k, v in specs.items():
            self.indexed[self._current_make][self._current_model][self._current_generation][self._current_variant]['specs'][k] = v

    def _write_json(self, ):
        with open(self._output_file, 'w') as f:
            json.dump(self.indexed, f, indent=2)


def xMasTree(n):
    hashtags = '#' * (n - 1)
    spaces = '_' * (n - 1)
    tree = []
    for i in range(n):
        left = spaces[i:] + hashtags[:i]
        tree.append(left + '#' + left[::-1])
    for i in range(2):
        tree.append(spaces + '#' + spaces)
    return tree
print(xMasTree(3))

Desktop/hello world/Basic Python programs/tree.py


import random
from collections import namedtuple

import pygame

import color
from block import Block

Position = namedtuple('Position', 'x y')

class Grid(object):
    def __init__(self, screen, preset):

        self.screen = screen
        self.num_x = preset.x_num
        self.num_y = preset.y_num
        self.width = preset.x_num * preset.block_size
        self.height = preset.y_num * preset.block_size
        self.block_size = preset.block_size
        self.allow_diagonal_neighbors = preset.allow_diagonals
        self.wall_percentage = preset.wall_percentage

        remaining_w = preset.screen_size[0] - self.width
        remaining_h = preset.screen_size[1] - self.height
        self.x_pos = int(remaining_w / 2)
        self.y_pos = int(remaining_h / 2)

        self.blocks = []
        for x in range(preset.x_num):
            col = []
            for y in range(preset.y_num):
                col.append(Block(x, y, self.x_pos, self.y_pos, self.block_size, False))
            self.blocks.append(col)

        self.refresh()

    def get_random_position(self, exclude=None):
        x = random.randint(0, self.num_x-1)
        if exclude is not None:
            while x == exclude.x:
                x = random.randint(0, self.num_x-1)
        y = random.randint(0, self.num_y-1)
        if exclude is not None:
            while y == exclude.y:
                y = random.randint(0, self.num_y-1)
        return Position(x, y)

    def set_start_end(self):
        start_pos = self.get_random_position()
        end_pos = self.get_random_position(exclude=start_pos)
        self.blocks[start_pos.x][start_pos.y].start = True
        self.blocks[end_pos.x][end_pos.y].end = True

        # self.blocks[0][int((self.num_y - 1) / 2)].start = True
        # self.blocks[self.num_x - 1][int((self.num_y - 1) / 2)].end = True

    def draw(self):
        pygame.draw.rect(self.screen, color.BLUE,
                         pygame.Rect(self.x_pos, self.y_pos, self.width, self.height))
        for block in self.iter_blocks():
            block.draw(self.screen)

    def draw_step(self):
        pygame.draw.rect(self.screen, color.BLUE,
                         pygame.Rect(self.x_pos, self.y_pos, self.width, self.height))
        yield
        for block in self.iter_blocks():
            block.draw(self.screen)
            yield

    def iter_blocks(self):
        for i in range(self.num_x):
            for j in range(self.num_y):
                yield self.blocks[i][j]

    def refresh(self):
        for block in self.iter_blocks():
            block.reset()
            block.wall = rand_wall(self.wall_percentage)
        self.set_start_end()

    def get_start_block(self):
        for block in self.iter_blocks():
            if block.start:
                return block
        else:
            self.blocks[0][0].start = True
            return self.blocks[0][0]

    def get_end_block(self):
        for block in self.iter_blocks():
            if block.end:
                return block
        else:
            self.blocks[self.num_x-1][self.num_y-1].end = True
            return self.blocks[self.num_x-1][self.num_y-1]

    @staticmethod
    def is_goal(block):
        return block.end

    def get_neighbors(self, block):
        if self.allow_diagonal_neighbors:
            neighbor_coordinates = [
                (block.x - 1, block.y),      # left
                (block.x - 1, block.y + 1),  # top-left
                (block.x, block.y + 1),      # top
                (block.x + 1, block.y + 1),  # top-right
                (block.x + 1, block.y),      # right
                (block.x + 1, block.y - 1),  # bottom-right
                (block.x, block.y - 1),      # bottom
                (block.x - 1, block.y - 1)   # bottom-left
            ]
        else:
            neighbor_coordinates = [
                (block.x - 1, block.y),  # left
                (block.x, block.y + 1),  # top
                (block.x + 1, block.y),  # right
                (block.x, block.y - 1)   # bottom
            ]
        for x, y in neighbor_coordinates:
            if x < 0 or y < 0:   # remove negatives so it doesn't continue across edges
                continue
            try:
                candidate = self.blocks[x][y]
            except IndexError:
                pass
            else:
                if not candidate.wall:
                    yield candidate

def rand_wall(wall_percentage):
    return random.random() < wall_percentage / 100


# NAME: Bhavadeep Bhukya
# Roll No : cs20b015
# Course: CS3205 Jan. 2023 semester
# Lab number: 2
# Date of submission: 04-02-2023
# I confirm that the source file is entirely written by me without
# resorting to any dishonest means.
# Website(s) that I used for basic socket programming code are:
# URLs : https://www.geeksforgeeks.org/socket-programming-python/
import socket
import sys
import os

serversocket = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
serversocket.bind((str(sys.argv[1]),int(sys.argv[2])))
while True:
    data,addr = serversocket.recvfrom(2048)
    data = data.decode()
    tld = data.split('.')[-1]
    filename = tld+'.'+'txt'
    mainname = data.split('.')[-2]
    with open(filename,'r') as f:
        lines = f.readlines()
    check=0
    found = ''
    for line in lines:
        if(line[-1]=='\n'):
            line = line[:-1]
        temp = line.split()
        if(temp[0].split('.')[0]==mainname):
            found = line
            check=1
            msg = temp[1]
    if check==0:
        msg ="ERROR 404"
    o = open('TLDOutput.txt','a+')
    o.write("recieved request : "+str(data))
    o.write('\n')
    o.write("found : "+str(found))
    o.write('\n')
    o.write("Sending : "+msg)
    o.write('\n')
    o.write('****************************************')
    o.write('\n')
    o.close()
    serversocket.sendto(msg.encode(),addr)


#!/usr/bin/py
# Head ends here

def substrings(s):
    result = []
    for i, v in enumerate(s):
        result.append(v)
        for j in range(i + 2, len(s) + 1):
            result.append(v + s[i + 1:j])

    print(sorted(set(result)))
    return sorted(set(result))

def substrings2(s):
    length = len(s)
    return [s[i:j + 1] for i in range(length) for j in range(i, length)]

def make_suffix_array(s):
    result = [x for x in s]

    length = len(s)
    for i, j in enumerate(s):
        result.append(s[i:length])

    for i, j in enumerate(s):
        result.append(s[0:i + 1])

    for n in range(1, len(s)):
        for i, j in enumerate(s):
            result.append(s[i:i + n])

    return sorted(set(result))

def findStrings(a, query):
    combinations = []

    for i in a:
        combinations += make_suffix_array(i)

    combinations = sorted(set(combinations))

    for q in query:

        if q <= len(combinations):
            print(combinations[q - 1])
        else:
            print('INVALID')

def test():
    n = 2
    string = ['aab', 'aac']
    string = ['rummykhan']
    q = 3
    query = [3, 8, 23]
    findStrings(string, query)

if __name__ == '__main__':
    test()


# -*- mode: python -*-
# -*- coding: utf-8 -*-

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2021 Emmanuel Viennet.  All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#   Emmanuel Viennet      emmanuel.viennet@viennet.net
#
##############################################################################

##############################################################################
#  Module "Avis de poursuite d'étude"
#  conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################

import os
import codecs
import re
from app.pe import pe_tagtable
from app.pe import pe_jurype
from app.pe import pe_tools

import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
from app.scodoc.gen_tables import GenTable, SeqGenTable
from app.scodoc import sco_preferences
from app.scodoc import sco_etud

DEBUG = False  # Pour debug et repérage des prints à changer en Log

DONNEE_MANQUANTE = (
    ""  # Caractère de remplacement des données manquantes dans un avis PE
)

# ----------------------------------------------------------------------------------------
def get_code_latex_from_modele(fichier):
    """Lit le code latex à partir d'un modèle. Renvoie une chaine unicode.

    Le fichier doit contenir le chemin relatif
    vers le modele : attention pas de vérification du format d'encodage
    Le fichier doit donc etre enregistré avec le même codage que ScoDoc (utf-8)
    """
    fid_latex = codecs.open(fichier, "r", encoding=scu.SCO_ENCODING)
    un_avis_latex = fid_latex.read()
    fid_latex.close()
    return un_avis_latex

# ----------------------------------------------------------------------------------------
def get_code_latex_from_scodoc_preference(formsemestre_id, champ="pe_avis_latex_tmpl"):
    """
    Extrait le template (ou le tag d'annotation au regard du champ fourni) des préférences LaTeX
    et s'assure qu'il est renvoyé au format unicode
    """
    template_latex = sco_preferences.get_preference(champ, formsemestre_id)

    return template_latex or ""

# ----------------------------------------------------------------------------------------
def get_tags_latex(code_latex):
    """Recherche tous les tags présents dans un code latex (ce code étant obtenu
    à la lecture d'un modèle d'avis pe).
    Ces tags sont répérés par les balises **, débutant et finissant le tag
    et sont renvoyés sous la forme d'une liste.

    result: liste de chaines unicode
    """
    if code_latex:
        # changé par EV: était r"([\*]{2}[a-zA-Z0-9:éèàâêëïôöù]+[\*]{2})"
        res = re.findall(r"([\*]{2}[^ \t\n\r\f\v\*]+[\*]{2})", code_latex)
        return [tag[2:-2] for tag in res]
    else:
        return []

def comp_latex_parcourstimeline(etudiant, promo, taille=17):
    """Interprète un tag dans un avis latex **parcourstimeline**
    et génère le code latex permettant de retracer le parcours d'un étudiant
    sous la forme d'une frise temporelle.
    Nota: modeles/parcourstimeline.tex doit avoir été inclu dans le préambule

    result: chaine unicode (EV:)
    """
    codelatexDebut = (
        """"
    \\begin{parcourstimeline}{**debut**}{**fin**}{**nbreSemestres**}{%d}
    """
        % taille
    )

    modeleEvent = """
    \\parcoursevent{**nosem**}{**nomsem**}{**descr**}
    """

    codelatexFin = """
    \\end{parcourstimeline}
    """
    reslatex = codelatexDebut
    reslatex = reslatex.replace("**debut**", etudiant["entree"])
    reslatex = reslatex.replace("**fin**", str(etudiant["promo"]))
    reslatex = reslatex.replace("**nbreSemestres**", str(etudiant["nbSemestres"]))
    # Tri du parcours par ordre croissant : de la forme descr, nom sem date-date
    parcours = etudiant["parcours"][::-1]  # EV: XXX je ne comprend pas ce commentaire ?

    for no_sem in range(etudiant["nbSemestres"]):
        descr = modeleEvent
        nom_semestre_dans_parcours = parcours[no_sem]["nom_semestre_dans_parcours"]
        descr = descr.replace("**nosem**", str(no_sem + 1))
        if no_sem % 2 == 0:
            descr = descr.replace("**nomsem**", nom_semestre_dans_parcours)
            descr = descr.replace("**descr**", "")
        else:
            descr = descr.replace("**nomsem**", "")
            descr = descr.replace("**descr**", nom_semestre_dans_parcours)
        reslatex += descr
    reslatex += codelatexFin
    return reslatex

# ----------------------------------------------------------------------------------------
def interprete_tag_latex(tag):
    """Découpe les tags latex de la forme S1:groupe:dut:min et renvoie si possible
    le résultat sous la forme d'un quadruplet.
    """
    infotag = tag.split(":")
    if len(infotag) == 4:
        return (
            infotag[0].upper(),
            infotag[1].lower(),
            infotag[2].lower(),
            infotag[3].lower(),
        )
    else:
        return (None, None, None, None)

# ----------------------------------------------------------------------------------------
def get_code_latex_avis_etudiant(
    donnees_etudiant, un_avis_latex, annotationPE, footer_latex, prefs
):
    """
    Renvoie le code latex permettant de générer l'avis d'un étudiant en utilisant ses
    donnees_etudiant contenu dans le dictionnaire de synthèse du jury PE et en suivant un
    fichier modele donné

    result: chaine unicode
    """
    if not donnees_etudiant or not un_avis_latex:  # Cas d'un template vide
        return annotationPE if annotationPE else ""

    # Le template latex (corps + footer)
    code = un_avis_latex + "\n\n" + footer_latex

    # Recherche des tags dans le fichier
    tags_latex = get_tags_latex(code)
    if DEBUG:
        log("Les tags" + str(tags_latex))

    # Interprète et remplace chaque tags latex par les données numériques de l'étudiant (y compris les
    # tags "macros" tels que parcourstimeline
    for tag_latex in tags_latex:
        # les tags numériques
        valeur = DONNEE_MANQUANTE

        if ":" in tag_latex:
            (aggregat, groupe, tag_scodoc, champ) = interprete_tag_latex(tag_latex)
            valeur = str_from_syntheseJury(
                donnees_etudiant, aggregat, groupe, tag_scodoc, champ
            )

        # La macro parcourstimeline
        elif tag_latex == "parcourstimeline":
            valeur = comp_latex_parcourstimeline(
                donnees_etudiant, donnees_etudiant["promo"]
            )

        # Le tag annotationPE
        elif tag_latex == "annotation":
            valeur = annotationPE

        # Le tag bilanParTag
        elif tag_latex == "bilanParTag":
            valeur = get_bilanParTag(donnees_etudiant)

        # Les tags "simples": par ex. nom, prenom, civilite, ...
        else:
            if tag_latex in donnees_etudiant:
                valeur = donnees_etudiant[tag_latex]
            elif tag_latex in prefs:  # les champs **NomResponsablePE**, ...
                valeur = pe_tools.escape_for_latex(prefs[tag_latex])

        # Vérification des pb d'encodage (debug)
        # assert isinstance(tag_latex, unicode)
        # assert isinstance(valeur, unicode)

        # Substitution
        code = code.replace("**" + tag_latex + "**", valeur)
    return code

# ----------------------------------------------------------------------------------------
def get_annotation_PE(etudid, tag_annotation_pe):
    """Renvoie l'annotation PE dans la liste de ces annotations ;
    Cette annotation est reconnue par la présence d'un tag **PE**
    (cf. .get_preferences -> pe_tag_annotation_avis_latex).

    Result: chaine unicode
    """
    if tag_annotation_pe:
        cnx = ndb.GetDBConnexion()
        annotations = sco_etud.etud_annotations_list(
            cnx, args={"etudid": etudid}
        )  # Les annotations de l'étudiant
        annotationsPE = []

        exp = re.compile(r"^" + tag_annotation_pe)

        for a in annotations:
            commentaire = scu.unescape_html(a["comment"])
            if exp.match(commentaire):  # tag en début de commentaire ?
                a["comment_u"] = commentaire  # unicode, HTML non quoté
                annotationsPE.append(
                    a
                )  # sauvegarde l'annotation si elle contient le tag

        if annotationsPE:  # Si des annotations existent, prend la plus récente
            annotationPE = sorted(annotationsPE, key=lambda a: a["date"], reverse=True)[
                0
            ]["comment_u"]

            annotationPE = exp.sub(
                "", annotationPE
            )  # Suppression du tag d'annotation PE
            annotationPE = annotationPE.replace("\r", "")  # Suppression des \r
            annotationPE = annotationPE.replace(
                "<br/>", "\n\n"
            )  # Interprète les retours chariots html
            return annotationPE
    return ""  # pas d'annotations

# ----------------------------------------------------------------------------------------
def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ):
    """Extrait du dictionnaire de synthèse du juryPE pour un étudiant donnée,
    une valeur indiquée par un champ ;
    si champ est une liste, renvoie la liste des valeurs extraites.

    Result: chaine unicode ou liste de chaines unicode
    """

    if isinstance(champ, list):
        return [
            str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, chp)
            for chp in champ
        ]
    else:  # champ = str à priori
        valeur = DONNEE_MANQUANTE
        if (
            (aggregat in donnees_etudiant)
            and (groupe in donnees_etudiant[aggregat])
            and (tag_scodoc in donnees_etudiant[aggregat][groupe])
        ):
            donnees_numeriques = donnees_etudiant[aggregat][groupe][tag_scodoc]
            if champ == "rang":
                valeur = "%s/%d" % (
                    donnees_numeriques[
                        pe_tagtable.TableTag.FORMAT_DONNEES_ETUDIANTS.index("rang")
                    ],
                    donnees_numeriques[
                        pe_tagtable.TableTag.FORMAT_DONNEES_ETUDIANTS.index(
                            "nbinscrits"
                        )
                    ],
                )
            elif champ in pe_tagtable.TableTag.FORMAT_DONNEES_ETUDIANTS:
                indice_champ = pe_tagtable.TableTag.FORMAT_DONNEES_ETUDIANTS.index(
                    champ
                )
                if (
                    len(donnees_numeriques) > indice_champ
                    and donnees_numeriques[indice_champ] != None
                ):
                    if isinstance(
                        donnees_numeriques[indice_champ], float
                    ):  # valeur numérique avec formattage unicode
                        valeur = "%2.2f" % donnees_numeriques[indice_champ]
                    else:
                        valeur = "%s" % donnees_numeriques[indice_champ]

        return valeur

# ----------------------------------------------------------------------------------------
def get_bilanParTag(donnees_etudiant, groupe="groupe"):
    """Renvoie le code latex d'un tableau récapitulant, pour tous les tags trouvés dans
    les données étudiants, ses résultats.
    result: chaine unicode
    """

    entete = [
        (
            agg,
            pe_jurype.JuryPE.PARCOURS[agg]["affichage_court"],
            pe_jurype.JuryPE.PARCOURS[agg]["ordre"],
        )
        for agg in pe_jurype.JuryPE.PARCOURS
    ]
    entete = sorted(entete, key=lambda t: t[2])

    lignes = []
    valeurs = {"note": [], "rang": []}
    for (indice_aggregat, (aggregat, intitule, _)) in enumerate(entete):
        # print("> " + aggregat)
        # listeTags = jury.get_allTagForAggregat(aggregat)  # les tags de l'aggrégat
        listeTags = [
            tag for tag in donnees_etudiant[aggregat][groupe].keys() if tag != "dut"
        ]  #
        for tag in listeTags:

            if tag not in lignes:
                lignes.append(tag)
                valeurs["note"].append(
                    [""] * len(entete)
                )  # Ajout d'une ligne de données
                valeurs["rang"].append(
                    [""] * len(entete)
                )  # Ajout d'une ligne de données
            indice_tag = lignes.index(tag)  # l'indice de ligne du tag

            # print(" --- " + tag + "(" + str(indice_tag) + "," + str(indice_aggregat) + ")")
            [note, rang] = str_from_syntheseJury(
                donnees_etudiant, aggregat, groupe, tag, ["note", "rang"]
            )
            valeurs["note"][indice_tag][indice_aggregat] = "" + note + ""
            valeurs["rang"][indice_tag][indice_aggregat] = (
                ("\\textit{" + rang + "}") if note else ""
            )  # rang masqué si pas de notes

    code_latex = "\\begin{tabular}{|c|" + "|c" * (len(entete)) + "|}\n"
    code_latex += "\\hline \n"
    code_latex += (
        " & "
        + " & ".join(["\\textbf{" + intitule + "}" for (agg, intitule, _) in entete])
        + " \\\\ \n"
    )
    code_latex += "\\hline"
    code_latex += "\\hline \n"
    for (i, ligne_val) in enumerate(valeurs["note"]):
        titre = lignes[i]  # règle le pb d'encodage
        code_latex += "\\textbf{" + titre + "} & " + " & ".join(ligne_val) + "\\\\ \n"
        code_latex += (
            " & "
            + " & ".join(
                ["{\\scriptsize " + clsmt + "}" for clsmt in valeurs["rang"][i]]
            )
            + "\\\\ \n"
        )
        code_latex += "\\hline \n"
    code_latex += "\\end{tabular}"

    return code_latex

# ----------------------------------------------------------------------------------------
def get_avis_poursuite_par_etudiant(
    jury, etudid, template_latex, tag_annotation_pe, footer_latex, prefs
):
    """Renvoie un nom de fichier et le contenu de l'avis latex d'un étudiant dont l'etudid est fourni.
    result: [ chaine unicode, chaine unicode ]
    """
    if pe_tools.PE_DEBUG:
        pe_tools.pe_print(jury.syntheseJury[etudid]["nom"] + " " + str(etudid))

    civilite_str = jury.syntheseJury[etudid]["civilite_str"]
    nom = jury.syntheseJury[etudid]["nom"].replace(" ", "-")
    prenom = jury.syntheseJury[etudid]["prenom"].replace(" ", "-")

    nom_fichier = scu.sanitize_filename(
        "avis_poursuite_%s_%s_%s" % (nom, prenom, etudid)
    )
    if pe_tools.PE_DEBUG:
        pe_tools.pe_print("fichier latex =" + nom_fichier, type(nom_fichier))

    # Entete (commentaire)
    contenu_latex = (
        "%% ---- Etudiant: " + civilite_str + " " + nom + " " + prenom + "\n"
    )

    # les annnotations
    annotationPE = get_annotation_PE(etudid, tag_annotation_pe=tag_annotation_pe)
    if pe_tools.PE_DEBUG:
        pe_tools.pe_print(annotationPE, type(annotationPE))

    # le LaTeX
    avis = get_code_latex_avis_etudiant(
        jury.syntheseJury[etudid], template_latex, annotationPE, footer_latex, prefs
    )
    # if pe_tools.PE_DEBUG: pe_tools.pe_print(avis, type(avis))
    contenu_latex += avis + "\n"

    return [nom_fichier, contenu_latex]

def get_templates_from_distrib(template="avis"):
    """Récupère le template (soit un_avis.tex soit le footer.tex) à partir des fichiers mémorisés dans la distrib des avis pe (distrib local
    ou par défaut et le renvoie"""
    if template == "avis":
        pe_local_tmpl = pe_tools.PE_LOCAL_AVIS_LATEX_TMPL
        pe_default_tmpl = pe_tools.PE_DEFAULT_AVIS_LATEX_TMPL
    elif template == "footer":
        pe_local_tmpl = pe_tools.PE_LOCAL_FOOTER_TMPL
        pe_default_tmpl = pe_tools.PE_DEFAULT_FOOTER_TMPL

    if template in ["avis", "footer"]:
        # pas de preference pour le template: utilise fichier du serveur
        if os.path.exists(pe_local_tmpl):
            template_latex = get_code_latex_from_modele(pe_local_tmpl)
        else:
            if os.path.exists(pe_default_tmpl):
                template_latex = get_code_latex_from_modele(pe_default_tmpl)
            else:
                template_latex = ""  # fallback: avis vides
        return template_latex

# ----------------------------------------------------------------------------------------
def table_syntheseAnnotationPE(syntheseJury, tag_annotation_pe):
    """Génère un fichier excel synthétisant les annotations PE telles qu'inscrites dans les fiches de chaque étudiant"""
    sT = SeqGenTable()  # le fichier excel à générer

    # Les etudids des étudiants à afficher, triés par ordre alphabétiques de nom+prénom
    donnees_tries = sorted(
        [
            (etudid, syntheseJury[etudid]["nom"] + " " + syntheseJury[etudid]["prenom"])
            for etudid in syntheseJury.keys()
        ],
        key=lambda c: c[1],
    )
    etudids = [e[0] for e in donnees_tries]
    if not etudids:  # Si pas d'étudiants
        T = GenTable(
            columns_ids=["pas d'étudiants"],
            rows=[],
            titles={"pas d'étudiants": "pas d'étudiants"},
            html_sortable=True,
            xls_sheet_name="dut",
        )
        sT.add_genTable("Annotation PE", T)
        return sT

    # Si des étudiants
    maxParcours = max(
        [syntheseJury[etudid]["nbSemestres"] for etudid in etudids]
    )  # le nombre de semestre le + grand

    infos = ["civilite", "nom", "prenom", "age", "nbSemestres"]
    entete = ["etudid"]
    entete.extend(infos)
    entete.extend(["P%d" % i for i in range(1, maxParcours + 1)])  # ajout du parcours
    entete.append("Annotation PE")
    columns_ids = entete  # les id et les titres de colonnes sont ici identiques
    titles = {i: i for i in columns_ids}

    rows = []
    for (
        etudid
    ) in etudids:  # parcours des étudiants par ordre alphabétique des nom+prénom
        e = syntheseJury[etudid]
        # Les info générales:
        row = {
            "etudid": etudid,
            "civilite": e["civilite"],
            "nom": e["nom"],
            "prenom": e["prenom"],
            "age": e["age"],
            "nbSemestres": e["nbSemestres"],
        }
        # Les parcours: P1, P2, ...
        n = 1
        for p in e["parcours"]:
            row["P%d" % n] = p["titreannee"]
            n += 1

        # L'annotation PE
        annotationPE = get_annotation_PE(etudid, tag_annotation_pe=tag_annotation_pe)
        row["Annotation PE"] = annotationPE if annotationPE else ""
        rows.append(row)

    T = GenTable(
        columns_ids=columns_ids,
        rows=rows,
        titles=titles,
        html_sortable=True,
        xls_sheet_name="Annotation PE",
    )
    sT.add_genTable("Annotation PE", T)
    return sT


"""
Compare SV2P and SVG. [SV2P is trained elsewhere, but the predictions file we use
will be in a standard pickle file that we can load anywhere.]

For SV2P prediction format, see:
https://github.com/ryanhoque/cloth-visual-mpc/blob/sv2p/vismpc/scripts/predict.py

NOTE: https://stackoverflow.com/questions/55178229/importerror-cannot-import-name-structural-similarity-error
we are using the older way of doing ssim_metric, so I don't know if documentation is up to date.
Probably easier to do each channel separately, then average, as Dr. Denton does it.

Update Jan 24: iterate through different model snapshots for SVG.
"""
import os
import cv2
import sys
import pickle
import random
import argparse
from svg import utils
import numpy as np
from os.path import join
from collections import defaultdict
from skimage.measure import compare_psnr as psnr_metric
from skimage.measure import compare_ssim as ssim_metric
import PIL
from PIL import (Image, ImageDraw, ImageFont)
np.set_printoptions(precision=3)

pp = argparse.ArgumentParser()
pp.add_argument('--datatype', default='')
pp.add_argument('--svg_model', default='model_0050')
pp.add_argument('--results_tail', default='results_svg_3', help='see predict_svg.sh')
args = pp.parse_args()
assert args.datatype in ['fabric-random', 'fabric-01-2021'], args.datatype
assert args.results_tail in ['results_svg_1', 'results_svg_2', 'results_svg_3'], args.results_tail
# NOTE(daniel): we hard-code saving into frand_3 and fnew_3.

# ----------------------------------------------------------------------------- #
# ------------------------------- DATA FILES ---------------------------------- #
# ----------------------------------------------------------------------------- #
HORIZON = 5
HEAD_SV2P = '/home/seita/cloth-visual-mpc/logs/'
HEAD_SVG  = join('/home/seita/svg/', args.results_tail)

# fr = fabric_random. Always load even though we might be using the other data.
TAIL_SV2P_01_fr = 'demos-fabric-random-epis_400_COMBINED_PREDS_SV2P_01.pkl'
TAIL_SV2P_10_fr = 'demos-fabric-random-epis_400_COMBINED_PREDS_SV2P_10.pkl'
TAIL_SVG_fr    = f'demos-fabric-random-epis_400_COMBINED_PREDS_SVG-LP_{args.svg_model}.pkl'
SV2P_01_fr_pth  = join(HEAD_SV2P, TAIL_SV2P_01_fr)
SV2P_10_fr_pth  = join(HEAD_SV2P, TAIL_SV2P_10_fr)
SVG_fr_pth      = join(HEAD_SVG,  TAIL_SVG_fr)

# fnew = fabric_01-2021 (new data). Always load even though we might be using the other data.
TAIL_SV2P_10_fnew = 'demos-fabric-01-2021-epis_400_COMBINED_PREDS_SV2P_10.pkl'
TAIL_SVG_fnew    = f'demos-fabric-01-2021-epis_400_COMBINED_PREDS_SVG-LP_{args.svg_model}.pkl'
SV2P_10_fnew_pth  = join(HEAD_SV2P, TAIL_SV2P_10_fnew)
SVG_fnew_pth      = join(HEAD_SVG,  TAIL_SVG_fnew)

# These are _lists_, one dict per episode.
with open(SV2P_01_fr_pth, 'rb') as fh:
    SV2P_01_fr = pickle.load(fh)
with open(SV2P_10_fr_pth, 'rb') as fh:
    SV2P_10_fr = pickle.load(fh)
with open(SVG_fr_pth, 'rb') as fh:
    SVG_fr     = pickle.load(fh)

# fnew data
with open(SV2P_10_fnew_pth, 'rb') as fh:
    SV2P_10_fnew = pickle.load(fh)
with open(SVG_fnew_pth, 'rb') as fh:
    SVG_fnew     = pickle.load(fh)

assert len(SV2P_01_fr) == len(SV2P_10_fr) == len(SVG_fr) == len(SV2P_10_fnew) == len(SVG_fnew)
# ----------------------------------------------------------------------------- #
# ------------------------------- DATA FILES ---------------------------------- #
# ----------------------------------------------------------------------------- #

def depth_to_3ch(img, cutoff):
    """Useful to turn the background into black into the depth images.
    """
    w,h = img.shape
    new_img = np.zeros([w,h,3])
    img = img.flatten()
    img[img>cutoff] = 0.0
    img = img.reshape([w,h])
    for i in range(3):
        new_img[:,:,i] = img
    return new_img

def depth_scaled_to_255(img):
    assert np.max(img) > 0.0
    img = 255.0/np.max(img)*img
    img = np.array(img,dtype=np.uint8)
    for i in range(3):
        img[:,:,i] = cv2.equalizeHist(img[:,:,i])
    return img

def make_depth_img(img, cutoff=1000):
    # NOTE! Depth will look noisy due to domain randomization.
    img = np.squeeze(img)
    img = depth_to_3ch(img, cutoff)
    img = depth_scaled_to_255(img)
    return img

def draw_ssim(draw, IM_WIDTH, ws, hoff, groupname, ssims):
    # Draw some structural similarity values as a sanity check.
    fontsize = 10
    ssim_width = IM_WIDTH - (ws + 56)  # Make this LAST column
    text_0 = f'ssim {ssims[0]:0.2f}'
    text_1 = f'ssim {ssims[1]:0.2f}'
    text_2 = f'ssim {ssims[2]:0.2f}'
    text_3 = f'ssim {ssims[3]:0.2f}'
    text_4 = f'ssim {ssims[4]:0.2f}'
    off = 9
    if 'SVG' in groupname:
        groupname += args.svg_model
        groupname = groupname.replace('model_', '')
    draw.text((ssim_width, hoff+0*off), groupname, fill=(0,0,0,0))
    draw.text((ssim_width, hoff+1*off), text_0, fill=(0,0,0,0))
    draw.text((ssim_width, hoff+2*off), text_1, fill=(0,0,0,0))
    draw.text((ssim_width, hoff+3*off), text_2, fill=(0,0,0,0))
    draw.text((ssim_width, hoff+4*off), text_3, fill=(0,0,0,0))
    draw.text((ssim_width, hoff+5*off), text_4, fill=(0,0,0,0))

def eval_ssim(true_imgs, pred_imgs, channel='both'):
    # By default, sum SSIMs across all channels, and average.
    assert channel in ['color', 'depth', 'both'], channel
    assert true_imgs.shape == pred_imgs.shape == (HORIZON, 56, 56, 4), true_imgs.shape
    if channel == 'both':
        start, end = 0, 4
    elif channel == 'color':
        start, end = 0, 3
    elif channel == 'depth':
        start, end = 3, 4

    ssims = []
    for t in range(HORIZON):
        true_img = true_imgs[t]
        pred_img = pred_imgs[t]
        ssim = 0
        for c in range(start, end):
            X = true_img[:,:,c]
            Y = pred_img[:,:,c]
            if channel == 'depth':
                # Makes it 3 channel, but we only want the 1st (it's replicated).
                X = (make_depth_img(X))[:,:,0]
                Y = (make_depth_img(Y))[:,:,0]
            ssim += ssim_metric(X=X, Y=Y)
        ssim /= (end-start)
        assert -1 <= ssim <= 1, ssim
        ssims.append(ssim)
    return ssims

def save_images_get_ssim(datatype):
    """Collect a bunch of images of ground truth sequences followed by predictions.
    Also, might as well compute SSIM since everything is already nicely loaded.

    Ideally we can then paste these together for a figure.
    BTW the ground truths should be the same among the different models, since we
    tested on the same data, so we can do another sanity check. Same for actions.
    All of these should be with context of 1, and predicting the next 5 frames.

    BTW: to make a figure we can just take screenshots of appropriate segments?
    Might make it easier since I'm grouping everything anyway.
    """
    assert datatype in ['fabric-random', 'fabric-01-2021'], datatype
    nb_eps = len(SV2P_10_fr)

    # SSIM: dict, maps model type (e.g., 'svg') --> ssim matrix of dim (num_eps, HORIZON)
    # We can then average across columns to get SSIM for 1-ahead predictions, 2-ahead, etc.
    # Imagine these as a grid of (episodes) x (horizon). The item in the 'top left' is for
    # 1st episode, 1-ahead prediction. In that episode, we have `N` sets of predictions, so
    # these store the average of the k-ahead predictions, k in {1,2,3,4,5=HORIZON}. BUT,
    # because some episodes are too short, it's easier to append a bunch of HORIZON-length
    # lists and then call np.array() on it later.
    SSIM_C = {'sv2p_01': [], 'sv2p_10': [], 'svg': [],}
    SSIM_D = {'sv2p_01': [], 'sv2p_10': [], 'svg': [],}

    for ep in range(nb_eps):
        if ep % 25 == 0:
            print(f'checking predictions on episode {ep}')

        # Predictions have shape (N, H=HORIZON, 56, 56, 4), N is number of predictions.
        # I load a bunch of un-necessary stuff, just make sure 'pred' is correct!
        if datatype == 'fabric-random':
            gt_obs          = SV2P_01_fr[ep]['gt_obs']
            gt_obs_0        = SV2P_10_fr[ep]['gt_obs']
            gt_obs_1        = SVG_fr[ep]['gt_obs']
            gt_context      = SV2P_01_fr[ep]['contexts']
            gt_context_0    = SV2P_10_fr[ep]['contexts']
            gt_context_1    = SVG_fr[ep]['contexts']
            gt_act          = SV2P_01_fr[ep]['act']
            gt_act_0        = SV2P_10_fr[ep]['act']
            gt_act_1        = SVG_fr[ep]['act']
            pred_sv2p_01_fr = SV2P_01_fr[ep]['pred']
            pred_sv2p_10_fr = SV2P_10_fr[ep]['pred']
            pred_svg_fr     = SVG_fr[ep]['pred']
            # We load multiple versions partly as an extra sanity check.
            assert gt_obs.shape     == gt_obs_0.shape     == gt_obs_1.shape
            assert gt_context.shape == gt_context_0.shape == gt_context_1.shape
            assert gt_act.shape     == gt_act_0.shape     == gt_act_1.shape
            assert np.allclose(gt_act, gt_act_0)
            assert np.allclose(gt_act, gt_act_1)
        elif datatype == 'fabric-01-2021':
            gt_obs_0          = SV2P_10_fnew[ep]['gt_obs']
            gt_obs_1          = SVG_fnew[ep]['gt_obs']
            gt_context_0      = SV2P_10_fnew[ep]['contexts']
            gt_context_1      = SVG_fnew[ep]['contexts']
            gt_act_0          = SV2P_10_fnew[ep]['act']
            gt_act_1          = SVG_fnew[ep]['act']
            pred_sv2p_10_fnew = SV2P_10_fnew[ep]['pred']
            pred_svg_fnew     = SVG_fnew[ep]['pred']
            # We load multiple versions partly as an extra sanity check.
            assert gt_obs_0.shape     == gt_obs_1.shape
            assert gt_context_0.shape == gt_context_1.shape
            assert gt_act_0.shape     == gt_act_1.shape
            assert np.allclose(gt_act_0, gt_act_1)

        # Actually cannot use len(gt_act), that's not the number of actions in an episode
        # Oh, the N can be negative so if that's the case shape of gt_context is (0,)
        num_obs = len(gt_obs_0)
        N = num_obs - HORIZON
        assert N <= gt_context_0.shape[0], f'{N} vs {gt_context_0.shape}'

        # New image for each episode? Actually maybe consider splitting depth vs color?
        # num_steps is number of actions but also need to add padding
        ws = 5                          # whitespace padding
        IM_WIDTH  = (num_obs) * 56 + (num_obs+1) * ws

        # Actually add another column for some SSIM measurements.
        IM_WIDTH += (ws + 56)

        # What do we want in the image? 2 rows for GT color and depth, then 2 for each
        # prediction of SV2P (1 and 10 masks) on the RGB image? N = number of predictions.
        # Then do 1 more row after that just to give empty breathing room to make sure we
        # did this right ... (sanity checks). Update: adding 2 more for depth, and we also
        # have to add the SVG predictions! But don't have 1 mask case for one case.
        if datatype == 'fabric-random':
            nrows = 2 + (N * 6) + 1
        elif datatype == 'fabric-01-2021':
            nrows = 2 + (N * 4) + 1
        nrows = max(3, nrows)  # handle negative N
        IM_HEIGHT = (nrows+1)*ws + (nrows*56)

        # Finally make this PIL Image.
        t_img = Image.new(mode='RGB', size=(IM_WIDTH,IM_HEIGHT), color=(255,255,255))
        draw = ImageDraw.Draw(t_img)

        # Ground truth sequence at the top
        for t in range(num_obs):
            _w = (t * 56) + ((t+1) * ws)
            _h = ws
            frame_rgb = gt_obs_0[t, :, :, :3]
            frame_d   = gt_obs_0[t, :, :, 3:]
            frame_d   = make_depth_img(frame_d)
            t_img.paste(PIL.Image.fromarray(frame_rgb), (_w, _h)          )
            t_img.paste(PIL.Image.fromarray(frame_d),   (_w, _h + 56 + ws))

        # Iterate through all PREDICTIONS, adding them sequentially. Update: actually
        # we have to check to see if the episode has enough length at all. If an episode
        # has under 5 actions then we could not do prediction on it.
        row = 2
        hoff = 0

        # SSIM FOR THIS EPISODE ONLY.
        ep_ssims_sv2p_01_c = []
        ep_ssims_sv2p_10_c = []
        ep_ssims_svg_c     = []
        ep_ssims_sv2p_01_d = []
        ep_ssims_sv2p_10_d = []
        ep_ssims_svg_d     = []

        for t in range(0, N):
            # NOTE: we can reorder the way we lay out the rows (e.g., I do SV2P then SVG)
            # if we want, just make sure `row` gets incremented / decremented as needed.
            if datatype == 'fabric-random':
                sv2p_01   = pred_sv2p_01_fr[t,:,:,:,:]  # (H,56,56,4)
                sv2p_10   = pred_sv2p_10_fr[t,:,:,:,:]  # (H,56,56,4)
                svg       = pred_svg_fr[t,:,:,:,:]      # (H,56,56,4)
                context   = gt_context[t,:,:,:]
                context_0 = gt_context_0[t,:,:,:]
                context_1 = gt_context_1[t,:,:,:]
            elif datatype == 'fabric-01-2021':
                sv2p_10   = pred_sv2p_10_fnew[t,:,:,:,:]  # (H,56,56,4)
                svg       = pred_svg_fnew[t,:,:,:,:]      # (H,56,56,4)
                context_0 = gt_context_0[t,:,:,:]
                context_1 = gt_context_1[t,:,:,:]
            # Now we know we can use `sv2p_10` and `svg`, and `sv2p_01` for the old data.
            # Here is also the ground truth for this full set of predictions.
            true_imgs = gt_obs_0[t:t+HORIZON, :, :, :]
            assert true_imgs.shape == sv2p_10.shape == svg.shape, true_imgs.shape

            # Starting width for this group, gets shifted for every batch of predictions.
            _w_start = ws + hoff

            # SV2P ONE MASK. Context, then predicted images.
            if datatype == 'fabric-random':
                img = PIL.Image.fromarray(context[:,:,:3])
                _h = (row+1)*ws + row*56
                t_img.paste(img, (_w_start, _h))
                for h in range(HORIZON):
                    img = PIL.Image.fromarray(sv2p_01[h, :, :, :3])
                    _w = (h+2)*ws + (h+1)*56 + hoff
                    t_img.paste(img, (_w, _h))
                ssims = eval_ssim(true_imgs=true_imgs, pred_imgs=sv2p_01, channel='color')
                draw_ssim(draw, IM_WIDTH, ws, hoff=_h, groupname='SV2P 01', ssims=ssims)
                ep_ssims_sv2p_01_c.append(ssims)
                row += 1

            # Next row, SV2P 10 MASK (Edit: putting row += 1 up earlier)
            img = PIL.Image.fromarray(context_0[:,:,:3])
            _h = (row+1)*ws + row*56
            t_img.paste(img, (_w_start, _h))
            for h in range(HORIZON):
                img = PIL.Image.fromarray(sv2p_10[h, :, :, :3])
                _w = (h+2)*ws + (h+1)*56 + hoff
                t_img.paste(img, (_w, _h))
            ssims = eval_ssim(true_imgs=true_imgs, pred_imgs=sv2p_10, channel='color')
            draw_ssim(draw, IM_WIDTH, ws, hoff=_h, groupname='SV2P 10', ssims=ssims)
            ep_ssims_sv2p_10_c.append(ssims)

            # Next row, SVG
            row += 1
            img = PIL.Image.fromarray(context_1[:,:,:3])
            _h = (row+1)*ws + row*56
            t_img.paste(img, (_w_start, _h))
            for h in range(HORIZON):
                img = PIL.Image.fromarray(svg[h, :, :, :3])
                _w = (h+2)*ws + (h+1)*56 + hoff
                t_img.paste(img, (_w, _h))
            ssims = eval_ssim(true_imgs=true_imgs, pred_imgs=svg, channel='color')
            draw_ssim(draw, IM_WIDTH, ws, hoff=_h, groupname='SVG', ssims=ssims)
            ep_ssims_svg_c.append(ssims)

            # SV2P ONE MASK, DEPTH.
            if datatype == 'fabric-random':
                row += 1
                img = PIL.Image.fromarray( make_depth_img(context[:,:,3:]) )
                _h = (row+1)*ws + row*56
                t_img.paste(img, (_w_start, _h))
                for h in range(HORIZON):
                    img = PIL.Image.fromarray( make_depth_img(sv2p_01[h, :, :, 3:]) )
                    _w = (h+2)*ws + (h+1)*56 + hoff
                    t_img.paste(img, (_w, _h))
                ssims = eval_ssim(true_imgs=true_imgs, pred_imgs=sv2p_01, channel='depth')
                draw_ssim(draw, IM_WIDTH, ws, hoff=_h, groupname='SV2P 01', ssims=ssims)
                ep_ssims_sv2p_01_d.append(ssims)

            # Next row, SV2P 10 MASK, DEPTH
            row += 1
            img = PIL.Image.fromarray( make_depth_img(context_0[:,:,3:]) )
            _h = (row+1)*ws + row*56
            t_img.paste(img, (_w_start, _h))
            for h in range(HORIZON):
                img = PIL.Image.fromarray( make_depth_img(sv2p_10[h, :, :, 3:]) )
                _w = (h+2)*ws + (h+1)*56 + hoff
                t_img.paste(img, (_w, _h))
            ssims = eval_ssim(true_imgs=true_imgs, pred_imgs=sv2p_10, channel='depth')
            draw_ssim(draw, IM_WIDTH, ws, hoff=_h, groupname='SV2P 10', ssims=ssims)
            ep_ssims_sv2p_10_d.append(ssims)

            # Next row, SVG, DEPTH
            row += 1
            img = PIL.Image.fromarray( make_depth_img(context_1[:,:,3:]) )
            _h = (row+1)*ws + row*56
            t_img.paste(img, (_w_start, _h))
            for h in range(HORIZON):
                img = PIL.Image.fromarray( make_depth_img(svg[h, :, :, 3:]) )
                _w = (h+2)*ws + (h+1)*56 + hoff
                t_img.paste(img, (_w, _h))
            ssims = eval_ssim(true_imgs=true_imgs, pred_imgs=svg, channel='depth')
            draw_ssim(draw, IM_WIDTH, ws, hoff=_h, groupname='SVG', ssims=ssims)
            ep_ssims_svg_d.append(ssims)

            # Prep for next row in next for loop.
            row += 1
            hoff += (ws + 56)

        # SSIMs if at least 1 prediction. Computed {1-ahead, ..., H-ahead} SSIMs for this episode.
        if N >= 1:
            ep_ssims_sv2p_01_c = np.array( ep_ssims_sv2p_01_c )
            ep_ssims_sv2p_10_c = np.array( ep_ssims_sv2p_10_c )
            ep_ssims_svg_c     = np.array( ep_ssims_svg_c     )
            ep_ssims_sv2p_01_d = np.array( ep_ssims_sv2p_01_d )
            ep_ssims_sv2p_10_d = np.array( ep_ssims_sv2p_10_d )
            ep_ssims_svg_d     = np.array( ep_ssims_svg_d     )
            assert ep_ssims_svg_c.shape == (num_obs - HORIZON, HORIZON), \
                    f'{num_obs}, {ep_ssims_svg_c.shape}'

            if datatype == 'fabric-random':
                ep_ssims_sv2p_01_c = np.mean(ep_ssims_sv2p_01_c, axis=0)
                ep_ssims_sv2p_01_d = np.mean(ep_ssims_sv2p_01_d, axis=0)
            ep_ssims_sv2p_10_c = np.mean(ep_ssims_sv2p_10_c, axis=0)
            ep_ssims_svg_c     = np.mean(ep_ssims_svg_c    , axis=0)
            ep_ssims_sv2p_10_d = np.mean(ep_ssims_sv2p_10_d, axis=0)
            ep_ssims_svg_d     = np.mean(ep_ssims_svg_d    , axis=0)

            SSIM_C['sv2p_01'].append( ep_ssims_sv2p_01_c )
            SSIM_C['sv2p_10'].append( ep_ssims_sv2p_10_c )
            SSIM_C['svg'    ].append( ep_ssims_svg_c     )
            SSIM_D['sv2p_01'].append( ep_ssims_sv2p_01_d )
            SSIM_D['sv2p_10'].append( ep_ssims_sv2p_10_d )
            SSIM_D['svg'    ].append( ep_ssims_svg_d     )

        # Save in BGR format, which means saving with OpenCV.
        estr = str(ep).zfill(3)
        sstr = str(num_obs).zfill(2)
        img_path = f'preds_{datatype}_ep_{estr}_obs_{sstr}_rgbd.png'
        if datatype == 'fabric-random':
            head_dir = join('results_frand_3', args.svg_model)
            os.makedirs(head_dir, exist_ok=True)
            img_path = join(head_dir, img_path)
        else:
            head_dir = join('results_fnew_3', args.svg_model)
            os.makedirs(head_dir, exist_ok=True)
            img_path = join(head_dir, img_path)
        #t_img.save(img_path)  # RGB, but we want BGR, hence do these two:
        t_img_np = np.array(t_img)
        cv2.imwrite(img_path, t_img_np)

    # Report SSIM metrics
    print(f'\nSome SSIM metrics for: {args}:\n')
    print('Length of lists: {} <= episodes {}'.format(len(SSIM_C['sv2p_10']), nb_eps))

    # Form numpy arrays.
    if datatype == 'fabric-random':
        SSIM_C['sv2p_01'] = np.array(SSIM_C['sv2p_01'])
        SSIM_D['sv2p_01'] = np.array(SSIM_D['sv2p_01'])
    SSIM_C['sv2p_10'] = np.array(SSIM_C['sv2p_10'])
    SSIM_C['svg'    ] = np.array(SSIM_C['svg'    ])
    SSIM_D['sv2p_10'] = np.array(SSIM_D['sv2p_10'])
    SSIM_D['svg'    ] = np.array(SSIM_D['svg'    ])

    # Print values. To make things easier to copy and paste, do color then depth.
    if datatype == 'fabric-random':
        print('SV2P01, C. {}'.format( np.mean(SSIM_C['sv2p_01'], axis=0)) )
    print('SV2P10, C. {}'.format( np.mean(SSIM_C['sv2p_10'], axis=0)) )
    print('SVG, C.    {}'.format( np.mean(SSIM_C['svg'],     axis=0)) )
    if datatype == 'fabric-random':
        print('SV2P01, D. {}'.format( np.mean(SSIM_D['sv2p_01'], axis=0)) )
    print('SV2P10, D. {}'.format( np.mean(SSIM_D['sv2p_10'], axis=0)) )
    print('SVG, D.    {}'.format( np.mean(SSIM_D['svg'],     axis=0)) )
    print('\nNote, shape SV2P, SVG: {}, {}'.format(SSIM_C['sv2p_10'].shape, SSIM_C['svg'].shape))

    print('\nNow standard deviations among the episodes:\n')
    if datatype == 'fabric-random':
        print('SV2P01, C. {}'.format( np.std(SSIM_C['sv2p_01'], axis=0)) )
    print('SV2P10, C. {}'.format( np.std(SSIM_C['sv2p_10'], axis=0)) )
    print('SVG, C.    {}'.format( np.std(SSIM_C['svg'],     axis=0)) )
    if datatype == 'fabric-random':
        print('SV2P01, D. {}'.format( np.std(SSIM_D['sv2p_01'], axis=0)) )
    print('SV2P10, D. {}'.format( np.std(SSIM_D['sv2p_10'], axis=0)) )
    print('SVG, D.    {}'.format( np.std(SSIM_D['svg'],     axis=0)) )
    print()

if __name__ == "__main__":
    save_images_get_ssim(datatype=args.datatype)


import sys
input = sys.stdin.readline
from collections import defaultdict

treedic = defaultdict(int)
totalNum = 0

while(True):
  name = input().rstrip()
  if(name):
    totalNum += 1
    treedic[name] += 1
  else:
    break

sortedtree = sorted(treedic.items(), key = lambda x:x[0])

for name, num in sortedtree:
  percentage = (num/totalNum)*100
  print(name, '%.4f' %percentage)


from __future__ import print_function

from sb1288 import rcv
from sb1288 import errors
from sb1288.ballot import Ballot  # this is probably not needed
from sb1288 import status
from sb1288 import validate

import sys
import json
import os.path

# A convenience method for using the rcv.Tabulation class

def tabulate(input_json='', output_json='', default_json=None):
  """
  Tabulate an RCV contest using JSON files for input and output

  Arguments
  ---------
  input_json
    A str name of a file or an opened file that is read to get a JSON
    specification of the tabulation to be performed.  If the value is an
    empty str, standard input is read.  If the value is None, nothing is
    read. The JSON specification should be a JSON object with names
    that correspond to the parameters of rcv.Tabulation() initialization.

    Additional names may be specified.  Some that are recognized
    include:

    description
      A description of the contest being tabulated.

    include
      An array of additional input JSON file names that are read.  The
      name / value pairs in included files are subject to being
      overridden by subsequent file names in the array of included file
      names and also, ultimately, contents of the input_json file.  Any
      include value from an included file is ignored.

  output_json
    A str name of a file or an opened file that is written to with a
    JSON specification of the tabulation results.  If the value is an
    empty str, results are written to standard output.  If the value is
    None, nothing is written.  The JSON specification of the tabulation
    result is a JSON object with the following names:

    elected
      An array of winners, corresponding to the first value returned by
      rcv.Tabulation().tabulate().

    status
      An array of status values, each expressed as an array,
      corresponding to the values of the second value returned by
      rcv.Tabulation().tabulate().  The status values are listed in the
      following order:  candidate, status, nbr_round, votes.  For STV,
      votes are expressed as a real number.

    tally
      An object of tally values, corresponding to the third value
      returned by rcv.Tabulation().tabulate().

    description
      A string value of the input description value, if a non-empty
      description value string was provided.  Otherwise, this name is
      not included in the JSON output.

  default_json
    A str name of a file or an opened file that is read to provide
    default values for input_json specification before that file is
    read.  If the value is an empty string, standard input is read.  If
    the value is None, no attempt to read defaults is made.  If both
    this value and the input_json value are empty strings, this value is
    treated as if it were None.  The include name is recognized from
    this file, but its value may be overridden by an include value
    specified in the input_json file.

  Returns
  -------
  A four-tuple consisting of the three values returned by the
  rcv.Tabulation().tabulate() function, and a dict of the input values
  built from input_json and default_json.

  Raises
  ------
  The same as the rcv.Tabulation().tabulate() method, plus other
  exceptions that might be related to accessing files to build the
  tabulation specification or to store its results.

  """
  tabulate_args, tabulation_spec = build_tabulate_args(
        input_json, default_json)
  try: description = tabulation_spec['description']
  except KeyError: description = None
  elected, status, tally = rcv.Tabulation(*tabulate_args).tabulate()
  json_str = results_to_json(elected, status, tally, description)
  write_file(output_json, s2u(json_str))
  return elected, status, tally, tabulation_spec

def results_to_json(elected, status, tally, description):
  """
  Convert tabulation results to a JSON string

  Arguments
  =========

  elected
    a set or sequence of elected candidates, as given by the first item
    in the return value of the rcv.Tabulation.tabulate() function.

  status
    a dict of sb1288.Status objects keyed by their candidates, as
    given by the second item in the return value of the
    rcv.Tabulation.tabulate() function.

  tally
    a dict of round-by-round vote tallies for each candidate or other
    tabulation category, as given by the third item in the return value
    of the rcv.Tabulation.tabulate() function.

  description
    a string, that is the value of the 'description' key, if it exists,
    from the dictionary of tabulation specification, otherwise None or
    False.
    in the return value of the rcv.Tabulation.tabulate() function.

  Returns
  =======
  a JSON string which represents the function arguments

  Raises
  ======
  May raise various exceptions if the function arguments are not of the
  anticipated structure.

  """
  description_str = ''
  if description is not None:
    description_str = '  "description": ' + json.dumps(description)
    description_str += ',\n'
  elected_str = '  "elected": ' + json.dumps(sorted(elected)) + ',\n'
  status_str = '  "status": [\n'
  for ix, (candidate, cstatus) in enumerate(sorted(status.items(),
        key=lambda item: get_tally_sort_key(item[0], status))):
    status_str += '    ' + json.dumps(cstatus.as_tuple(as_float=True))
    if ix < len(status) - 1:
      status_str += ','
    status_str += '\n'
  status_str += '  ],\n'
  tally_str = '  "tally": {\n'
  tally_sorted = sorted(tally.items(), key=lambda item:
        get_tally_sort_key(item[0], status))
  tally_sorted = [[code, [
        votes if type(votes) == int else float(str(votes))
        for votes in votes_by_round]]
        for code, votes_by_round in tally_sorted]
  for ix, (code, votes_by_round) in enumerate(tally_sorted):
    tally_str += '    "%s": ' % code + json.dumps(votes_by_round)
    if ix < len(tally) - 1:
      tally_str += ','
    tally_str += '\n'
  tally_str += '  }\n'
  json_str = '{\n' + description_str + elected_str
  json_str += status_str + tally_str + '}\n'
  return json_str

def get_tally_sort_key(code, status):
  """
  Get a tally sort key

  The sort key can be used to sort candidates and other tabulation
  categories, for example the status and tally collections returned by
  rcv.Tabulation().tabulate().

  The sort codes will sort candidates before other tabulation
  categories; elected candidates before defeated candidates; elected
  candidates by increasing round of election, then by decreasing votes;
  defeated candidates by decreasing round of election, then by
  decreasing votes; any remaining ties are broken by the sort order of
  candidate names and labels for other tabulation categories.

  Arguments
  =========
  code
    A string representing a candidate name or label of another
    tabulation category.

  status
    A dictionary of tabulation result statuses, as given by the second
    item of the return value from rcv.Tabulation().tabulate().

  Returns
  =======
  A sort key in the form of a tuple of integers and/or strings.

  """
  sort_key = tuple([9, code])
  if code in status:
    nbr_round = status[code].nbr_round
    votes = status[code].votes
    if status[code].status == 'elected':
      sort_key = (1, 1, nbr_round, -votes, code)
    else:
      sort_key = (1, 2, -nbr_round, -votes, code)
  else:
    sort_key = (2, code)
  # print('code =', code, '  sort_key =', sort_key)
  return sort_key

def build_tabulate_args(input_json, default_json):

  """
  From JSON files, build tabulation args and specification

  Arguments
  =========
  See the description of input_json and default_json for this module's
  tabulate() function for details.

  Returns
  =======
  Two values in a tuple:

    + A tuple of tabulation args that can be passed to
      rcv.Tabulation().tabulate()

    + A dict that should specify an RCV tabulation.  Additional
      key/values may be ignored or used for testing.

  Raises
  ======
  Various exceptions may be raised which are related to reading JSON
  files.

  """
  tabulation_spec = read_tabulation_spec(input_json, default_json)
  tabulation_spec['options'] = {u2s(name): u2s(value)
        for name, value in tabulation_spec['options'].items()}
  arg_ballots = tabulation_spec['ballots']
  if 'ballots_more' in tabulation_spec:
    arg_ballots = list(arg_ballots)
    arg_ballots.extend(list(tabulation_spec['ballots_more']))
  tabulate_args = (
        tabulation_spec['nbr_seats_to_fill'],
        tabulation_spec['candidates'],
        arg_ballots,
        tabulation_spec['max_ranking_levels'],
        tabulation_spec['tie_breaker'],
        tabulation_spec['options']
        )
  return tabulate_args, tabulation_spec

def write_file(file_name, text):
  """
  Write text to a file
  """
  if file_name == '':
    sys.stdout.write(text)
  elif type(file_name) != str and hasattr(file_name, 'write'):
    file_name.write(text)
  elif file_name is None:
    pass
  else:
    with open(file_name, 'w') as otfile:
      otfile.write(text)

def read_file(file_name):
  """
  Read text from a file and return as a list of strings
  """
  if file_name == '':
    text = sys.stdin.read()
  elif type(file_name) != str and hasattr(file_name, 'read'):
    text = file_name.read()
  elif file_name is None:
    text = ''
  else:
    with open(file_name, 'r') as infile:
      text = infile.read()
  return text

def read_json(file_name):
  """
  Read JSON data from a text file and return as a python value
  """
  text = read_file(file_name)
  json_result = json.loads(text)
  return json_result

def read_optional_json(file_name):
  """
  Read JSON data if the file exists
  """
  result = {}
  if (file_name == '' or
        (type(file_name) is str and file_name != '' and
        os.path.isfile(file_name)) or
        (type(file_name) is not str and hasattr(file_name, 'read'))
        ):
    file_result = read_json(file_name)
    result.update(file_result)
  return result

def read_tabulation_spec(input_json, default_json):
  """
  Read tabulation specs from primary and default JSON text files

  The order of building test specs, with later values overriding earlier
  values, is:

    * hard-coded default values
    * values in the default JSON file
    * values from the include files in the order listed
    * values from the primary JSON input file

  However, a list include files is only taken from the primary JSON
  file.

  """
  tabulation_spec = {
        'description': 'Test description not provided.',
        'nbr_seats_to_fill': 0,
        'candidates': '',
        'ballots': '',
        'max_ranking_levels': 0,
        'tie_breaker': '',
        'options': {},
        'elected': '',
        'status_codes': [],
        'tally': {},
        }
  if (default_json is not None and
        not (default_json == '' and input_input_json == '')):
    default_spec = read_optional_json(default_json)
    tabulation_spec.update(default_spec)
  primary_spec = read_json(input_json)
  try: include_list = primary_spec['include']
  except KeyError: include_list = []
  for include_input_json in include_list:
    include_spec = read_json(include_input_json)
    tabulation_spec.update(include_spec)
  tabulation_spec.update(primary_spec)
  return tabulation_spec

def s2u(value):
  """conditionally, only for Python 2.x, convert from str to unicode"""
  if sys.version_info[0] == 2 and type(value) == str:
    value = value.decode('ascii')
  return value

def u2s(value):
  """conditionally, only for Python 2.x, convert from unicode to str"""
  if sys.version_info[0] == 2 and type(value) == unicode:
    value = value.encode('ascii')
  return value

if __name__ == '__main__':
  if len(sys.argv) > 2:
    tabulate(sys.argv[1], sys.argv[2])
  elif len(sys.argv) > 1:
    tabulate(sys.argv[1], '')


import control
import random
import adventure
import settings
import menu
import character
import cast
from move_types import ATK, DEF, FIN
from control import takeover
from core import term

VIEW_UPPER = (0,0,settings.BAT_WIDTH, settings.VIEW_HEIGHT//2)
VIEW_LOWER = (0,0+settings.VIEW_HEIGHT//2,
        settings.BAT_WIDTH, settings.VIEW_HEIGHT//2)
VIEW_LEFT = (0,0,settings.BAT_WIDTH//2, settings.VIEW_HEIGHT)
VIEW_RIGHT = (0+settings.BAT_WIDTH//2,0,settings.BAT_WIDTH//2,
        settings.VIEW_HEIGHT)

class BattleRecord:
    def __init__(self):
        self.koed = False
        self.num_kos = 0
        self.lost = False
        self.forefeit = False
        self.damage_dealt = 0
        self.damage_taken = 0
        self.participated = True

def calc_damage(mon, atk, defns, state):
    damage = 2
    if state.mon_type in atk.strong_vs:
        damage += 2
    if state.mon_type in atk.resisted_by:
        damage -= 2
    first_def = True
    if defns:
        for def_type in defns.strong_vs:
            if atk.type1 == def_type or atk.type2 == def_type:
                if first_def:
                    damage -= 2
                    first_def = False
                else:
                    damage -= 1
    if damage > 6:
        damage = 6
    if damage < 0:
        damage = 0
    return damage
damage_descriptors = [
        ("nothing",),
        ("decent",),
        ("strong",),
        ("powerful",),
        ("mighty",),
        ("devastating",),
        ("fearsome",),
        ]
class Battle:
    def __init__(self, opposing_team):
        self.opp = opposing_team
        self.current_opp = opposing_team[0]
        available_mons = [m for m in adventure.current.party if m.can_battle]
        self.current_mon = available_mons[0]
        self.player_actions = []
        self.opp_actions = []
        self.player_tag = None
        self.opp_tag = None
        for mon in adventure.current.party + self.opp:
            mon.battle_record = BattleRecord()
    def show_message(self, lines):
        msgbox = menu.MessageBox(lines, bg = self)
        control.takeover(msgbox, clear = False)
    def update(self, key):
        self.player_actions = []
        self.opp_actions = []
        actions_picked = 0
        action_menu = menu.FloatingMenu(0,settings.VIEW_HEIGHT-4,[
            ("Attack", "atk"),
            ("Defend", "def"),
            ("Finish", "fin"),
            ("Tag-out","tag"),
            ("Forfeit","for"),
            ], bg = self, title = "Action 1")
        cancel_option = [("Cancel", "x")]
        mon = self.current_mon
        while actions_picked < 2:
            action_menu.title = f"Action {1+actions_picked}"
            action = takeover(action_menu)
            if action == "atk":
                options =  list([(move.name, move) for move in mon.attacks])
                options += cancel_option
                info = []
                for move in mon.attacks:
                    t1 = move.type1.name
                    t2 = move.type2.name
                    strong = list(set([typ.name for typ in move.strong_vs]))
                    resist = list(set([typ.name for typ in move.resisted_by]))
                    move_info = [f"{t1} / {t2}",
                            f"Strong vs: {', '.join(strong)}",
                            f"Resisted by: {', '.join(resist)}"]
                    info.append(move_info)
                info.append("")
                move_menu = menu.FloatingMenu(0, settings.VIEW_HEIGHT,
                        options, bg = self, item_info = info)
                move = takeover(move_menu)
                if move != "x":
                    self.player_actions.append(move)
                    actions_picked += 1
                    action_menu.remove(("Attack", "atk"))
            elif action == "def":
                options =  list([(move.name, move) for move in mon.defences])
                options += cancel_option
                info = []
                for move in mon.defences:
                    t1 = move.type1.name
                    t2 = move.type2.name
                    strong = list(set([typ.name for typ in move.strong_vs]))
                    resist = list(set([typ.name for typ in move.resisted_by]))
                    move_info = [f"{t1} / {t2}",
                            f"Strong vs: {', '.join(strong)}"]
                    info.append(move_info)
                info.append("")
                move_menu = menu.FloatingMenu(0, settings.VIEW_HEIGHT,
                        options, bg = self, item_info = info)
                move = takeover(move_menu)
                if move != "x":
                    self.player_actions.append(move)
                    actions_picked += 1
                    action_menu.remove(("Defend", "def"))
            elif action == "fin":
                options =  list([(move.name, move) for move in mon.finishers])
                options += cancel_option
                info = []
                for move in mon.finishers:
                    t1 = move.type1.name
                    t2 = move.type2.name
                    strong = list(set([typ.name for typ in move.strong_vs]))
                    resist = list(set([typ.name for typ in move.resisted_by]))
                    move_info = [f"{t1} / {t2}",
                            f"Strong vs: {', '.join(strong)}",
                            f"Resisted by: {', '.join(resist)}"]
                    info.append(move_info)
                info.append("")
                move_menu = menu.FloatingMenu(0, settings.VIEW_HEIGHT,
                        options, bg = self, item_info = info)
                move = takeover(move_menu)
                if move != "x":
                    self.player_actions.append(move)
                    actions_picked += 1
                    action_menu.remove(("Finish", "fin"))
            elif action == "tag":
                options =  list([(teammate.name, teammate)
                    for teammate in adventure.current.party
                    if teammate != self.current_mon and teammate.can_battle])
                options += cancel_option
                move_menu = menu.FloatingMenu(0, settings.VIEW_HEIGHT-1,
                        options, bg = self)
                move = takeover(move_menu)
                if move != "x":
                    self.player_tag = takeover(move_menu)
                    actions_picked += 1
                    action_menu.remove(("Tag-out", "tag"))
            elif action == "for":
                options = [
                        ("Forfeit", "yes"),
                        ("Actually, maybe not", "x")
                        ]
                move_menu = menu.FloatingMenu(0, settings.VIEW_HEIGHT-1,
                        options, bg = self)
                move = takeover(move_menu)
                if move != "x":
                    self.show_message("You forfeit the battle!")
                    for mon in adventure.current.party:
                        mon.battle_record.loss = True
                        mon.battle_record.forfeit = True
                        mon.handle_battle_result()
                    return "LOSS"
        #OPP decision making (lol)
        tag_options =  list([mon
            for mon in self.opp
            if mon != self.current_opp and mon.can_battle])
        if random.random() < 0.05 and len(tag_options) > 0:
            self.opp_actions.append(random.choice(self.current_opp.attacks))
            self.opp_tag = random.choice(tag_options)
        elif self.current_mon.body.current_state <= 2 and random.random()< 0.3:
            self.opp_actions.append(random.choice(self.current_opp.finishers))
            self.opp_actions.append(random.choice(self.current_opp.attacks))
        elif self.current_mon.body.current_state <= 2:
            self.opp_actions.append(random.choice(self.current_opp.defences))
            self.opp_actions.append(random.choice(self.current_opp.finishers))
        else:
            self.opp_actions.append(random.choice(self.current_opp.attacks))
            self.opp_actions.append(random.choice(self.current_opp.defences))
        # ATK/FIN phase
        if self.current_mon.speed >= self.current_opp.speed:
            self.do_player_attack()
            self.do_opp_attack()
            self.do_fin(self.current_mon, self.player_actions,
                    self.current_opp, self.opp_actions)
            self.do_fin(self.current_opp, self.opp_actions,
                    self.current_mon, self.player_actions)
        else:
            self.do_opp_attack()
            self.do_player_attack()
            self.do_fin(self.current_opp, self.opp_actions,
                    self.current_mon, self.player_actions)
            self.do_fin(self.current_mon, self.player_actions,
                    self.current_opp, self.opp_actions)
        # TAG phase
        if self.player_tag:
            self.show_message(f"{self.current_mon.name} taggged out for "
                    f"{self.player_tag.name}")
            self.current_mon = self.player_tag
            self.player_tag = None
        if self.opp_tag:
            self.show_message(f"{self.current_opp.name} taggged out for "
                    f"{self.opp_tag.name}")
            self.current_opp = self.opp_tag
            self.opp_tag = None
        # KO swap and victory/loss phase
        if not self.current_mon.can_battle:
            self.show_message(f"{self.current_mon.name} can no longer battle.")
            options =  list([(teammate.name, teammate)
                for teammate in adventure.current.party
                if teammate != self.current_mon and teammate.can_battle])
            if len(options) == 0:
                self.show_message("The last member fell. You lost the fight.")
                return "LOSS"
            else:
                move_menu = menu.FloatingMenu(0, settings.VIEW_HEIGHT-1,
                        options, bg = self)
                self.player_tag = takeover(move_menu)
                self.show_message(f"{self.current_mon.name} taggged out for "
                        f"{self.player_tag.name}")
                self.current_mon = self.player_tag
                self.player_tag = None
        if not self.current_opp.can_battle:
            self.show_message(f"{self.current_opp.name} can no longer battle.")
            options =  list([teammate for teammate in self.opp
                if teammate != self.current_opp and teammate.can_battle])
            if len(options) == 0:
                self.show_message("The dust clears, and you are victorious.")
                for mon in adventure.current.party:
                    mon.battle_result()
                return "WIN"
            else:
                self.current_opp = random.choice(options)
                self.show_message(f"{self.current_opp.name} stood up to fight.")
    def do_fin(self, actor, actions, defender, defns):
        if not actor.can_battle:
            return
        def_move = None
        for action in defns:
            if action.move_type.action == DEF:
                def_move = action
        for action in actions:
            if action.move_type.action == ATK:
                def_move = None
        fin_move = None
        for action in actions:
            if action.move_type.action == FIN:
                fin_move = action
        if fin_move == None:
            return
        self.show_message(f"{actor.name} used {fin_move.name}")
        hit_state = None
        for state in defender.states:
            if state.mon_type in fin_move.strong_vs:
                self.show_message(f"{fin_move.name} pierces "
                    f"{defender.name}'s {state.name}!")
            elif state.current_state != 1:
                hit_state = state
                break
        if hit_state == None:
            hit_state = defender.body
        else:
            self.show_message(f"{defender.name}'s {hit_state.name}"
                    " protects it.")
            return
        damage = calc_damage(defender, fin_move, def_move, hit_state)
        if damage > hit_state.current_state:
            ko_verb = random.choice(fin_move.move_type.verbs)
            self.show_message(f"{actor.name} {ko_verb} {defender.name}")
            defender.can_battle = False
            defender.battle_record.koed = True
            actor.battle_record.num_kos += 1
        else:
            self.show_message("The attack is not enough to finish "+
                    defender.name)
    def do_player_attack(self):
        opp_def = None
        for action in self.opp_actions:
            if action.move_type.action == DEF:
                opp_def = action
        player_atk = None
        for action in self.player_actions:
            if action.move_type.action == ATK:
                player_atk = action
        if player_atk == None:
            return
        self.do_attack(self.current_mon, player_atk, self.current_opp, opp_def)
    def do_attack(self, attacker, attack, defender, defence):
        if not attacker.can_battle:
            return
        hit_state = None
        for state in defender.states:
            if state.current_state != 1:
                hit_state = state
                break
        if hit_state == None:
            hit_state = defender.body
        damage = calc_damage(defender, attack, defence, hit_state)
        self.show_message(f"{attacker.name} "
                f"{random.choice(attack.move_type.verbs)} "
                f"{defender.name} with {attack.name}.")
        if damage > 0:
            if defence:
                self.show_message(f"{defender.name}"
                        f" tried to defend with {defence.name}.")
            self.show_message("It's a "
                    f"{random.choice(damage_descriptors[damage])} hit!")
            hit_state.do_damage(damage)
            self.show_message(f"{defender.name}'s {hit_state.name} "
                    f"is {hit_state.state_descriptor()}.")
            attacker.battle_record.damage_dealt += damage
            defender.battle_record.damage_taken += damage
        elif defence == None:
            self.show_message(f"{defender.name}'s {hit_state.name}"
                    " resisted the attack")
        else:
            self.show_message(f"{defender.name}'s {defence.name} "
                    f"{random.choice(defence.move_type.verbs)} the attack.")
    def do_opp_attack(self):
        if not self.current_opp.can_battle:
            return
        player_def = None
        for action in self.player_actions:
            if action.move_type.action == DEF:
                player_def = action
        opp_atk = None
        for action in self.opp_actions:
            if action.move_type.action == ATK:
                opp_atk = action
        if opp_atk == None:
            return
        self.do_attack(self.current_opp, opp_atk, self.current_mon, player_def)
    def render(self):
        player_info = [
                self.current_mon.name,
                self.current_mon.species.description,
                f"{self.current_mon.type1.name}  {self.current_mon.type2.name}",
                "",
                self.current_mon.body.description()
        ] + list([state.description() for state in self.current_mon.states])
        menu.draw_textbox(VIEW_LEFT, player_info)
        opp_info = [
                self.current_opp.name,
                self.current_opp.species.description,
                f"{self.current_opp.type1.name} {self.current_opp.type2.name}",
                "",
                self.current_opp.body.description()
        ] + list([state.description() for state in self.current_opp.states])
        menu.draw_textbox(VIEW_RIGHT, opp_info)

class BattleTeam:
    def __init__(self):
        self.members = []

class BattleLeader(character.Character):
    def __init__(self, x, y, mon, team):
        super().__init__(x, y)
        self.mon = mon
        self.name = self.mon.name
        self.symbol = self.name[0]
        self.color = "red"
        self.team = team
        self.message = (f"{self.mon.name} :"
                f"{self.team.members[0].mon.name}! Let's rock!")
        self.team.members.append(self)
        self.battled = False
    def interact(self, scene):
        if not self.battled:
            scene.show_message("Up for a battle?")
            ques = menu.FloatingMenu(
                    0, settings.VIEW_HEIGHT -4,
                    [
                        ("Yeah", True),
                        ("Nah", False),
                        ("Yeah, nah.", False)],
                    bg = scene)
            up_for_it = takeover(ques)
            if up_for_it:
                scene.show_message(self.message)
                mons = list([teammate.mon for teammate in self.team.members])
                result = takeover(Battle(mons))
                if result != "LOSS":
                    scene.show_message("Nice fighting!")
                    self.battled = True
                else:
                    handle_loss(scene)
            else:
                scene.show_message("Maybe another time.")
        else:
            scene.show_message("You're pretty strong!")

def handle_loss(scene):
    revive_scene, revive_position = adventure.current.revive_point
    x,y = revive_position
    for mon in adventure.current.party:
        mon.revive()
        if mon.body.current_state == 1:
            mon.body.current_state = 2
    revive_scene.hero = scene.hero
    revive_scene.hero.x = x
    revive_scene.hero.y = y
    adventure.current.scene = revive_scene
    revive_scene.show_message("You don't remember much after the loss, "
            "but somehow you find yourself back in a familiar place.")

class BattleMember(character.Character):
    def __init__(self, x, y, mon, team):
        super().__init__(x, y)
        self.mon = mon
        self.name = self.mon.name
        self.symbol = self.name[0]
        self.color = "green"
        self.team = team
        openings = ["I'm proud of my ",
                "Can you get past my ",
                "My ultimate technique is ",
                "I've mastered ",
                "My teammates respect me for my "]
        move = random.choice(self.mon.defences+self.mon.attacks+
                self.mon.finishers)
        self.message = (f"{self.mon.name} : {random.choice(openings)}"
            f"{move.name}!")
        self.team.members.append(self)
    def interact(self, scene):
        scene.show_message(self.message)

class Wanderer(character.Character):
    def __init__(self, x, y, mon):
        super().__init__(x, y)
        self.mon = mon
        self.name = self.mon.name
        self.symbol = self.name[0]
        self.color = "orange"
        self.message = f"{self.mon.name}: Let's see what you've got!"
        self.battled = False
    def interact(self, scene):
        if not self.battled:
            scene.show_message("Up for a battle?")
            ques = menu.FloatingMenu(
                    0, settings.VIEW_HEIGHT -4,
                    [
                        ("Yeah", True),
                        ("Nah", False),
                        ("Yeah, nah.", False)],
                    bg = scene)
            up_for_it = takeover(ques)
            if up_for_it:
                scene.show_message(self.message)
                result = takeover(Battle([self.mon]))
                if result != "LOSS":
                    scene.show_message("Nice fighting!")
                    self.battled = True
                    if len(adventure.current.party) < adventure.party_limit:
                        scene.show_message("How about joining forces?")
                        ques = menu.FloatingMenu(
                                0, settings.VIEW_HEIGHT -4,
                                [
                                    ("Definitely.", True),
                                    ("Hell yeah.", True),
                                    ("I dunno...", False),
                                    ("I'll think about it.", False)],
                                bg = scene)
                        up_for_it = takeover(ques)
                        if up_for_it:
                            scene.show_message("Right on!")
                            self.mon.body.heal(2)
                            scene.foreground.remove(self)
                            m = cast.PartyMember(self.x, self.y, self.mon)
                            scene.foreground.append(m)
                            adventure.current.map_party.append(m)
                            adventure.current.party.append(self.mon)
                else:
                    handle_loss(scene)
            else:
                scene.show_message("Maybe another time.")
        else:
            pass

if __name__ == "__main__":
    import mon_species
    import mons
    from core import term
    opponents = []
    for i in range(6):
        opponents.append(mons.Mon(mon_species.Species()))
    battle = Battle(opponents)
    with term.fullscreen(), term.cbreak(), term.hidden_cursor(), term.keypad():
        takeover(battle)