# -*- coding: utf-8 -*-
"""
.. codeauthor:: Jaume Bonet <jaume.bonet@gmail.com>

.. affiliation::
    Laboratory of Protein Design and Immunoengineering <lpdi.epfl.ch>
    Bruno Correia <bruno.correia@epfl.ch>

.. class:: FragmentFrame
"""
# Standard Libraries
import os
import math
import sys
from collections import Counter
import itertools

# External Libraries
import pandas as pd
import numpy as np
import networkx as nx

# This Library
from rstoolbox.utils import make_rosetta_app_path, execute_process

__all__ = ["FragmentFrame"]

def _metadata_defaults(name):
    if name == "_source_file":
        return None
    return None

class FragmentFrame( pd.DataFrame ):
    """Data container for Fragment data.

    Extends :py:class:`pandas.DataFrame` adding some functionalities in order
    to facilitate analysis, plotting and comparisson.

    Filled through the functions provided through this library, each
    row represents a position of a fragment, while columns describe the
    properties.

    .. seealso::
        :func:`.parse_rosetta_fragments`
        :func:`.plot_fragments`
        :func:`.plot_fragment_profiles`

    . rubric:: Example

    .. ipython::

        In [1]: from rstoolbox.io import parse_rosetta_fragments
           ...: import pandas as pd
           ...: pd.set_option('display.width', 1000)
           ...: pd.set_option('display.max_columns', 500)
           ...: df = parse_rosetta_fragments("../rstoolbox/tests/data/wauto.200.3mers.gz")
           ...: df.head()
    """
    _metadata = ['_source_file']
    _internal_names = pd.DataFrame._internal_names + ['_crunched']
    _internal_names_set = set(_internal_names)

    def __init__(self, *args, **kw):
        source_file = kw["file"] if "file" in kw else None
        if "file" in kw:
            del(kw["file"])
        super(FragmentFrame, self).__init__(*args, **kw)
        self._crunched = {}
        self._source_file = source_file

    def add_source_file( self, file ):
        """Adds a source file to the :class:`.FragmentFrame`.

        If loaded through :func:`.parse_rosetta_fragments`, the source file is
        automatically added.

        This can be used to automatically generate the fragment RMSD quality.

        :param str file: Name of the file.

        .. seealso:
            :meth:`FragmentFrame.add_quality_measure`
        """
        self._source_file = os.path.abspath(file)

    def get_source_file( self ):
        """Returns the file name linked to this :class:`.FragmentFrame`.

        :return: :class:`str` - Source file path.
        """
        return self._source_file

    def has_source_file( self ):
        """Checks if there is a source file added.

        :return: bool
        """
        return self._source_file is not None

    def slice_region( self, ini, end ):
        """Retrieve only fragments within certain positions.

        :param int ini: Starting position.
        :param int end: Ending position.

        :return: :class:`.FragmentFrame`
        """
        return self[(self['frame'] >= ini) & (self['frame'] <= end)]

    def top_limit( self, limit ):
        """Remove all frames whose position puts the frames over a given limit.

        :param int limit: Max position that the fragments are expected to reach.

        :return: :class:`.FragmentFrame`
        """
        df = self.copy()
        max_frame = df[df['position'] == limit]['frame'].min()
        # If we don't find the top limit frame
        if max_frame is np.nan:
            # If the fragments set is smaller, we fix it,
            # otherwise we will return empty FragmentFrame, which is fine if the
            # smallest frame is over the top limit.
            if df['frame'].max() < limit:
                max_frame = df['frame'].max()
        return df[df['frame'] <= max_frame]

    def is_comparable( self, df ):
        """Evaluate if the current :class:`.FragmentFrame` is comparable to
        the provided one.

        This is checked on terms of (a) covered sequence length -range- and
        (2) fragment size.

        :return: :class:`bool` - True if the two :class:`.FragmentFrame` are comparable.
        """
        if max(self["position"]) != max(df["position"]):
            return False
        if min(self["position"]) != min(df["position"]):
            return False
        if self["size"].values[0] != df["size"].values[0]:
            return False
        return True

    def coerce( self ):
        """Make fragment data coherent.

        Evaluates that the columns ``neighbors``, ``neighbor`` and ``position`` are coherent
        with the data contained according to ``frame`` and ``size``.

        :return: :class:`.FragmentFrame`
        """
        df = self.copy()
        gcond = ['neighbor', 'pdb'] if 'source' not in df.columns else ['neighbor', 'pdb', 'source']
        for frame_id, frame in df.groupby('frame'):
            g = frame.groupby(gcond)
            neighbors = len(g)
            neighbor = list(g.ngroup() + 1)
            position = list(g.cumcount() + frame_id)
            df.loc[(df['frame'] == frame_id), 'neighbors'] = [neighbors] * frame.shape[0]
            df.loc[(df['frame'] == frame_id), 'neighbor'] = neighbor
            df.loc[(df['frame'] == frame_id), 'position'] = position
        return df

    def order( self ):
        """Properly reorder the :class:`.FragmentFrame`.

        :return: :class:`.FragmentFrame`
        """
        df = self.copy()
        return df.sort_values(['frame', 'neighbor', 'position'])

    def add_fragments( self, fragments, ini, how='replace' ):
        """Add to a given position a set of fragments more fragments.

        Provided a :class:`.FragmentFrame`, use it to add those fragments
        to the set of available ones starting in the given position. The
        size and positions of the new fragments is used to assess up to where
        they should go.

        :param fragments: New fragments to add.
        :type fragments: :class:`.FragmentFrame`
        :param int ini: Initial position to where to add the new fragments.
        :param str how: Adding mode: ``replace`` deletes the actual fragments
            and adds the new provided ones. ``append`` adds them to the actual
            set of fragments for the required positions.

        :return: :class:`.FragmentFrame` - with the requested modifications.

        :raises:
            :ValueError: If the size of the provided fragments is not the same
                as that of the actual ones.
        """
        if (self['size'].unique() != fragments['size'].unique()).any():
            raise ValueError('Only same-sized fragments can be merged.')

        frags = fragments.copy()
        df = self.copy()
        columns = ['frame', 'neighbor', 'position']

        # Setup new frame range
        frags = frags.renumber(ini)

        if how.lower() == 'replace':
            df = df[(df['frame'] < ini) | (df['frame'] > max(frags['frame'].unique()))]
            df = pd.concat([df, frags.coerce()]).sort_values(columns).reset_index(drop=True)
        if how.lower() == 'append':
            df = pd.concat([df, frags]).reset_index(drop=True)
            df = df.coerce().sort_values(columns)

        return df

    def renumber( self, ini=1 ):
        """Make the fragment set start at the given position.

        :param int ini: Initial position of the new :class:`.FragmentFrame`.

        :return: :class:`.FragmentFrame` - with the requested modifications.
        """
        df = self.copy()
        df['frame'] = df['frame'] + ini - df['frame'].values[0]
        return df.coerce()

    def sample_top_neighbors( self, max_count=200 ):
        """Limit to the top selected neighbors for each frame.

        :param int max_count: Maximum neighbors for frame

        :return: :class:`.FragmentFrame` - with the requested modifications.
        """
        df = self.copy()
        return df[df['neighbor'] <= max_count].coerce()

    def add_quality_measure( self, filename, pdbfile=None ):
        """Add RMSD quality measure to the fragment data.

        The RMSD quality measurement is performed by the ``r_fraq_qual`` application
        from **Rosetta**. It can be called as:

        .. code-block:: bash

           r_fraq_qual.linuxgccrelease -in:file:native <pdb> -f <fragfile> -out:qual <output>

        :param str filename: Name containing the quality measure. If ``filename`` is None,
            it assumes that RMSD quality has not been calculated yet, so it'll run the
            ``r_fraq_qual`` application as long as the :class:`.FragmentFrame`
            has a ``source_file``. Standart output will be the name of the source
            file with the extension ".qual". If a file with this naming schema exists,
            it will be automatically picked. To be able to run *Rosetta* it will need
            a ``pdbfile``.
        :param str pdbfile: In case the quality has to be calculated. Provide the
            PDB over which to calculate it. Default is :data:`None`.

        :return: :class:`.FragmentFrame`

        :raises:
            :IOError: if ``filename`` does not exist.
            :IOError: if ``pdbfile`` is provided and does not exist.
            :IOError: if the *Rosetta* executable is not found.
            :AttributeError: if ``filename`` is :data:`None` and there is no attached
                ``source_file`` to the object.
            :ValueError: if no rmsd data is assigned. Might indicate that wrong data is
                trying to be assigned.

        .. note::
            Depends on :ref:`rosetta.path <options>` and :ref:`rosetta.compilation <options>`,
            if the quality file is not provided.

        .. attention::
            Some configurations of this function require a local installation of **Rosetta**.
        """
        if filename is None and not self.has_source_file():
            raise AttributeError("No quality file is provided and no source file can be found.")

        # Make the quality fragmet eval if needed.
        if filename is None:
            sofi = self._source_file
            if sofi.endswith(".gz"):
                sofi = ".".join(sofi.split(".")[:-1])
            filename = sofi + ".qual"
            if not os.path.isfile(filename) and not os.path.isfile(filename + ".gz"):
                # Check rosetta executable
                exe = make_rosetta_app_path('r_frag_quality')
                if not os.path.isfile(pdbfile):
                    raise IOError("{0} not found".format(pdbfile))
                command = "{0} -in:file:native {1} -f {2} -out:qual {3}".format(
                          exe, pdbfile, self._source_file, filename )
                error = execute_process( command )
                if not bool(error):
                    sys.stdout.write("Execution has finished\n")
                else:
                    sys.stdout.write("Execution has failed\n")
            elif os.path.isfile(filename + ".gz"):
                filename = filename + ".gz"

        # Load the data
        df = pd.read_csv(filename, header=None, sep=r'\s+',
                         names=["size", "frame", "neighbor", "rmsd", "_null1", "_null2"],
                         usecols=["size", "frame", "neighbor", "rmsd"])

        df = self.merge(df, how='left', on=["size", "frame", "neighbor"])
        if (df['rmsd'].isnull()).all():
            raise ValueError('No rmsd was assigned to the fragment data. '
                             'Check that the correct quality data is being assigned.')
        return df

    def select_quantile( self, quantile=0.25 ):
        """Returns fragments under the rmsd threshold of the specified uantile.

        :param float quantile: Quantile maximum limit.

        :return: :class:`.FragmentFrame` - The filtered data.

        :raises:
            :KeyError: if the ``rmsd`` column cannot be found.

        .. seealso::
            :meth:`.FragmentFrame.add_quality_measure`
        """
        def _select_quantile(group, quantile):
            qtl = group["rmsd"].quantile(.25)
            return group[group["rmsd"] <= qtl]

        df = self.groupby("frame").apply(lambda g: _select_quantile(g, quantile))
        df.index = df.index.get_level_values(1)
        df._source_file = self._source_file
        return df

    def make_sequence_matrix( self, frequency=False, round_data=False ):
        """Generate a PSSM-like matrix from the fragments.

        The matrix will contain, for each position the relative enrichment of each
        residue type. By default, this will be:

        :math:`logodd(f(ni)/f(bi))`

        unless ``frequency`` is requested.

        :param bool frequency: Return the matrix with frequency values..
        :param bool round_data: Round-floor the values.

        :return: :class:`~pandas.DataFrame`
        """
        alphabet = "ARNDCQEGHILKMFPSTWYV"
        baseline = dict.fromkeys(alphabet, 1.0)
        total = sum(baseline.values())
        for k in baseline:
            baseline[k] = float(baseline[k]) / total

        matrix = {}
        for i in range(1, max(self["position"].values) + 1):
            qseq = Counter(self[self["position"] == i]["aa"].values)
            qttl = sum(qseq.values(), 0.0)
            for k in qseq:
                qseq[k] /= qttl
            for aa in baseline:
                q = qseq[aa]
                if not frequency:
                    if q > 0:
                        logodds = math.log(q / baseline[aa], 2)
                    else:
                        logodds = -9
                    matrix.setdefault(aa, []).append(logodds)
                else:
                    matrix.setdefault(aa, []).append(q)
        df = pd.DataFrame(matrix)
        if round_data:
            df = df.applymap(np.around).astype(np.int64).reindex(columns=list(alphabet))
        else:
            df = df.reindex(columns=list(alphabet))
        df.index = range(1, df.shape[0] + 1)
        return df

    def make_per_position_frequency_network( self ):
        """Generate a graph representation of the per-residue frequency.

        Generate a directed graph of class :class:`~networkx.DiGraph` in which each node is
        a residue type and each edge the frequency expected for that residue type in that
        position according to the frequency matrix. As a matter of fact, the edge is the
        *inverted frequency*, as the idea is to use the graph to calculate the shortest paths
        (i.e. the more probable sequences).

        :return: :class:`~networkx.DiGraph` - with as many nodes as the 20 possible amino acids
            by the length of the sequence.

        .. warning::
            This is an experimental feature.

        .. seealso::
            :meth:`.FragmentFrame.make_frequency_network`
        """
        matrix = self.make_sequence_matrix(frequency=True)

        g = nx.DiGraph()

        for i, row in matrix.iterrows():
            if i == matrix.iloc[0].name:
                nterm = ["0X", ]
            invrow = (1 - row[row > 0])
            cterm = [str(i) + _ for _ in list(invrow.index)]
            for n, c in itertools.product(nterm, cterm):
                g.add_edge(n, c, weight=invrow[c[-1]])
            nterm = cterm
        for n in nterm:
            g.add_edge(n, "-1X", weight=0)
        return g

    def make_frequency_network( self, use_rmsd=False ):
        """Generate a graph with per-position frequency between residue types at each position.

        Generate a :class:`~networkx.DiGraph` in which each node is a residue type and each
        edge the frequency expected for the transition between residue-type in position i and
        residue-type in position i+1.

        :param bool use_rmsd: When :data:`True`, correct the pair counts by the RMSD. Basically,
            the smaller the RMSD, the more the count weights.

        :return: :class:`~networkx.DiGraph` - with as many nodes as the 20 possible amino acids
            by the length of the sequence.

        .. warning::
            This is an experimental feature.

        .. seealso::
            :meth:`.FragmentFrame.make_per_position_frequency_network`
        """
        G = nx.DiGraph()
        min_nodes = []
        max_nodes = []

        def window(seq, n=2):
            it = iter(seq)
            result = tuple(itertools.islice(it, n))
            if len(result) == n:
                yield result
            for elem in it:
                result = result[1:] + (elem,)
                yield result

        data = {}
        for tp, df in self.groupby(["frame", "neighbor"]):
            transitions = list(window(list(df["position"].unique())))
            rmsd = self[self["frame"] == tp[0]]["rmsd"].max() - df["rmsd"].values[0]
            rmsd = int(rmsd * 100)
            for tr in transitions:
                n = df[df["position"] == tr[0]]["aa"].values[0]
                c = df[df["position"] == tr[1]]["aa"].values[0]
                if use_rmsd:
                    data.setdefault(tr[0], []).extend([(n, c), ] * rmsd)
                else:
                    data.setdefault(tr[0], []).append((n, c))

        for k in data:
            options = len(data[k])
            data[k] = Counter(data[k])
            options = data[k].most_common(1)[0][1]
            for p in data[k]:
                n = str(k) + p[0]
                G.add_node(n, order=k, type=p[0])
                c = str(k + 1) + p[1]
                G.add_node(c, order=k + 1, type=p[1])
                G.add_edge(n, c, weight=(options - data[k][p]) / options)
                if k == min(data):
                    min_nodes.append(n)
                if k == max(data):
                    max_nodes.append(c)

        for node in min_nodes:
            G.add_node("0X", order=0, type="X")
            G.add_edge("0X", node, weight=0)
        for node in max_nodes:
            G.add_node("-1X", order=max(data) + 2, type="X")
            G.add_edge(node, "-1X", weight=0)

        return G

    def quick_consensus_sequence( self ):
        """Consensus sequence with the highest representative per position.

        .. warning::
            This is an experimental feature.

        :return: :class:`str` - consensus sequence
        """
        consensus = []
        for i in range(1, max(self["position"].values) + 1):
            values = self[self["position"] == i]["aa"].values
            qseq = sorted(Counter(values).most_common(), key=lambda x: (-x[1], x[0]))[0]
            consensus.append(qseq[0])
        return "".join(consensus)

    def quick_consensus_secondary_structure( self ):
        """Consensus secondary structure with the highest representative per position.

        .. warning::
            This is an experimental feature.

        :return: :class:`str` - consensus secondary structure
        """
        consensus = []
        for i in range(1, max(self["position"].values) + 1):
            values = self[self["position"] == i]["sse"].values
            qseq = sorted(Counter(values).most_common(), key=lambda x: (-x[1], x[0]))[0]
            consensus.append(qseq[0])
        return "".join(consensus)

    #
    # Implement pandas methods
    #
    @property
    def _constructor(self):
        return FragmentFrame

    def __finalize__(self, other, method=None, **kwargs):

        if method == 'merge':
            for name in self._metadata:
                setattr(self, name, getattr(other.left, name, _metadata_defaults(name)))
        else:
            for name in self._metadata:
                setattr(self, name, getattr(other, name, _metadata_defaults(name)))

        return self


import argparse
import secrets

import yaml
from symbolchain.CryptoTypes import Hash256, PrivateKey
from symbolchain.facade.NemFacade import NemFacade
from symbolchain.nem.Network import Network
from zenlog import log

MICROXEM_PER_XEM = 1000000

class NemesisConfigurationGenerator:
	def __init__(self, network_name, custom_network_identifier):
		network_identifier = (
			int(custom_network_identifier) if custom_network_identifier else Network.MAINNET.identifier if 'mainnet' == network_name else
			Network.TESTNET.identifier
		)
		epoch = Network.MAINNET.datetime_converter.epoch if 'mainnet' == network_name else Network.TESTNET.datetime_converter.epoch
		self.facade = NemFacade(Network(network_name, network_identifier, epoch))
		self.signer_private_key_pair = self.facade.KeyPair(PrivateKey.random())
		self.generation_hash = Hash256(self._random_bytes(Hash256.SIZE))
		self.account_key_pairs = []

	def print_header(self, amount):
		signer_address = self.facade.network.public_key_to_address(self.signer_private_key_pair.public_key)
		total_xem = (len(self.account_key_pairs) * amount) / MICROXEM_PER_XEM

		log.info('Generated Nemesis Configuration')
		log.info(f' *  SIGNER ADDRESS: {signer_address}')
		log.info(f' * GENERATION HASH: {self.generation_hash}')
		log.info(f' *  TOTAL ACCOUNTS: {len(self.account_key_pairs)}')
		log.info(f' *       TOTAL XEM: {total_xem:.6f}')

	def generate_keys(self, count):
		self.account_key_pairs = [self.facade.KeyPair(PrivateKey.random()) for _ in range(0, count)]

	def save_nemesis_configuration(self, amount, network_name, output_filepath):
		accounts = [{
			'address': str(self.facade.network.public_key_to_address(key_pair.public_key)),
			'amount': amount
		} for key_pair in self.account_key_pairs]

		configuration = {
			'signer_private_key': str(self.signer_private_key_pair.private_key),
			'generation_hash': str(self.generation_hash),
			'network': network_name,
			'epoch_time': int(self.facade.network.datetime_converter.epoch.timestamp()),
			'accounts': accounts,
			'identifier': self.facade.network.identifier
		}

		self._save_configuration_file(output_filepath, configuration)

	def save_account_configuration(self, output_filepath):
		account_descriptors = [{
			'privatekey': str(key_pair.private_key),
			'publickey': str(key_pair.public_key),
			'address': str(self.facade.network.public_key_to_address(key_pair.public_key))
		} for key_pair in self.account_key_pairs]

		configuration = {
			'generation_hash': str(self.generation_hash),
			'accounts': account_descriptors,
			'nemesis': {
				'privatekey': str(self.signer_private_key_pair.private_key),
				'publickey': str(self.signer_private_key_pair.public_key),
				'address': str(self.facade.network.public_key_to_address(self.signer_private_key_pair.public_key))
			}
		}

		self._save_configuration_file(output_filepath, configuration)

	@staticmethod
	def _save_configuration_file(output_filepath, configuration):
		with open(output_filepath, 'wt', encoding='utf8') as configuration_file:
			yaml.dump(configuration, configuration_file)

	@staticmethod
	def _random_bytes(size):
		return secrets.token_bytes(size)

def main():
	run_command_name = None
	if globals().get('__spec__'):
		program_name = __spec__.name.partition('.')[0]
		run_command_name = f'python -m {program_name}'

	parser = argparse.ArgumentParser(
		prog=run_command_name,
		description='NEM nemesis configuration generator'
	)
	parser.add_argument('-c', '--count', type=int, help='Number of accounts in the nemesis', required=True)
	parser.add_argument('-s', '--seed', type=int, help='Seed amount', required=True)
	parser.add_argument('-o', '--output', help='Nemesis configuration file', required=True)
	parser.add_argument('-a', '--accounts-output', help='Account configuration file', required=True)
	parser.add_argument('-n', '--network-name', help='Network name', choices=('mainnet', 'testnet'), required=True)
	parser.add_argument('-i', '--network-identifier', help='Network identifier', required=False)
	args = parser.parse_args()

	generator = NemesisConfigurationGenerator(args.network_name, args.network_identifier)
	generator.generate_keys(args.count)
	generator.save_nemesis_configuration(args.seed, args.network_name, args.output)
	generator.save_account_configuration(args.accounts_output)
	generator.print_header(args.seed)

if '__main__' == __name__:
	main()


class Solution:
    # this function will return the nth fibonacci number
    def fib(self, n: int) -> int:
        # if n is 0, return 0
        if n == 0:
            return 0
        # if n is 1, return 1
        if n == 1:
            return 1
        # if n is greater than 1, return the nth fibonacci number
        else:
            # create two variables to store the first and second fibonacci number
            first_val = 0
            second_val = 1
            # iterate n-1 times
            for i in range(n-1):
                # update the first and second fibonacci number
                first_val, second_val = second_val, first_val + second_val
            # return the second fibonacci number
            return second_val


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : cyl
# @Time : 2021/11/25 8:52
from typing import List

class Solution:
    def reverseString(self, s: List[str]) -> None:
        """
        Do not return anything, modify s in-place instead.
        """
        if not s:return s
        left, right = 0, len(s) - 1
        while left < right:
            s[left], s[right] = s[right], s[left]
            left += 1
            right -= 1


import datetime
import os
import shutil
import subprocess

from __init__ import open_iv_exe, vehicles_folder, files_folder

def get_car_name_from_folder(folder, root):
    return folder.replace(root, "").split('\\')[1]

def open_rpf(file):
    # print(f'[OpenIV] Processing {file}')
    os.popen(f'"{open_iv_exe}" "{file}" -core.game:Five').readlines()

def is_folder_ignored(folder):
    folders_ignored = ['lang']
    ignored = False

    for folder_ignored in folders_ignored:
        if f"\\{folder_ignored}" in folder:
            ignored = True

    return ignored

def process_vehicle_rpf(root, file):
    car_name = get_car_name_from_folder(root, vehicles_folder)
    print(f'[{car_name}] Processing {file}')
    vehicle_folder = os.path.join(files_folder, car_name)
    vehicle_input_folder = os.path.join(vehicle_folder, 'input')
    vehicle_output_folder = os.path.join(vehicle_folder, car_name.lower().replace(' ', '-'))
    if not os.path.exists(vehicle_input_folder):
        os.makedirs(vehicle_input_folder)
    if not os.path.exists(vehicle_output_folder):
        os.makedirs(vehicle_output_folder)
    subprocess.call(f'explorer "{vehicle_input_folder}"', shell=True)
    open_rpf(os.path.join(root, file))

def extract_vehicle_data(folder):
    car_name = get_car_name_from_folder(folder, files_folder)
    print(f'[{car_name}] Extracting Data')
    subprocess.call(f'explorer "{os.path.join(folder, "input")}"', shell=True)
    for root, dirs, files in os.walk(folder):
        if not is_folder_ignored(root):
            for file in files:
                if file.endswith('.rpf'):
                    open_rpf(os.path.join(root, file))

def create_fivem_files(folder):
    car_name = get_car_name_from_folder(folder, files_folder)
    print(f'[{car_name}] Generating FiveM files')

    input_folder = os.path.join(folder, 'input')
    fivem_folder = os.path.join(folder, car_name.lower().replace(' ', '-'))

    data_folder = os.path.join(fivem_folder, 'data')
    stream_folder = os.path.join(fivem_folder, 'stream')

    if not os.path.exists(data_folder):
        os.makedirs(data_folder)
    if not os.path.exists(stream_folder):
        os.makedirs(stream_folder)

    files_mapping = {
        '.meta': data_folder,
        '.ytd': stream_folder,
        '.yft': stream_folder,
    }

    for root, dirs, files in os.walk(input_folder):
        if not is_folder_ignored(root):
            for file in files:
                for extension in files_mapping:
                    if file.endswith(extension):
                        shutil.copyfile(os.path.join(root, file), os.path.join(files_mapping[extension], file))

    with open(os.path.join(fivem_folder, '__resource.lua'), "w") as f:
        f.writelines("resource_manifest_version '77731fab-63ca-442c-a67b-abc70f28dfa5'\n")
        f.write("\n")
        f.write("{\n")
        for file in os.listdir(data_folder):
            f.write(f"\t'data/{file}',\n")
        f.write("}\n")
        f.write("\n")
        for file in os.listdir(data_folder):
            data = generate_data_file(f"data/{file}")
            if data is not None:
                f.write(f"{data}\n")
        f.write("\n")

    with open(os.path.join(vehicles_folder, car_name, 'done.txt'), "w") as f:
        f.write(f"{datetime.datetime.now()}")

def generate_data_file(file):
    if file.endswith('contentunlocks.meta') or file.endswith('contentunlock.meta'):
        return f"data_file 'CONTENT_UNLOCKING_META_FILE' 'data/{file}'"
    if file.endswith('vehiclelayouts.meta') or file.endswith('vehiclelayout.meta'):
        return f"data_file 'VEHICLE_LAYOUTS_FILE' 'data/{file}'"
    if file.endswith('carvariations.meta') or file.endswith('carvariation.meta'):
        return f"data_file 'VEHICLE_VARIATION_FILE' 'data/{file}'"
    if file.endswith('carcols.meta') or file.endswith('carcol.meta'):
        return f"data_file 'CARCOLS_FILE' 'data/{file}'"
    if file.endswith('dlctext.meta'):
        return f"data_file 'DLC_TEXT_FILE' 'data/{file}'"
    if file.endswith('shop_vehicle.meta'):
        return f"data_file 'VEHICLE_SHOP_DLC_FILE' 'data/{file}'"
    if file.endswith('handling.meta'):
        return f"data_file 'HANDLING_FILE' 'data/{file}'"
    if file.endswith('vehicles.meta') or file.endswith('vehicle.meta'):
        return f"data_file 'VEHICLE_METADATA_FILE' 'data/{file}'"

    print(f"Unhandled file '{file}'")
    return None

def main():
    for root, dirs, files in os.walk(vehicles_folder):
        if root == vehicles_folder:
            continue
        car_name = get_car_name_from_folder(root, vehicles_folder)
        if os.path.exists(os.path.join(vehicles_folder, car_name, "done.txt")):
            continue
        for file in files:
            if file.endswith('.rpf'):
                process_vehicle_rpf(root, file)

    for folder in os.listdir(files_folder):
        car_folder = os.path.join(files_folder, folder)
        car_name = get_car_name_from_folder(car_folder, files_folder)
        if os.path.exists(os.path.join(vehicles_folder, car_name, "done.txt")):
            continue
        extract_vehicle_data(car_folder)
        create_fivem_files(car_folder)

if __name__ == '__main__':
    main()


import re
from datetime import datetime, timedelta, tzinfo
from time import time
from urllib import request
import logging

from django.contrib.auth.models import User
from django.utils import timezone
from rest_framework import serializers
from django.core.exceptions import ObjectDoesNotExist
from agenda.models import (
    Agendamento,
    Endereco,
    Estabelecimento,
    Fidelidade,
    Funcionarios,
    Servicos,
)
from agenda.utils import get_horarios_disponiveis, verifica_cep

class AgendamentoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Agendamento
        fields = "__all__"

    prestador = serializers.CharField()
    estabelecimento = serializers.CharField()
    servico = serializers.CharField()

    def validate_prestador(self, value):
        try:
            prestador_obj = User.objects.get(username=value)
        except User.DoesNotExist:
            raise serializers.ValidationError("Username incorreto!")
        return prestador_obj

    def validate_estabelecimento(self, value):
        try:
            estabelecimento_obj = Estabelecimento.objects.get(
                nome_estabelecimento=value
            )
        except ObjectDoesNotExist:
            raise serializers.ValidationError("Estabelecimento não encontrado!")
        return estabelecimento_obj

    def validate_servico(self, value):
        try:
            servico_obj = Servicos.objects.get(servico=value)
        except ObjectDoesNotExist:
            raise serializers.ValidationError("Serviço não encontrado!")
        return servico_obj

    def validate_data_horario(self, value):
        if value < timezone.now():
            raise serializers.ValidationError(
                "Agendamento não pode ser feito no passado!"
            )
        if value not in get_horarios_disponiveis(value.date()):
            raise serializers.ValidationError("Este horário não está disponível!")

        return value

    def validate(self, attrs):
        nome_cliente = attrs.get("nome_cliente", "")
        data_horario = attrs.get("data_horario", "")
        telefone_cliente = attrs.get("telefone_cliente", "")
        email_cliente = attrs.get("email_cliente", "")
        prestador = attrs.get("prestador", "")
        estabelecimento = attrs.get("estabelecimento", "")
        servico = attrs.get("servico", "")

        if prestador and estabelecimento:
            if not Funcionarios.objects.filter(
                prestador__username=prestador,
                estabelecimento=estabelecimento,
                servico=servico,
            ).exists():
                raise serializers.ValidationError(
                    "Funcionário, estabelecimento ou serviço não estão corretos ou não existem!"
                )

        if data_horario and email_cliente:
            if Agendamento.objects.filter(
                nome_cliente=nome_cliente,
                email_cliente=email_cliente,
                data_horario__date=data_horario,
                prestador__username=prestador,
                estabelecimento=estabelecimento,
                cancelado=False,
            ).exists():
                raise serializers.ValidationError(
                    "Cada cliente pode ter apenas uma reverva por dia!"
                )

        if (
            email_cliente.endswith(".br")
            and telefone_cliente.startswith("+")
            and not telefone_cliente.startswith("+55")
        ):
            raise serializers.ValidationError(
                "E-mail brasileiro deve estar associado a um número do Brasil (+55)"
            )

        return attrs

    def validate_telefone_cliente(self, value):
        if len(value) < 8:
            raise serializers.ValidationError(
                "Telefone precisa conter no mínimo 8 dígitos"
            )
        if "+" in value and not value.startswith("+"):
            raise serializers.ValidationError("Formato inválido")
        if not re.sub(r"[^0-9]", "", value).isdigit():
            raise serializers.ValidationError("Telefone deve ser composto por dígitos!")
        return value

class PrestadorSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["id", "username", "agendamentos"]

    agendamentos = AgendamentoSerializer(many=True, read_only=True)

class FidelidadeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Fidelidade
        fields = "nome_cliente", "nivel_fidelidade"

class FuncionarioSerializer(serializers.ModelSerializer):
    class Meta:
        model = Funcionarios
        fields = ["estabelecimento", "prestador", "servico"]

    estabelecimento = serializers.CharField()
    prestador = serializers.CharField()
    servico = serializers.CharField()

    def validate_prestador(self, value):
        try:
            prestador_obj = User.objects.get(username=value)
        except User.DoesNotExist:
            raise serializers.ValidationError("Prestador não existe!")
        return prestador_obj

    def validate_estabelecimento(self, value):
        try:
            estabelecimento_obj = Estabelecimento.objects.get(
                nome_estabelecimento=value
            )
        except ObjectDoesNotExist:
            raise serializers.ValidationError("Estabelecimento não existe!")
        return estabelecimento_obj

    def validate_servico(self, value):
        try:
            servico_obj = Servicos.objects.get(servico=value)
        except ObjectDoesNotExist:
            raise serializers.ValidationError(
                "O serviço informado está incorreto ou não existe!"
            )
        return servico_obj

    def validate(self, attrs):
        prestador = attrs.get("prestador", "")
        estabelecimento = attrs.get("estabelecimento", "")
        servico = attrs.get("servico", "")

        servico = Servicos.objects.filter(servico=servico).first()

        if servico:
            if Funcionarios.objects.filter(
                prestador__username=prestador,
                estabelecimento=estabelecimento,
                servico=servico,
            ).exists():
                raise serializers.ValidationError(
                    "O funcionário já está cadastrado neste estabelecimento com o serviço informado!"
                )

        return attrs

class EstabelecimentoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Estabelecimento
        fields = "__all__"

    nome_estabelecimento = serializers.CharField()

    def validate(self, attrs):
        estabelecimento_request = attrs.get("nome_estabelecimento", "")

        if Estabelecimento.objects.filter(
            nome_estabelecimento=estabelecimento_request
        ).exists():
            raise serializers.ValidationError("O estabelecimento informado já existe!")

        return attrs

class ServicosSerializer(serializers.ModelSerializer):
    class Meta:
        model = Servicos
        fields = "__all__"

    servico = serializers.CharField()

    def validate(self, attrs):
        servico_request = attrs.get("servico", "")

        if Servicos.objects.filter(servico=servico_request).exists():
            raise serializers.ValidationError("O serviço informado já existe!")

        return attrs

class EnderecoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Endereco
        fields = "__all__"

    estabelecimento = serializers.CharField()
    estado = serializers.CharField(default="")
    cidade = serializers.CharField(default="")
    bairro = serializers.CharField(default="")
    rua = serializers.CharField(default="")

    def validate_estabelecimento(self, value):
        try:
            estabelecimento_obj = Estabelecimento.objects.filter(
                nome_estabelecimento=value
            )
        except not estabelecimento_obj.exists():
            raise serializers.ValidationError(
                "O estabelecimento informado não foi encontrado!"
            )

        return estabelecimento_obj.first()

    def validate_cep(self, value):
        verifica_caracteres = re.sub(r"[^0-9]", "", value)

        if value == "":
            raise serializers.ValidationError("O CEP precisa ser informado.")

        if value != verifica_caracteres:
            raise serializers.ValidationError(
                "O CEP precisa ser composto apenas por números!"
            )

        if len(value) != 8:
            raise serializers.ValidationError("O CEP deve ter 8 digítos!")

        validacao_cep = verifica_cep(value)

        if not validacao_cep:
            raise serializers.ValidationError("O CEP informado não é válido!")

        return value

    def validate_estado(self, value):
        if value == "":
            return value

        if len(value) != 2:
            raise serializers.ValidationError("Você deve informar a sigla do Estado!")

        verifica_caracteres = re.sub(r"[^A-Za-z]", "", value)

        if value != verifica_caracteres:
            raise serializers.ValidationError(
                "A sigla do Estado precisa ser composta apenas por letras!"
            )

        return value

    def validate_cidade(self, value):
        if value == "":
            return value

        verifica_caracteres = re.sub(r"[^A-Za-z]", "", value)

        if value != verifica_caracteres:
            raise serializers.ValidationError(
                "A nome da cidade precisa ser composto apenas por letras!"
            )

        return value

    def validate_bairro(self, value):
        if value == "":
            return value

        verifica_caracteres = re.sub(r"[^A-Za-z]", "", value)

        if value != verifica_caracteres:
            raise serializers.ValidationError(
                "O nome do bairro precisa ser composto apenas por letras!"
            )

        return value

    def validate_rua(self, value):
        return value

    def validate_complemento(self, value):
        return value

    def validate(self, attrs):
        cep = attrs.get("cep", None)
        estado = attrs.get("estado", None)
        cidade = attrs.get("cidade", None)
        bairro = attrs.get("bairro", None)
        rua = attrs.get("rua", None)

        verifica_valores = verifica_cep(cep)

        verifica_estado = verifica_valores["state"]
        verifica_cidade = verifica_valores["city"]
        verifica_bairro = verifica_valores["neighborhood"]
        verifica_rua = verifica_valores["street"]

        if cep and rua and cidade and bairro and rua:
            if estado != verifica_estado:
                logging.warning("Estado informado não coincide com o CEP!")
            if cidade != verifica_cidade:
                logging.warning("Cidade informada não coincide com o CEP!")
            if bairro != verifica_bairro:
                logging.warning("Bairro informado não coincide com o CEP!")
            if rua != verifica_rua:
                logging.warning("Rua informada não coincide com o CEP!")

        if not estado and cidade and bairro and rua:
            attrs["state"] = verifica_estado
            attrs["city"] = verifica_cidade
            attrs["neighborhood"] = verifica_bairro
            attrs["street"] = verifica_rua

        return attrs


import contextlib
import functools
from typing import Optional

import torch
from colossalai.context.parallel_mode import ParallelMode
from colossalai.core import global_context as gpc
from colossalai.context.singleton_meta import SingletonMeta
from colossalai.logging import get_dist_logger
from colossalai.zero.shard_utils import BaseShardStrategy
from colossalai.zero.sharded_model._utils import cast_tensor_to_fp16
from colossalai.zero.sharded_param import ShardedParamV2
from torch.distributed import ProcessGroup
from contextlib import AbstractContextManager

def _substitute_init_recursively(cls, func):
    for subcls in cls.__subclasses__():
        _substitute_init_recursively(subcls, func)
        func(subcls)

class InsertPostInitMethodToModuleSubClasses(object):

    def __init__(self):
        pass

    def __enter__(self):
        r"""
        Enter the context scope.
        """

        def preprocess_after(f):

            @functools.wraps(f)
            def wrapper(module: torch.nn.Module, *args, **kwargs):
                f(module, *args, **kwargs)
                self._post_init_method(module)

            return wrapper

        def _enable_class(cls):
            cls._old_init = cls.__init__
            cls.__init__ = preprocess_after(cls.__init__)

        # The function is called during init subclass.
        def _init_subclass(cls, **kwargs):
            cls.__init__ = preprocess_after(cls.__init__)

        # Replace .__init__() for all existing subclasses of torch.nn.Module
        # Excution self._post_init_method after the default init function.
        _substitute_init_recursively(torch.nn.modules.module.Module, _enable_class)

        # holding on to the current __init__subclass__ for exit
        torch.nn.modules.module.Module._old_init_subclass = (torch.nn.modules.module.Module.__init_subclass__)
        # Replace .__init__() for future subclasses of torch.nn.Module
        torch.nn.modules.module.Module.__init_subclass__ = classmethod(_init_subclass)

        self._pre_context_exec()

    def __exit__(self, exc_type, exc_value, traceback):

        def _disable_class(cls):
            cls.__init__ = cls._old_init

        # Replace .__init__() for all existing subclasses of torch.nn.Module
        _substitute_init_recursively(torch.nn.modules.module.Module, _disable_class)

        # Replace .__init__() for future subclasses of torch.nn.Module
        torch.nn.modules.module.Module.__init_subclass__ = (torch.nn.modules.module.Module._old_init_subclass)

        self._post_context_exec()
        # Now that we cleaned up the metaclass injection, raise the exception.
        if exc_type is not None:
            return False

    # To be implemented by inheriting classes
    def _post_init_method(self, module):
        pass

    def _pre_context_exec(self):
        pass

    def _post_context_exec(self):
        pass

class ZeroContextConfig(object):
    """The configuration used to control zero context initialization.

    Args:
        target_device (torch.device): The device where param data are after exiting the context.
        replicated (bool, optional): Whether the param is replicated across data parallel group.
            Some parameters are not replicated, e.g. parameters in MOE experts.
        shard_param (bool, optional): Is param sharded after exiting the context. Defaults to False.
        rm_torch_payload_on_the_fly (bool, optional): If set to `True`, remove tensor payload on `param.data` after module init finished.
            This will reduce memory usage when initializing model.
            But it's not suitable for all models, especially when there are `weight init` operations in `__init__`.
            If set to `False`, remove tensor payload on param.data afther the context exist.
            This is used when you add some logic to operate tensors in __init__ of module.
            See torchvision resnet18. Defaults to False.
    """

    def __init__(self,
                 target_device: torch.device,
                 replicated: bool = True,
                 shard_param: bool = False,
                 rm_torch_payload_on_the_fly: bool = False):
        super().__init__()
        self.target_device = target_device
        self.is_replicated: bool = replicated
        self.shard_param: bool = shard_param
        self.rm_torch_payload_on_the_fly: bool = rm_torch_payload_on_the_fly

class ZeroInitContext(InsertPostInitMethodToModuleSubClasses):
    """A context to initialize model.

    1. Convert the model to fp16.
    2. The paramaters of the module are adapted to type ShardedParameter.
    3. Shard the param and grad according to flags.

    Args:
        target_device (torch.device): The device where param data are after exiting the context.
        shard_strategy (BaseShardStrategy): Shard strategy instance.
        shard_param (bool, optional): Is param sharded after exiting the context. Defaults to False.
        rm_torch_payload_on_the_fly (bool, optional): If set to `True`, remove tensor payload on `param.data` after module init finished.
            This will reduce memory usage when initializing model.
            But it's not suitable for all models, especially when there are `weight init` operations in `__init__`.
            If set to `False`, remove tensor payload on param.data afther the context exist.
            This is used when you add some logic to operate tensors in __init__ of module.
            See torchvision resnet18. Defaults to False.
        model_numel_tensor (torch.Tensor, optional): A tensor which will store the number of elements of model. Defaults to torch.zeros(1, dtype=torch.int).
        dp_process_group (Optional[ProcessGroup], optional): Data parallel process group. Defaults to None.
    """

    def __init__(self,
                 target_device: torch.device,
                 shard_strategy: BaseShardStrategy,
                 shard_param: bool = False,
                 rm_torch_payload_on_the_fly: bool = False,
                 model_numel_tensor: torch.Tensor = torch.zeros(1, dtype=torch.long),
                 dp_process_group: Optional[ProcessGroup] = None):

        super().__init__()
        self.shard_strategy = shard_strategy
        self.initialized_param_list = []
        self.model_numel_tensor = model_numel_tensor
        self.dp_process_group = dp_process_group or gpc.get_group(ParallelMode.DATA)

        self.config = ZeroContextConfig(target_device=target_device,
                                        replicated=True,
                                        shard_param=shard_param,
                                        rm_torch_payload_on_the_fly=rm_torch_payload_on_the_fly)

        ZeroContextMgr().current_context = self

    @property
    def target_device(self):
        return self.config.target_device

    @property
    def is_replicated(self):
        return self.config.is_replicated

    @property
    def shard_param(self):
        return self.config.shard_param

    @property
    def rm_torch_payload_on_the_fly(self):
        return self.config.rm_torch_payload_on_the_fly

    def _pre_context_exec(self):
        """
        The Callback function when entering the context
        """
        self.logger = get_dist_logger("ZeroInitContext")

    def _post_context_exec(self):
        """The callback function when exiting context.
        """
        if not self.rm_torch_payload_on_the_fly:
            for param in self.initialized_param_list:
                assert hasattr(param, 'colo_attr')
                param.colo_attr.remove_torch_payload()

            del self.initialized_param_list

    def _post_init_method(self, module: torch.nn.Module):
        """
        The function to call at the end of the constructor of each module.
        NOTE() The module may be passed to this function multiple times.
        """

        def half_fn(t: torch.Tensor):
            return t.half() if t.is_floating_point() else t

        for param in module.parameters(recurse=False):
            # avoid adapting a param to ShardedParam twice
            if hasattr(param, 'colo_attr'):
                continue

            self.model_numel_tensor += param.numel()

            # mark whether the param is replicated
            param.is_replicated = self.is_replicated

            # convert parameters to half
            param_half = half_fn(param)
            param.data = param_half
            if param.grad is not None:
                grad_half = half_fn(param.grad)
                param.grad.data = grad_half

            # move torch parameters to the target device
            target_device = self.target_device
            param.data = param.data.to(target_device)
            if param.grad is not None:
                param.grad = param.grad.to(target_device)

            param.colo_attr = ShardedParamV2(param, rm_torch_payload=self.rm_torch_payload_on_the_fly)

            if self.shard_param:
                self.shard_strategy.shard([param.colo_attr.sharded_data_tensor], self.dp_process_group)
                self.initialized_param_list.append(param)

        # We must cast buffers
        # If we use BN, buffers may be on CPU and Float
        # We must cast them
        for buffer in module.buffers(recurse=False):
            buffer.data = buffer.data.to(device=torch.cuda.current_device())
            buffer.data = cast_tensor_to_fp16(buffer.data)

class ZeroContextMgr(metaclass=SingletonMeta):
    current_context: Optional[ZeroInitContext] = None

    @contextlib.contextmanager
    def hijack_context_config(self, **kwargs):
        if self.current_context is None:
            yield
        else:
            old_config = self.current_context.config
            self.current_context.config = ZeroContextConfig(**kwargs)
            yield
            self.current_context.config = old_config

def no_shard_zero_context(is_replicated: bool = True) -> AbstractContextManager:
    return ZeroContextMgr().hijack_context_config(target_device=torch.device('cuda', torch.cuda.current_device()),
                                                  replicated=is_replicated,
                                                  shard_param=False,
                                                  rm_torch_payload_on_the_fly=False)

def no_shard_zero_decrator(is_replicated: bool = True):

    def _wrapper(init_func):

        def _no_shard(*args, **kwargs):
            with no_shard_zero_context(is_replicated):
                init_func(*args, **kwargs)

        return _no_shard

    return _wrapper


import numpy as np
from random import *
from math import *
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D

# Co się udało zrobić:
# generowanie labiryntu z hexagonalnymi komórkami

# Nie udało się zaimplementować algorytmu rozwiązującego labirynt

# generowanie środków sześciokątów
def generate_points(width, height):
    list_of_x = []
    list_of_y = []
    x = 1  # długość boku hexagonu
    for i in range(0, height):
        for j in range(0, width):
            if i % 2 == 1:
                displacement = 0.5 * x * sqrt(3)
            else:
                displacement = 0
            point_x = displacement + 0.5 * x * sqrt(3) + j * x * sqrt(3)
            point_y = x + i * 1.5 * x
            list_of_x.append(point_x)
            list_of_y.append(point_y)

    return list_of_x, list_of_y

# obliczanie wierzchołków szeciokątów
def hexagons_tops(list_x, list_y, wall):
    list_of_hexagons = []
    for i in range(0, len(list_x)):
        px = list_x[i]
        py = list_y[i]
        hexagon = []
        # wektory do obliczania wierzchołków gdy dany jest środek
        vec_point = [[px, py - wall], [px + 0.5 * wall * sqrt(3), py - 0.5 * wall],
                     [px + 0.5 * wall * sqrt(3), py + 0.5 * wall], [px, py + wall],
                     [px - 0.5 * wall * sqrt(3), py + 0.5 * wall], [px - 0.5 * wall * sqrt(3), py - 0.5 * wall]]
        for vec in vec_point:
            hexagon.append(vec)
        list_of_hexagons.append(hexagon)

    return list_of_hexagons

# sprawdzam czy pomiędzy wierzchołkami sześciokąta ma być narysowana krawędź
def draw_walls(figure, tab_walls, iterator):
    list_x = [figure[0, 0]]
    list_y = [figure[0, 1]]
    px = int(iterator / size)
    py = int(iterator % size)
    for i in range(0, len(figure) - 1):
        if tab_walls[px, py, i] == 1:
            list_x.append(figure[i + 1, 0])
            list_y.append(figure[i + 1, 1])
        else:
            line = Line2D(list_x, list_y)
            ax.add_line(line)
            list_x = [figure[i + 1, 0]]
            list_y = [figure[i + 1, 1]]
    if len(list_x) > 1:
        line = Line2D(list_x, list_y)
        ax.add_line(line)

def draw_maze(hexagons, hexagon_walls):
    count = 0
    for hexagon in hexagons:
        array = np.hstack(hexagon)
        array = np.append(array, array[0])
        array = np.append(array, array[1])
        array = array.reshape(7, 2)
        draw_walls(array, hexagon_walls, count)
        count = count + 1

def neighbour_available(been, px, py, offset):
    px = px + 1
    py = py + 1
    if (been[px + 1, py + offset] == 0 or been[px, py + 1] == 0 or been[px - 1, py + offset] == 0
            or been[px - 1, py - 1 + offset] == 0 or been[px, py - 1] == 0
            or been[px + 1, py - 1 + offset] == 0):
        return True
    else:
        return False

def new_start_point(row, col, been, offset):
    pos_x = 0
    pos_y = 0
    for i in range(row - 1, -1, -1):
        for j in range(0, col):
            if neighbour_available(been, i, j, offset) and been[i + 1, j + 1] == 1:
                pos_x = i
                pos_y = j
    return pos_x, pos_y

# definiowanie ilości hexagonów we wierszach i kolumnach
size = 15

# dzieki tym tablicom wiem, w którą stronę mogę się poruszyć
prawo = np.ones((size, size))
prawo_gora = np.ones((size, size))
lewo_gora = np.ones((size, size))
lewo = np.ones((size, size))
lewo_dol = np.ones((size, size))
prawo_dol = np.ones((size, size))

prawo[:, size - 1] = np.zeros((1, size))
prawo_gora[0, :] = np.zeros((1, size))
lewo_gora[0, :] = np.zeros((1, size))
lewo[:, 0] = np.zeros((1, size))
lewo_dol[size - 1, :] = np.zeros((1, size))
prawo_dol[size - 1, :] = np.zeros((1, size))
for i in range(0, size):
    for j in range(0, size):
        if i % 2 == 1 and j == size - 1:
            prawo_dol[i, j] = 0
            prawo_gora[i, j] = 0
        elif i % 2 == 0 and j == 0:
            lewo_dol[i, j] = 0
            lewo_gora[i, j] = 0

# tablica która informuje mnie o tym, gdzie są ściany
hexagon_walls = np.ones(shape=(size, size, 6))

# otworzenie wejścia i wyjścia z labiryntu
hexagon_walls[0, 0, 5] = 0
hexagon_walls[size - 1, size - 1, 2] = 0

fig = plt.figure()
ax = fig.add_subplot(111)

punkty_x, punkty_y = generate_points(size, size)
hexagons = hexagons_tops(punkty_x, punkty_y, 1)

# stworzenie tablicy do sprawdzania na jakich polach byłem, dodaję ramkę dookoła, że przy sprawdzaniu czy mam sąsiada
# nie wyjdę poza zakres tablicy
visited = np.zeros(shape=(size + 2, size + 2))
visited[:, 0] = np.ones((1, size + 2))
visited[:, size + 1] = np.ones((1, size + 2))
visited[0, :] = np.ones((1, size + 2))
visited[size + 1, :] = np.ones((1, size + 2))
unvisited = size * size

# tutaj generowanie labiryntu

# pozycja początkowa
pos_x = size - 1
pos_y = 0
found_new_point = True

# wykonuję pętlę dopóki wszystkie komórki nie będą ze sobą połączone
while unvisited > 0:

    # w zależności czy jestem we wierszu parzystym czy nieparzystym, to muszę uwzględnić zmianę przy poruszaniu się po
    # skosie, dla parzystego ruch w prawo do góry sprowadza się do zmniejszenia pozycji x o 1, y bez zmian,
    # dla nieparzystego wykonanie tego samego ruchu powoduje z mniejszenia pozycji x o 1, zwiększenie pozycji y o 1
    if pos_x % 2 == 0:
        offset = 0
    else:
        offset = 1

    # gdy wykona się pętla while i jestem w nowym punkcie to muszę zaznaczyć ją jako odwiedzoną,
    # oraz zmniejszyć liczbę komórek nieodwiedzonych
    if found_new_point:
        visited[pos_x + 1, pos_y + 1] = 1
        unvisited = unvisited - 1

    # jeżeli natomiast po iteracji nie jestem w nowej komórce to sprawdzam czy są nieodwiedzeni sąsiedzi,
    # do których mogę przejść, jeżeli tak to nic nie robię w kolejnych iteracjach zostaną oni znalezieni
    # jeżeli nie ma sąsiadów, to szukam odwiedzonego już punktu, który ma nieodwiedzonych sąsiadów
    elif neighbour_available(visited, pos_x, pos_y, offset) is False:
        pos_x, pos_y = new_start_point(size, size, visited, offset)

        # ustalam czy wystąpi przesunięcie dla nowych współrzędnych
        if pos_x % 2 == 0:
            offset = 0
        else:
            offset = 1

    # generacja jednego z 6 kierunków ruchów
    direction = randint(1, 6)
    found_new_point = False

    # w zależności od tego jaki kierunek ruchu został wygenerowany sprawdzam czy jest możliwy ruch w tamtą stronę,
    # oraz czy komórka do której mam się udać była już odwiedzona
    # dodatkowo wyburzając ściany współrzędna x jest postaci abs(pos_x - rows + 1), gdyż indeksowanie tablicy zaczyna
    # się od lewego górnego rogu, a wypisywanie hexagonów od lewego dolnego
    if direction == 1 and prawo_dol[pos_x, pos_y] == 1 and visited[pos_x + 2, pos_y + offset + 1] == 0:
        hexagon_walls[abs(pos_x - size + 1), pos_y, 0] = 0
        pos_x = pos_x + 1
        pos_y = pos_y + offset
        hexagon_walls[abs(pos_x - size + 1), pos_y, 3] = 0
        found_new_point = True
    elif direction == 2 and prawo[pos_x, pos_y] == 1 and visited[pos_x + 1, pos_y + 2] == 0:
        hexagon_walls[abs(pos_x - size + 1), pos_y, 1] = 0
        pos_y = pos_y + 1
        hexagon_walls[abs(pos_x - size + 1), pos_y, 4] = 0
        found_new_point = True
    elif direction == 3 and prawo_gora[pos_x, pos_y] == 1 and visited[pos_x, pos_y + offset + 1] == 0:
        hexagon_walls[abs(pos_x - size + 1), pos_y, 2] = 0
        pos_x = pos_x - 1
        pos_y = pos_y + offset
        hexagon_walls[abs(pos_x - size + 1), pos_y, 5] = 0
        found_new_point = True
    elif direction == 4 and lewo_gora[pos_x, pos_y] == 1 and visited[pos_x, pos_y + offset] == 0:
        hexagon_walls[abs(pos_x - size + 1), pos_y, 3] = 0
        pos_x = pos_x - 1
        pos_y = pos_y - 1 + offset
        hexagon_walls[abs(pos_x - size + 1), pos_y, 0] = 0
        found_new_point = True
    elif direction == 5 and lewo[pos_x, pos_y] == 1 and visited[pos_x + 1, pos_y] == 0:
        hexagon_walls[abs(pos_x - size + 1), pos_y, 4] = 0
        pos_y = pos_y - 1
        hexagon_walls[abs(pos_x - size + 1), pos_y, 1] = 0
        found_new_point = True
    elif direction == 6 and lewo_dol[pos_x, pos_y] == 1 and visited[pos_x + 2, pos_y + offset] == 0:
        hexagon_walls[abs(pos_x - size + 1), pos_y, 5] = 0
        pos_x = pos_x + 1
        pos_y = pos_y - 1 + offset
        hexagon_walls[abs(pos_x - size + 1), pos_y, 2] = 0
        found_new_point = True

axes = plt.gca()
axes.set_xlim([-1, sqrt(3)*(size + 1) + 1])
axes.set_ylim([-1, 1.5 * size + 2])
draw_maze(hexagons, hexagon_walls)
plt.show()