import os
from enum import Enum, auto
from random import randint

import pygame

class Main:
    @staticmethod
    def start():
        pygame.font.init()
        os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (400, 100)
        surface = pygame.display.set_mode((1200, 900))
        pygame.display.set_caption('Minesweeper')
        state = States.running
        player = Player()
        grid = Grid(player)
        running = True
        clock = pygame.time.Clock()
        while running:
            clock.tick(30)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                if event.type == pygame.MOUSEBUTTONDOWN and state == States.running:
                    if pygame.mouse.get_pressed()[0]:
                        pos = pygame.mouse.get_pos()
                        grid.click(pos[0], pos[1])
                    elif pygame.mouse.get_pressed()[2]:
                        pos = pygame.mouse.get_pos()
                        grid.mark_mine(pos[0] // 30, pos[1] // 30)
                    if grid.check_if_win():
                        state = States.win
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_SPACE and (state == States.game_over or state == States.win):
                        grid.reload()
                        state = States.running
                    if event.key == pygame.K_b:
                        grid.show_mines()
            surface.fill((0, 0, 0))
            if player.get_health() == 0:
                state = States.game_over
            if state == States.game_over:
                Stats.draw(surface, 'Game over!', (970, 350))
                Stats.draw(surface, 'Press Space to restart', (920, 400))
            elif state == States.win:
                Stats.draw(surface, 'You win!', (1000, 350))
                Stats.draw(surface, 'Press Space to restart', (920, 400))
            grid.draw(surface)
            Stats.draw(surface, 'Lives remaining', (950, 100))
            Stats.draw(surface, str(player.get_health()), (1020, 200))
            pygame.display.flip()

class States(Enum):
    running = auto()
    game_over = auto()
    win = auto()

class Player:
    def __init__(self):
        self.health = 5

    def sub_health(self):
        self.health -= 1

    def get_health(self):
        return self.health

class Stats:
    @staticmethod
    def draw(surface, label, pos):
        textsurface = pygame.font.SysFont('Comic Sans MS', 24).render(label, False, (255, 255, 255))
        surface.blit(textsurface, (pos[0], pos[1]))

class Cell:
    def __init__(self, pos, random_mine):
        self.visible = False
        self.mine = random_mine
        self.show_mine = False
        self.size = 30
        self.color = (200, 200, 200)
        self.pos = pos
        self.label = False
        self.mine_counter = 0
        self.font_color = (0, 0, 0)
        self.marked = False
        self.explosion = False
        self.img_flag = pygame.image.load('../resources/minesweeper/cell-flagged.png')
        self.img_flag = pygame.transform.scale(self.img_flag, (self.size, self.size))

        self.img_explode = pygame.image.load('../resources/minesweeper/mine-exploded.png')
        self.img_explode = pygame.transform.scale(self.img_explode, (self.size, self.size))

        self.img_mine = pygame.image.load('../resources/minesweeper/mine.png')
        self.img_mine = pygame.transform.scale(self.img_mine, (self.size, self.size))

        self.img_cell = []
        for i in range(9):
            _img = pygame.image.load(f'../resources/minesweeper/cell-{i}.png')
            _img = pygame.transform.scale(_img, (self.size, self.size))
            self.img_cell.append(_img)

    def draw(self, surface):
        if self.visible and not self.label and not (self.show_mine and self.mine):
            surface.blit(self.img_cell[0], (self.pos[0], self.pos[1]))
        elif self.label:
            self.show_label(surface, self.mine_counter, self.pos)
        elif self.marked:
            surface.blit(self.img_flag, (self.pos[0], self.pos[1]))
        elif self.show_mine and self.mine:
            surface.blit(self.img_mine, (self.pos[0], self.pos[1]))
        elif self.explosion:
            surface.blit(self.img_explode, (self.pos[0], self.pos[1]))
        else:
            pygame.draw.rect(surface, (50, 50, 50), (self.pos[0], self.pos[1], self.size, self.size))

    def show_label(self, surface, label, pos):
        # textsurface = pygame.font.SysFont('Comic Sans MS', 18).render(label, False, self.font_color)
        # surface.blit(textsurface, (pos[0] + 10, pos[1] + 4))
        surface.blit(self.img_cell[int(label)], (pos[0], pos[1]))

class Grid:
    def __init__(self, player):
        self.player = player
        self.cells = []
        self.search_dirs = [(0, -1), (-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1), (1, 0), (1, -1)]

        for y in range(30):
            self.cells.append([])
            for x in range(30):
                self.cells[y].append(Cell((x * 30, y * 30), self.random_mines()))

        self.lines = []

        for y in range(1, 31, 1):
            temp = []
            temp.append((0, y * 30))
            temp.append((900, y * 30))
            self.lines.append(temp)

        for x in range(1, 31, 1):
            temp = []
            temp.append((x * 30, 0))
            temp.append((x * 30, 900))
            self.lines.append(temp)

    def random_mines(self):
        r = randint(0, 10)
        if r > 9:
            return True
        else:
            return False

    def draw(self, surface):
        for row in self.cells:
            for cell in row:
                cell.draw(surface)
        for line in self.lines:
            pygame.draw.line(surface, (0, 125, 0), line[0], line[1])

    def is_within_bounds(self, x, y):
        return x >= 0 and x < 30 and y >= 0 and y < 30

    def search(self, x, y):
        if not self.is_within_bounds(x, y):
            return

        cell = self.cells[y][x]

        if cell.visible:
            return

        if cell.mine:
            cell.explosion = True
            self.player.sub_health()
            return

        cell.visible = True

        num_mines = self.num_of_mines(x, y)

        if num_mines > 0:
            cell.label = True
            cell.mine_counter = str(num_mines)
            return

        for xx, yy in self.search_dirs:
            self.search(x + xx, y + yy)

    def num_of_mines(self, x, y):
        counter = 0
        for xx, yy in self.search_dirs:
            if self.is_within_bounds(x + xx, y + yy) and self.cells[y + yy][x + xx].mine:
                counter += 1
        return counter

    def click(self, x, y):
        grid_x, grid_y = x // 30, y // 30
        self.search(grid_x, grid_y)

    def reload(self):
        self.player.health = 5
        for row in self.cells:
            for cell in row:
                cell.visible = False
                cell.label = False
                cell.marked = False
                cell.show_mine = False
                cell.explosion = False
                cell.mine = self.random_mines()

    def check_if_win(self):
        if self.player.health < 1:
            return False
        for row in self.cells:
            for cell in row:
                if not cell.visible and not cell.mine:
                    return False
        return True

    def show_mines(self):
        for row in self.cells:
            for cell in row:
                if not cell.show_mine:
                    cell.show_mine = True
                else:
                    cell.show_mine = False

    def mark_mine(self, x, y):
        self.cells[y][x].marked = True

if __name__ == "__main__":
    Main.start()


from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.utils.html import mark_safe
from rooms.models import Room
from .models import User

# admin.ModelAdmin을 상속받는 경우
# @admin.register(User)
# class CustomUserAdmin(admin.ModelAdmin):

#     """ Custom User Admin """

#     list_display = ("username", "email", "gender", "language", "currency", "superhost")
#     list_filter = (
#         "language",
#         "currency",
#         "superhost",
#     )

class RoomInline(admin.TabularInline):
    model = Room

# 방법 1
@admin.register(User)  # 데코레이터를 붙여 주면 CustomUserAdmin 클래스가 models.User를 사용한다는 의미
class CustomUserAdmin(UserAdmin):

    """Custom User Admin"""

    inlines = (RoomInline,)

    fieldsets = UserAdmin.fieldsets + (
        (
            "Custom Profile",
            {
                "fields": (
                    "avatar",
                    "gender",
                    "bio",
                    "birthdate",
                    "language",
                    "currency",
                    "superhost",
                    "login_method",
                )
            },
        ),
    )

    list_display = (
        "username",
        # "get_thumbnail",
        "first_name",
        "last_name",
        "email",
        "is_active",
        "language",
        "currency",
        "superhost",
        "is_staff",
        "is_superuser",
        "email_verified",
        "email_secret",
        "login_method",
    )
    list_filter = UserAdmin.list_filter + ("superhost",)

    # def get_thumbnail(self, obj):
    #     return mark_safe(f"<img width='50px' src='{obj.avatar}' />")

    # get_thumbnail.short_description = "avatar"

# admin.site.register(models.User, CustomUserAdmin)  # 방법 2


# read the sequence file to python
n = 0

for line in open("ampR.fastq"):
    line = line.strip()
    if not line:
        continue
    n += 1
    # starts with '@'
    if line.startswith("@") and n != 4:
        name = line[1:].split(" ", maxsplit=1)[0]
        seq = score = ""
        n = 1
    elif n == 2:
        seq = line
    elif n == 3:
        assert line.startswith("+"), "error fastq record"
    elif n == 4:
        score = line
    else:
        pass

print("name: %s\nseq: %s" % (name, seq))

# length
print("length: %s" % len(seq))

# score
phred33 = [ord(i)-33 for i in score]
print("score: %s\nPhred33: %s" % (score, phred33))


from flask import Flask, render_template, request

from mbta_helper import find_stop_near

app = Flask(__name__, template_folder="templates")

@app.route("/")
def index():
    """
    This function asks for the user's location
    """
    return render_template("index.html")

@app.route("/POST/nearest", methods=["POST","GET"])
def find():
    """
    This function returns whether the location entered has a station nearby and if there is wheelchair accessibility
    """
    if request.method == "POST":
        place = request.form["location"]
        place = str(place)
        result = find_stop_near(place)
        if result == "MBTA Not Available":
            return render_template("notavailable.html")
        else:
            result = result.split(",")
            return render_template("available.html", location = result[0], wheelchair = result[1])

if __name__ == "__main__":
    app.run(debug=True)


# LinearlyVariableInfill
"""
Linearly Variable Infill for 3D prints.

Author: Barnabas Nemeth
Version: 1.5

"""

from ..Script import Script
from UM.Logger import Logger
from UM.Application import Application
import re #To perform the search
from cura.Settings.ExtruderManager import ExtruderManager
from collections import namedtuple
from enum import Enum
from typing import List, Tuple
from UM.Message import Message
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")

__version__ = '1.5'

##-----------------------------------------------------------------------------------------------------------------------------------------------------------------

Point2D = namedtuple('Point2D', 'x y')
Segment = namedtuple('Segment', 'point1 point2')

class Infill(Enum):
    """Enum for infill type."""

    LINEAR = 1  # Linear infill like rectilinear or triangles

class Section(Enum):
    """Enum for section type."""

    NOTHING = 0
    INNER_WALL = 1
    OUTER_WALL = 2
    INFILL = 3

def dist(segment: Segment, point: Point2D) -> float:
    """Calculate the distance from a point to a line with finite length.

    Args:
        segment (Segment): line used for distance calculation
        point (Point2D): point used for distance calculation

    Returns:
        float: distance between ``segment`` and ``point``
    """
    px = segment.point2.x - segment.point1.x
    py = segment.point2.y - segment.point1.y
    norm = px * px + py * py
    u = ((point.x - segment.point1.x) * px + (point.y - segment.point1.y) * py) / float(norm)
    if u > 1:
        u = 1
    elif u < 0:
        u = 0
    x = segment.point1.x + u * px
    y = segment.point1.y + u * py
    dx = x - point.x
    dy = y - point.y

    return (dx * dx + dy * dy) ** 0.5

def two_points_distance(point1: Point2D, point2: Point2D) -> float:
    """Calculate the euclidean distance between two points.

    Args:
        point1 (Point2D): first point
        point2 (Point2D): second point

    Returns:
        float: euclidean distance between the points
    """
    return ((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2) ** 0.5

def min_distance_to_segment(segment: Segment, segments: List[Segment]) -> float:
    """Calculate the minimum distance from the midpoint of ``segment`` to the nearest segment in ``segments``.

    Args:
        segment (Segment): segment to use for midpoint calculation
        segments (List[Segment]): segments list

    Returns:
        float: the smallest distance from the midpoint of ``segment`` to the nearest segment in the list
    """
    middlePoint = Point2D((segment.point1.x + segment.point2.x) / 2, (segment.point1.y + segment.point2.y) / 2)

    return min(dist(s, middlePoint) for s in segments)

def getXY(currentLineINcode: str) -> Point2D:
    """Create a ``Point2D`` object from a gcode line.

    Args:
        currentLineINcode (str): gcode line

    Raises:
        SyntaxError: when the regular expressions cannot find the relevant coordinates in the gcode

    Returns:
        Point2D: the parsed coordinates
    """
    searchX = re.search(r"X(\d*\.?\d*)", currentLineINcode)
    searchY = re.search(r"Y(\d*\.?\d*)", currentLineINcode)
    if searchX and searchY:
        elementX = searchX.group(1)
        elementY = searchY.group(1)
    else:
        raise SyntaxError('Gcode file parsing error for line {currentLineINcode}')

    return Point2D(float(elementX), float(elementY))

def mapRange(a: Tuple[float, float], b: Tuple[float, float], s: float) -> float:
    """Calculate a multiplier for the extrusion value from the distance to the perimeter.

    Args:
        a (Tuple[float, float]): a tuple containing:
            - a1 (float): the minimum distance to the perimeter (always zero at the moment)
            - a2 (float): the maximum distance to the perimeter where the interpolation is performed
        b (Tuple[float, float]): a tuple containing:
            - b1 (float): the maximum flow as a fraction
            - b2 (float): the minimum flow as a fraction
        s (float): the euclidean distance from the middle of a segment to the nearest perimeter

    Returns:
        float: a multiplier for the modified extrusion value
    """
    (a1, a2), (b1, b2) = a, b

    return b1 + ((s - a1) * (b2 - b1) / (a2 - a1))

def gcode_template(x: float, y: float, extrusion: float) -> str:
    """Format a gcode string from the X, Y coordinates and extrusion value.

    Args:
        x (float): X coordinate
        y (float): Y coordinate
        extrusion (float): Extrusion value

    Returns:
        str: Gcode line
    """
    return "G1 X{} Y{} E{}".format(round(x, 3), round(y, 3), round(extrusion, 5))

def is_layer(line: str) -> bool:
    """Check if current line is the start of a layer section.

    Args:
        line (str): Gcode line

    Returns:
        bool: True if the line is the start of a layer section
    """
    return line.startswith(";LAYER:")

def is_innerwall(line: str) -> bool:
    """Check if current line is the start of an inner wall section.

    Args:
        line (str): Gcode line

    Returns:
        bool: True if the line is the start of an inner wall section
    """
    return line.startswith(";TYPE:WALL-INNER")

def is_outerwall(line: str) -> bool:
    """Check if current line is the start of an outer wall section.

    Args:
        line (str): Gcode line

    Returns:
        bool: True if the line is the start of an outer wall section
    """
    return line.startswith(";TYPE:WALL-OUTER")

def ez_nyomtatasi_vonal(line: str) -> bool:
    """Check if current line is a standard printing segment.

    Args:
        line (str): Gcode line

    Returns:
        bool: True if the line is a standard printing segment
    """
    return "G1" in line and " X" in line and "Y" in line and "E" in line

def is_infill(line: str) -> bool:
    """Check if current line is the start of an infill.

    Args:
        line (str): Gcode line

    Returns:
        bool: True if the line is the start of an infill section
    """
    return line.startswith(";TYPE:FILL")

def fill_type(Mode):
    """Definie the type of Infill pattern

       Linearly Variable Infill like lineas or triangles = 1

    Args:
        line (Mode): Infill Pattern

    Returns:
        Int: the Type of infill pattern
    """
    iMode=0
    if Mode == 'grid':
        iMode=1
    if Mode == 'lines':
        iMode=1
    if Mode == 'triangles':
        iMode=1
    if Mode == 'trihexagon':
        iMode=1
    if Mode == 'cubic':
        iMode=1
    if Mode == 'cubicsubdiv':
        iMode=0
    if Mode == 'tetrahedral':
        iMode=1
    if Mode == 'quarter_cubic':
        iMode=1
    if Mode == 'concentric':
        iMode=0
    if Mode == 'zigzag':
        iMode=0
    if Mode == 'cross':
        iMode=0
    if Mode == 'cross_3d':
        iMode=0
    if Mode == 'gyroid':
        iMode=0

    return iMode

class LinearlyVariableInfill(Script):
    def getSettingDataString(self):
        return """{
            "name": "Linearly Variable Infill",
            "key": "LinearlyVariableInfill",
            "metadata": {},
            "version": 2,
            "settings":
            {
                "variableSegmentLength":
                {
                    "label": "Valtoztatott szakasz hossza",
                    "description": "Distance of the gradient (max to min) in mm",
                    "unit": "mm",
                    "type": "float",
                    "default_value": 6.0,
                    "minimum_value": 1.0,
                    "minimum_value_warning": 2.0
                },
                "divisionNR":
                {
                    "label": "Szakasz felosztasanak szama",
                    "description": "Only applicable for Linearly Variable Infills; number of segments within the gradient(fullSegmentLength=variableSegmentLength / divisionNR); use sensible values to not overload",
                    "type": "int",
                    "default_value": 4,
                    "minimum_value": 1,
                    "minimum_value_warning": 2
                },

                "variableSpeed":
                {
                    "label": "Valtozo sebesseg",
                    "description": "Activate also Valtozo sebesseg linked to the gradual flow",
                    "type": "bool",
                    "default_value": false
                },
                "maxSpeedFactor":
                {
                    "label": "Max sebesseg szorzo",
                    "description": "Maximum over speed factor",
                    "unit": "%",
                    "type": "int",
                    "default_value": 200,
                    "minimum_value": 100,
                    "maximum_value": 400,
                    "minimum_value_warning": 110,
                    "maximum_value_warning": 370,
                    "enabled": "variableSpeed"

                },
                "minSpeedFactor":
                {
                    "label": "Min sebesseg szorzo",
                    "description": "Minimum over speed factor",
                    "unit": "%",
                    "type": "int",
                    "default_value": 60,
                    "minimum_value": 10,
                    "maximum_value": 100,
                    "minimum_value_warning": 40,
                    "maximum_value_warning": 90,
                    "enabled": "variableSpeed"

                },
                "extruderNR":
                {
                    "label": "Extruder sorszam",
                    "description": "Define Extruder szam in case of multi extruders",
                    "unit": "",
                    "type": "int",
                    "default_value": 1

                }
            }
        }"""

## -----------------------------------------------------------------------------
#
#  Main Prog
#
## -----------------------------------------------------------------------------

    def execute(self, data):
        Logger.log('w', 'Plugin is starting '  )
        print('naygvera')
        division_nr = float(self.getSettingValueByKey("divisionNR"))
        variable_segment_lengh = float(self.getSettingValueByKey("variableSegmentLength"))
        extruder_nr  = self.getSettingValueByKey("extruderNR")
        extruder_nr = extruder_nr -1
        variable_speed= bool(self.getSettingValueByKey("variableSpeed"))
        max_speed_factor = float(self.getSettingValueByKey("maxSpeedFactor"))
        max_speed_factor = max_speed_factor /100
        min_speed_factor = float(self.getSettingValueByKey("minSpeedFactor"))
        min_speed_factor = min_speed_factor /100

        #   machine_extruder_count
     #   extruder_count=Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value")
      #  extruder_count = extruder_count-1
       # if extruder_nr>extruder_count :
        #    extruder_nr=extruder_count

        # Deprecation function
        extrud = list(Application.getInstance().getGlobalContainerStack().extruders.values())
        #extrud = Application.getInstance().getGlobalContainerStack().extruderList
        Message('Extrud:{}'.format(extrud), title = catalog.i18nc("@info:title", "Post Processing")).show()
        infillpattern = extrud[extruder_nr].getProperty("infill_pattern", "value")
        connectinfill = extrud[extruder_nr].getProperty("zig_zaggify_infill", "value")

        """Parse Gcode and modify infill portions with an extrusion width gradient."""
        currentSection = Section.NOTHING
        lastPosition = Point2D(-10000, -10000)
        littleSegmentLength = variable_segment_lengh / division_nr

        infill_type=fill_type(infillpattern)
        if infill_type == 0:
            #
            Logger.log('d', 'Infill Pattern not supported : ' + infillpattern)
            Message('Infill Pattern not supported : ' + infillpattern , title = catalog.i18nc("@info:title", "Post Processing")).show()

            return None

        if connectinfill == True:
            #
            Logger.log('d', 'Connect Infill Lines no supported')
            Message('Gcode must be generate without Connect Infill Lines mode activated' , title = catalog.i18nc("@info:title", "Post Processing")).show()
            return None

        Logger.log('d',  "GradientFill Param : " + str(littleSegmentLength) + "/" + str(division_nr)+ "/" + str(variable_segment_lengh) ) #str(max_flow) + "/" + str(min_flow) + "/" +
        Logger.log('d',  "Pattern Param : " + infillpattern + "/" + str(infill_type) )

        for layer in data:
            layer_index = data.index(layer)
            lines = layer.split("\n")
            for currentLineINcode in lines:
                new_Line=""
                stringFeed = ""
                line_index = lines.index(currentLineINcode)

                if is_layer(currentLineINcode):
                    perimeterSegments = []

                if is_innerwall(currentLineINcode):
                    currentSection = Section.INNER_WALL
                    # Logger.log('d', 'is_innerwall'  )

                if is_outerwall(currentLineINcode):
                    currentSection = Section.OUTER_WALL
                    # Logger.log('d', 'is_outerwall' )

                if currentSection == Section.INNER_WALL:
                    if ez_nyomtatasi_vonal(currentLineINcode):
                        Logger.log('d', 'Ez sor rossz ' + currentLineINcode)
                        perimeterSegments.append(Segment(getXY(currentLineINcode), lastPosition))

                if is_infill(currentLineINcode):
                    # Log Size of perimeterSegments for debuging
                    Logger.log('d', 'PerimeterSegments seg : {}'.format(len(perimeterSegments)))
                    currentSection = Section.INFILL
                    # ! Important
                    continue

                if currentSection == Section.INFILL:
                    if "F" in currentLineINcode and "G1" in currentLineINcode:
                        searchSpeed = re.search(r"F(\d*\.?\d*)", currentLineINcode)

                        if searchSpeed:
                            current_speed=float(searchSpeed.group(1))
                            new_Line="G1 F{}\n".format(current_speed)
                        else:
                            Logger.log('d', 'Gcode file parsing error for line : ' + currentLineINcode )

                    if "E" in currentLineINcode and "G1" in currentLineINcode and "X" in currentLineINcode and "Y" in currentLineINcode:
                        currentPosition = getXY(currentLineINcode)
                        splitLine = currentLineINcode.split(" ")

                        # ha lineraris
                        if infill_type == 1:
                            for element in splitLine:
                                if "E" in element:
                                    E_inCode = float(element[1:])

                            fullSegmentLength = two_points_distance(lastPosition, currentPosition)
                            segmentSteps = fullSegmentLength / littleSegmentLength
                            extrudeLengthPERsegment = (0.006584 * fullSegmentLength) / segmentSteps
                            E_inCode_last = E_inCode - (extrudeLengthPERsegment * segmentSteps)
                            littlesegmentDirectionandLength = Point2D((currentPosition.x - lastPosition.x) / fullSegmentLength * littleSegmentLength,(currentPosition.y - lastPosition.y) / fullSegmentLength * littleSegmentLength)
                            speed_deficit = ((current_speed * max_speed_factor + current_speed * min_speed_factor) / division_nr)
                            step_number = 0
                            last_step_number = 0

                            if segmentSteps >= 2:
                                # new_Line=new_Line+"; LinearlyVariableInfill segmentSteps >= 2\n"
                                for step in range(int(segmentSteps)):
                                    segmentEnd = Point2D(lastPosition.x + littlesegmentDirectionandLength.x, lastPosition.y + littlesegmentDirectionandLength.y)
                                    extrudeLength=E_inCode_last+extrudeLengthPERsegment
                                    if perimeterSegments==[] :
                                        Logger.log('d', 'Itt a hiba ' + currentLineINcode)
                                    shortestDistance = min_distance_to_segment(Segment(lastPosition, segmentEnd), perimeterSegments)
                                    if shortestDistance < variable_segment_lengh:
                                        segmentSpeed = current_speed

                                        if variable_speed:
                                            if variable_speed:

                                             if step_number < division_nr:
                                                 segmentSpeed = current_speed * min_speed_factor + (speed_deficit * step_number)

                                             if step_number >= division_nr:
                                                 segmentSpeed = current_speed * max_speed_factor

                                             if step_number >= segmentSteps - division_nr:
                                                 segmentSpeed = current_speed * max_speed_factor - (speed_deficit * last_step_number)
                                                 last_step_number=last_step_number + 1
                                            stringFeed = " F{}".format(int(segmentSpeed))

                                    else:
                                         segmentSpeed = current_speed * min_speed_factor

                                         if variable_speed:
                                            if step_number < division_nr:
                                                 segmentSpeed = current_speed * min_speed_factor + (speed_deficit * step_number)

                                            if step_number >= division_nr:
                                                 segmentSpeed = current_speed * max_speed_factor

                                            if step_number >= segmentSteps - division_nr:
                                                 segmentSpeed = current_speed * max_speed_factor - (speed_deficit * last_step_number)
                                                 last_step_number=last_step_number + 1

                                            stringFeed = " F{}".format(int(segmentSpeed))

                                    new_Line=new_Line + gcode_template(segmentEnd.x, segmentEnd.y, extrudeLength) + stringFeed + "\n" #szakaszExtrudalas
                                    lastPosition  = segmentEnd
                                    E_inCode_last = extrudeLength
                                    step_number = step_number + 1

                                segmentSpeed = current_speed * min_speed_factor
                                lastSpeed = " F{}".format(int(segmentSpeed))
                                new_Line=new_Line + gcode_template(currentPosition.x, currentPosition.y, E_inCode, ) + lastSpeed + "\n" #Original line for finish
                                lines[line_index] = new_Line

                            else :
                                outPutLine = ""
                                # outPutLine = "; LinearlyVariableInfill segmentSteps < 2\n"

                                for element in splitLine:
                                    if "E" in element:
                                        outPutLine = outPutLine + "E" + str(round(E_inCode, 5))
                                    else:
                                        outPutLine = outPutLine + element + " "
                                outPutLine = outPutLine # + "\n"
                                lines[line_index] = outPutLine

                            # writtenToFile = 1

                    #
                    # comment like ;MESH:NONMESH
                    #
                    if ";" in currentLineINcode:
                        currentSection = Section.NOTHING
                        lines[line_index] = currentLineINcode # other Comment
                #
                # line with move
                #
                if "X" in currentLineINcode and "Y" in currentLineINcode and ("G1" in currentLineINcode or "G0" in currentLineINcode):
                    lastPosition  = getXY(currentLineINcode)

            final_lines = "\n".join(lines)
            data[layer_index] = final_lines
        return data


from random import choice

def get_binary():
    output = []
    for i in range(8):
        output.append(choice([0,1]))
    return output

def get_binary_sum():
    output = []
    for i in range(8):
        output.append(choice([0,1]))
    return sum(output)

samps = []
counts = 0

while 0 not in samps:
    samps.append(get_binary_sum())
    counts += 1

print(samps)
print(counts)


from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.db.models import Q

from .models import Employee
from .forms import AddEmployeeForm

@login_required(login_url='authapp:login')
def index(request):
    context = dict()
    context['employees'] = Employee.objects.all().order_by('-joined_date')[:5]
    return render(request, 'employee/index.html', context)

@login_required(login_url='authapp:login')
def view_all(request):
    context = dict()
    search = ''
    try:
        search = request.GET['search']
    except:
        pass
    context['employees'] = Employee.objects.all().order_by('-joined_date')
    if search is not None:
        print(search)
        context['employees'] = Employee.objects.filter(
            Q(name__icontains=search) |
            Q(email__icontains=search) |
            Q(phone__icontains=search) |
            Q(department__name__icontains=search) |
            Q(role__name__icontains=search)
        ).order_by('-joined_date')
    return render(request, 'employee/view_all.html', context)

@login_required(login_url='authapp:login')
def view_single_employee(request, id):
    context = dict()
    try:
        employee = Employee.objects.get(pk=id)
    except:
        pass

    context['employee'] = employee

    return render(request, 'employee/employee.html', context)

@login_required(login_url='authapp:login')
def add(request):
    context = dict()
    context['form'] = AddEmployeeForm()
    if request.method == 'POST':
        form = AddEmployeeForm(request.POST)
        context['form'] = form
        if form.is_valid():
            form.save()
            messages.info(request, 'Employee added successfully.')
            return redirect('index')
        else:
            return render(request, 'employee/add.html', context)
    else:
        context['form'] = AddEmployeeForm()
    return render(request, 'employee/add.html', context)

@login_required(login_url='authapp:login')
def update(request, id):
    context = dict()
    try:
        employee = Employee.objects.get(pk=id)
    except Employee.DoesNotExist:
        messages.error(request, f'Empoyee does not exist with id {id}')
        return render(request, 'employee/update.html', context)

    form = AddEmployeeForm(instance=employee)
    context['form'] = form

    if request.method == 'POST':
        form = AddEmployeeForm(request.POST, instance=employee)
        context['form'] = form
        if form.is_valid():
            form.save()
            messages.info(request, 'Employee updated successfully.')
            return redirect('employee:view_single_employee', employee.id)
    print(request.resolver_match.url_name)
    return render(request, 'employee/update.html', context)

@login_required(login_url='authapp:login')
def delete(request, id):
    try:
        employee = Employee.objects.get(pk=id)
    except Employee.DoesNotExist:
        messages.error(request, 'Employee does not exist.')
        return redirect('employee:view_all')

    employee.delete()

    messages.info(request, 'Employee deleted successfully.')
    return redirect('employee:view_all')


import time
import numpy as np
import json
from simplex_algorithm.Interaction import Interaction

class SimplexSolver():
    '''
    Class is responsable to solve maximization Linear Programming Problems.
        @author: Matheus Phelipe
    '''
    def __init__(self, matrix_a, matrix_b, matrix_c, max_iteractions, has_slack_var = True) -> None:

        try:
            self.convert_data_2_np_array(matrix_a, matrix_b, matrix_c)
            self.has_slack_var = has_slack_var
            self.is_done = False
            self.iterations = 0
            self.max_iteractions = max_iteractions
            self.previous_interactions = []
        except Exception as ex:
            print(f'Failed to create numpy matrices\n {ex}')

    def convert_data_2_np_array(self, matrix_a, matrix_b, matrix_c):
        self.matrix_a = np.array([item for item in matrix_a])
        self.matrix_b = np.reshape(np.array([item for item in matrix_b]), (len(matrix_b), 1))
        self.matrix_c = np.reshape(np.array([item for item in matrix_c]), (1, len(matrix_c)))

    def start(self, set_B, set_N):
        if self.has_slack_var is False:
            self.add_slack_variable()

        start = time.time()

        while self.is_done is False and self.iterations <= self.max_iteractions:
            print(f'Starting iteraction {self.iterations}')
            b_inverted, list_z_index, matrix_XB = self.make_iteraction(set_B, set_N)
            print(f'Z score:{self.previous_interactions[-1].z_value}')
            self.iterations += 1
            if self.is_done is False:
                index_entering = self.compare_tuples(list_z_index)[1]
                leaving_data = self.calculate_leaving_variable(b_inverted, index_entering)
                alpha_data = self.calculate_alpha(leaving_data, matrix_XB, set_B)
                index_leaving = set_B[alpha_data[0][1]]
                print(f'XB({index_leaving}) is leaving. XB({index_entering}) is entering')
                set_B = self.swap_matrices_index(set_B, index_leaving, index_entering)
                set_N = self.swap_matrices_index(set_N, index_entering, index_leaving)

        end = time.time()
        print(f'Simplex Iteractions has finished: {end-start}')
        self.generate_report()
        return self.previous_interactions

    def add_slack_variable(self):
        pass

    def swap_matrices_index(self, set_list, leaving, entering):
        aux = []
        for index in set_list:
            if index == leaving:
                aux.append(entering)
            else:
                aux.append(index)
        return aux

    def make_iteraction(self, set_B, set_N):
        try:
            matrix_B = self.matrix_a[:, set_B]
            matrix_N = self.matrix_a[:, set_N]
            matrix_CB = self.matrix_c[:, set_B]
            return self.calculate(matrix_B=matrix_B, matrix_CB=matrix_CB, set_B=set_B, set_N=set_N, matrix_N=matrix_N)
        except Exception as ex:
            print(f'Failed on make_iteraction method\n{ex}')

    def calculate(self, matrix_B, matrix_CB, set_B, set_N, matrix_N):
        b_inverted = np.linalg.inv(matrix_B)
        u = np.matmul(matrix_CB, b_inverted)
        matrix_XB = np.matmul(b_inverted, self.matrix_b)
        z = np.matmul(u, self.matrix_b)[:,0][0]

        list_z_index = [(np.matmul(u, self.matrix_a[:, index])[0] - self.matrix_c[:, index][0], index) for index in set_N]

        self.previous_interactions.append(Interaction(matrix_B, set_B, matrix_N, set_N, matrix_XB, z))
        self.is_done = all(i[0] >= 0 for i in list_z_index)

        return b_inverted, list_z_index, matrix_XB

    def compare_tuples(self, tuple): #(z_value, index)
        smaller = tuple[0]
        for index in range(1, len(tuple)):
            if tuple[index][0] < smaller[0]:
                smaller = tuple[index]
        return smaller

    def calculate_leaving_variable(self, b_inverted, index_N):
        y_index_N = np.matmul(b_inverted, self.matrix_a[:, index_N])
        greater_0 = []
        for index in range(len(y_index_N)):
            if y_index_N[index] > 0:
                greater_0.append(index)

        return y_index_N, greater_0

    def calculate_alpha(self, leaving_data, matrix_XB, set_B):
        alpha_list = [(matrix_XB[index][0]/ leaving_data[0][index], index) for index in leaving_data[1]]
        smaller = alpha_list[0]
        index_leaving = set_B[0]
        for index in range(1, len(alpha_list[0])):
            if(alpha_list[index][0]<smaller[0]):
                smaller = alpha_list[index]
                index_leaving = set_B[index]

        return smaller, index_leaving #retorna posição que deve ser removida

    def generate_report(self):
        #dict_list = [item.__dict__ for item in self.previous_interactions]
        #return json.dumps(dict_list)
        print(self.previous_interactions)


import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from mayavi import mlab
from scipy.ndimage import map_coordinates
from scipy import signal, interpolate
from PIL import Image, ImageDraw
from matplotlib.colors import ListedColormap
from tqdm import tqdm, trange

def create_block_diagram(strat, prop, facies, dx, ve, xoffset, yoffset, scale, ci, plot_strat, plot_contours, plot_sides, color_mode, bottom, topo_min, topo_max, export, opacity):
    """function for creating a 3D block diagram in Mayavi
    strat - input array with stratigraphic surfaces
    facies - property or facies array
    dx - size of gridcells in the horizontal direction in 'strat'
    ve - vertical exaggeration
    offset - offset in the y-direction relative to 0
    scale - scaling factor
    ci - contour interval
    strat_switch - 1 if you want to plot stratigraphy on the sides; 0 otherwise
    contour_switch - 1 if you want to plot contours on the top surface; 0 otherwise
    bottom - elevation value for the bottom of the block"""

    r,c,ts = np.shape(strat)

    # if z is increasing downward:
    if np.max(strat[:, :, -1] - strat[:, :, 0]) < 0:
        strat = -1 * strat

    z = scale*strat[:,:,ts-1].T
    if plot_strat:
        z1 = strat[:,:,0].T
    else:
        z1 = strat[:,:,-1].T

    X1 = scale*(xoffset + np.linspace(0,c-1,c)*dx) # x goes with c and y with r
    Y1 = scale*(yoffset + np.linspace(0,r-1,r)*dx)
    X1_grid , Y1_grid = np.meshgrid(X1, Y1)

    if export == 1:
        surf = mlab.surf(X1,Y1,z,warp_scale=ve,colormap='gist_earth',vmin=scale*topo_min,vmax=scale*topo_max, opacity = opacity)
        # cmapf = matplotlib.cm.get_cmap('Blues_r',256)
        BluesBig = matplotlib.cm.get_cmap('Blues_r', 512)
        newcmp = ListedColormap(BluesBig(np.linspace(0.0, 1.0, 256)))
        normf = matplotlib.colors.Normalize(vmin=scale*topo_min,vmax=scale*topo_max)
        z_range = np.linspace(scale*topo_min, scale*topo_max, 256)
        surf.module_manager.scalar_lut_manager.lut.table = (np.array(newcmp(normf(z_range)))*255).astype('uint8')
    else:
        # if color_mode == 'property':
        #     mlab.mesh(X1_grid, Y1_grid, ve*z, scalars = prop[:, :, -1], colormap='YlOrBr', vmin=0, vmax=1, opacity = opacity)
        #     if not plot_sides:
        #         mlab.mesh(X1_grid, Y1_grid, ve*scale*strat[:,:,0].T, scalars = facies[:, :, 0], colormap='YlOrBr', vmin=0, vmax=1, opacity = opacity)
        # else:
        mlab.surf(X1, Y1, z, warp_scale=ve, colormap='gist_earth', opacity = opacity) #, line_width=5.0, vmin=scale*topo_min,vmax=scale*topo_max, representation='wireframe')
        if not plot_sides:
            mlab.surf(X1, Y1, scale*strat[:,:,0].T, warp_scale=ve, colormap='gist_earth', opacity=opacity) #colormap='gist_earth',vmin=scale*topo_min,vmax=scale*topo_max, opacity = opacity)
    if plot_contours:
        vmin = scale * topo_min #np.min(strat[:,:,-1])
        vmax = scale * topo_max #np.max(strat[:,:,-1])
        contours = list(np.arange(vmin, vmax, ci*scale)) # list of contour values
        mlab.contour_surf(X1, Y1, z, contours=contours, warp_scale=ve, color=(0,0,0), line_width=1.0)

    if plot_sides:
        gray = (0.6,0.6,0.6) # color for plotting sides

        # updip side:
        vertices, triangles = create_section(z1[:,0],dx,bottom)
        x = scale*(xoffset + vertices[:,0])
        y = scale*(yoffset + np.zeros(np.shape(vertices[:,0])))
        z = scale*ve*vertices[:,1]
        mlab.triangular_mesh(x, y, z, triangles, color=gray, opacity = opacity)

        # downdip side:
        vertices, triangles = create_section(z1[:,-1],dx,bottom)
        x = scale*(xoffset + vertices[:,0])
        y = scale*(yoffset + (r-1)*dx*np.ones(np.shape(vertices[:,0])))
        z = scale*ve*vertices[:,1]
        mlab.triangular_mesh(x, y, z, triangles, color=gray, opacity = opacity)

        # left edge (looking downdip):
        vertices, triangles = create_section(z1[0,:],dx,bottom)
        x = scale*(xoffset + np.zeros(np.shape(vertices[:,0])))
        y = scale*(yoffset + vertices[:,0])
        z = scale*ve*vertices[:,1]
        mlab.triangular_mesh(x, y, z, triangles, color=gray, opacity = opacity)

        # right edge (looking downdip):
        vertices, triangles = create_section(z1[-1,:],dx,bottom)
        x = scale*(xoffset + (c-1)*dx*np.ones(np.shape(vertices[:,0])))
        y = scale*(yoffset + vertices[:,0])
        z = scale*ve*vertices[:,1]
        mlab.triangular_mesh(x, y, z, triangles, color=gray, opacity = opacity)

        # bottom face of block:
        vertices = dx*np.array([[0,0],[c-1,0],[c-1,r-1],[0,r-1]])
        triangles = [[0,1,3],[1,3,2]]
        x = scale*(xoffset + vertices[:,0])
        y = scale*(yoffset + vertices[:,1])
        z = scale*bottom*np.ones(np.shape(vertices[:,0]))
        mlab.triangular_mesh(x, y, ve*z, triangles, color=gray, opacity = opacity)

def add_stratigraphy_to_block_diagram(strat, prop, facies, dx, ve, xoffset, yoffset, scale, plot_surfs, color_mode, colors, colormap, line_thickness, export, opacity):
    """function for adding stratigraphy to the sides of a block diagram
    colors layers by relative age
    strat - input array with stratigraphic surfaces
    facies - 1D array of facies codes for layers
    h - channel depth (height of point bar)
    thalweg_z - array of thalweg elevations for each layer
    dx - size of gridcells in the horizontal direction in 'strat'
    ve - vertical exaggeration
    offset - offset in the y-direction relative to 0
    scale - scaling factor
    plot_surfs - if equals 1, stratigraphic boundaries will be plotted on the sides as black lines
    color_mode - determines what kind of plot is created; can be 'property', 'time', or 'facies'
    colors - colors scheme for facies (list of RGB values)
    line_thickness - tube radius for plotting layers on the sides
    export - if equals 1, the display can be saved as a VRML file for use in other programs (e.g., 3D printing)"""
    r,c,ts=np.shape(strat)
    if color_mode == 'time':
        norm = matplotlib.colors.Normalize(vmin=0.0, vmax=ts-1)
        cmap = matplotlib.cm.get_cmap(colormap)
    if (color_mode == 'property') | (color_mode == 'facies'):
        norm = matplotlib.colors.Normalize(vmin=0.0, vmax=0.35)
        cmap = matplotlib.cm.get_cmap(colormap)

    for layer_n in trange(ts-1): # main loop
        vmin = 0.0
        vmax = 0.35
        top = strat[:,0,layer_n+1]  # updip side
        base = strat[:,0,layer_n]
        if color_mode == "property":
            props = prop[:,0,layer_n]
        if plot_surfs:
            Y1 = scale*(yoffset + dx*np.arange(0,r))
            X1 = scale*(xoffset + np.zeros(np.shape(base)))
            Z1 = ve*scale*base
            mlab.plot3d(X1,Y1,Z1,color=(0,0,0),tube_radius=line_thickness)
        if np.max(top-base)>0:
            Points,Inds = triangulate_layers(top,base,dx)
            for i in range(len(Points)):
                vertices = Points[i]
                triangles, scalars = create_triangles(vertices)
                Y1 = scale*(yoffset + vertices[:,0])
                X1 = scale*(xoffset + dx*0*np.ones(np.shape(vertices[:,0])))
                Z1 = scale*vertices[:,1]
                if color_mode == "property":
                    scalars = props[Inds[i]]
                else:
                    scalars = []
                plot_layers_on_one_side(layer_n, facies, color_mode, colors, X1, Y1, Z1, ve, triangles, vertices, scalars, colormap, norm, vmin, vmax, export, opacity)

        top = strat[:,-1,layer_n+1]  # downdip side
        base = strat[:,-1,layer_n]
        if color_mode == "property":
            props = prop[:,-1,layer_n]
        if plot_surfs:
            Y1 = scale*(yoffset + dx*np.arange(0,r))
            X1 = scale*(xoffset + dx*(c-1)*np.ones(np.shape(base)))
            Z1 = ve*scale*base
            mlab.plot3d(X1,Y1,Z1,color=(0,0,0),tube_radius=line_thickness)
        if np.max(top-base)>0:
            Points,Inds = triangulate_layers(top,base,dx)
            for i in range(len(Points)):
                vertices = Points[i]
                triangles, scalars = create_triangles(vertices)
                Y1 = scale*(yoffset + vertices[:,0])
                X1 = scale*(xoffset + dx*(c-1)*np.ones(np.shape(vertices[:,0])))
                Z1 = scale*vertices[:,1]
                if color_mode == "property":
                    scalars = props[Inds[i]]
                else:
                    scalars = []
                plot_layers_on_one_side(layer_n, facies, color_mode, colors, X1, Y1, Z1, ve, triangles, vertices, scalars, colormap, norm, vmin, vmax, export, opacity)

        top = strat[0,:,layer_n+1]  # left edge (looking downdip)
        base = strat[0,:,layer_n]
        if color_mode == "property":
            props = prop[0,:,layer_n]
        if plot_surfs:
            Y1 = scale*(yoffset + np.zeros(np.shape(base)))
            X1 = scale*(xoffset + dx*np.arange(0,c))
            Z1 = ve*scale*base
            mlab.plot3d(X1,Y1,Z1,color=(0,0,0),tube_radius=line_thickness)
        if np.max(top-base)>0:
            Points,Inds = triangulate_layers(top,base,dx)
            for i in range(len(Points)):
                vertices = Points[i]
                triangles, scalars = create_triangles(vertices)
                Y1 = scale*(yoffset + dx*0*np.ones(np.shape(vertices[:,0])))
                X1 = scale*(xoffset + vertices[:,0])
                Z1 = scale*vertices[:,1]
                if color_mode == "property":
                    scalars = props[Inds[i]]
                else:
                    scalars = []
                plot_layers_on_one_side(layer_n, facies, color_mode, colors, X1, Y1, Z1, ve, triangles, vertices, scalars, colormap, norm, vmin, vmax, export, opacity)

        top = strat[-1,:,layer_n+1] # right edge (looking downdip)
        base = strat[-1,:,layer_n]
        if color_mode == "property":
            props = prop[-1,:,layer_n]
        if plot_surfs:
            Y1 = scale*(yoffset + dx*(r-1)*np.ones(np.shape(base)))
            X1 = scale*(xoffset + dx*np.arange(0,c))
            Z1 = ve*scale*base
            mlab.plot3d(X1,Y1,Z1,color=(0,0,0),tube_radius=line_thickness)
        if np.max(top-base)>0:
            Points,Inds = triangulate_layers(top,base,dx)
            for i in range(len(Points)):
                vertices = Points[i]
                triangles, scalars = create_triangles(vertices)
                Y1 = scale*(yoffset + dx*(r-1)*np.ones(np.shape(vertices[:,0])))
                X1 = scale*(xoffset + vertices[:,0])
                Z1 = scale*vertices[:,1]
                if color_mode == "property":
                    scalars = props[Inds[i]]
                else:
                    scalars = []
                plot_layers_on_one_side(layer_n, facies, color_mode, colors, X1, Y1, Z1, ve, triangles, vertices, scalars, colormap, norm, vmin, vmax, export, opacity)

def create_exploded_view(strat, prop, facies, x0, y0, nx, ny, gap, dx, ve, scale, plot_strat, plot_surfs, plot_contours, plot_sides, color_mode, colors, colormap, line_thickness, bottom,export, topo_min, topo_max, ci, opacity):
    """function for creating an exploded-view block diagram
    inputs:
    strat - stack of stratigraphic surfaces
    facies - 1D array of facies codes for layers
    topo - stack of topographic surfaces
    nx - number of blocks in x direction
    ny - number of blocks in y direction
    gap - gap between blocks (number of gridcells)
    dx - gridcell size
    ve - vertical exaggeration
    scale - scaling factor (for whole model)
    strat_switch - if equals 1, the stratigraphy will be plotted on the sides of the blocks
    plot_surfs - if equals 1, the stratigraphic surfaces will be plotted on the sides (adds a lot of triangles - not good for 3D printing)
    contour_swicth - if equals 1, contours will be plotted on the top surface
    color_mode - determines what kind of plot is created; can be 'property', 'time', or 'facies'
    colors - colors scheme for facies (list of RGB values)
    line_thickness - - tube radius for plotting layers on the sides
    bottom - elevation value for the bottom of the block
    export - if equals 1, the display can be saved as a VRML file for use in other programs (e.g., 3D printing)"""
    r,c,ts = np.shape(strat)
    count = 0
    for i in range(nx):
        for j in range(ny):
            x1 = i * int(c/nx)
            x2 = (i+1) * int(c/nx)
            y1 = j * int(r/ny)
            y2 = (j+1) * int(r/ny)
            xoffset = x0 + (x1+i*gap)*dx
            yoffset = y0 + (y1+j*gap)*dx
            if color_mode == "property":
                create_block_diagram(strat[y1:y2,x1:x2,:], prop[y1:y2,x1:x2,:], facies[y1:y2,x1:x2,:], dx, ve, xoffset, yoffset, scale, ci, plot_strat, plot_contours, plot_sides, color_mode, bottom, topo_min, topo_max, export, opacity)
                if plot_strat:
                    add_stratigraphy_to_block_diagram(strat[y1:y2,x1:x2,:], prop[y1:y2,x1:x2,:], facies[y1:y2,x1:x2,:], dx, ve, xoffset, yoffset, scale, plot_surfs, color_mode, colors, colormap, line_thickness, export, opacity)
            else:
                create_block_diagram(strat[y1:y2,x1:x2,:], prop, facies[y1:y2,x1:x2,:], dx, ve, xoffset, yoffset, scale, ci, plot_strat, plot_contours, plot_sides, color_mode, bottom, topo_min, topo_max, export, opacity)
                if plot_strat:
                    add_stratigraphy_to_block_diagram(strat[y1:y2,x1:x2,:], prop, facies[y1:y2,x1:x2,:], dx, ve, xoffset, yoffset, scale, plot_surfs, color_mode, colors, colormap, line_thickness, export, opacity)
            count = count+1
            print("block "+str(count)+" done, out of "+str(nx*ny)+" blocks")

def create_fence_diagram(strat, prop, facies, x0, y0, nx, ny, dx, ve, scale, plot_surfs, plot_sides, color_mode, colors, colormap, line_thickness, bottom, export, opacity):
    """function for creating a fence diagram
    inputs:
    strat - stack of stratigraphic surfaces
    facies - 1D array of facies codes for layers
    topo - stack of topographic surfaces
    nx - number of strike sections
    ny - number of dip sections
    dx - gridcell size
    ve - vertical exaggeration
    scale - scaling factor (for whole model)
    plot_surfs - if equals 1, the stratigraphic surfaces will be plotted on the sides (adds a lot of triangles - not good for 3D printing)
    color_mode - determines what kind of plot is created; can be 'property', 'time', or 'facies'
    colors - colors scheme for facies (list of RGB values)
    line_thickness - - tube radius for plotting layers on the sides
    bottom - elevation value for the bottom of the block
    export - if equals 1, the display can be saved as a VRML file for use in other programs (e.g., 3D printing)"""
    r,c,ts=np.shape(strat)
    gray = (0.6,0.6,0.6)
    norm = matplotlib.colors.Normalize(vmin=0.0, vmax=ts-1)
    cmap = matplotlib.cm.get_cmap(colormap)
    vmin = np.min(prop)
    vmax = np.max(prop)

    gray = (0.6,0.6,0.6) # color for plotting sides

    z = scale*strat[:,:,ts-1].T
    z1 = strat[:,:,0].T
    xoffset = 0; yoffset = 0

    # updip side:
    vertices, triangles = create_section(z1[:,0],dx,bottom)
    x = scale*(xoffset + vertices[:,0])
    y = scale*(yoffset + np.zeros(np.shape(vertices[:,0])))
    z = scale*ve*vertices[:,1]
    mlab.triangular_mesh(x, y, z, triangles, color=gray, opacity = opacity)

    # downdip side:
    vertices, triangles = create_section(z1[:,-1],dx,bottom)
    x = scale*(xoffset + vertices[:,0])
    y = scale*(yoffset + (r-1)*dx*np.ones(np.shape(vertices[:,0])))
    z = scale*ve*vertices[:,1]
    mlab.triangular_mesh(x, y, z, triangles, color=gray, opacity = opacity)

    # left edge (looking downdip):
    vertices, triangles = create_section(z1[0,:],dx,bottom)
    x = scale*(xoffset + np.zeros(np.shape(vertices[:,0])))
    y = scale*(yoffset + vertices[:,0])
    z = scale*ve*vertices[:,1]
    mlab.triangular_mesh(x, y, z, triangles, color=gray, opacity = opacity)

    # right edge (looking downdip):
    vertices, triangles = create_section(z1[-1,:],dx,bottom)
    x = scale*(xoffset + (c-1)*dx*np.ones(np.shape(vertices[:,0])))
    y = scale*(yoffset + vertices[:,0])
    z = scale*ve*vertices[:,1]
    mlab.triangular_mesh(x, y, z, triangles, color=gray, opacity = opacity)

    # bottom face of block:
    vertices = dx*np.array([[0,0],[c-1,0],[c-1,r-1],[0,r-1]])
    triangles = [[0,1,3],[1,3,2]]
    x = scale*(xoffset + vertices[:,0])
    y = scale*(yoffset + vertices[:,1])
    z = scale*bottom*np.ones(np.shape(vertices[:,0]))
    mlab.triangular_mesh(x, y, ve*z, triangles, color=gray, opacity = opacity)

    section_inds = np.hstack((0, int(c/(nx+1)) * np.arange(1, nx+1), c-1))
    for x1 in tqdm(section_inds): # strike sections
        if plot_sides:
            vertices, triangles = create_section(strat[:,x1,0],dx,bottom)
            y = y0 + scale*(vertices[:,0])
            x = x0 + scale*(x1*dx+np.zeros(np.shape(vertices[:,0])))
            z = scale*ve*vertices[:,1]
            mlab.triangular_mesh(x,y,z,triangles,color=gray)
        for layer_n in range(ts-1): # main loop
            top = strat[:,x1,layer_n+1]
            base = strat[:,x1,layer_n]
            if color_mode == 'property':
                props = prop[:,x1,layer_n]
            if plot_surfs:
                Y1 = y0 + scale*(dx*np.arange(0,r))
                X1 = x0 + scale*(x1*dx+np.zeros(np.shape(base)))
                Z1 = ve*scale*base
                mlab.plot3d(X1,Y1,Z1,color=(0,0,0),tube_radius=line_thickness)
            if np.max(top-base)>0:
                Points,Inds = triangulate_layers(top,base,dx)
                for i in range(len(Points)):
                    vertices = Points[i]
                    triangles, scalars = create_triangles(vertices)
                    Y1 = y0 + scale*(vertices[:,0])
                    X1 = x0 + scale*(x1*dx+dx*0*np.ones(np.shape(vertices[:,0])))
                    Z1 = scale*vertices[:,1]
                    if color_mode == 'property':
                        scalars = props[Inds[i]]
                    else:
                        scalars = []
                    # plot_layers_on_one_side(layer_n,facies,color_mode,colors,X1,Y1,Z1,ve,triangles,vertices,scale*scalars,cmap,norm,vmin,vmax,export)
                    plot_layers_on_one_side(layer_n, facies, color_mode, colors, X1, Y1, Z1, ve, triangles, vertices, scalars, colormap, norm, vmin, vmax, export, opacity)

    section_inds = np.hstack((0, int(r/(ny+1)) * np.arange(1, ny+1), r-1))
    for y1 in tqdm(section_inds): # dip sections
        if plot_sides:
            vertices, triangles = create_section(strat[y1,:,0],dx,bottom)
            y = y0 + scale*(y1*dx+np.zeros(np.shape(vertices[:,0])))
            x = x0 + scale*(vertices[:,0])
            z = scale*ve*vertices[:,1]
            mlab.triangular_mesh(x,y,z,triangles,color=gray)
        for layer_n in range(ts-1): # main loop
            top = strat[y1,:,layer_n+1]
            base = strat[y1,:,layer_n]
            if color_mode == 'property':
                props = prop[y1,:,layer_n]
            if plot_surfs:
                Y1 = y0 + scale*(y1*dx+np.zeros(np.shape(base)))
                X1 = x0 + scale*(dx*np.arange(0,c))
                Z1 = ve*scale*base
                mlab.plot3d(X1,Y1,Z1,color=(0,0,0),tube_radius=line_thickness)
            if np.max(top-base)>0:
                Points,Inds = triangulate_layers(top,base,dx)
                for i in range(len(Points)):
                    vertices = Points[i]
                    triangles, scalars = create_triangles(vertices)
                    Y1 = y0 + scale*(y1*dx + dx*0*np.ones(np.shape(vertices[:,0])))
                    X1 = x0 + scale*(vertices[:,0])
                    Z1 = scale*vertices[:,1]
                    if color_mode == 'property':
                        scalars = props[Inds[i]]
                    else:
                        scalars = []
                    # plot_layers_on_one_side(layer_n,facies,color_mode,colors,X1,Y1,Z1,ve,triangles,vertices,scale*scalars,cmap,norm,vmin,vmax,export)
                    plot_layers_on_one_side(layer_n, facies, color_mode, colors, X1, Y1, Z1, ve, triangles, vertices, scalars, colormap, norm, vmin, vmax, export, opacity)
        # print('done with section '+str(nsec)+' of '+str(ny)+' dip sections')
        r,c = np.shape(strat[:,:,-1])
        Y1 = scale*(np.linspace(0,r-1,r)*dx)
        X1 = scale*(np.linspace(0,c-1,c)*dx)
        topo_min = np.min(strat[:,:,-1])
        topo_max = np.max(strat[:,:,-1])
        mlab.surf(X1, Y1, scale*strat[:,:,-1].T, warp_scale=ve, colormap='gist_earth', vmin=scale*topo_min, vmax=scale*topo_max, opacity=0.15)

def triangulate_layers(top,base,dx):
    """function for creating vertices of polygons that describe one layer"""
    x = dx * np.arange(0,len(top))
    ind1 = np.argwhere(top-base>0).flatten()
    ind2 = np.argwhere(np.diff(ind1)>1)
    ind2 = np.vstack((np.array([[-1]]),ind2))
    ind2 = np.vstack((ind2,np.array([[len(top)]])))
    Points = [] # list for points to be triangulated
    Inds = []
    for i in range(len(ind2)-1):
        ind3 = ind1[int(ind2[i])+1:int(ind2[i+1])+1]
        if (ind3[0] != 0) & (ind3[-1] != len(top)-1):
            ind3 = np.hstack((ind3[0]-1,ind3))
            ind3 = np.hstack((ind3,ind3[-1]+1))
            top1 = top[ind3][:-1]
            base1 = base[ind3][1:]
            x1 = np.concatenate((x[ind3][:-1], x[ind3][::-1][:-1]))
            inds = np.concatenate((ind3[:-1], ind3[::-1][:-1]))
        if (ind3[0] == 0) & (ind3[-1] != len(top)-1):
            ind3 = np.hstack((ind3,ind3[-1]+1))
            top1 = top[ind3][:-1]
            base1 = base[ind3]
            x1 = np.concatenate((x[ind3][:-1], x[ind3][::-1]))
            inds = np.concatenate((ind3[:-1], ind3[::-1]))
        if (ind3[0] != 0) & (ind3[-1] == len(top)-1):
            ind3 = np.hstack((ind3[0]-1,ind3))
            top1 = top[ind3]
            base1 = base[ind3][1:]
            x1 = np.concatenate((x[ind3], x[ind3][::-1][:-1]))
            inds = np.concatenate((ind3, ind3[::-1][:-1]))
        if (ind3[0] == 0) & (ind3[-1] == len(top)-1):
            top1 = top[ind3]
            base1 = base[ind3]
            x1 = np.concatenate((x[ind3], x[ind3][::-1]))
            inds = np.concatenate((ind3, ind3[::-1]))
        npoints = len(top1)+len(base1)
        y = np.hstack((top1,base1[::-1]))
        vertices = np.vstack((x1,y)).T
        Points.append(vertices)
        Inds.append(inds)
    return Points,Inds

def create_triangles(vertices):
    """function for creating list of triangles from vertices
    inputs:
    vertices - 2 x n array with coordinates of polygon
    returns:
    triangles - indices of the 'vertices' array that from triangles (for triangular mesh)
    scalars - 'fake' elevation values for each vertex of the polygon, used for coloring (relies on the base of the polygon)"""
    n = len(vertices[:,0])
    Z1 = vertices[:,1]
    triangles = []
    if (np.mod(n,2)==0) & (vertices[int((n-1)/2),0] != vertices[int((n-1)/2+1),0]): # if polygon is in the interior of the block
        triangles.append([0,1,n-1])
        for i in range(1,int(n/2-1)):
            triangles.append([i,i+1,n-i])
            triangles.append([i+1,n-i,n-i-1])
        triangles.append([int(n/2-1),int(n/2),int(n/2+1)])
        scalars = np.hstack((Z1[0],Z1[int(n/2):][::-1],Z1[int(n/2)+1:]))
    if (np.mod(n,2)==0) & (vertices[int((n-1)/2),0] == vertices[int((n-1)/2+1),0]): # if polygon touches both sides of the block
        for i in range(0,int(n/2-1)):
            triangles.append([i,i+1,n-i-1])
            triangles.append([i+1,n-i-1,n-i-2])
        scalars = np.hstack((Z1[int(n/2):][::-1],Z1[int(n/2):]))
    if np.mod(n,2)!=0: # if polygon has one segment on the side of the block
        if vertices[int((n-1)/2),0] == vertices[int((n-1)/2+1),0]: # if polygon touches the right side of the block
            triangles.append([0,1,n-1])
            for i in range(1,int((n-1)/2)):
                triangles.append([i,i+1,n-i])
                triangles.append([i+1,n-i,n-i-1])
            scalars = np.hstack((Z1[0],Z1[int((n+1)/2):][::-1],Z1[int((n+1)/2):]))
        else:
            for i in range(0,int((n-1)/2)-1): # if polygon touches the left side of the block
                triangles.append([i,i+1,n-i-1])
                triangles.append([i+1,n-i-1,n-i-2])
            triangles.append([int((n-1)/2-1),int((n-1)/2),int((n-1)/2+1)])
            scalars = np.hstack((Z1[int((n+1)/2)-1:][::-1],Z1[int((n+1)/2):]))
    return triangles, scalars

def create_section(profile,dx,bottom):
    """function for creating a cross section from a top surface
    inputs:
    profile - elevation data for top surface
    dx - gridcell size
    bottom - elevation value for the bottom of the block
    returns:
    vertices - coordinates of vertices
    triangles - indices of the 'vertices' array that from triangles (for triangular mesh)
    """
    x1 = dx*np.linspace(0, len(profile)-1, len(profile))
    x = np.hstack((x1, x1[::-1]))
    y = np.hstack((profile, bottom*np.ones(np.shape(x1))))
    vertices = np.vstack((x, y)).T
    n = len(x)
    triangles = []
    for i in range(0,int((n-1)/2)):
        triangles.append([i,i+1,n-i-1])
        triangles.append([i+1,n-i-1,n-i-2])
    return vertices, triangles

def plot_layers_on_one_side(layer_n, facies, color_mode, colors, X1, Y1, Z1, ve, triangles, vertices, scalars, colormap, norm, vmin, vmax, export, opacity):
    """function for plotting layers on one side of a block
    inputs:
    layer_n - layer number
    facies - 1D array of facies codes for layers
    color_mode - determines what kind of plot is created; can be 'property', 'time', or 'facies'
    colors - list of RGB values used if color_mode is 'facies'
    X1,Y1,Z1 - coordinates of mesh vertices
    ve - vertical exaggeration
    triangles - indices of triangles used in mesh
    vertices - coordinates of the vertices
    scalars - scalars used for coloring the mesh in 'property' mode (= z-value of the base of current layer)
    cmap - colormap used for layers in 'time' mode
    norm - color normalization function used in 'time' mode
    export - if equals 1, the display can be saved as a VRML file for use in other programs (e.g., 3D printing)
    """
    if color_mode == 'time':
        cmap = matplotlib.cm.get_cmap(colormap)
        mlab.triangular_mesh(X1, Y1, ve*Z1, triangles, color = cmap(norm(layer_n))[:3], opacity = opacity)
    if color_mode == 'property': # color based on property map
        mlab.triangular_mesh(X1, Y1, ve*Z1, triangles, scalars=scalars, colormap=str(colormap), vmin=vmin, vmax=vmax, opacity = opacity)
    if color_mode == 'facies':
        mlab.triangular_mesh(X1,Y1,ve*Z1, triangles, color=tuple(colors[int(facies[0, 0, layer_n])]), opacity = opacity)

def create_random_section_2_points(strat,facies,scale,ve,color_mode,colors,colormap,x1,x2,y1,y2,s1,dx,bottom,export,opacity):
    r, c, ts = np.shape(strat)
    dist = dx*((x2-x1)**2 + (y2-y1)**2)**0.5
    s2 = s1*dx+dist
    num = int(dist/float(dx))
    cmap = matplotlib.cm.get_cmap(colormap)
    norm = matplotlib.colors.Normalize(vmin=0.0, vmax=ts-1)
    Xrand, Yrand, Srand = np.linspace(x1,x2,num), np.linspace(y1,y2,num), np.linspace(s1*dx,s2,num)
    base = map_coordinates(strat[:,:,0], np.vstack((Yrand,Xrand)))
    vertices, triangles = create_section(base,dx,bottom)
    gray = (0.6,0.6,0.6) # color for plotting basal part of panel
    mlab.triangular_mesh(scale*np.hstack((dx*Xrand,dx*Xrand[::-1])),scale*np.hstack((dx*Yrand,dx*Yrand[::-1])),scale*ve*vertices[:,1],triangles,color=gray)
    for layer_n in trange(0,ts-1):
        top = map_coordinates(strat[:,:,layer_n+1], np.vstack((Yrand,Xrand)))
        base = map_coordinates(strat[:,:,layer_n], np.vstack((Yrand,Xrand)))
        if np.max(top-base)>1e-6:
            Points, Inds = triangulate_layers(top,base,dx)
            for i in range(len(Points)):
                vertices = Points[i]
                inds = Inds[i]
                triangles, scalars = create_triangles(vertices)
                X1 = scale*dx*Xrand[inds]
                Y1 = scale*dx*Yrand[inds]
                Z1 = scale*vertices[:,1]
                mlab.plot3d(X1,Y1,Z1*ve,color=(0,0,0),tube_radius=0.5)
                vmin = 0; vmax = 1
                plot_layers_on_one_side(layer_n,facies,color_mode,colors,X1,Y1,Z1,ve,triangles,vertices,scalars,colormap,norm,vmin,vmax,export,opacity)

def create_random_section_n_points(strat,facies,topo,scale,ve,color_mode,colors,colormap,x1,x2,y1,y2,dx,bottom,export,opacity):
    r, c, ts = np.shape(strat)
    if len(x1)==1:
        create_random_section_2_points(strat,facies,scale,ve,color_mode,colors,colormap,x1,x2,y1,y2,0,dx,bottom,export,opacity)
    else:
        count = 0
        dx1,dy1,ds1,s1 = compute_derivatives(x1,y1)
        for i in range(len(x1)):
            create_random_section_2_points(strat,facies,scale,ve,color_mode,colors,colormap,x1[i],x2[i],y1[i],y2[i],s1[i],dx,bottom,export,opacity)
            count = count+1
            # print("panel "+str(count)+" done, out of "+str(len(x1))+" panels")

def create_random_cookie(strat,facies,topo,scale,ve,color_mode,colors,colormap,x1,x2,y1,y2,dx,bottom,export,opacity):
    r, c, ts = np.shape(strat)
    count = 0
    dx1,dy1,ds1,s1 = compute_derivatives(x1,y1)
    for i in range(len(x1)):
        create_random_section_2_points(strat,facies,scale,ve,color_mode,colors,colormap,x1[i],x2[i],y1[i],y2[i],s1[i],dx,bottom,export,opacity)
        count = count+1
        # print("panel "+str(count)+" done, out of "+str(len(x1)+1)+" panels")
    create_random_section_2_points(strat,facies,scale,ve,color_mode,colors,colormap,x2[-1],x1[0],y2[-1],y1[0],s1[-1]+np.sqrt((x1[0]-x2[-1])**2+(y1[0]-y2[-1])**2),dx,bottom,export,opacity)
    polygon = []
    for i in range(len(x1)):
        polygon.append((x1[i]+0.5, y1[i]+0.5))
    polygon.append((x2[-1]+0.5, y2[-1]+0.5))
    img = Image.fromarray(np.zeros(np.shape(strat[:,:,-1])))
    ImageDraw.Draw(img).polygon(polygon, outline=1, fill=1)
    img = np.array(img)
    mask = np.ones_like(strat[:,:,-1]).astype(bool)
    mask[img == 1] = False
    r,c = np.shape(strat[:,:,-1])
    Y1 = scale*(np.linspace(0,r-1,r)*dx)
    X1 = scale*(np.linspace(0,c-1,c)*dx)
    topo_min = np.min(strat[:,:,-1])
    topo_max = np.max(strat[:,:,-1])
    mlab.surf(X1, Y1, scale*strat[:,:,-1].T, mask=mask.T, warp_scale=ve, colormap='gist_earth', vmin=scale*topo_min, vmax=scale*topo_max)

def compute_derivatives(x,y):
    dx = np.diff(x) # first derivatives
    dy = np.diff(y)
    ds = np.sqrt(dx**2+dy**2)
    s = np.hstack((0,np.cumsum(ds)))
    return dx, dy, ds, s

class LineBuilder:
    def __init__(self, line):
        self.line = line
        self.xs = list(line.get_xdata())
        self.ys = list(line.get_ydata())
        self.cid = line.figure.canvas.mpl_connect('button_press_event', self)

    def __call__(self, event):
        if event.inaxes!=self.line.axes: return
        self.xs.append(event.xdata)
        self.ys.append(event.ydata)
        self.line.set_data(self.xs, self.ys)
        self.line.figure.canvas.draw()

def select_random_section(strat):
    fig = plt.figure(figsize=(8,6))
    ax = fig.add_subplot(111)
    ax.imshow(strat[:,:,-1],cmap='viridis')
    plt.tight_layout()
    ax.set_title('click to build line segments')
    line, = ax.plot([], [])  # empty line
    linebuilder = LineBuilder(line)
    xcoords = linebuilder.xs
    ycoords = linebuilder.ys
    return xcoords, ycoords

def plot_strat_diagram(time, elevation, time_units, elev_units, end_time, max_elevation):
    fig = plt.figure(figsize=(9,6))
    ax1 = fig.add_axes([0.07, 0.08, 0.85, 0.76]) # [left, bottom, width, height]
    ax1.set_xlabel('time (' + time_units + ')', fontsize = 12)
    ax1.set_ylabel('elevation (' + elev_units + ')', fontsize = 12)
    for tick in ax1.xaxis.get_major_ticks():
        tick.label.set_fontsize(10)
    for tick in ax1.yaxis.get_major_ticks():
        tick.label.set_fontsize(10)
    ax2 = fig.add_axes([0.92, 0.08, 0.05, 0.76])
    ax2.set_xticks([])
    ax2.set_yticks([])
    ax3 = fig.add_axes([0.07, 0.84, 0.85, 0.08])
    ax3.set_yticks([])
    ax3.set_xticks([])
    ax1.set_xlim(0, end_time)
    elev_range = max_elevation - np.min(elevation)
    ylim1 = np.min(elevation)# - 0.02 * elev_range
    ylim2 = max_elevation + 0.02 * elev_range
    ax1.set_ylim(ylim1, ylim2)
    ax2.set_xlim(0, 1)
    ax2.set_ylim(ylim1, ylim2)
    ax3.set_ylim(0, 1)
    ax3.set_xlim(0, end_time)
    ax4 = fig.add_axes([0.07, 0.92, 0.6, 0.08])
    ax4.set_xlim(0, 10)
    ax4.set_ylim(0, 1)

    ax1.plot(time, elevation, 'xkcd:medium blue', linewidth = 3)

    strat = np.minimum.accumulate(elevation[::-1])[::-1] # stratigraphic 'elevation'

    unconf_inds = np.where(strat != elevation)[0] # indices where 'strat' curve is different from elevation
    inds = np.where(np.diff(unconf_inds)>1)[0] # indices where deposition starts again, after erosion
    inds = np.hstack((inds, len(unconf_inds)-1)) # add last index

    if strat[-1] - strat[-2] == 0:
        inds = np.hstack((inds, len(unconf_inds)-1))

    if len(unconf_inds) > 0:
        strat_tops = strat[unconf_inds[inds]+1] # stratigraphic tops
    else:
        strat_tops = []
    strat_top_ages = [] # ages of the stratigraphic tops
    for i in range(len(strat_tops)): # generate list of ages of stratigraphic tops
        strat_top_ages.append(np.min(time[strat >= strat_tops[i]]))

    loc_max_elev = signal.find_peaks(elevation)[0]
    loc_min_elev = signal.find_peaks(-elevation)[0]
    if elevation[-1] < elevation[-2]:
        loc_min_elev = np.hstack((loc_min_elev, len(elevation)-1))

    if (len(loc_min_elev) > 0) & (len(loc_max_elev) > 0):
        if elevation[1] < elevation[0]: # add first point as a local maximum elevation if the series starts out erosionally
            loc_max_elev = np.hstack((0, loc_max_elev))
        if loc_min_elev[0] < loc_max_elev[0]:
            ind = np.argmax(elevation[0 : loc_min_elev[0]])
            loc_max_elev = np.sort(np.hstack((loc_max_elev, ind)))
        for i in range(len(loc_min_elev)-1):
            if len(loc_max_elev[loc_max_elev > loc_min_elev[i]]) > 0:
                if np.min(loc_max_elev[loc_max_elev > loc_min_elev[i]]) > loc_min_elev[i+1]:
                    ind = np.argmax(elevation[loc_min_elev[i] : loc_min_elev[i+1]])
                    ind = loc_min_elev[i] + ind
                    loc_max_elev = np.sort(np.hstack((loc_max_elev, ind)))
            else:
                ind = np.argmax(elevation[loc_min_elev[i] : loc_min_elev[i+1]])
                ind = loc_min_elev[i] + ind
                loc_max_elev = np.sort(np.hstack((loc_max_elev, ind)))
        for i in range(len(loc_max_elev)-1):
            if len(loc_min_elev[loc_min_elev > loc_max_elev[i]]) > 0:
                if np.min(loc_min_elev[loc_min_elev > loc_max_elev[i]]) > loc_max_elev[i+1]:
                    ind = np.argmin(elevation[loc_max_elev[i] : loc_max_elev[i+1]])
                    ind = loc_max_elev[i] + ind
                    loc_min_elev = np.sort(np.hstack((loc_min_elev, ind)))
            else:
                ind = np.argmin(elevation[loc_max_elev[i] : loc_max_elev[i+1]])
                ind = loc_max_elev[i] + ind
                loc_min_elev = np.sort(np.hstack((loc_min_elev, ind)))
    erosion_start_times = time[loc_max_elev] # times when erosion starts
    erosion_end_times = time[loc_min_elev] # times when erosion ends
    erosion_start_elevations = elevation[loc_max_elev] # elevations when erosion starts
    erosion_end_elevations = elevation[loc_min_elev] # elevations when erosion ends

    if (len(loc_min_elev) > 0) & (len(loc_max_elev) > 0):
        for i in range(len(erosion_end_times)): # plot erosional segments
            ax1.plot(time[loc_max_elev[i]:loc_min_elev[i]+1],
                     elevation[loc_max_elev[i]:loc_min_elev[i]+1], 'xkcd:red', linewidth=3)
        if len(erosion_start_times) > len(erosion_end_times): # plot last erosional segment (if needed)
            ax1.plot(time[loc_max_elev[-1]:],
                     elevation[loc_max_elev[-1]:], 'xkcd:red', linewidth=3)

    strat_top_labels = ['s' for strat_top in strat_tops] # labels for stratigraphic tops
    erosion_start_labels = ['es' for erosion_start_time in erosion_start_times] # labels for start of erosion
    erosion_end_labels = ['ee' for erosion_end_time in erosion_end_times] # labels for end of erosion

    time_bounds = np.hstack((strat_top_ages, erosion_start_times, erosion_end_times)) # all time boundaries
    sort_inds = np.argsort(time_bounds) # indices for sorting
    time_bounds = time_bounds[sort_inds] # sort time boundaries
    elevation_bounds = np.hstack((strat_tops, erosion_start_elevations, erosion_end_elevations)) # all elevation boundaries
    elevation_bounds = elevation_bounds[sort_inds] # sort elevation boundaries
    bound_labels = np.hstack((strat_top_labels, erosion_start_labels, erosion_end_labels)) # all boundary labels
    bound_labels = bound_labels[sort_inds] # sort boundary labels

    time_bounds = np.hstack((time[0], time_bounds, time[-1])) # add first and last time step to time boundaries
    elevation_bounds = np.hstack((elevation[0], elevation_bounds, elevation[-1])) # add first and last elevation values
    if elevation[-1] - elevation[-2] < 0: # add first and last boundary labels
        bound_labels = np.hstack(('s', bound_labels, 'ee'))
    else:
        bound_labels = np.hstack(('s', bound_labels, 's'))

    inds = []
    for i in range(len(bound_labels)-1):
        if (bound_labels[i] == 'es') & (bound_labels[i+1] == 's'):
            inds.append(i)
    if len(inds)>0:
        for i in range(len(inds)):
            bound_labels[inds[i]] = 's'
            bound_labels[inds[i]+1] = 'es'

    time_labels = []
    for i in range(len(time_bounds)-1): # plot chronostratigraphic units
        x = [time_bounds[i], time_bounds[i+1], time_bounds[i+1], time_bounds[i]]
        y = [0, 0, 1, 1]
        if (bound_labels[i] == 's') and (bound_labels[i+1] == 'es'): # vacuity
            ax3.fill(x, y, facecolor='xkcd:light grey', edgecolor='k')
            time_labels.append('v')
        elif (bound_labels[i] == 'ee') and (bound_labels[i+1] == 'es'): # vacuity
            ax3.fill(x, y, facecolor='xkcd:light grey', edgecolor='k')
            time_labels.append('v')
        elif (bound_labels[i] == 'es') and (bound_labels[i+1] == 'ee'): # erosion
            ax3.fill(x, y, facecolor='xkcd:red', edgecolor='k')
            time_labels.append('e')
        elif (bound_labels[i] == 's') and (bound_labels[i+1] == 'ee'): # erosion
            time_labels.append('e')
        elif (bound_labels[i] == 'ee') and (bound_labels[i+1] == 's'): # deposition
            ax3.fill(x, y, facecolor='xkcd:medium blue', edgecolor='k')
            time_labels.append('d')
        elif (bound_labels[i] == 's') and (bound_labels[i+1] == 's'): # deposition
            ax3.fill(x, y, facecolor='xkcd:medium blue', edgecolor='k')
            time_labels.append('d')
        ax1.plot([time_bounds[i], time_bounds[i]], [elevation_bounds[i], max_elevation + 0.02 * elev_range], 'k--', linewidth=0.5)

    for i in range(len(strat_tops)):
        ax2.plot([0, 1], [strat_tops[i], strat_tops[i]], color = 'xkcd:red', linewidth = 3)

    if len(strat_tops) > 0:
        if elevation[0] < np.min(strat_tops):
            strat_tops = np.hstack((elevation[0], strat_tops, elevation[-1]))
            strat_top_ages = np.hstack((0, strat_top_ages, time[-1]))
        else:
            strat_tops = np.hstack((strat_tops, elevation[-1]))
            strat_top_ages = np.hstack((strat_top_ages, time[-1]))
    else:
        strat_tops = np.hstack((elevation[0], strat_tops, elevation[-1]))
        strat_top_ages = np.hstack((0, strat_top_ages, time[-1]))

    for i in range(len(strat_tops)-1): # plot stratigraphic units
        x = [0, 1, 1, 0]
        y = [strat_tops[i], strat_tops[i], strat_tops[i+1], strat_tops[i+1]]
        ax2.fill(x, y, facecolor='xkcd:medium blue', edgecolor='k')
        if i > 0:
            ax1.plot([strat_top_ages[i], end_time], [strat_tops[i], strat_tops[i]], 'k--', linewidth=0.5)

    times = np.diff(time_bounds)
    thicknesses = np.diff(elevation_bounds)
    deposition_time = np.sum([item[0] for item in zip(times, time_labels) if item[1] == 'd' ])
    vacuity_time = np.sum([item[0] for item in zip(times, time_labels) if item[1] == 'v' ])
    erosion_time = np.sum([item[0] for item in zip(times, time_labels) if item[1] == 'e' ])
    deposition_thickness = np.sum([item[0] for item in zip(thicknesses, time_labels) if item[1] == 'd' ])
    vacuity_thickness = np.sum([item[0] for item in zip(thicknesses, time_labels) if item[1] == 'v' ])
    eroded_thickness = np.sum([item[0] for item in zip(thicknesses, time_labels) if item[1] == 'e' ])
    dve_data = [deposition_time, vacuity_time, erosion_time, deposition_thickness, vacuity_thickness, eroded_thickness]

    y1 = 0.55
    y2 = 0.15
    y = [y1, y1, y2, y2]
    x1 = 0
    x2 = 3 * deposition_time/time[-1]
    x = [x1, x2, x2, x1]
    ax4.fill(x, y, facecolor='xkcd:medium blue', edgecolor = 'k', zorder = 1000)
    ax4.axis('off')
    ax4.text(x1, y1 + 0.07, 'deposition', fontsize = 12)
    ax4.text(x1 + 0.05, 0.27, str(np.round(deposition_time/time[-1], 3)), fontsize = 10, color = 'w',zorder=2000)

    x1 = 3
    x2 = x1 + 3 * erosion_time/time[-1]
    x = [x1, x2, x2, x1]
    ax4.fill(x, y, facecolor='xkcd:red', edgecolor = 'k', zorder = 1001)
    ax4.text(x1, y1 + 0.07, 'erosion', fontsize = 12)
    ax4.text(x1 + 0.05, 0.27, str(np.round(erosion_time/time[-1], 3)), fontsize = 10, color = 'w',zorder=2000)

    x1 = 6
    x2 = x1 + 3 * vacuity_time/time[-1]
    x = [x1, x2, x2, x1]
    ax4.fill(x, y, facecolor='xkcd:light grey', edgecolor = 'k', zorder = 1002)
    ax4.text(x1, y1 + 0.07, 'vacuity', fontsize = 12)
    ax4.text(x1 + 0.05, 0.27, str(np.round(vacuity_time/time[-1], 3)), fontsize = 10, color = 'w',zorder=2000)

    return fig

def topostrat(topo):
    # convert topography to stratigraphy
    if len(np.shape(topo)) == 2:
        strat = np.minimum.accumulate(topo[::-1, :], axis=0)[::-1, :]
    if len(np.shape(topo)) == 3:
        strat = np.minimum.accumulate(topo[:, :, ::-1], axis=2)[:, :, ::-1]
    return strat

def create_wheeler_diagram(topo):
    """create Wheeler (chronostratigraphic) diagram from a set of topographic surfaces
    """
    strat = topostrat(topo) # convert topography to stratigraphy
    wheeler = np.diff(topo, axis=2) # 'normal' Wheeler diagram
    wheeler_strat = np.diff(strat, axis=2) # array for Wheeler diagram with vacuity blanked out; this array will be a positive number if there is preserved depostion, zero otherwise
    vacuity = np.zeros(np.shape(wheeler)) # array for vacuity
    vacuity[(wheeler>0) & (wheeler_strat==0)] = 1 # make the 'vacuity' array 1 where there was deposition (wheeler > 0) but stratigraphy is not preserved (wheeler_strat = 0)
    wheeler_strat[wheeler<0] = wheeler[wheeler<0] # add erosion to 'wheeler_strat' (otherwise it would only show deposition)
    return strat, wheeler, wheeler_strat, vacuity

def plot_model_cross_section_EW(strat, prop, facies, dx, xsec, color_mode, line_freq = 1, ve = False, map_aspect = 1, flattening_ind = False, units = 'm', list_of_colors = ['lemonchiffon', 'peru', 'sienna']):
    """Plots an E-W oriented cross section through a stratigraphic model

    :param WG: well graph
    :param strat: stratigraphic grid
    :param prop: property array
    :param facies: facies array
    :param dx: gridcell size in the x- and y directions
    :param xsec: index of cross section to be displayed
    :param color_mode: determines what kind of plot is created; can be 'property' or 'facies'
    :param flattening_ind: index of stratigraphic top that should be used for flattening; default is 'False' (= no flattening)
    :param ve: vertical exaggeration; default is 'False'
    :param units: units used in the model
    :param map_aspect: the aspect ratio of the inset map that shows the location of the cross section
    :param list_of_colors: list of named matplotlib colors that will be used when 'color_mode' is set to 'facies'

    :return fig: figure handle
    """
    fig = plt.figure(figsize = (10, 6))
    ax = fig.add_subplot(111)
    axin = ax.inset_axes([0.03, 0.03, 0.3, 0.3])
    r,c,ts = np.shape(strat)
    for i in trange(0, ts-1):
        if flattening_ind:
            top = (strat[xsec, :, i] - strat[xsec, :, flattening_ind])
            base = (strat[xsec, :, i+1] - strat[xsec, :, flattening_ind])
        else:
            top = strat[xsec, :, i]
            base = strat[xsec, :, i+1]
        props = prop[xsec, :, i]
        faciess = facies[xsec, :, i]
        if np.max(base - top)>0:
            Points, Inds = triangulate_layers(base,top,dx)
            for j in range(len(Points)):
                vertices = Points[j]
                triangles, scalars = create_triangles(vertices)
                x = vertices[:,0]
                y = vertices[:,1]
                if color_mode == 'property':
                    colors = props[Inds[j]]
                    colors = np.mean(colors[np.array(triangles)], axis = 1)
                    ax.tripcolor(x, y, triangles=triangles, facecolors = colors, cmap = 'YlOrBr_r',
                                  edgecolors = 'none', vmin = 0, vmax = 0.35)
                if color_mode == 'facies':
                    colors = faciess[Inds[j]]
                    colors = np.median(colors[np.array(triangles)], axis = 1)
                    cmap = ListedColormap(list_of_colors)
                    ax.tripcolor(x, y, triangles=triangles, facecolors = colors, edgecolors = 'none', cmap = cmap, vmin = 0, vmax = len(list_of_colors))
            if np.mod(i, line_freq) == 0:
                ax.plot(np.arange(0, dx*c, dx), top, 'k', linewidth = 0.25)
            if i == ts-2:
                ax.plot(np.arange(0, dx*c, dx), base, 'k', linewidth = 0.5)
    ax.set_xlim(0, dx*(c-1))
    if flattening_ind:
        ax.set_ylim(np.nanmin(strat[:,:,0] - strat[:, :, flattening_ind]),
                    np.nanmax(strat[:,:,-1] - strat[:, :, flattening_ind]))
    else:
        ax.set_ylim(np.nanmin(strat), np.nanmax(strat))
    ax.set_xlabel('distance (' + units + ')')
    ax.set_ylabel('depth (' + units + ')')
    axin.imshow(strat[:, :, -1], cmap='viridis', aspect = map_aspect)
    axin.set_xticks([])
    axin.set_yticks([])
    axin.plot([0, c-1], [xsec, xsec], 'k')
    # axin.set_aspect('equal')
    if ve:
        ax.set_aspect(ve, adjustable='datalim')
    # plt.tight_layout()
    return fig

def plot_model_cross_section_NS(strat, prop, facies, dx, xsec, color_mode, line_freq = 1, ve = False, flattening_ind = False, units = 'm', map_aspect = 1, list_of_colors = ['lemonchiffon', 'peru', 'sienna']):
    """Plots an E-W oriented cross section through a stratigraphic model

    :param WG: well graph
    :param strat: stratigraphic grid
    :param prop: property array
    :param facies: facies array
    :param dx: gridcell size in the x- and y directions
    :param xsec: index of cross section to be displayed
    :param color_mode: determines what kind of plot is created; can be 'property' or 'facies'
    :param flattening_ind: index of stratigraphic top that should be used for flattening; default is 'False' (= no flattening)
    :param units: units used in the model
    :param map_aspect: the aspect ratio of the inset map that shows the location of the cross section
    :param list_of_colors: list of named matplotlib colors that will be used when 'color_mode' is set to 'facies'

    :return fig: figure handle
    """
    fig = plt.figure(figsize = (10, 6))
    ax = fig.add_subplot(111)
    axin = ax.inset_axes([0.03, 0.03, 0.3, 0.3])
    r,c,ts = np.shape(strat)
    for i in trange(0, ts-1):
        if flattening_ind:
            top = (strat[:, xsec, i] - strat[:, xsec, flattening_ind])
            base = (strat[:, xsec, i+1] - strat[:, xsec, flattening_ind])
        else:
            top = strat[:, xsec, i]
            base = strat[:, xsec, i+1]
        props = prop[:, xsec, i]
        faciess = facies[:, xsec, i]
        if np.max(base - top)>0:
            Points, Inds = triangulate_layers(base,top,dx)
            for j in range(len(Points)):
                vertices = Points[j]
                triangles, scalars = create_triangles(vertices)
                x = vertices[:,0]
                y = vertices[:,1]
                if color_mode == 'property':
                    colors = props[Inds[j]]
                    colors = np.mean(colors[np.array(triangles)], axis = 1)
                    ax.tripcolor(x, y, triangles=triangles, facecolors = colors, cmap = 'YlOrBr_r',
                                  edgecolors = 'none', vmin = 0, vmax = 0.35)
                if color_mode == 'facies':
                    colors = faciess[Inds[j]]
                    colors = np.median(colors[np.array(triangles)], axis = 1)
                    cmap = ListedColormap(list_of_colors)
                    ax.tripcolor(x, y, triangles=triangles, facecolors = colors, edgecolors = 'none', cmap = cmap, vmin = 0, vmax = len(list_of_colors))
            if np.mod(i, line_freq) == 0:
                ax.plot(np.arange(0, dx*r, dx), top, 'k', linewidth = 0.5)
            if i == ts-2:
                ax.plot(np.arange(0, dx*r, dx), base, 'k', linewidth = 0.5)
    ax.set_xlim(0, dx*(r-1))
    if flattening_ind:
        ax.set_ylim(np.nanmin(strat[:,:,0] - strat[:, :, flattening_ind]),
                    np.nanmax(strat[:,:,-1] - strat[:, :, flattening_ind]))
    else:
        ax.set_ylim(np.nanmin(strat), np.nanmax(strat))
    ax.set_xlabel('distance (' + units + ')')
    ax.set_ylabel('depth (' + units + ')')
    axin.imshow(strat[:, :, -1], cmap='viridis', aspect = map_aspect)
    axin.set_xticks([])
    axin.set_yticks([])
    axin.plot([xsec, xsec], [0, r-1],  'k')
    # plt.tight_layout()
    if ve:
        ax.set_aspect(ve, adjustable='datalim')
    return fig

def resample_elevation_spl(time, elevation, sampling_rate):
    spl = interpolate.splrep(time, elevation, s=0.5)
    time_new = np.arange(time[0], time[-1]+1, sampling_rate)
    elevation_new = interpolate.splev(time_new, spl)
    return time_new, elevation_new
def resample_elevation_int1d(time, elevation, sampling_rate):
    f = interpolate.interp1d(time, elevation)
    time_new = np.arange(time[0], time[-1]+1, sampling_rate)
    elevation_new = f(time_new)
    return time_new, elevation_new


import numpy as np
import argparse

# parser = argparse.ArgumentParser(description='Keypoints distance computing script')

# parser.add_argument(
#     '--origin_image_file', type=str, required=False,
#     help='path to a file containing the keypoints and descriptors of the first image'
# )

# parser.add_argument(
#     '--destination_image_file', type=str, required=False,
#     help='path to a file containing the keypoints and descriptors of the second image'
# )

# parser.add_argument(
#     '--matrix_file', type=str, required=False,
#     help='path to a file containing the transform matrix from the origin image to the destination image'
# )

# parser.add_argument(
#     '--threshold', type=int, default=1,
#     help='the maximum distance between two points to be considered as a pair'
# )

# args = parser.parse_args()

def keypoints_distance(file1, file2, matrix_file, threshold):

    origin_keypoints = np.load(file1)['keypoints']
    destination_keypoints = np.load(file2)['keypoints']
    transform_matrix = np.loadtxt(matrix_file)

    origin_keypoints_transform = np.hstack(np.array([0,0]))
    keypoints_distances = np.hstack(np.array([0 for _ in range(destination_keypoints.shape[0])]))

    # print("----")
    # print("Counting keypoints pairs between", file1, "and", file2)
    # print("Threshold is set to", threshold)
    # print(f"{origin_keypoints.shape[0]} keypoints in {file1}")
    # print(f"{destination_keypoints.shape[0]} keypoints in {file2}")
    # print("----")

    for og_keypoint in origin_keypoints:
        og_keypoint = np.append(og_keypoint, 1)
        keypoint_transform = np.matmul(og_keypoint, transform_matrix)
        keypoint_transform = keypoint_transform[:-1]

        keypoint_distance_vector = np.empty(0)
        for dst_keypoint in destination_keypoints:
            keypoint_distance_vector = np.append(keypoint_distance_vector, np.linalg.norm(keypoint_transform-dst_keypoint))

        keypoints_distances = np.vstack((keypoints_distances, keypoint_distance_vector))
        origin_keypoints_transform = np.vstack((origin_keypoints_transform, keypoint_transform))

    keypoints_distances = np.delete(keypoints_distances, 0, 0)
    valid_pairs_count = 0

    for distances_list in keypoints_distances:
        for distance in distances_list:
            if distance <= threshold:
                valid_pairs_count += 1
                break

    return(round((valid_pairs_count / origin_keypoints.shape[0])*100, 2))

# print(keypoints_distance(args.origin_image_file, args.destination_image_file, args.matrix_file, args.threshold))


from copy import deepcopy

arr = [[None]*4 for _ in range(4)]
for i in range(4):
    row = list(map(int, input().split()))
    for j in range(4):
        # (번호, 방향)
        arr[i][j] = [row[j*2], row[j*2+1]-1]

dirs = [(-1, 0), (-1, -1), (0, -1),
        (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1)]

# 현재 위치에서 왼쪽으로 회전된 결과 반환
def turn_left(direction):
    return (direction+1) % 8

# 특정 번호 물고기 찾기
def find_fish(arr, idx):
    for i in range(4):
        for j in range(4):
            if arr[i][j][0] == idx:
                return (i,j)
    return None

# 모든 물고기 회전 및 이동 - (arr, 상어위치)
def move_all_fishes(arr, now_r, now_c):
    for i in range(1, 17):
        position = find_fish(arr, i)
        if position != None:
            r, c = position[0], position[1]
            direction = arr[r][c][1]

            # 물고기 회전하며 이동가능하지 확인
            for j in range(8):
                dr, dc = dirs[direction]
                nr, nc = dr + r, dc + c
                # 이동 가능한 경우 이동
                if 0 <= nr < 4 and 0 <= nc < 4:
                    if not (nr == now_r and nc == now_c):
                        arr[r][c][1] = direction
                        arr[r][c], arr[nr][nc] = arr[nr][nc], arr[r][c]
                        break
                direction = turn_left(direction)

# 상어가 먹을 수 있는 물고기 위치 반환
def can_eat_fishes(arr, now_r, now_c):
    fishes = []
    direction = arr[now_r][now_c][1]

    # 현재 방향으로 계속 이동
    for i in range(4):
        dr, dc = dirs[direction]
        now_r, now_c = dr+now_r, dc+now_c
        # 범위를 벗어나는지 확인
        if 0 <= now_r < 4 and 0 <= now_c <4:
            # 물고기 존재시
            if arr[now_r][now_c][0] != -1:
                fishes.append((now_r, now_c))
    return fishes

# 모든 경우 탐색
def dfs(arr, now_r, now_c, total):
    global result
    arr = deepcopy(arr)

    total += arr[now_r][now_c][0] # 현재 위치 물고기 먹기
    arr[now_r][now_c][0] = -1 # 물고기 먹은 후 -1

    move_all_fishes(arr, now_r, now_c)

    # 상어가 이동가능한 위치 찾기
    fishes = can_eat_fishes(arr, now_r, now_c)

    if not fishes:
        result = max(result, total)
        return

    for next_r, next_c in fishes:
        dfs(arr, next_r, next_c, total)

result = 0
dfs(arr, 0, 0, 0)
print(result)


"""
Find the LCA of Binary Tree.

https://www.youtube.com/watch?v=13m9ZCB8gjw
"""

def lca(root, n1, n2):

    if root is None:
        return None
    if root.data == n1 or root.data == n2:
        return root
    node_left = lca(root.left, n1, n2)
    node_right = lca(root.right, n1, n2)

    if node_left is not None and node_right is not None:
        return root
    if node_left is None and root.right is None:
        return None

    return node_left if node_left is not None else node_right