import matplotlib.pyplot as plt
import cv2
import os, glob
import numpy as np
import matplotlib._png as png
from moviepy.editor import VideoFileClip

#%matplotlib inline
#%config InlineBackend.figure_format = 'retina'

def show_images(images, cmap=None):
    cols = 2
    rows = (len(images) + 1) // cols

    plt.figure(figsize=(10, 11))
    for i, image in enumerate(images):
        plt.subplot(rows, cols, i + 1)
        # use gray scale color map if there is only one channel
        cmap = 'gray' if len(image.shape) == 2 else cmap
        plt.imshow(image, cmap=cmap)
        plt.xticks([])
        plt.yticks([])
    plt.tight_layout(pad=0, h_pad=0, w_pad=0)
    plt.show()

#test_images = [plt.imread(path) for path in glob.glob('test_images/*.jpg')]

#show_images(test_images)

# image is expected be in RGB color space
def select_rgb_white_yellow(image):
    # white color mask
    lower = np.uint8([200, 200, 200])
    upper = np.uint8([255, 255, 255])
    white_mask = cv2.inRange(image, lower, upper)
    # yellow color mask
    lower = np.uint8([190, 190,   0])
    upper = np.uint8([255, 255, 255])
    yellow_mask = cv2.inRange(image, lower, upper)
    # combine the mask
    mask = cv2.bitwise_or(white_mask, yellow_mask)
    masked = cv2.bitwise_and(image, image, mask = mask)
    return masked

#show_images(list(map(select_rgb_white_yellow, test_images)))

def convert_hsv(image):
    return cv2.cvtColor(image, cv2.COLOR_RGB2HSV)

#show_images(list(map(convert_hsv, test_images)))

def convert_hls(image):
    return cv2.cvtColor(image, cv2.COLOR_RGB2HLS)

#show_images(list(map(convert_hls, test_images)))

def select_white_yellow(image):
    converted = convert_hls(image)
    # white color mask
    lower = np.uint8([  0, 200,   0])
    upper = np.uint8([255, 255, 255])
    white_mask = cv2.inRange(converted, lower, upper)
    # yellow color mask
    lower = np.uint8([ 10,   0, 100])
    upper = np.uint8([ 40, 255, 255])
    yellow_mask = cv2.inRange(converted, lower, upper)
    # combine the mask
    mask = cv2.bitwise_or(white_mask, yellow_mask)
    return cv2.bitwise_and(image, image, mask = mask)

#mask detect only white not yellow

#white_yellow_images = list(map(select_white_yellow, test_images))

#show_images(white_yellow_images)

def convert_gray_scale(image):
    return cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)

def apply_smoothing(image, kernel_size=15):
    """
    kernel_size must be postivie and odd
    """
    return cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)

def detect_edges(image, low_threshold=50, high_threshold=150):
    return cv2.Canny(image, low_threshold, high_threshold)

def filter_region(image, vertices):
    """
    Create the mask using the vertices and apply it to the input image
    """
    mask = np.zeros_like(image)
    if len(mask.shape) == 2:
        cv2.fillPoly(mask, vertices, 255)
    else:
        cv2.fillPoly(mask, vertices, (255,) * mask.shape[2])  # in case, the input image has a channel dimension
    return cv2.bitwise_and(image, mask)

def select_region(image):
    """
    It keeps the region surrounded by the `vertices` (i.e. polygon).  Other area is set to 0 (black).
    """
    # first, define the polygon by vertices
    rows, cols = image.shape[:2]
    bottom_left = [cols * 0.1, rows * 0.95]
    top_left = [cols * 0.4, rows * 0.6]
    bottom_right = [cols * 0.9, rows * 0.95]
    top_right = [cols * 0.6, rows * 0.6]
    # the vertices are an array of polygons (i.e array of arrays) and the data type must be integer
    vertices = np.array([[bottom_left, top_left, top_right, bottom_right]], dtype=np.int32)
    return filter_region(image, vertices)

def hough_lines(image):
    """
    `image` should be the output of a Canny transform.

    Returns hough lines (not the image with lines)
    """
    return cv2.HoughLinesP(image, rho=1, theta=np.pi / 180, threshold=20, minLineLength=40, maxLineGap=20)

def draw_lines(image, lines, color=[255, 0, 0], thickness=2, make_copy=True):
    # the lines returned by cv2.HoughLinesP has the shape (-1, 1, 4)
    if make_copy:
        image = np.copy(image) # don't want to modify the original
    for line in lines:
        for x1,y1,x2,y2 in line:
            cv2.line(image, (x1, y1), (x2, y2), color, thickness)
    return image

def average_slope_intercept(lines):
    left_lines = []  # (slope, intercept)
    left_weights = []  # (length,)
    right_lines = []  # (slope, intercept)
    right_weights = []  # (length,)

    for line in lines:
        for x1, y1, x2, y2 in line:
            if x2 == x1:
                continue  # ignore a vertical line
            slope = (y2 - y1) / (x2 - x1)
            intercept = y1 - slope * x1
            length = np.sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2)
            if slope < 0:  # y is reversed in image
                left_lines.append((slope, intercept))
                left_weights.append((length))
            else:
                right_lines.append((slope, intercept))
                right_weights.append((length))

    # add more weight to longer lines
    left_lane = np.dot(left_weights, left_lines) / np.sum(left_weights) if len(left_weights) > 0 else None
    right_lane = np.dot(right_weights, right_lines) / np.sum(right_weights) if len(right_weights) > 0 else None

    return left_lane, right_lane  # (slope, intercept), (slope, intercept)

def make_line_points(y1, y2, line):
    """
    Convert a line represented in slope and intercept into pixel points
    """
    if line is None:
        return None

    slope, intercept = line

    # make sure everything is integer as cv2.line requires it
    x1 = int((y1 - intercept) / slope)
    x2 = int((y2 - intercept) / slope)
    y1 = int(y1)
    y2 = int(y2)

    return ((x1, y1), (x2, y2))

def lane_lines(image, lines):
    left_lane, right_lane = average_slope_intercept(lines)

    y1 = image.shape[0]  # bottom of the image
    y2 = y1 * 0.6  # slightly lower than the middle

    left_line = make_line_points(y1, y2, left_lane)
    right_line = make_line_points(y1, y2, right_lane)

    return left_line, right_line

def draw_lane_lines(image, lines, color=[255, 0, 0], thickness=20):
    # make a separate image to draw lines and combine with the orignal later
    line_image = np.zeros_like(image)
    for line in lines:
        if line is not None:
            cv2.line(line_image, *line, color, thickness)
    # image1 * α + image2 * β + λ
    # image1 and image2 must be the same shape.
    return cv2.addWeighted(image, 1.0, line_image, 0.95, 0.0)

def shadow(img):
    or_img = img
    #or_img = cv2.imread('./data_road/training/image_2/um_000007.png')

    # covert the BGR image to an YCbCr image
    y_cb_cr_img = cv2.cvtColor(or_img, cv2.COLOR_BGR2YCrCb)

    # copy the image to create a binary mask later
    binary_mask = np.copy(y_cb_cr_img)

    # get mean value of the pixels in Y plane
    y_mean = np.mean(cv2.split(y_cb_cr_img)[0])

    # get standard deviation of channel in Y plane
    y_std = np.std(cv2.split(y_cb_cr_img)[0])

    # classify pixels as shadow and non-shadow pixels
    for i in range(y_cb_cr_img.shape[0]):
        for j in range(y_cb_cr_img.shape[1]):

            if y_cb_cr_img[i, j, 0] < y_mean - (y_std / 3):
                # paint it white (shadow)
                binary_mask[i, j] = [255, 255, 255]
            else:
                # paint it black (non-shadow)
                binary_mask[i, j] = [0, 0, 0]

    # Using morphological operation
    # The misclassified pixels are
    # removed using dilation followed by erosion.
    kernel = np.ones((3, 3), np.uint8)
    erosion = cv2.erode(binary_mask, kernel, iterations=1)

    # sum of pixel intensities in the lit areas
    spi_la = 0

    # sum of pixel intensities in the shadow
    spi_s = 0

    # number of pixels in the lit areas
    n_la = 0

    # number of pixels in the shadow
    n_s = 0

    # get sum of pixel intensities in the lit areas
    # and sum of pixel intensities in the shadow
    for i in range(y_cb_cr_img.shape[0]):
        for j in range(y_cb_cr_img.shape[1]):
            if erosion[i, j, 0] == 0 and erosion[i, j, 1] == 0 and erosion[i, j, 2] == 0:
                spi_la = spi_la + y_cb_cr_img[i, j, 0]
                n_la += 1
            else:
                spi_s = spi_s + y_cb_cr_img[i, j, 0]
                n_s += 1

    # get the average pixel intensities in the lit areas
    average_ld = spi_la / n_la

    # get the average pixel intensities in the shadow
    average_le = spi_s / n_s

    # difference of the pixel intensities in the shadow and lit areas
    i_diff = average_ld - average_le

    # get the ratio between average shadow pixels and average lit pixels
    ratio_as_al = average_ld / average_le

    # added these difference
    for i in range(y_cb_cr_img.shape[0]):
        for j in range(y_cb_cr_img.shape[1]):
            if erosion[i, j, 0] == 255 and erosion[i, j, 1] == 255 and erosion[i, j, 2] == 255:
                y_cb_cr_img[i, j] = [y_cb_cr_img[i, j, 0] + i_diff, y_cb_cr_img[i, j, 1] + ratio_as_al,
                                     y_cb_cr_img[i, j, 2] + ratio_as_al]

    # covert the YCbCr image to the BGR image
    return cv2.cvtColor(y_cb_cr_img, cv2.COLOR_YCR_CB2BGR)
def thresholding(img):
    return cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)

if  __name__=="__main__":
    test_images = np.array([
        png.read_png_int('./data_road/training/image_2/um_000001.png'),
        png.read_png_int('./data_road/training/image_2/um_000002.png'),
        png.read_png_int('./data_road/training/image_2/um_000003.png'),
        png.read_png_int('./data_road/training/image_2/um_000004.png'),
        png.read_png_int('./data_road/training/image_2/um_000005.png'),
        png.read_png_int('./data_road/training/image_2/um_000006.png'),
    ])
    #show_images(test_images)
    white_yellow_images = list(map(select_white_yellow, test_images))
    #show_images(white_yellow_images)
    gray_images = list(map(convert_gray_scale, white_yellow_images))
    #show_images(gray_images)
    #gray_images = list(map(thresholding, gray_images))

    blurred_images = list(map(lambda image: apply_smoothing(image), gray_images))
    #show_images(blurred_images)
    edge_images = list(map(lambda image: detect_edges(image), blurred_images))
    #show_images(edge_images)
    roi_images = list(map(select_region, edge_images))
    #show_images(roi_images)
    list_of_lines = list(map(hough_lines, roi_images))
    lane_images = []
    for image, lines in zip(test_images, list_of_lines):
        lane_images.append(draw_lane_lines(image, lane_lines(image, lines)))
    show_images(lane_images)


import streamlit as st
import datetime
import requests
import json
import pandas as pd
import time

page = st.sidebar.selectbox('chose your page', ['users', 'checkin', 'checkout'])

if page == 'users':
    st.title('ユーザー登録画面')
    with st.form(key='user'):
        username: str = st.text_input('ユーザー名', max_chars=12)
        data = {
            'username': username
        }
        submit_button = st.form_submit_button(label='ユーザー登録')

    if submit_button:
        url = 'http://127.0.0.1:8000/users'
        res = requests.post(
            url,
            data=json.dumps(data)
        )
        if res.status_code == 200:
            st.success("ユーザー登録完了")
        st.json(res.json())

elif page == 'checkin':
    st.title('checkin画面')

    #ユーザー一覧取得
    url_users = 'http://127.0.0.1:8000/users'
    res = requests.get(url_users)
    users = res.json()

    #ユーザー名をキー、IDをバリュー
    users_name = {}
    for user in users:
        users_name[user['username']] = user['user_id']

    with st.form(key='checkin'):
        url_check = 'http://127.0.0.1:8000/checkin'
        url_users_delete = 'http://127.0.0.1:8000/users_delete'
        username: str = st.selectbox('名前', list(users_name.keys()))
        submit_button_users_delete = st.form_submit_button(label='ユーザー削除')
        submit_button_checkin = st.form_submit_button(label='checkin実行')
        submit_button_checkout = st.form_submit_button(label='checkout実行')

# users delete
    if submit_button_users_delete:
        data_users_delete = {'username': username}
        res = requests.post(
            url_users_delete,
            data=json.dumps(data_users_delete)
        )
        if res.status_code == 200:
            st.success(f"{username}の削除完了しました")
        st.json(res.json())

# checkin
    if submit_button_checkin:
        data_checkin = {'user_id': users_name[username],'status': 'checkin'}
        res = requests.post(
            url_check,
            data=json.dumps(data_checkin)
        )
        if res.status_code == 200:
            st.success(f"{username}のcheckin完了しました")
        #st.json(res.json())

        url_check_query = 'http://127.0.0.1:8000/checkin/'
        res = requests.get(f"{url_check_query}?user_id={users_name[username]}")
        records = res.json()
        df_records = pd.DataFrame(records)
        st.table(df_records)
# checkout
    if submit_button_checkout:
        data_checkout = {'user_id': users_name[username],'status': 'checkout'}
        res = requests.post(
            url_check,
            data=json.dumps(data_checkout)
        )
        if res.status_code == 200:
            st.success(f"{username}のcheckout完了しました。")
        #st.json(res.json())

        url_check_query = 'http://127.0.0.1:8000/checkin/'
        res = requests.get(f"{url_check_query}?user_id={users_name[username]}")
        records = res.json()
        df_records = pd.DataFrame(records)
        st.table(df_records)

elif page == 'bookings':
    st.title('会議室予約画面')

    #ユーザー一覧取得
    url_users = 'http://127.0.0.1:8000/users'
    res = requests.get(url_users)
    users = res.json()

    #ユーザー名をキー、IDをバリュー
    users_name = {}
    for user in users:
        users_name[user['username']] = user['user_id']

    #会議室一覧取得
    url_rooms = 'http://127.0.0.1:8000/rooms'
    res = requests.get(url_rooms)
    rooms = res.json()
    rooms_name = {}
    for room in rooms:
        rooms_name[room['room_name']] = {
            'room_id': room['room_id'],
            'capacity': room['capacity']
        }

    st.write('### 会議室一覧')
    df_rooms = pd.DataFrame(rooms)
    df_rooms.columns = ['会議室名', '定員', '会議室ID']
    st.table(df_rooms)

    url_bookings = 'http://127.0.0.1:8000/bookings'
    res = requests.get(url_bookings)
    bookings = res.json()
    df_bookings = pd.DataFrame(bookings)

    users_id = {}
    for user in users:
        users_id[user['user_id']] = user['username']

    rooms_id = {}
    for room in rooms:
        rooms_id[room['room_id']] = {
            'room_name': room['room_name'],
            'capacity': room['capacity'],
        }

    # IDを各値に変更
    to_username = lambda x: users_id[x]
    to_room_name = lambda x: rooms_id[x]['room_name']
    to_datetime = lambda x: datetime.datetime.fromisoformat(x).strftime('%Y/%m/%d %H:%M')
    #特定の列に適用
    df_bookings['user_id'] = df_bookings['user_id'].map(to_username)
    df_bookings['room_id'] = df_bookings['room_id'].map(to_room_name)
    df_bookings['start_datetime'] = df_bookings['start_datetime'].map(to_datetime)
    df_bookings['end_datetime'] = df_bookings['end_datetime'].map(to_datetime)

    df_bookings = df_bookings.rename(columns={
        'user_id': '予約者名',
        'room_id': '会議室名',
        'booked_num': '予約人数',
        'start_datetime': '開始時間',
        'end_datetime': '終了時間',
        'booking_id': '予約番号'
    })

    st.write('###　予約一覧')
    st.table(df_bookings)

    with st.form(key='booking'):
        #booking_id: int = random.randint(0, 10)
        username: str = st.selectbox('予約者名', list(users_name.keys()))
        room_name: str = st.selectbox('会議室名', list(rooms_name.keys()))
        booked_num: int = st.number_input('予約人数', step=1, min_value=1)
        date = st.date_input('日付を入力', min_value=datetime.date.today())
        start_time = st.time_input('開始時刻: ', value=datetime.time(hour=9, minute=0))
        end_time = st.time_input('終了時刻: ', value=datetime.time(hour=20, minute=0))
        submit_button = st.form_submit_button(label='予約登録')

    if submit_button:
        user_id: int = users_name[username]
        room_id: int = rooms_name[room_name]['room_id']
        capacity: int = rooms_name[room_name]['capacity']

        data = {
            'user_id': user_id,
            'room_id': room_id,
            'booked_num': booked_num,
            'start_datetime': datetime.datetime(
                year=date.year,
                month=date.month,
                day=date.day,
                hour=start_time.hour,
                minute=start_time.minute
            ).isoformat(),

            'end_datetime': datetime.datetime(
                year=date.year,
                month=date.month,
                day=date.day,
                hour=end_time.hour,
                minute=end_time.minute
            ).isoformat()
        }

        #定員以下の予約場合

        if booked_num <= capacity:
            url = 'http://127.0.0.1:8000/bookings'
            res = requests.post(
                url,
                data=json.dumps(data)
            )
            if res.status_code == 200:
                st.success('予約完了しました')
            st.json(res.json())

        else:
            st.error(f'{room_name}の定員は{capacity}名です')


import itertools
#taking input
k=int(input())
a=list(map(int,input().split()))
#generating prime numbers
soe=[True]*(100000)
for i in range(2,100000):
    if soe[i]==True:
        j=i+i
        while j<100000:
            soe[j]=False
            j+=i
#storing prime numbers whith in given input
p=[i for i in range(2,len(soe)) if soe[i]==True and i<=a[0]]
c=0
g=0
for j in range(1,len(p)+1):
    for i in itertools.combinations(p,j):
        if soe[sum(i)]:
            #print(sum(i))
            c+=1
        if max(i)<=a[1]:
            g+=1
print(c%(10**16),g)


#!/usr/bin/env python3

import sys
import re
import sqlite3
import codecs

season_length = 14

def __grab(term, lines):
    for line in lines:
        if term in line:
            return line

def __get_line(term, lines):
    num = 0
    for line in lines:
        num = num+1
        if term in line:
            return num

def __updatedb(dbPath, p):

    conn = sqlite3.connect(dbPath)

    #check if db file has the requisite table#
    new = True

    tables = conn.execute("SELECT name FROM sqlite_master WHERE type='table';")
    for tname in tables:
        if tname[0] == 'cdda_scores':
            new = False

    if new:
        conn.execute('''CREATE TABLE cdda_scores
                    (id integer primary key,
                    name text,
                    prof text,
                    days integer,
                    dist integer,
                    kills integer,
                    hshot integer,
                    dmg integer,
                    heal integer,
                    cause text,
                    last text,
                    fmsg text,
                    ver text,
                    created_at DATETIME DEFAULT CURRENT_TIMESTAMP);''')

    conn.execute('''INSERT INTO cdda_scores (name, prof, days, dist, kills, hshot, dmg, heal, cause, last, fmsg, ver, created_at)
                                values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);''',
                 (str(p["name"]), str(p["prof"]), int(p["days"]), int(p["dist"]), int(p["kills"]), int(p["hshot"]), int(p["dmg"]), int(p["heal"]), p["cause"], str(p["last"]), str(p["fmsg"]), str(p["ver"]), str(p["date"])))

    conn.commit()
    conn.close()

def parse_player(date, linelist):

    ## Get version ##
    ver = __grab("Cataclysm - Dark Days Ahead version", linelist)
    ver = ver.split(" ")[6].strip()

    ## Get name ##
    name = __grab("In memory of:", linelist)
    name = name.split(':')[1].strip()

    ## Get last words, if any ##
    last_words = __grab("Last words:", linelist)

    if last_words == None:
        cause = linelist[-1]
    else:
        last_words = last_words.split("|")[3].split(":")[1].strip()
        cause = linelist[len(linelist)-2]

    ## Get cause of death and check if suicide ##
    cause = __grab("was killed in", linelist)
    cause = cause.split(' ',1)[1].strip()

    suicide_check = __grab("committed suicide", linelist)

    if suicide_check != None:
       cause = re.sub('was killed', 'committed suicide', cause)

    ## Get profession ##
    prof = __grab("when the apocalypse began", linelist)
    prof = prof.split("when the apocalypse began")[0]

    ## Ignore "he/she was a " words
    temp = prof.split(" ")
    prof = ""

    for i in range(3,len(temp)):
        prof = prof + " " + temp[i]
    prof=prof.strip()

    ## Find number of kills ##
    kills = __grab("Total kills", linelist)

    if kills != None:
       kills = kills.split(':')[1].strip()
       kills = int(kills)
    else:
        kills = 0

    ## Find days survived ##
    day_lines = linelist[-1]

    season = day_lines.split(",")[1].strip()
    season = season[:-2]
    num_list = re.findall("\d+", day_lines)

    days = (int(num_list[0])-1)*56 + int(num_list[1])

    if season == "Summer":
        days += 14
    elif season == "Autumn":
        days += 28
    elif season == "Winter":
        days += 42

    ## Get Distance Traveled ##
    dist = __grab("Distance walked", linelist).split(":")[1].strip()
    dist = dist.split(" ")[0]

    ## Get Damage taken ##
    dmg = __grab("Damage taken", linelist).split(":")[1].strip()
    dmg = dmg.split(" ")[0]

    ## Get Damage healed ##
    heal = __grab("Damage healed", linelist).split(":")[1].strip()
    heal = heal.split(" ")[0]

    ## Get Headshots ##
    hshot = __grab("Headshots", linelist).split(":")[1].strip()
    hshot = hshot.split(" ")[0]

    ## Get final message ##
    num= __get_line("Kills", linelist)
    final_message = linelist[num-3].strip()

    ## Get final message given to player, there are probably more things I need to filter out. ##
    if final_message.count('Unknown') == 0 and final_message.count('Safe mode') == 0:
        final_message = final_message.split(' ',1)[1]
        final_message = final_message[:-1]
        final_message = re.sub('AM|PM', '', final_message).strip()

         #__updatedb(self, name, prof, days, dist, kills, hshot, dmg, heal)
    player = {
        "name" : name,
        "prof" : prof,
        "days" : days,
        "dist" : dist,
        "kills": kills,
        "hshot": hshot,
        "dmg": dmg,
        "heal": heal,
        "cause" : cause,
        "last" : last_words,
        "fmsg" : final_message,
        "ver"  : ver,
        "date" : date
    }

    return player

#if __name__ == "__main__":
def autopsy_cdda(filePath, dbPath):

    with codecs.open(filePath, 'r', 'utf-8') as autopsy:
        linelist = autopsy.readlines()

    rawDate = filePath.split("-")[1:7]
    rawDate[5]=rawDate[5][:-4]

    date = rawDate[0]+ "-" + rawDate[1] + "-" + rawDate[2] \
         + " " + rawDate[3] + ":" + rawDate[4] + ":" + rawDate[5]

    p = parse_player(date, linelist)

    __updatedb(dbPath, p)

    return


from ast import main
import collections
from statistics import mean
import numpy as np
import random
from collections import defaultdict
import matplotlib.pyplot as plt

class MonteCarlo():
    def __init__(self, gamma):
        self.actions = [(-1,0), (0,1), (1,0), (0,-1)] # up, right, down, left
        self.arrows = ["↑", "→","↓", "←"]
        self.gamma = gamma
        self.v = np.array([[0.0 for j in range(5)] for i in range(5)])
        # self.policy = np.array([["" for j in range(5)] for i in range(5)])
        self.policy = np.array([[(0, 1) for j in range(5)] for i in range(5)])
        # self.states = np.array([[(i, j) for j in range(5)] for i in range(5)])
        self.states = [(i, j) for j in range(5) for i in range(5)]
        self.pi_star = [[(0, 1), (0, 1), (0, 1), (1, 0), (1, 0)],
                   [(0, 1), (0, 1), (0, 1), (1, 0), (1, 0)],
                   [(-1, 0), (-1, 0), (-1, 0), (1, 0), (1, 0)],
                   [(-1, 0), (-1, 0), (-1, 0), (1, 0), (1, 0)],
                   [(-1, 0), (-1, 0), (0, 1), (0, 1), (-1, 0)]]
        self.v_star = [[4.0187,4.5548,5.1576,5.8337,6.4553],
                       [4.3716,5.0324,5.8013,6.6473,7.3907],
                       [3.8672,4.39,  0.0,   7.5769,8.4637],
                       [3.4183,3.8319,0.0,   8.5738,9.6946],
                       [2.9978,2.9309,6.0733,9.6946,0.]]
        # print(self.states)

        self.q = np.array([[[0.0 for a in range(len(self.actions))] for j in range(5)] for i in range(5)])
        self.pi_esoft = collections.defaultdict(list) # key: state, value: list(best action)
        for s in self.states:
            self.pi_esoft[s] = self.actions

        self.test_pol = np.array([[[0.25 for k in range(4)]for j in range(5)] for i in range(5)])
        pass

    def transition_function(self):
        counter = 0
        p = defaultdict(list)
        for state in self.states:
            for next_direction in [(-1,0), (0,1), (1,0), (0,-1), (0,0)]:
                # print("next_direction = {}".format(next_direction[0]))
                # print("state = {}".format(state))
                next_state = (state[0] + next_direction[0], state[1] + next_direction[1])
                # print(next_state)
                # print()
                for action in self.actions:

                    prob = 0
                    if ((next_state[0] < 0) or (next_state[1] < 0) or (next_state[0] > 4) or (next_state[1] > 4)):
                        continue
                    if ((state[0] == 2) and (state[1] == 2)) or ((state[0] == 3) and (state[1] == 2)):
                        prob = 0
                        p[state, next_state].append(prob)
                        continue
                    if ((next_state[0] == 2) and (next_state[1] == 2)) or ((next_state[0] == 3) and (next_state[1] == 2)):
                        prob = 0
                        p[state, next_state].append(prob)
                        continue
                    if ((state[0] == 4) and (state[1] == 4)):
                        if ((next_state[0] == 4) and (next_state[1] == 4)):
                            prob = 1
                        else:
                            prob = 0
                        p[state, next_state].append(prob)
                        continue
                    # if ((state[0] == 0) and (state[1] == 2)):
                    #     if ((next_state[0] == 0) and (next_state[1] == 2)):
                    #         prob = 1
                    #     else:
                    #         prob = 0
                    #     p[state, next_state].append(prob)
                    #     continue
                    if action == next_direction:
                        prob = 0.8
                    elif (next_direction == self.actions[(self.actions.index(action) + 1) % 4]) or (next_direction == self.actions[(self.actions.index(action) - 1) % 4]):
                        prob = 0.05
                    elif (next_direction == (0,0)):
                        prob = 0.1
                        if ((state[0] + action[0]) == 2 and (state[1] + action[1]) == 2): # going into the obstacle directly when moving towards the obstacle
                            prob += 0.8
                        if ((state[0] + action[0]) == 3 and (state[1] + action[1]) == 2):# going into the obstacle directly when moving towards the obstacle
                            prob += 0.8
                        if (((state[0] + action[0]) < 0)  or ((state[1] + action[1]) < 0) or ((state[0] + action[0]) > 4)  or ((state[1] + action[1]) > 4)): # going directly into the wall
                            prob += 0.8
                        if (((state[0] + self.actions[(self.actions.index(action) + 1) % 4][0]) == 2) and ((state[1] + self.actions[(self.actions.index(action) + 1) % 4][1]) == 2)): # going into the obstacle mistakenly towrads right
                            prob += 0.05
                        if (((state[0] + self.actions[(self.actions.index(action) + 1) % 4][0]) == 3) and ((state[1] + self.actions[(self.actions.index(action) + 1) % 4][1]) == 2)):# going into the obstacle
                            prob += 0.05
                        if (((state[0] + self.actions[(self.actions.index(action) - 1) % 4][0]) == 2) and ((state[1] + self.actions[(self.actions.index(action) - 1) % 4][1]) == 2)): # going into the obstacle
                            prob += 0.05
                        if (((state[0] + self.actions[(self.actions.index(action) - 1) % 4][0]) == 3) and ((state[1] + self.actions[(self.actions.index(action) - 1) % 4][1]) == 2)):# going into the obstacle
                            prob += 0.05
                        if ((state[0] + self.actions[(self.actions.index(action) + 1) % 4][0]) < 0): # going into the wall
                            prob += 0.05
                        if ((state[1] + self.actions[(self.actions.index(action) + 1) % 4][1]) < 0): # going into the wall
                            prob += 0.05
                        if ((state[0] + self.actions[(self.actions.index(action) + 1) % 4][0]) > 4): # going into the wall
                            prob += 0.05
                        if ((state[1] + self.actions[(self.actions.index(action) + 1) % 4][1]) > 4): # going into the wall
                            prob += 0.05
                        if ((state[0] + self.actions[(self.actions.index(action) - 1) % 4][0]) < 0): # going into the wall
                            prob += 0.05
                        if ((state[1] + self.actions[(self.actions.index(action) - 1) % 4][1]) < 0): # going into the wall
                            prob += 0.05
                        if ((state[0] + self.actions[(self.actions.index(action) - 1) % 4][0]) > 4): # going into the wall
                            prob += 0.05
                        if ((state[1] + self.actions[(self.actions.index(action) - 1) % 4][1]) > 4): # going into the wall
                            prob += 0.05

                    # print("state = {}, action = {}, next_state = {}, prob = {}".format(state, action, next_state, round(prob, 3)))
                    p[state, next_state].append(round(prob, 3))
        # print(len(p))
        return p

    def trans_func(self, s, a):
        """
        Args:
            s = tuple(row, col)
            a = action (tuple)
        Returns:
            next state (tuple)
        """
        if s == (4, 4): return s
        rand = random.uniform(0, 1)
        if rand < 0.8:
            s_prime = (s[0] + a[0], s[1] + a[1])
        elif 0.8 < rand <  0.85:
            a = self.actions[(self.actions.index(a) + 1) % 4]
            s_prime = (s[0] + a[0], s[1] + a[1])
        elif  0.85 < rand <  0.9:
            a = self.actions[(self.actions.index(a) - 1) % 4]
            s_prime = (s[0] + a[0], s[1] + a[1])
        else:
            s_prime = s
        if (s_prime == (2,2)) or (s_prime == (3,2)) or (s_prime[0] < 0) or (s_prime[0] > 4) or (s_prime[1] < 0) or (s_prime[1] > 4):
            s_prime = s
        return s_prime

    def reward(self, s, a, s_prime):
        if (s == (4, 4)):
            return 0
        elif s_prime == (4, 4):
            return 10
        elif s_prime == (4, 2):
            return -10
        # elif s_prime == (0, 2):
        #     # return 5
        #     return 4.4844 # found using binary search
        else:
            return 0

    def d0(self):
        states = self.states.copy()
        states.remove((2,2))
        states.remove((3,2))
        random_index = random.randint(0,len(states)-1)
        return states[random_index]

    def run(self, threshold):
        # self.v = np.array([[0 for j in range(5)] for i in range(5)])
        p = self.transition_function()
        count = 0
        while True:
            count += 1
            delta = 0
            v_old = np.copy(self.v)
            # print(np.amax(v_old))
            for s in self.states:
                max_val = -float("inf")
                max_a = None
                for i, a in enumerate(self.actions):
                    val = 0
                    for s_prime in self.states:

                        # print(s, s_prime)
                        try:
                            # print(v_old[s_prime])
                            val += p[s, s_prime][i]*(self.reward(s, a, s_prime) + (self.gamma*v_old[s_prime]))
                        except:
                            continue
                    # print("val = {}".format(val))
                    if max_val < val:
                        max_val = val
                        max_a = i
                # if (s == (1, 1)): print("val = {}".format(val))
                self.v[s] = round(max_val, 4)
                self.policy[s] = self.actions[max_a]
            delta = max(delta, np.amax(abs(self.v - v_old)))
            if delta < threshold:
                break
        return self.v, self.policy, count

    def pi_func(self, pi, s):
        # self.gamma = 0.9
        # v_star, pi_star, iterations = self.run(0.0001)

        return pi[s[0]][s[1]]

    def v_star_func(self, s):
        # self.gamma = 0.9
        # v_star, pi_star, iterations = self.run(0.0001)
        return self.v_star[s[0]][s[1]]

    def generateEpisode(self, pi):
        trajectory = []
        s = self.d0()
        while(s != (4, 4)):
            a = self.pi_func(pi, s)
            s_prime = self.trans_func(s, a)
            r = self.reward(s, a, s_prime)
            trajectory.append((s, r))
            s = s_prime
        trajectory.append(((4,4), 0))
        return trajectory

    def pi_esoft_func(self, pi, s, eps):
        """
        Args:
            pi: dictionary key = states, value = list of actions
            s = tuple(row, col)
            eps = float
        Returns:
            action in state s (tuple)
        """
        # rand = random.uniform(0, 1)
        # A_star = pi[s]
        # A = self.actions
        # A_ = list(set(A) - set(A_star))
        # # print(A_star, A, A_)
        # prob = ((1- eps)/len(A_star)) + (eps/len(A))
        # for i in range(len(A_star)):
        #     if prob*(i) < rand < prob*(i+1):
        #         return A_star[i]
        # for i in range(len(A_)):
        #     if (prob*len(A_star) + (eps/len(A))*(i)) < rand < (prob*len(A_star) + (eps/len(A))*(i+1)):
        #         return A_[i]
        # print(s)
        chosen_action = np.random.choice(4, 1, p=pi[s[0]][s[1]])
        return self.actions[chosen_action[0]]

    def policy_prob(self, s, a, pi, eps):
        A_star = pi[s]
        A = self.actions
        # print(a, A_star)
        if a in A_star:
            # print(((1- eps)/len(A_star)) + (eps/len(A)))
            return ((1- eps)/len(A_star)) + (eps/len(A))
        else:
            # print("else condition")
            # print((eps/len(A)))
            return (eps/len(A))

    def generateEpisode_esoft(self, pi, eps):
        """
        Args:
            pi = dictionary key = states, value = list of actions
            eps
        """
        trajectory = []
        s = self.d0()
        while(s != (4, 4)):
            a = self.pi_esoft_func(pi, s, eps)
            s_prime = self.trans_func(s, a)
            r = self.reward(s, a, s_prime)
            trajectory.append(((s, a), r))
            s = s_prime
        trajectory.append((((4,4), (0, 1)), 0))
        return trajectory

    def first_visit(self,):
        returns = collections.defaultdict(list)
        max_norm = []
        count = 0
        while True:
            count += 1
            episode = self.generateEpisode(self.pi_star)
            # print("episode = {}".format(episode))
            states_present = []
            rewards = []
            for s, r in episode:
                states_present.append(s)
                rewards.append(r)
            for s in set(states_present):
                first_index = states_present.index(s)
                G = 0
                temp_rewards = rewards[first_index:]
                # print("temp_rewards = {}".format(temp_rewards))
                for pow in range(len(temp_rewards)):
                    G += (self.gamma**pow) * temp_rewards[pow]
                # print(G)
                returns[s].append(G)
                self.v[s[0]][s[1]] = mean(returns[s])
            max_norm.append(np.amax(abs(self.v - self.v_star)))
            if np.amax(abs(self.v - self.v_star)) < 0.1:
                break
        print("max norm = {}".format(max_norm[-1]))
        print("Iterations to converge = {}".format(count))
        plt.plot(max_norm)
        plt.title("Max norm")
        plt.xlabel("Iterations")
        plt.ylabel("Max norm")
        plt.show()
        return count

    def every_visit(self, ):
        returns = collections.defaultdict(list)
        max_norm = []
        count = 0
        while True:
            count += 1
            episode = self.generateEpisode(self.pi_star)
            # print("episode = {}".format(episode))
            states_present = []
            rewards = []
            for s, r in episode:
                states_present.append(s)
                rewards.append(r)
            for i, s in enumerate(states_present):
                G = 0
                temp_rewards = rewards[i:]
                # print("temp_rewards = {}".format(temp_rewards))
                for pow in range(len(temp_rewards)):
                    G += (self.gamma**pow) * temp_rewards[pow]
                # print(G)
                returns[s].append(G)
                # print(returns)
                self.v[s[0]][s[1]] = mean(returns[s])
            max_norm.append(np.amax(abs(self.v - self.v_star)))
            if np.amax(abs(self.v - self.v_star)) < 0.1 or count > 10000:
                break
        print("max norm = {}".format(max_norm[-1]))
        print("Iterations to converge = {}".format(count))
        plt.plot(max_norm)
        plt.title("Max norm")
        plt.xlabel("Iterations")
        plt.ylabel("Max norm")
        plt.show()
        return count

    def e_soft(self, eps, decay=False):
        returns = collections.defaultdict(list)
        max_norm = []
        count = 0
        mse = []
        itr_number = []
        while True and eps > 0:
            count += 1
            if decay and count % 500 == 0:
                eps -= 0.05
            # episode = self.generateEpisode_esoft(self.pi_esoft, eps)
            episode = self.generateEpisode_esoft(self.test_pol, eps)
            # print("episode = {}".format(episode))
            states_action_present = []
            rewards = []
            for s_a, r in episode:
                states_action_present.append(s_a)
                rewards.append(r)
            for s_a in set(states_action_present):
                first_index = states_action_present.index(s_a)
                G = 0
                temp_rewards = rewards[first_index:]
                for pow in range(len(temp_rewards)):
                    G += (self.gamma**pow) * temp_rewards[pow]
                # print("state= {}, action = {}".format(s_a[0], s_a[1]))
                returns[s_a].append(G)
                index_a = self.actions.index(s_a[1])
                row = s_a[0][0]
                col = s_a[0][1]
                # print("q update value = {}".format(mean(returns[s_a])))
                self.q[row][col][index_a] = mean(returns[s_a]) # Update q
                # print("udated q value = {}".format(self.q[row][col][index_a]))
                best_a_list = []
                # print("list of best actions b4 = {}".format(best_a_list))
                best_qsa = -float("inf")
                # for i, expl_a in enumerate(self.actions):
                #     if best_qsa < self.q[row][col][i]:
                #         best_qsa = self.q[row][col][i]
                #         best_a_list = [expl_a]
                #     elif best_qsa == self.q[row][col][i]:
                #         best_a_list.append(expl_a)
                # self.pi_esoft[s_a[0]] = best_a_list

                for i, expl_a in enumerate(self.actions):
                    if best_qsa < self.q[row][col][i]:
                        best_qsa = self.q[row][col][i]
                        best_a_list = [i]
                    elif best_qsa == self.q[row][col][i]:
                        best_a_list.append(i)
                # print("list of best actions after = {}".format(best_a_list))
                not_best_list = list(set(range(4)) - set(best_a_list))
                new_prob = max(0, ((1- eps)/len(best_a_list)) + (eps/len(self.actions)))
                remaining_prob = (eps/len(self.actions))
                np.put(self.test_pol[row][col], best_a_list, [new_prob]*len(best_a_list))
                np.put(self.test_pol[row][col], not_best_list, [remaining_prob]*len(not_best_list))
            for s in self.states:
                # self.v[s[0]][s[1]] = sum([self.policy_prob(s, a, self.pi_esoft, eps)*self.q[s[0]][s[1]][a_index] for a_index, a in enumerate(self.actions)])
                self.v[s[0]][s[1]] = sum([self.test_pol[s[0]][s[1]][a_index]*self.q[s[0]][s[1]][a_index] for a_index, a in enumerate(self.actions)])
            max_norm.append(np.amax(abs(self.v - self.v_star)))
            # if np.amax(abs(self.v - self.v_star)) < 0.1:
            if count % 250 == 0:
                mse.append(self.mse(self.v, self.v_star))
                itr_number.append(count)

            if count > 10000:
                break
        print("max norm = {}".format(np.amax(abs(self.v - self.v_star))))
        print("MSE = {}".format(mse[-1]))
        # plt.plot(max_norm)
        # plt.title("Plotting Max norm")

        plt.plot(itr_number, mse)
        # plt.title("Mean squared Error for eps = {}".format(eps))
        plt.title("Mean squared Error for eps = {}".format("decaying"))
        plt.xlabel("Iterations")
        plt.ylabel("MSE")

        plt.show()
        # print(returns)

        pass

    def mse(self, m1, m2):
        return np.square(np.subtract(m1, m2)).mean()

def main():
    def replace(inp, positions, char):
        for pos in positions:
            inp[pos] = char
    obstacles = [(2,2), (3,2)]
    goal = [(0,2), (4,4)]

    # gamma = 0.9133
    gamma = 0.9
    monte_carlo = MonteCarlo(gamma=gamma)

    # print("test esoft pi")
    # print(monte_carlo.pi_esoft_func(monte_carlo.pi_esoft, s=()))

    print("running first visit monte carlo")
    # itr_list = []
    # from tqdm import tqdm
    # for _ in tqdm(range(20)):
    #     itr_list.append(monte_carlo.first_visit())
    # print("average nuumber of iterations = {}".format(mean(itr_list)))
    print(monte_carlo.first_visit())
    print(monte_carlo.v)

    print("running the every visit monte carlo")
    # itr_list = []
    # from tqdm import tqdm
    # for _ in tqdm(range(20)):
    #     itr_list.append(monte_carlo.every_visit())
    #     print(mean(itr_list))
    # print("average nuumber of iterations = {}".format(mean(itr_list)))
    print(monte_carlo.every_visit())
    print(monte_carlo.v)

    for eps in [0.2, 0.1, 0.05]:
        print("running e soft with e = {}".format(eps))
        print(monte_carlo.e_soft(eps))
        print(monte_carlo.v)

    print("running e soft with decay")
    print(monte_carlo.e_soft(1, True))
    print(monte_carlo.v)

if __name__ == '__main__':
    main()

    # # 0.2
    # max norm = 2.635907184410178
    # MSE = 1.0916569585432456

    # 0.1
    # max norm = 1.0146242375768457
    # MSE = 0.20658440173913825

    # 0.05
    # max norm = 0.6371196286440819
    # MSE = 0.053532617694715456

    # decay
    # max norm = 10.652396821340675
    # MSE = 12.980199028027773

    # Every visit
    # max norm = 0.09976012680428381
    # Iterations to converge = 5495

    # max norm = 0.0777421256165125
    # Iterations to converge = 13800

    # average nuumber of iterations = 8353.1


from commands.Command import Command
import discord
import asyncio

class Cat(Command):
    def __init__(self):
        super().__init__(
            {
                'name': 'cat',
                'description': 'extracts the text content of your file',
                'argc': 1
            }
        )

    async def run(self, message: discord.Message, argc: int, argv: list):
        extension = argv[0]
        valid_extensions = ['cpp']
        if extension not in valid_extensions:
            emb = discord.Embed(
                title='Error', description="Invalid file extension.", colour=discord.Color.from_rgb(255, 0, 0)
            )
            await message.channel.send(embed=emb)
            return

        file = open(f'./files/{message.author}.{extension}', 'r')
        msg = '```cpp\n'
        for line in file.readlines():
            if len(line.strip()) != 0:
                msg += line
            else:
                msg += '\n'
        msg += '\n```'
        file.close()
        await message.channel.send(msg)


# -*- coding: utf-8 -*-
"""
Created on Wed Feb  9 12:39:55 2022

@author: pkinn
"""
def cvTrain(model, features, targets, nSplits, nEpochs, batchSz, initWts):
    from sklearn.model_selection import KFold
    import numpy as np
    kf = KFold(n_splits = nSplits, shuffle = True)
    fn = 1
    # Define per-fold score containers <-- these are new
    acc_per_fold = []
    loss_per_fold = []
    allHist = np.zeros((nSplits, nEpochs))
    for train, test in kf.split(features, targets):
        print('------------------------------------------------------------------------')
        print(f'Training for fold {fn} ...')
        model.set_weights(initWts)
        for kk in range(nEpochs):
            history = model.fit(features[train], targets[train],
                                batch_size = batchSz,
                                epochs = 1,
                                verbose = 0)
            scores = model.evaluate(features[test], targets[test], verbose = 0)
            allHist[fn-1, kk] = scores[1]
        print(f'Score for fold {fn}: {model.metrics_names[0]} of {scores[0]}; {model.metrics_names[1]} of {scores[1]*100}%')
        acc_per_fold.append(scores[1] * 100)
        loss_per_fold.append(scores[0])
        fn += 1

    return acc_per_fold, loss_per_fold, allHist


from abc import ABCMeta, abstractmethod
from bisect import bisect_right
from typing import Any, Dict, Iterable, List, Optional, Tuple

from volatility3.framework import exceptions, interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import linear

class NonLinearlySegmentedLayer(
    interfaces.layers.TranslationLayerInterface, metaclass=ABCMeta
):
    """A class to handle a single run-based layer-to-layer mapping.

    In the documentation "mapped address" or "mapped offset" refers to
    an offset once it has been mapped to the underlying layer
    """

    def __init__(
        self,
        context: interfaces.context.ContextInterface,
        config_path: str,
        name: str,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> None:
        super().__init__(
            context=context, config_path=config_path, name=name, metadata=metadata
        )

        self._base_layer = self.config["base_layer"]
        self._segments: List[Tuple[int, int, int, int]] = []
        self._minaddr: Optional[int] = None
        self._maxaddr: Optional[int] = None

        self._load_segments()

    @abstractmethod
    def _load_segments(self) -> None:
        """Populates the _segments variable.

        Segments must be (address, mapped address, length, mapped_length) and must be
        sorted by address when this method exits
        """

    def is_valid(self, offset: int, length: int = 1) -> bool:
        """Returns whether the address offset can be translated to a valid
        address."""
        try:
            base_layer = self._context.layers[self._base_layer]
            return all(
                [
                    base_layer.is_valid(mapped_offset)
                    for _i, _i, mapped_offset, _i, _s in self.mapping(offset, length)
                ]
            )
        except exceptions.InvalidAddressException:
            return False

    def _find_segment(
        self, offset: int, next: bool = False
    ) -> Tuple[int, int, int, int]:
        """Finds the segment containing a given offset.

        Returns the segment tuple (offset, mapped_offset, length, mapped_length)
        """

        if not self._segments:
            self._load_segments()

        # Find rightmost value less than or equal to x
        i = bisect_right(
            self._segments,
            (offset, self.context.layers[self._base_layer].maximum_address),
        )
        if i and not next:
            segment = self._segments[i - 1]
            if segment[0] <= offset < segment[0] + segment[2]:
                return segment
        if next:
            if i < len(self._segments):
                return self._segments[i]
        raise exceptions.InvalidAddressException(
            self.name, offset, f"Invalid address at {offset:0x}"
        )

    # Determines whether larger segments are in use and the offsets within them should be tracked linearly
    # When no decoding of the data occurs, this should be set to true
    _track_offset = False

    def mapping(
        self, offset: int, length: int, ignore_errors: bool = False
    ) -> Iterable[Tuple[int, int, int, int, str]]:
        """Returns a sorted iterable of (offset, length, mapped_offset, mapped_length, layer)
        mappings."""
        done = False
        current_offset = offset
        while not done:
            try:
                # Search for the appropriate segment that contains the current_offset
                logical_offset, mapped_offset, size, mapped_size = self._find_segment(
                    current_offset
                )
                # If it starts before the current_offset, bring the lower edge up to the right place
                if current_offset > logical_offset:
                    difference = current_offset - logical_offset
                    logical_offset += difference
                    if self._track_offset:
                        mapped_offset += difference
                    size -= difference
            except exceptions.InvalidAddressException:
                if not ignore_errors:
                    # If we're not ignoring errors, raise the invalid address exception
                    raise
                try:
                    # Find the next valid segment after our current_offset
                    (
                        logical_offset,
                        mapped_offset,
                        size,
                        mapped_size,
                    ) = self._find_segment(current_offset, next=True)
                    # We know that the logical_offset must be greater than current_offset so skip to that value
                    current_offset = logical_offset
                    # If it starts too late then we're done
                    if logical_offset > offset + length:
                        return
                except exceptions.InvalidAddressException:
                    return
            # Crop it to the amount we need left
            chunk_size = min(size, length + offset - logical_offset)
            yield logical_offset, chunk_size, mapped_offset, mapped_size, self._base_layer
            current_offset += chunk_size
            # Terminate if we've gone (or reached) our required limit
            if current_offset >= offset + length:
                done = True

    @property
    def minimum_address(self) -> int:
        if not self._segments:
            raise ValueError("SegmentedLayer must contain some segments")
        if self._minaddr is None:
            mapped, _, _, _ = self._segments[0]
            self._minaddr = mapped
        return self._minaddr

    @property
    def maximum_address(self) -> int:
        if not self._segments:
            raise ValueError("SegmentedLayer must contain some segments")
        if self._maxaddr is None:
            mapped, _, length, _ = self._segments[-1]
            self._maxaddr = mapped + length
        return self._maxaddr

    @property
    def dependencies(self) -> List[str]:
        """Returns a list of the lower layers that this layer is dependent
        upon."""
        return [self._base_layer]

    @classmethod
    def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
        return [
            requirements.TranslationLayerRequirement(name="base_layer", optional=False)
        ]

class SegmentedLayer(
    NonLinearlySegmentedLayer, linear.LinearlyMappedLayer, metaclass=ABCMeta
):
    _track_offset = True

    def mapping(
        self, offset: int, length: int, ignore_errors: bool = False
    ) -> Iterable[Tuple[int, int, int, int, str]]:
        # Linear mappings must return the same length of segment as that requested
        for offset, length, mapped_offset, mapped_length, layer in super().mapping(
            offset, length, ignore_errors
        ):
            yield offset, length, mapped_offset, length, layer


import copy
import re

from knack.log import get_logger

from azdev.utilities import get_name_index

logger = get_logger(__name__)

_LOADER_CLS_RE = re.compile('.*azure/cli/command_modules/(?P<module>[^/]*)/__init__.*')

def filter_modules(command_loader, help_file_entries, modules=None, include_whl_extensions=False):
    """ Modify the command table and help entries to only include certain modules/extensions.

    : param command_loader: The CLICommandsLoader containing the command table to filter.
    : help_file_entries: The dict of HelpFile entries to filter.
    : modules: [str] list of module or extension names to retain.
    """
    return _filter_mods(command_loader, help_file_entries, modules=modules,
                        include_whl_extensions=include_whl_extensions)

def exclude_commands(command_loader, help_file_entries, module_exclusions, include_whl_extensions=False):
    """ Modify the command table and help entries to exclude certain modules/extensions.

    : param command_loader: The CLICommandsLoader containing the command table to filter.
    : help_file_entries: The dict of HelpFile entries to filter.
    : modules: [str] list of module or extension names to remove.
    """
    return _filter_mods(command_loader, help_file_entries, modules=module_exclusions, exclude=True,
                        include_whl_extensions=include_whl_extensions)

def _filter_mods(command_loader, help_file_entries, modules=None, exclude=False, include_whl_extensions=False):
    modules = modules or []

    # command tables and help entries must be copied to allow for seperate linter scope
    command_table = command_loader.command_table.copy()
    command_group_table = command_loader.command_group_table.copy()
    command_loader = copy.copy(command_loader)
    command_loader.command_table = command_table
    command_loader.command_group_table = command_group_table
    help_file_entries = help_file_entries.copy()
    name_index = get_name_index(include_whl_extensions=include_whl_extensions)

    for command_name in list(command_loader.command_table.keys()):
        try:
            source_name, _ = _get_command_source(command_name, command_loader.command_table)
        except LinterError as ex:
            # command is unrecognized
            logger.warning(ex)
            source_name = None

        try:
            long_name = name_index[source_name]
            is_specified = source_name in modules or long_name in modules
        except KeyError:
            is_specified = False
        if is_specified == exclude:
            # brute force method of ignoring commands from a module or extension
            command_loader.command_table.pop(command_name, None)
            help_file_entries.pop(command_name, None)

    # Remove unneeded command groups
    retained_command_groups = {' '.join(x.split(' ')[:-1]) for x in command_loader.command_table}
    excluded_command_groups = set(command_loader.command_group_table.keys()) - retained_command_groups

    for group_name in excluded_command_groups:
        command_loader.command_group_table.pop(group_name, None)
        help_file_entries.pop(group_name, None)

    return command_loader, help_file_entries

def share_element(first_iter, second_iter):
    return any(element in first_iter for element in second_iter)

def _get_command_source(command_name, command_table):
    from azure.cli.core.commands import ExtensionCommandSource  # pylint: disable=import-error
    command = command_table.get(command_name)
    # see if command is from an extension
    if isinstance(command.command_source, ExtensionCommandSource):
        return command.command_source.extension_name, True
    if command.command_source is None:
        raise LinterError('Command: `%s`, has no command source.' % command_name)
    # command is from module
    return command.command_source, False

# pylint: disable=line-too-long
def merge_exclusion(left_exclusion, right_exclusion):
    for command_name, value in right_exclusion.items():
        for rule_name in value.get('rule_exclusions', []):
            left_exclusion.setdefault(command_name, {}).setdefault('rule_exclusions', []).append(rule_name)
        for param_name in value.get('parameters', {}):
            for rule_name in value.get('parameters', {}).get(param_name, {}).get('rule_exclusions', []):
                left_exclusion.setdefault(command_name, {}).setdefault('parameters', {}).setdefault(param_name, {}).setdefault('rule_exclusions', []).append(rule_name)

class LinterError(Exception):
    """
    Exception thrown by linter for non rule violation reasons
    """
    pass  # pylint: disable=unnecessary-pass


from swift.common.swob import wsgify, HTTPInternalServerError, HTTPException
from swift.common.utils import get_logger
from zion.handlers import ProxyHandler
from zion.handlers import ComputeHandler
from zion.handlers import ObjectHandler
from zion.handlers.base import NotFunctionRequest
from distutils.util import strtobool
import redis

class FunctionHandlerMiddleware(object):

    def __init__(self, app, conf):
        self.app = app
        self.conf = conf
        self.exec_server = self.conf.get('execution_server')
        self.logger = get_logger(conf, name=self.exec_server +
                                 "-server Zion",
                                 log_route='function_handler')
        redis_host = self.conf.get('redis_host')
        redis_port = self.conf.get('redis_port')
        redis_db = self.conf.get('redis_db')
        self.redis_conn_pool = redis.ConnectionPool(host=redis_host,
                                                    port=redis_port,
                                                    db=redis_db)

        self.handler_class = self._get_handler(self.exec_server)

    def _get_handler(self, exec_server):
        """
        Generate Handler class based on execution_server parameter

        :param exec_server: Where this storlet_middleware is running.
                            This should value should be 'proxy' or 'compute'
        :raise ValueError: If exec_server is invalid
        """
        if exec_server == 'proxy':
            return ProxyHandler
        elif exec_server == 'compute':
            return ComputeHandler
        elif exec_server == 'object':
            return ObjectHandler
        else:
            raise ValueError('configuration error: execution_server must be '
                             'either proxy, object or compute but is %s' % exec_server)

    @wsgify
    def __call__(self, req):
        try:
            r = redis.Redis(connection_pool=self.redis_conn_pool)
            handler = self.handler_class(req, self.conf, self.app, self.logger, r)
            self.logger.debug('%s call in %s' % (req.method, req.path))

            return handler.handle_request()

        except NotFunctionRequest:
            self.logger.debug('No Zion Request, bypassing middleware')
            return req.get_response(self.app)

        except HTTPException:
            self.logger.exception('Zion execution failed')
            raise

        except Exception:
            self.logger.exception('Zion execution failed')
            raise HTTPInternalServerError(body='Unable to execute the requested operation: Zion execution failed\n')

def filter_factory(global_conf, **local_conf):
    """Standard filter factory to use the middleware with paste.deploy"""
    conf = global_conf.copy()
    conf.update(local_conf)
    # Common
    conf['execution_server'] = conf.get('execution_server')
    conf['functions_container'] = conf.get('functions_container', 'functions')
    conf['functions_visibility'] = strtobool(conf.get('functions_visibility', 'True'))
    # Paths
    conf['main_dir'] = conf.get('main_dir', '/opt/zion')
    # Worker paths
    conf['workers_dir'] = conf.get('workers_dir', 'workers')
    conf['java_runtime_dir'] = conf.get('java_runtime_dir', 'runtime/java')
    # Function Paths
    conf['functions_dir'] = conf.get('functions_dir', 'functions')
    conf['cache_dir'] = conf.get('cache_dir', 'cache')
    conf['log_dir'] = conf.get('log_dir', 'logs')
    conf['bin_dir'] = conf.get('bin_dir', 'bin')
    # Redis metastore
    conf['redis_host'] = conf.get('redis_host', 'localhost')
    conf['redis_port'] = int(conf.get('redis_port', 6379))
    conf['redis_db'] = int(conf.get('redis_db', 10))
    # Function defaults
    conf['default_function_timeout'] = int(conf.get('default_function_timeout', 10))
    conf['default_function_memory'] = int(conf.get('default_function_memory', 1024))
    conf['max_function_memory'] = int(conf.get('max_function_memory', 1024))
    # Compute Nodes
    conf['disaggregated_compute'] = strtobool(conf.get('disaggregated_compute', 'True'))
    conf['compute_nodes'] = conf.get('compute_nodes', 'localhost:8585')
    conf['docker_pool_dir'] = conf.get('docker_pool_dir', 'docker_pool')

    def swift_functions(app):
        return FunctionHandlerMiddleware(app, conf)

    return swift_functions


import numpy as np
import random
import matplotlib.pyplot as plt
import pickle

class Dataset:
    def __init__(self):
        self.index = 0

        self.obs = []
        self.classes = []
        self.num_obs = 0
        self.num_classes = 0
        self.indices = []

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= self.num_obs:
            self.index = 0
            raise StopIteration
        else:
            self.index += 1
            return self.obs[self.index - 1], self.classes[self.index - 1]

    def reset(self):
        self.index = 0

    def get_obs_with_target(self, k):
        index_list = [index for index, value in enumerate(self.classes) if value == k]
        return [self.obs[i] for i in index_list]

    def get_all_obs_class(self, shuffle=False):
        if shuffle:
            random.shuffle(self.indices)
        return [(self.obs[i], self.classes[i]) for i in self.indices]

    def get_mini_batches(self, batch_size, shuffle=False):
        if shuffle:
            random.shuffle(self.indices)

        batches = [(self.obs[self.indices[n:n + batch_size]],
                    self.classes[self.indices[n:n + batch_size]])
                   for n in range(0, self.num_obs, batch_size)]
        return batches

class IrisDataset(Dataset):
    def __init__(self, path):
        super(IrisDataset, self).__init__()
        self.file_path = path
        self.loadFile()
        self.indices = np.arange(self.num_obs)

    def loadFile(self):
        # load a comma-delimited text file into an np matrix
        resultList = []
        f = open(self.file_path, 'r')
        for line in f:
            line = line.rstrip('\n')  # "1.0,2.0,3.0"
            sVals = line.split(',')  # ["1.0", "2.0, "3.0"]
            fVals = list(map(np.float32, sVals))  # [1.0, 2.0, 3.0]
            resultList.append(fVals)  # [[1.0, 2.0, 3.0] , [4.0, 5.0, 6.0]]
        f.close()
        data = np.asarray(resultList, dtype=np.float32)  # not necessary
        self.obs = data[:, 0:4]
        self.classes = data[:, 4:7]
        self.num_obs = data.shape[0]
        self.num_classes = 3

# Helpers
def square(list):
    return np.array([i ** 2 for i in list])

# Activations
def tanh(x, deriv):
    '''
	d/dx tanh(x) = 1 - tanh^2(x)
	during backpropagation when we need to go though
	the derivative we have already computed tanh(x),
	therefore we pass tanh(x) to the function which reduces the gradient to:
	1 - tanh(x)
    '''
    if deriv == True:
        return 1.0 - np.power(np.tanh(x), 2)
    else:
        return np.tanh(x)

def sigmoid(x, deriv):
    '''
    Task 2a
    This function is the sigmoid function.
    It gets an input digit or vector and should return sigmoid(x).
    The parameter "deriv" toggles between the sigmoid and the derivate of the sigmoid.
     Hint: In the case of the derivate
    you can expect the input to be sigmoid(x) instead of x
    :param x:
    :param deriv:
    :return:
    '''
    if deriv == True:
        #return 0.5 * (1 + tanh(0.5 * x, False))
        #return (1 / (1 + np.exp(-x)))
        return sigmoid(x, False) * (1 - sigmoid(x, False))
        #return x*(1-x)
    else:
        #if type(x) != 'float32':
         #   return [0.5 * (1 + tanh(0.5 * i)) for i in x]
        #else:
        #return sigmoid(x, False) * (1 - sigmoid(x, False))
        return 0.5 * (1 + tanh(0.5 * x, False))
        #return (1 / (1 + np.exp(-x)))

def softmax(x, deriv):
    '''
    Task 2a
    This function is the sigmoid function with a softmax applied.
    This will be used in the last layer of the network
    The derivate will be the same as of sigmoid(x)
    :param x:
    :param deriv:
    :return:
    '''
    if deriv:
        return sigmoid(x, True)
    else:
        exps = [np.exp(i) for i in x]
        sumOfExps = np.sum(exps)
        return np.array([np.divide(i, sumOfExps) for i in exps])

class Layer:
    def __init__(self, numInput, numOutput, activation=sigmoid):
        print('Create layer with: {}x{} @ {}'.format(numInput, numOutput, activation))
        self.ni = numInput
        self.no = numOutput
        self.weights = np.zeros(shape=[self.ni, self.no], dtype=np.float32)
        self.biases = np.zeros(shape=[self.no], dtype=np.float32)
        self.initializeWeights()

        self.activation = activation
        self.last_input = np.zeros(shape=[self.ni], dtype=np.float32)
        self.last_output = np.zeros(shape=[self.no], dtype=np.float32)
        self.last_nodes = np.zeros(shape=[self.no], dtype=np.float32)

        self.mbs = None

    def initializeWeights(self):
        """
        Task 2d
        Initialized the weight matrix of the layer.
        Weights should be initialized to something other than 0.
        You can search the literature for possible initialization methods.
        :return: None
        """
        #####RANDOM
        #self.weights = np.random.rand(self.weights.shape[0], self.weights.shape[1])
        #self.biases = np.random.rand(self.biases.shape[0])
        #####NORMAL
        self.weights = np.random.normal(0, np.sqrt(2/(self.ni * self.no)), (self.weights.shape[0], self.weights.shape[1]))
        self.biases = np.random.normal(0, np.sqrt(2/(self.ni * self.no)), self.biases.shape[0])

    def inference(self, x):
        """
        Task 2b
        This transforms the input x with the layers weights and bias and applies the activation function
        Hint: you should save the input and output of this function usage in the backpropagation
        :param x:
        :return: output of the layer
        :rtype: np.array
        """
        self.last_input = x
        z = np.zeros(shape=[self.no], dtype=np.float32)
        for i in range(0, self.no):
            for j in range(0, self.ni):
                z[i] += self.weights[j][i] * x[j]
            z[i] += self.biases[i]
        self.last_nodes = z
        self.last_output = self.activation(z, False)
        return self.last_output

    def backprop(self, error):
        """
        Task 2c
        This function applied the backpropagation of the error signal.
        The Layer receives the error signal from the following
        layer or the network. You need to calculate the error signal
        for the next layer by backpropagating thru this layer.
         You also need to compute the gradients for the weights and bias.
        :param error:
        :return: error signal for the preceeding layer
        :return: gradients for the weight matrix
        :return: gradients for the bias
        :rtype: np.array
        """
        #Gradient for weights
        grad_weights = np.zeros(shape=[self.ni, self.no], dtype=np.float32)
        for i in range(0, self.no):
            for j in range(0, self.ni):
                grad_weights[j][i] = error[i] \
                                     * self.activation(self.last_nodes[i], True) \
                                     * self.last_input[j]

        #Gradient for biases
        grad_biases = np.zeros(shape=[self.no], dtype=np.float32)
        for i in range(0, self.no):
            grad_biases[i] = error[i] \
                             * self.activation(self.last_nodes[i], True)
        #Error signal for prev Layer E/y(l-1)
        errorSignal = np.zeros(shape=[self.ni], dtype=np.float32)
        for i in range(0, self.ni):
            for k in range(0, self.no):
                errorSignal[i] += error[k] \
                                  * self.activation(self.last_nodes[k], True) \
                                  * self.weights[i][k]
        return errorSignal, grad_weights, grad_biases

class BasicNeuralNetwork():
    def __init__(self, layer_sizes=5, num_input=4, num_output=3, num_epoch=300, learning_rate=0.1,
                 mini_batch_size=4, number_of_hiddenlayers=0):
        self.layers = []
        self.ls = layer_sizes
        self.ni = num_input
        self.no = num_output
        self.lr = learning_rate
        self.num_epoch = num_epoch
        self.mbs = mini_batch_size
        self.nhl = number_of_hiddenlayers
        self.constructNetwork()

    def forward(self, x):
        """
        Task 2b
        This function forwards a single feature vector through every layer and
         return the output of the last layer
        :param x: input feature vector
        :return: output of the network
        :rtype: np.array
        """
        y = self.layers[0].inference(x)
        for layerIterator in range(1, len(self.layers)):
            y = self.layers[layerIterator].inference(y)
        return y

    def train(self, train_dataset, eval_dataset=None, monitor_ce_train=True, monitor_accuracy_train=True,
              monitor_ce_eval=True, monitor_accuracy_eval=True, monitor_plot='monitor.png'):
        ce_train_array = []
        ce_eval_array = []
        acc_train_array = []
        acc_eval_array = []
        for e in range(self.num_epoch):
            if self.mbs:
                self.mini_batch_SGD(train_dataset)
            else:
                self.online_SGD(train_dataset)
            print('Finished training epoch: {}'.format(e))
            if monitor_ce_train:
                ce_train = self.ce(train_dataset)
                ce_train_array.append(ce_train)
                print('CE (train): {}'.format(ce_train))
            if monitor_accuracy_train:
                acc_train = self.accuracy(train_dataset)
                acc_train_array.append(acc_train)
                print('Accuracy (train): {}'.format(acc_train))
            if monitor_ce_eval:
                ce_eval = self.ce(eval_dataset)
                ce_eval_array.append(ce_eval)
                print('CE (eval): {}'.format(ce_eval))
            if monitor_accuracy_eval:
                acc_eval = self.accuracy(eval_dataset)
                acc_eval_array.append(acc_eval)
                print('Accuracy (eval): {}'.format(acc_eval))

        if monitor_plot:
            fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(9, 4))
            line1, = ax[0].plot(ce_train_array, '--', linewidth=2, label='ce_train')
            line2, = ax[0].plot(ce_eval_array, label='ce_eval')

            line3, = ax[1].plot(acc_train_array, '--', linewidth=2, label='acc_train')
            line4, = ax[1].plot(acc_eval_array, label='acc_eval')

            ax[0].legend(loc='upper right')
            ax[1].legend(loc='upper left')
            ax[1].set_ylim([0, 1])

            plt.savefig(monitor_plot)

    def online_SGD(self, dataset):
        """
        Task 2d
        This function trains the network in an online fashion.
        Meaning the weights are updated after each observation.
        :param dataset:
        :return: None
        """
        for o, k in dataset:
            temp = self.forward(o)
            #############SQUARED ERROR##############
            #error = (temp - k)
            ############ CROSS ENTROPY ERROR#############
            k*np.log(temp) + (1-k)*np.log(1-temp)
            for layer in reversed(self.layers):
                (e, w, b) = layer.backprop(error)
                layer.weights = layer.weights - self.lr * w
                layer.biases = layer.biases - self.lr * b
                error = e

    def mini_batch_SGD(self, dataset):
        """
        Task 2d
        This function trains the network using mini batches.
        Meaning the weights updates are accumulated and applied after each mini batch.
        :param dataset:
        :return: None
        """
        for o, k in dataset.get_mini_batches(self.mbs):
            error = np.zeros(shape=[self.no])
            for b in range(0, self.mbs):
                error += (self.forward(o[b]) - k[b])
            for layer in reversed(self.layers):
                (e, w, b) = layer.backprop(error)
                layer.weights = layer.weights - self.lr * w
                layer.biases = layer.biases - self.lr * b
                error = e

    def constructNetwork(self):
        """
        Task 2d
        uses self.ls self.ni and self.no to construct a list of layers.
        The last layer should use sigmoid_softmax as an activation function.
        any preceeding layers should use sigmoid.
        :return: None
        """
        input_layer = Layer(self.ni, self.ls, sigmoid)
        self.layers = [input_layer]
        for i in range(0, self.nhl):
            hidden_layer = Layer(self.ls, self.ls, sigmoid)
            self.layers.append(hidden_layer)
        output_layer = Layer(self.ls, self.no, softmax)
        self.layers.append(output_layer)

    def ce(self, dataset):
        ce = 0
        for x, t in dataset:
            t_hat = self.forward(x)
            ce += np.sum(np.nan_to_num(-t * np.log(t_hat) - (1 - t) * np.log(1 - t_hat)))

        return ce / dataset.num_obs

    def accuracy(self, dataset):
        cm = np.zeros(shape=[dataset.num_classes, dataset.num_classes], dtype=np.int)
        for x, t in dataset:
            t_hat = self.forward(x)
            c_hat = np.argmax(t_hat)  # index of largest output value
            c = np.argmax(t)
            cm[c, c_hat] += 1

        correct = np.trace(cm)
        return correct / dataset.num_obs

    def load(self, path=None):
        if not path:
            path = './network.save'
        with open(path, 'rb') as f:
            self.layers = pickle.load(f)

    def save(self, path=None):
        if not path:
            path = './network.save'
        with open(path, 'wb') as f:
            pickle.dump(self.layers, f)


__author__ = ["fkiraly"]

from sktime.tests import test_all_estimators

def pytest_addoption(parser):
    """Pytest command line parser options adder."""
    parser.addoption(
        "--matrixdesign",
        default=False,
        help="sub-sample estimators in tests by os/version matrix partition design",
    )

def pytest_configure(config):
    """Pytest configuration preamble."""
    if config.getoption("--matrixdesign") in [True, "True"]:
        test_all_estimators.MATRIXDESIGN = True


import tqdm
import torch
import csv
import os
import os.path as osp
import random
import json
import h5py
import time

from collections import defaultdict

if __name__ == '__main__':
    from MiniImageNet import MiniImageNetDataset, TransformedImageLoader, h5load
    from base import MultiProcessImageLoader
else:
    from .base import MultiProcessImageLoader
    from .MiniImageNet import MiniImageNetDataset, TransformedImageLoader, h5load

def shuffle_slice(a, start, stop):
    assert stop <= len(a)
    i = start
    while (i < stop-1):
        idx = random.randrange(i, stop)
        a[i], a[idx] = a[idx], a[i]
        i += 1

class MiniImageNetMAMLDataset(MiniImageNetDataset):

    def __init__(self,
                 csv_dir,
                 split,
                 image_dir,
                 shuffle=True,
                 num_threads=2,
                 transform=None,
                 transformed_images=None,
                 imname_index_file=None,
                 separate_metasample={'train':True, 'test':False, 'val':False},
                 metasample_holdout=200,
                 ):

        super(MiniImageNetMAMLDataset, self).__init__(csv_dir, split, image_dir, shuffle, num_threads, transform,
                                                      transformed_images, imname_index_file)

        assert len(self.split_names) == len(separate_metasample)
        self.separate_metasample = separate_metasample
        #for i in range(len(separate_metasample)):
        #    self.separate_metasample.update({self.split_names[i]: separate_metasample[i]})
        self.metasample_holdout = metasample_holdout

        self.cat_ixs_meta = dict()      # holds current read index for the meta-update images

        for sp in self.split_names:
            if self.separate_metasample[sp]:
                self.cat_ixs_meta[sp] = dict()
            else:
                self.cat_ixs_meta[sp] = self.cat_ixs[sp]            # make the meta index refer to the normal index
            if self.separate_metasample[sp]:
                for cat in self.cat_to_files[sp].keys():
                    self.cat_ixs_meta[sp][cat] = len(self.cat_to_files[sp][cat]) - self.metasample_holdout

    def shuffle(self, split, cat):
        # shuffle all samples of given split and category
        if 'separate_metasample' in self.__dict__:
            if self.separate_metasample[split]:
                # shuffle separately
                self.shuffle_sq(split, cat)
                self.shuffle_meta(split, cat)
        else:
            random.shuffle(self.cat_to_files[split][cat])

    def shuffle_sq(self, split, cat):
        #print('sq shuffle', split, cat)
        num_files = len(self.cat_to_files[split][cat])
        shuffle_slice(self.cat_to_files[split][cat], 0, num_files - self.metasample_holdout)

    def shuffle_meta(self,split, cat):
        #print('meta shuffle', split, cat)
        num_files = len(self.cat_to_files[split][cat])
        shuffle_slice(self.cat_to_files[split][cat], num_files - self.metasample_holdout, num_files)

    def update_sq_iterator(self, split, cat):
        ix = self.cat_ixs[split][cat]
        ix += 1
        maxlen = len(self.cat_to_files[split][cat])
        if self.separate_metasample[split]:
            maxlen -= self.metasample_holdout
        if ix == maxlen:
            self.cat_ixs[split][cat] = 0
            if self.do_shuffle:
                if self.separate_metasample[split]:
                    self.shuffle_sq(split, cat)
                else:
                    self.shuffle(split, cat)
        else:
            self.cat_ixs[split][cat] = ix

    def update_meta_iterator(self, split, cat):
        ix = self.cat_ixs_meta[split][cat]
        ix += 1

        if ix == len(self.cat_to_files[split][cat]):
            if self.separate_metasample[split]:
                self.cat_ixs_meta[split][cat] = len(self.cat_to_files[split][cat]) - self.metasample_holdout
                if self.do_shuffle:
                    self.shuffle_meta(split, cat)
            else:
                self.cat_ixs_meta[split][cat] = 0
                if self.do_shuffle:
                    self.shuffle(split, cat)
        else:
            self.cat_ixs_meta[split][cat] = ix

    def fetch_images_MAML(self, split, cat, num, metanum):
        image_files = []
        for _ in range(num):
            ix = self.cat_ixs[split][cat]
            file = self.cat_to_files[split][cat][ix]
            image_files.append(file)
            self.update_sq_iterator(split, cat)
        for _ in range(metanum):
            ix = self.cat_ixs_meta[split][cat]
            file = self.cat_to_files[split][cat][ix]
            image_files.append(file)
            self.update_meta_iterator(split, cat)
        return image_files

    def get_episode_MAML(self, nway=5, nshot=4, nquery=1, nmeta=15, split='train', transform=None):
        categories = self.sample_categories(split, nway)
        images = dict()
        support_data = []
        support_lbls = []
        support_cats = []
        query_data = []
        query_lbls = []
        query_cats = []
        metasample_data = []
        metasample_lbls = []
        metasample_cats = []
        for lbl, cat in enumerate(categories):
            images[cat] = self.fetch_images_MAML(split, cat, nshot+nquery, nmeta)
            supports = images[cat][:nshot]
            queries = images[cat][nshot:nshot+nquery]
            metasamples = images[cat][nshot+nquery:]

            support_data.extend(supports)
            query_data.extend(queries)
            metasample_data.extend(metasamples)

            support_lbls.extend([lbl] * nshot)
            query_lbls.extend([lbl] * nquery)
            metasample_lbls.extend([lbl] * nmeta)

            support_cats.extend([cat] * nshot)
            query_cats.extend([cat] * nquery)
            metasample_cats.extend([cat] * nmeta)

        data_files = support_data + query_data + metasample_data
        data = self.load_images(data_files, transform)
        lbls = support_lbls + query_lbls + metasample_lbls
        cats = support_cats + query_cats + metasample_cats

        return {'data': data,
                'label': torch.LongTensor(lbls),
                'file': data_files,
                'category': cats,
                'nway': nway,
                'nshot': nshot,
                'nquery': nquery,
                'nmeta': nmeta,
                'split': split}

if __name__ == '__main__':
    import torchvision.transforms as transforms
    csv_dir = 'data/splits/mini_imagenet_split'
    split = 'Ravi'
    image_dir = 'data/raw/mini-imagenet'
    shuffle = True
    num_threads = 4
    transform = transforms.Compose([transforms.Resize((84, 84)),
                                    transforms.ToTensor()])
    separate_metasample = {'train': True, 'test': True, 'val': False}
    mini_loader = MiniImageNetMAMLDataset(csv_dir, split,
                                      image_dir, shuffle,
                                      num_threads, transform, separate_metasample=separate_metasample)
    for idx in tqdm.tqdm(range(100)):
        data = mini_loader.get_episode_MAML(20, 5, 15, 5, split='test')

        #data = mini_loader.get_episode_MAML(5, 4, 1, 15, split='test')
        #import ipdb
        #ipdb.set_trace()
    for idx in tqdm.tqdm(range(1000)):
        data = mini_loader.get_episode_MAML(5, 4, 1, 15, split='test')


from graphviz import Digraph
import pandas as pd
import numpy as np
import glob
import os

#Associa cada cor na planilha a um par (cor de fundo,cor da fonte) do Graphviz
colors = {'Amarelo':('yellow','black'),'Azul':('blue','white'),'Branco':('white','black'),
         'Cinza':('grey','black'),'Marrom':('brown','white'),'Ouro':('gold3','black'),
         'Preto':('black','white'),'Roxo':('purple','white'),'Verde':('green','black'),
         'Vermelho':('red','black'),'Laranja':('orangered','black')}
colors_sub = { 'Amarelo':'#ffff005f','Azul':'cyan','Branco':'white','Cinza':'gray95',
             'Marrom':'#a52a2a5f','Ouro':'gold','Preto':'black','Roxo':'mediumpurple1',
             'Verde':'palegreen','Vermelho':'tomato','Laranja':'orange' }

#Associa os tipos de relação na planilha aos vértices no Graphviz
rel_types = {'Positivo':'blue', 'Negativo':'red', 'Neutro':'black'}
dirs = {'Sim':'both', 'Não':'forward'}

'''
Obtém o data frame do Pandas
Recebe o nome do arquivo Excel
Retorna as listas de atores e relacionamentos
'''
def get_data(fileloc, ext):
    #lendo o arquivo usando Pandas
    ext_engine={'.xls':'xlrd','.xlsx':'openpyxl','.ods':'odf'}
    try:
        df_at_rel = pd.read_excel(fileloc, ['atores','relacionamentos'], engine=ext_engine.get(ext,None))
    except:
        return None, None

    #Obtendo os atores e relacionamentos
    df_atores = df_at_rel['atores'].loc[:,['ator', 'cor', 'grupo']].dropna(subset=['ator']).replace({np.nan: None})
    df_relacionamentos = df_at_rel['relacionamentos'].loc[:,['de', 'relacionamento', 'para', 'tipo', 'bilateral']].dropna()
    return df_atores.to_numpy().tolist(), df_relacionamentos.to_numpy().tolist()

'''
Faz os nós dos atores
Recebe o objeto Digraph e a lista de atores
Retorna o Dicionário com os grupos e membros
'''
def make_actor_nodes(graph,atores):
    lst_grupos = set()
    for ator in atores:
        ator_nome, ator_cor, ator_grupo = ator
        if ator_grupo is not None: lst_grupos.add(ator_grupo)
    grupos = {}
    grupos_cores = {}
    for ator in atores:
        #expandindo os parâmetros da lista
        ator_nome, ator_cor, ator_grupo = ator
        if ator_cor is None: ator_cor = "Branco"
        if ator_nome in lst_grupos:
            grupos_cores[ator_nome] = ator_cor
        else:
            graph.node(ator_nome,shape='circle',fillcolor=colors[ator_cor][0],style='filled', fontcolor=colors[ator_cor][1], fixedsize='false',width='1')
        if ator_grupo is None: continue
        if ator_grupo not in grupos:
            grupos[ator_grupo] = list()
        grupos[ator_grupo].append(ator_nome)
    return grupos, grupos_cores

'''
Cria um subgrafo
Recebe o nome e cor do grupo
Retorna o subgrafo criado
'''
def makeGroup(grupo_nome, grupo_cor, nivel=1 ):
    sub_g = Digraph(name='cluster_'+grupo_nome)
    sub_g.attr(label=grupo_nome)
    sub_g.attr(style='rounded')
    sub_g.attr(bgcolor=colors_sub[grupo_cor])
    return sub_g

'''
Insere os nós e grupos em um subgrafo
'''
def fillGroup(sub_g, grupos, grupos_cores, membros, not_root, nivel=1 ):
    for membro in membros:
        if membro not in grupos.keys():
            sub_g.node(membro)
        else:
            new_g = makeGroup(membro, grupos_cores.get(membro,'Branco'), nivel+1)
            fillGroup(new_g, grupos, grupos_cores, grupos[membro], not_root, nivel+1)
            sub_g.subgraph(new_g)
            not_root.add(membro)

'''
Faz os agrupamentos
Recebe o objeto Digraph e Dicionário com os grupos e membros
'''
def makeGroups(graph, grupos, grupos_cores):
    sub_graphs = {}
    not_root = set()
    #Cria todos os grupos
    for grupo,membros in grupos.items():
        sub_g = makeGroup(grupo, grupos_cores.get(grupo,'Branco'), 1)
        fillGroup(sub_g, grupos, grupos_cores, membros, not_root, 1)
        sub_graphs[grupo]=sub_g

    #Insere tudo no grafo principal
    for k,v in sub_graphs.items():
        if k not in not_root:
            graph.subgraph(v)

def find_actor(grupos, grupo):
    for membro in grupos[grupo]:
        if membro not in grupos.keys():
            return membro
        else:
            return find_actor(grupos, membro)

'''
Cria os relacionamentos
Recebe o objeto Digraph, a lista de relacionamentos e o Dicionário com os grupos e membros
'''
def make_relationships(graph, relacionamentos, grupos):
    for relacionamento in relacionamentos:
        #expandindo os parâmetros da lista
        de, legenda, para, tipo, bilateral = relacionamento
        #Parâmetros básicos
        params = {'dir':dirs[bilateral], 'color':rel_types[tipo], 'fontcolor':rel_types[tipo], 'penwidth':'1.0', 'decorate':'false', 'minlen':'2'}

        #Ajustando caso o relacionamento envolva clusters
        if de in grupos.keys():
            #Criando a chave ltail para o relacionamento com o cluster
            params['ltail'] = 'cluster_'+de
            #Pegando um ator qualquer (o primeiro) dentro do cluster
            deN = find_actor(grupos, de)
        else:
            deN = de
        if para in grupos.keys():
            #Criando a chave lhead para o relacionamento com o cluster
            params['lhead'] = 'cluster_'+para
             #Pegando um ator qualquer (o primeiro) dentro do cluster
            paraN = find_actor(grupos, para)
        else:
            paraN = para
        graph.edge(deN, paraN, label=legenda, **params)

'''
Loop principal
'''
#Monta a lista arquivos .xls, .xlsx e .ods na pasta
files = {f:glob.glob('*'+f) for f in ['.ods','.xls','.xlsx']}

#Cria o gráfico para cada arquivo encontrado
for ext,lista_f in files.items():
    for fileloc in lista_f:
        #ignora arquivos temporários do Excel
        if fileloc.startswith('~$'):
            continue

        print("Processando arquivo: " + fileloc)

        k = fileloc.rfind(ext)
        gattr = dict()
        #allow edges between clusters
        gattr['compound'] = 'true'
        gattr['rankdir'] = 'LR'
        gattr['ranksep'] = '1.0'
        #gattr['dpi'] = '300'
        #gattr['ratio'] = '0.5625'
        gattr['newrank'] = 'true'
        gattr['overlap'] = 'false'
        gattr['fontsize'] = '20'

        g = Digraph(filename=fileloc[:k], engine='dot', format='png', graph_attr=gattr)

        #Constroi os dataframes do excel
        atores, relacionamentos = get_data(fileloc, ext)
        #Checa se get_data foi bem sucedida
        if atores is None and relacionamentos is None:
            continue
        #Faz os nós dos atores
        grupos, grupos_cores = make_actor_nodes(g, atores)
        #Cria os clusters
        makeGroups(g, grupos, grupos_cores)
        #Faz os relacionamentos
        make_relationships(g, relacionamentos, grupos)
        #Cria e abre o arquivo
        g.view()
        #Remove arquivo intermediário
        os.remove(fileloc[:k])


from pyspark.sql import SparkSession
from pyspark.ml.feature import MinMaxScaler
from pyspark.ml.linalg import Vectors

spark = SparkSession.builder.appName('normalization').getOrCreate()
spark.sparkContext.setLogLevel("WARN")

print("### spark starting ###")

records = [
    (1, Vectors.dense([10.0, 10000.00, 1.0]),),
    (2, Vectors.dense([20.0, 30000.00, 2.0]),),
    (3, Vectors.dense([30.0, 40000.00, 3.0]),)
]
columns = ["id", "features"]

features_df = spark.createDataFrame(records, columns)

#print(features_df.take(1))

feature_scaler = MinMaxScaler(inputCol="features", outputCol="sfeatures")
s_model = feature_scaler.fit(features_df)
s_features_df = s_model.transform(features_df)

#print(s_features_df.take(1))

s_features_df.select("features", "sfeatures").show()

spark.stop()


import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import os

all_models = pd.read_csv('model_results.csv')
all_models['Accuracy'] = all_models['target_response']

folder = 'accuracy_plots'

mymax = all_models.query('Task == "Different"').groupby(
    ['c', 'Representation', 'Category', 'Subcategory', 'Cleaned'], as_index=False).mean().groupby(
    ['Representation', 'Category', 'Subcategory', 'Cleaned'], as_index=False)['Accuracy'].max()
sns.catplot('Category', 'Accuracy', col='Representation', hue='Cleaned', kind='bar', data=mymax, aspect=1.5, legend_out=True)
plt.savefig(os.path.join(folder, 'vet_maximimum_accuracy.jpg'))
# plt.show()
plt.close()

mymax.to_csv('model_results_max_accuracy_subcategories.csv')

by_network = all_models.query('Task == "Different"').groupby(
    ['c', 'Representation', 'Cleaned'], as_index=False)['Accuracy'].mean()
sns.lineplot('c', 'Accuracy', hue='Representation', style='Cleaned', data=by_network)
legend = plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
# plt.savefig(os.path.join(folder, 'vet_accuracy_overall.jpg'),
#             bbox_extra_artists=(legend,), bbox_inches='tight')
plt.show()
plt.close()

nrep = all_models['Representation'].nunique()
f, axes = plt.subplots(ncols=nrep, sharex=True, sharey=True, figsize=(16, 12))
for i, rep in enumerate(set(all_models['Representation'])):
    by_category = all_models.query('Task == "Different" and Representation == "{}"'.format(rep)).groupby(
        ['c', 'Category', 'Cleaned'], as_index=False)['Accuracy'].mean()
    if i == nrep-1:
        lgd = 'full'
    else:
        lgd = False
    sns.lineplot('c', 'Accuracy', hue='Category', style='Cleaned', data=by_category, ci=None, ax=axes[i], legend=lgd,
                 ).set_title('Network = {}'.format(rep))
legend = plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.savefig(os.path.join(folder, 'vet_accuracy_categories.jpg'),
            bbox_extra_artists=(legend,), bbox_inches='tight')
# plt.show()
plt.close()

for cat in set(all_models['Category']):
    category = all_models.query('Category == "{}" and Task == "Different"'.format(cat))

    f, axes = plt.subplots(ncols=nrep, sharex=True, sharey=True, figsize=(16, 12))
    for i, rep in enumerate(set(all_models['Representation'])):
        by_subcategory = category.query('Task == "Different" and Representation == "{}"'.format(rep)).groupby(
            ['c', 'Subcategory', 'Cleaned'], as_index=False)['Accuracy'].mean()
        if i == nrep-1:
            lgd = 'full'
        else:
            lgd = False
        sns.lineplot('c', 'Accuracy', hue='Subcategory', style='Cleaned', data=by_subcategory, ci=None, ax=axes[i],
                     legend=lgd).set_title('Network = {}'.format(rep))

    legend = plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
    # plt.show()
    plt.savefig(os.path.join(folder, 'vet_accuracy_{}_subcategories.jpg'.format(cat)),
                bbox_extra_artists=(legend,), bbox_inches='tight')
    plt.close()