# Написать программу, которая состоит 4 из этапов:
# - создает список из рандомных четырехзначных чисел
# - принимает с консоли цифру и удаляет ее из всех элементов списка
# - цифры каждого элемента суммирует пока результат не станет однозначным числом
# - из финального списка убирает все дублирующиеся элементы
# - после каждого этапа выводить результат в консоль
# Пример:
# - 1 этап: [2634, 6934, 7286, 3353, 4602, 3176, 3796]
# - 2 этап: Введите цифру: 3
# - 2 этап: [264, 694, 7286, 5, 4602, 176, 796]
# - 3 этап: 264 -> 2+6+4 -> 12 -> 1+2 -> 3
# - 3 этап: [3, 1, 5, 5, 3, 5, 4]
# - 4 этап: [3, 1, 5, 4]
# import random
#
# k = 10  # количество 4значных чисел
# num_array = []
#
# for _ in range(0, k + 1):
#     num_array.append(random.randint(1000, 10000))
# print('1 этап: ', num_array)
#
# # 2
# num = int(input('Введите цифру: '))
# for i in range(0, len(num_array)):
#     temp = num_array[i]
#     temp2 = 0
#     while temp > 0:
#         if temp % 10 == num:
#             pass
#         else:
#             temp2 = temp2 * 10 + temp % 10
#         temp //= 10
#     num_array[i] = temp2
# print(num_array)
#
# # 3
# for i in range(0, len(num_array)):
#     while num_array[i] > 9:
#         num_array[i] = int(num_array[i] / 100) + (num_array[i] % 100 - num_array[i] % 10) // 10 + num_array[i] % 10
# print('сумма цифр = ', num_array)
#
# #4
# num_array = set(num_array)
# print(num_array)
import random

my_list = [random.randint(1000, 9999) for _ in range(10)]
print(my_list)

number = input('Введите число: ')
for i in range(len(my_list)):
    my_list[i] = str(my_list[i]).replace(number, '')
print(my_list)

for i in range(len(my_list)):
    while len(my_list[i]) > 1:
        summa = 0
        # my_list[i] = str(sum(list(map(int, list(my_list[i])))))  # функция map применяет int ко всем элементам листа my_list
        for elem in my_list[i]:
            summa += int(elem)
        my_list[i] = str(summa)
print(my_list)

print(set(my_list))


from typing import List
from urllib.parse import urlparse
import pandas as pd
from pathlib import Path
from behave import Given, When, Then, Step
from csvcubeddevtools.behaviour.file import get_context_temp_dir_path
from csvcubeddevtools.helpers.file import get_test_cases_dir
from rdflib import Graph

from csvcubed.models.cube import *
from csvcubed.models.cube import (
    ExistingQbAttribute,
    NewQbAttribute,
    NewQbConcept,
    QbMultiMeasureDimension,
    QbMultiUnits,
)

from csvcubed.models.validationerror import ValidationError
from csvcubed.models.cube.uristyle import URIStyle
from csvcubed.writers.qbwriter import QbWriter
from csvcubed.utils.qb.validation.cube import validate_qb_component_constraints
from csvcubed.utils.csvw import get_first_table_schema
from csvcubed.utils.pandas import read_csv

_test_case_dir = get_test_cases_dir()

def get_standard_catalog_metadata_for_name(
    name: str, identifier: Optional[str] = None
) -> CatalogMetadata:
    return CatalogMetadata(
        name,
        summary="Summary",
        identifier=identifier,
        description="Description",
        creator_uri="https://www.gov.uk/government/organisations/office-for-national-statistics",
        publisher_uri="https://www.gov.uk/government/organisations/office-for-national-statistics",
        theme_uris=["http://gss-data.org.uk/def/gdp#some-test-theme"],
        keywords=["Key word one", "Key word two"],
        landing_page_uris=["http://example.org/landing-page"],
        license_uri="http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/",
        public_contact_point_uri="mailto:something@example.org",
    )

_standard_data = pd.DataFrame(
    {"A": ["a", "b", "c"], "D": ["e", "f", "g"], "Value": [1, 2, 3]}
)

@Given('a single-measure QbCube named "{cube_name}"')
def step_impl(context, cube_name: str):
    context.cube = _get_single_measure_cube_with_name_and_id(cube_name, None)

@Given('a single-measure QbCube named "{cube_name}" with missing observation values')
def step_impl(context, cube_name: str):
    cube = _get_single_measure_cube_with_name_and_id(cube_name, None)
    cube.data["Value"] = [1, None, 3]
    context.cube = cube

@Given(
    'a single-measure QbCube named "{cube_name}" with missing observation values and `sdmxa:obsStatus` replacements'
)
def step_impl(context, cube_name: str):
    data = pd.DataFrame(
        {
            "A": ["a", "b", "c"],
            "D": ["e", "f", "g"],
            "Marker": ["Suppressed", None, None],
            "Value": [None, 2, 3],
        }
    )
    columns = [
        QbColumn("A", NewQbDimension.from_data(label="A", data=data["A"])),
        QbColumn("D", NewQbDimension.from_data(label="D", data=data["D"])),
        QbColumn(
            "Marker",
            NewQbAttribute.from_data(
                "Marker",
                data["Marker"],
                parent_attribute_uri="http://purl.org/linked-data/sdmx/2009/attribute#obsStatus",
            ),
        ),
        QbColumn(
            "Value",
            QbSingleMeasureObservationValue(
                NewQbMeasure("Some Measure"), NewQbUnit("Some Unit")
            ),
        ),
    ]

    context.cube = Cube(
        get_standard_catalog_metadata_for_name(cube_name), data, columns
    )

@Given(
    'a single-measure QbCube named "{cube_name}" with missing observation values and missing `sdmxa:obsStatus` replacements'
)
def step_impl(context, cube_name: str):
    data = pd.DataFrame(
        {
            "A": ["a", "b", "c"],
            "D": ["e", "f", "g"],
            "Marker": [None, "Provisional", None],
            "Value": [None, 2, 3],
        }
    )
    columns = [
        QbColumn("A", NewQbDimension.from_data(label="A", data=data["A"])),
        QbColumn("D", NewQbDimension.from_data(label="D", data=data["D"])),
        QbColumn(
            "Marker",
            NewQbAttribute.from_data(
                "Marker",
                data["Marker"],
                parent_attribute_uri="http://purl.org/linked-data/sdmx/2009/attribute#obsStatus",
            ),
        ),
        QbColumn(
            "Value",
            QbSingleMeasureObservationValue(
                NewQbMeasure("Some Measure"), NewQbUnit("Some Unit")
            ),
        ),
    ]

    context.cube = Cube(
        get_standard_catalog_metadata_for_name(cube_name), data, columns
    )

@Given(
    'a QbCube named "{cube_name}" with code-list defined in an existing CSV-W "{csvw_file_path}"'
)
def step_impl(context, cube_name: str, csvw_file_path: str):
    tmp_dir = get_context_temp_dir_path(context)
    csvw_path = tmp_dir / csvw_file_path
    columns = [
        QbColumn("A", NewQbDimension.from_data("A code list", _standard_data["A"])),
        QbColumn(
            "D",
            NewQbDimension("D code list", code_list=NewQbCodeListInCsvW(csvw_path)),
        ),
        QbColumn(
            "Value",
            QbSingleMeasureObservationValue(
                NewQbMeasure("Some Measure"), NewQbUnit("Some Unit")
            ),
        ),
    ]

    csv_path, _ = get_first_table_schema(csvw_path)
    code_list_data, data_loading_errors = read_csv(csv=csvw_path.parent / csv_path)
    code_list_values = code_list_data["Notation"].sample(3, random_state=1)

    context.data_loading_errors = data_loading_errors
    context.cube = Cube(
        get_standard_catalog_metadata_for_name(cube_name, None),
        pd.DataFrame({"A": ["a", "b", "c"], "D": code_list_values, "Value": [1, 2, 3]}),
        columns,
    )

@Given('a single-measure QbCube with identifier "{cube_id}" named "{cube_name}"')
def step_impl(context, cube_name: str, cube_id: str):
    context.cube = _get_single_measure_cube_with_name_and_id(cube_name, cube_id)

def _get_single_measure_cube_with_name_and_id(
    cube_name: str, cube_id: str, uri_style: URIStyle = URIStyle.Standard
) -> Cube:
    columns = [
        QbColumn("A", NewQbDimension.from_data("A code list", _standard_data["A"])),
        QbColumn("D", NewQbDimension.from_data("D code list", _standard_data["D"])),
        QbColumn(
            "Value",
            QbSingleMeasureObservationValue(
                NewQbMeasure("Some Measure"), NewQbUnit("Some Unit")
            ),
        ),
    ]

    return Cube(
        get_standard_catalog_metadata_for_name(cube_name, cube_id),
        _standard_data,
        columns,
        uri_style=uri_style,
    )

@Given('a single-measure QbCube named "{cube_name}" with existing dimensions')
def step_impl(context, cube_name: str):
    columns = [
        QbColumn(
            "A",
            ExistingQbDimension("http://example.org/some/dimension/a"),
            csv_column_uri_template="http://example.org/some/codelist/a",
        ),
        QbColumn(
            "D",
            ExistingQbDimension("http://example.org/some/dimension/d"),
            csv_column_uri_template="http://example.org/some/codelist/d",
        ),
        QbColumn(
            "Value",
            QbSingleMeasureObservationValue(
                NewQbMeasure("Some Measure"), NewQbUnit("Some Unit")
            ),
        ),
    ]

    context.cube = Cube(
        get_standard_catalog_metadata_for_name(cube_name), _standard_data, columns
    )

@Given('a single-measure QbCube named "{cube_name}" with duplicate rows')
def step_impl(context, cube_name: str):
    data = pd.DataFrame({"A": ["a", "a"], "Value": [1, 1]})
    columns = [
        QbColumn("A", NewQbDimension.from_data("A Dimension", data["A"])),
        QbColumn(
            "Value",
            QbSingleMeasureObservationValue(
                NewQbMeasure("Some Measure"), NewQbUnit("Some Unit")
            ),
        ),
    ]

    context.cube = Cube(
        get_standard_catalog_metadata_for_name(cube_name), data, columns
    )

@Given(
    'a single-measure QbCube named "{cube_name}" with codes not defined in the code-list'
)
def step_impl(context, cube_name: str):
    columns = [
        QbColumn(
            "A",
            NewQbDimension(
                "A code list",
                code_list=NewQbCodeList(
                    get_standard_catalog_metadata_for_name("A code list"),
                    [NewQbConcept("a"), NewQbConcept("b")],  # Deliberately missing "c"
                ),
            ),
        ),
        QbColumn("D", NewQbDimension.from_data("D code list", _standard_data["D"])),
        QbColumn(
            "Value",
            QbSingleMeasureObservationValue(
                NewQbMeasure("Some Measure"), NewQbUnit("Some Unit")
            ),
        ),
    ]

    context.cube = Cube(
        get_standard_catalog_metadata_for_name(cube_name), _standard_data, columns
    )

@Given(
    'a single-measure QbCube named "{cube_name}" with optional attribute values missing'
)
def step_impl(context, cube_name: str):
    data = pd.DataFrame(
        {
            "Some Dimension": ["a", "b", "c"],
            "Some Attribute": ["attr-a", float("nan"), "attr-c"],
            "Value": [1, 2, 3],
        }
    )
    columns = [
        QbColumn(
            "Some Dimension",
            NewQbDimension.from_data("Some Dimension", data["Some Dimension"]),
        ),
        QbColumn(
            "Some Attribute",
            NewQbAttribute.from_data("Some Attribute", data["Some Attribute"]),
        ),
        QbColumn(
            "Value",
            QbSingleMeasureObservationValue(
                NewQbMeasure("Some Measure"), NewQbUnit("Some Unit")
            ),
        ),
    ]

    context.cube = Cube(
        get_standard_catalog_metadata_for_name(cube_name), data, columns
    )

@Given('a multi-measure QbCube named "{cube_name}"')
def step_impl(context, cube_name: str):
    data = pd.DataFrame(
        {
            "A": ["a_height", "a_length"],
            "Measure": ["height", "length"],
            "Value": [1, 20],
        }
    )
    columns = [
        QbColumn("A", NewQbDimension.from_data("A Dimension", data["A"])),
        QbColumn(
            "Measure", QbMultiMeasureDimension.new_measures_from_data(data["Measure"])
        ),
        QbColumn(
            "Value",
            QbMultiMeasureObservationValue(unit=NewQbUnit("meters")),
        ),
    ]

    context.cube = Cube(
        get_standard_catalog_metadata_for_name(cube_name), data, columns
    )

@Given('a multi-measure QbCube named "{cube_name}" with duplicate rows')
def step_impl(context, cube_name: str):
    data = pd.DataFrame(
        {
            "A": ["a_height", "a_height", "a_length"],
            "Measure": ["height", "height", "length"],
            "Value": [1, 1, 20],
        }
    )
    columns = [
        QbColumn("A", NewQbDimension.from_data("A Dimension", data["A"])),
        QbColumn(
            "Measure", QbMultiMeasureDimension.new_measures_from_data(data["Measure"])
        ),
        QbColumn(
            "Value",
            QbMultiMeasureObservationValue(unit=NewQbUnit("meters")),
        ),
    ]

    context.cube = Cube(
        get_standard_catalog_metadata_for_name(cube_name), data, columns
    )

@Given(
    'a single-measure QbCube named "{cube_name}" with new attribute values and units'
)
def step_impl(context, cube_name: str):
    data = pd.DataFrame(
        {
            "Existing Dimension": ["a", "b", "c"],
            "New Attribute": ["pending", "final", "in-review"],
            "Value": [2, 2, 2],
        }
    )
    columns = [
        QbColumn(
            "Existing Dimension", ExistingQbDimension("http://existing-dimension")
        ),
        QbColumn(
            "New Attribute",
            NewQbAttribute.from_data("New Attribute", data["New Attribute"]),
        ),
        QbColumn(
            "Value",
            QbSingleMeasureObservationValue(
                NewQbMeasure("Some Measure"), NewQbUnit("Some Unit")
            ),
        ),
    ]

    context.cube = Cube(
        get_standard_catalog_metadata_for_name(cube_name), data, columns
    )

@Given(
    'a single-measure QbCube named "{cube_name}" with one new unit extending another new unit'
)
def step_impl(context, cube_name: str):
    columns = [
        QbColumn(
            "A",
            ExistingQbDimension("http://example.org/some/dimension/a"),
            csv_column_uri_template="http://example.org/some/codelist/a",
        ),
        QbColumn(
            "D",
            ExistingQbDimension("http://example.org/some/dimension/d"),
            csv_column_uri_template="http://example.org/some/codelist/d",
        ),
        QbColumn(
            "Value",
            QbSingleMeasureObservationValue(
                NewQbMeasure("Some Measure"),
                NewQbUnit(
                    "Some Extending Unit",
                    base_unit=NewQbUnit("Some Base Unit"),
                    base_unit_scaling_factor=1000,
                    qudt_quantity_kind_uri="http://some-quantity-kind",
                    si_base_unit_conversion_multiplier=25.123123,
                ),
            ),
        ),
    ]

    context.cube = Cube(
        get_standard_catalog_metadata_for_name(cube_name), _standard_data, columns
    )

@Then('turtle should be written to "{file}"')
def step_impl(context, file: str):
    temp_dir = get_context_temp_dir_path(context)

    with open(Path(temp_dir / file), "w") as ttl_file:
        ttl_file.write(context.turtle)

@When("the cube is serialised to CSV-W")
def step_impl(context):
    writer = QbWriter(context.cube)
    temp_dir = get_context_temp_dir_path(context)
    writer.write(temp_dir)
    context.csv_file_name = writer.csv_file_name

@When("the cube is serialised to CSV-W (suppressing missing uri value exceptions)")
def step_impl(context):
    writer = QbWriter(context.cube, raise_missing_uri_safe_value_exceptions=False)
    temp_dir = get_context_temp_dir_path(context)
    writer.write(temp_dir)

@Step('the CSVqb should fail validation with "{validation_error}"')
def step_impl(context, validation_error: str):
    cube: Cube = context.cube
    errors = cube.validate()
    errors += validate_qb_component_constraints(context.cube)
    assert any([e for e in errors if validation_error in e.message]), [
        e.message for e in errors
    ]

@Step("the CSVqb should pass all validations")
def step_impl(context):
    cube: QbCube = context.cube
    data_loading_errors: List[ValidationError] = (
        context.data_loading_errors if hasattr(context, "data_loading_errors") else []
    )
    errors = cube.validate() + data_loading_errors
    errors += validate_qb_component_constraints(context.cube)
    assert len(errors) == 0, [e.message for e in errors]
    assert (
        len(data_loading_errors) == 0
    ), f"Errors were found in the csv: {[e.message for e in errors]}"

@Given(
    'a single-measure QbCube named "{cube_name}" with "{type}" "{data_type}" attribute'
)
def step_impl(context, cube_name: str, type: str, data_type: str):
    data = pd.DataFrame(
        {
            "A": ["uss-cerritos", "uss-titan"],
            "Value": [1, 1],
            "Reg": [75567, 80102],
            "Appeared": ["2020-08-06", "2020-10-08"],
            "First_Captain": ["William Riker", "Carol Freeman"],
        }
    )
    dim = QbColumn("A", NewQbDimension.from_data("A Dimension", data["A"]))
    val = QbColumn(
        "Value",
        QbSingleMeasureObservationValue(
            NewQbMeasure("Some Measure"), NewQbUnit("Some Unit")
        ),
    )
    if data_type == "int":
        if type == "new":
            att = QbColumn(
                "Reg",
                NewQbAttributeLiteral(data_type="int", label="Reg"),
            )
        else:
            att = QbColumn(
                "Reg",
                ExistingQbAttributeLiteral(
                    data_type="int", attribute_uri="http://some-uri"
                ),
            )
        sp1 = SuppressedCsvColumn("Appeared")
        sp2 = SuppressedCsvColumn("First_Captain")
        columns = [dim, val, att, sp1, sp2]
    elif data_type == "date":
        sp1 = SuppressedCsvColumn("Reg")
        if type == "new":
            att = QbColumn(
                "Appeared", NewQbAttributeLiteral(data_type="date", label="Appeared")
            )
        else:
            att = QbColumn(
                "Appeared",
                ExistingQbAttributeLiteral(
                    data_type="date", attribute_uri="http://some-uri"
                ),
            )
        sp2 = SuppressedCsvColumn("First_Captain")
        columns = [dim, val, sp1, att, sp2]
    elif data_type == "string":
        sp1 = SuppressedCsvColumn("Reg")
        sp2 = SuppressedCsvColumn("Appeared")
        if type == "new":
            att = QbColumn(
                "First_Captain",
                NewQbAttributeLiteral(data_type="string", label="First Captain"),
            )
        else:
            att = QbColumn(
                "First_Captain",
                ExistingQbAttributeLiteral(
                    data_type="string", attribute_uri="http://some-uri"
                ),
            )
        columns = [dim, val, sp1, sp2, att]

    context.cube = Cube(
        get_standard_catalog_metadata_for_name(cube_name), data, columns
    )

@Given(
    'a single-measure QbCube named "{cube_name}" with all new units/measures/dimensions/attributes/codelists'
)
def step_impl(context, cube_name: str):
    data = pd.DataFrame(
        {
            "New Dimension": ["a", "b", "c"],
            "New Attribute": ["university", "students", "masters"],
            "Observed Value": [1, 2, 3],
        }
    )

    columns = [
        QbColumn(
            "New Dimension",
            NewQbDimension(
                "a new codelist",
                code_list=NewQbCodeList(
                    get_standard_catalog_metadata_for_name("a new codelist"),
                    [NewQbConcept("a"), NewQbConcept("b"), NewQbConcept("c")],
                ),
            ),
        ),
        QbColumn(
            "New Attribute",
            NewQbAttribute.from_data("new_Qb_attribute", data["New Attribute"]),
        ),
        QbColumn(
            "Observed Value",
            QbSingleMeasureObservationValue(
                NewQbMeasure("Part-time"), NewQbUnit("Num of Students")
            ),
        ),
    ]

    cube = Cube(get_standard_catalog_metadata_for_name(cube_name), data, columns)

    errors = cube.validate()
    errors += validate_qb_component_constraints(cube)

    assert len(errors) == 0, [e.message for e in errors]

    context.cube = cube

@Given(
    'a multi-measure QbCube named "{cube_name}" with all new units/measures/dimensions/attributes/codelists'
)
def step_impl(context, cube_name: str):
    data = pd.DataFrame(
        {
            "New Dimension": ["a", "b", "c"],
            "New Attribute": ["university", "students", "masters"],
            "Observed Value": [1, 2, 3],
            "Measure": ["part-time", "full-time", "flex-time"],
        }
    )

    columns = [
        QbColumn(
            "New Dimension",
            NewQbDimension(
                "New Dimension",
                code_list=NewQbCodeList(
                    get_standard_catalog_metadata_for_name("a new codelist"),
                    [NewQbConcept("a"), NewQbConcept("b"), NewQbConcept("c")],
                ),
            ),
        ),
        QbColumn(
            "New Attribute",
            NewQbAttribute.from_data("New Attribute", data["New Attribute"]),
        ),
        QbColumn(
            "Observed Value",
            QbMultiMeasureObservationValue(unit=NewQbUnit("Num of students")),
        ),
        QbColumn(
            "Measure", QbMultiMeasureDimension.new_measures_from_data(data["Measure"])
        ),
    ]

    cube = Cube(get_standard_catalog_metadata_for_name(cube_name), data, columns)

    errors = cube.validate()
    errors += validate_qb_component_constraints(cube)

    assert len(errors) == 0, [e.message for e in errors]

    context.cube = cube

@Given(
    'a single measure QbCube named "{cube_name}" with existing units/measure/dimensions/attribute/codelists'
)
def step_impl(context, cube_name: str):
    data = pd.DataFrame(
        {
            "Existing Dimension": ["a", "b", "c"],
            "New Dimension": ["d", "e", "f"],
            "Existing Attribute": ["university", "students", "masters"],
            "Observed Value": [1, 2, 3],
        }
    )

    columns = [
        QbColumn(
            csv_column_title="Existing Dimension",
            structural_definition=ExistingQbDimension("http://existing/dimension"),
            csv_column_uri_template="http://existing/dimension/code-list/{+existing_dimension}",
        ),
        QbColumn(
            csv_column_title="New Dimension",
            structural_definition=NewQbDimension(
                label="existing codelist",
                code_list=ExistingQbCodeList(
                    concept_scheme_uri="http://existing/concept/scheme/uri"
                ),
            ),
        ),
        QbColumn(
            csv_column_title="Existing Attribute",
            structural_definition=ExistingQbAttribute("http://existing/attribute"),
            csv_column_uri_template="http://existing/attribute/{+existing_attribute}",
        ),
        QbColumn(
            csv_column_title="Observed Value",
            structural_definition=QbSingleMeasureObservationValue(
                ExistingQbMeasure("http://existing/measure"),
                ExistingQbUnit("http://exisiting/unit"),
            ),
        ),
    ]

    cube = Cube(get_standard_catalog_metadata_for_name(cube_name), data, columns)

    errors = cube.validate()
    errors += validate_qb_component_constraints(cube)

    assert len(errors) == 0, [e.message for e in errors]

    context.cube = cube

@Given(
    'a multi measure QbCube named "{cube_name}" with existing units/measure/dimensions/attribute/codelists'
)
def step_impl(context, cube_name: str):
    data = pd.DataFrame(
        {
            "Existing Dimension": ["a", "b", "c"],
            "New Dimension": ["d", "e", "f"],
            "Existing Attribute": ["university", "students", "masters"],
            "Observed Value": [1, 2, 3],
            "Units": ["gbp", "count", "count"],
            "Existing Measures": ["part-time", "full-time", "flex-time"],
        }
    )

    columns = [
        QbColumn(
            "Existing Dimension",
            ExistingQbDimension("http://existing/dimension"),
            csv_column_uri_template="http://existing/dimension/code-list/{+existing_dimension}",
        ),
        QbColumn(
            csv_column_title="New Dimension",
            structural_definition=NewQbDimension(
                label="existing codelist",
                code_list=ExistingQbCodeList(
                    concept_scheme_uri="http://gss-data.org.uk/def/concept-scheme/some-existing-codelist"
                ),
            ),
        ),
        QbColumn(
            csv_column_title="Existing Attribute",
            structural_definition=ExistingQbAttribute("http://existing/attribute"),
            csv_column_uri_template="http://existing/attribute/{+existing_attribute}",
        ),
        QbColumn(
            "Observed Value",
            QbMultiMeasureObservationValue("number"),
        ),
        QbColumn(
            "Units",
            QbMultiUnits(
                [
                    ExistingQbUnit("http://existing/unit/gbp"),
                    ExistingQbUnit("http://existing/unit/count"),
                ]
            ),
            csv_column_uri_template="http://existing/unit/{+units}",
        ),
        QbColumn(
            "Existing Measures",
            QbMultiMeasureDimension(
                [
                    ExistingQbMeasure("http://existing/measure/part-time"),
                    ExistingQbMeasure("http://existing/measure/full-time"),
                    ExistingQbMeasure("http://existing/measure/flex-time"),
                ]
            ),
            csv_column_uri_template="http://existing/measure/{+existing_measures}",
        ),
    ]

    cube = Cube(get_standard_catalog_metadata_for_name(cube_name), data, columns)

    errors = cube.validate()
    errors += validate_qb_component_constraints(cube)

    assert len(errors) == 0, [e.message for e in errors]

    context.cube = cube

@Given('a QbCube named "{cube_name}" which references a legacy composite code-list')
def step_impl(context, cube_name: str):

    data = pd.DataFrame(
        {
            "Location": [
                "http://data.europa.eu/nuts/code/UKC",
                "http://data.europa.eu/nuts/code/UKL",
                "http://data.europa.eu/nuts/code/UKD",
            ],
            "Observed Value": [1, 2, 3],
        }
    )

    columns = [
        QbColumn(
            "Location",
            NewQbDimension(
                "Location",
                code_list=NewQbCodeListInCsvW(
                    _test_case_dir
                    / "readers"
                    / "skoscodelistreader"
                    / "location.csv-metadata.json"
                ),
            ),
        ),
        QbColumn(
            "Observed Value",
            QbSingleMeasureObservationValue(
                unit=NewQbUnit("Num of students"), measure=NewQbMeasure("Total")
            ),
        ),
    ]

    cube = Cube(get_standard_catalog_metadata_for_name(cube_name), data, columns)

    errors = cube.validate()
    errors += validate_qb_component_constraints(cube)

    assert len(errors) == 0, [e.message for e in errors]

    context.cube = cube

@Then("some additional turtle is appended to the resulting RDF")
def step_impl(context):
    rdf_to_add = context.text.strip()
    context.turtle += rdf_to_add

@Then("the cube's metadata should contain URLs with file endings")
def step_impl(context):
    temp_dir = get_context_temp_dir_path(context)
    assertURIStyle(URIStyle.Standard, temp_dir, context.csv_file_name)

@Then("the cube's metadata should contain URLs without file endings")
def step_impl(context):
    temp_dir = get_context_temp_dir_path(context)
    assertURIStyle(URIStyle.WithoutFileExtensions, temp_dir, context.csv_file_name)

@Given(
    'a single-measure QbCube named "{cube_name}" configured with "{uri_style}" URI style'
)
def step_impl(context, cube_name: str, uri_style: str):
    context.cube = _get_single_measure_cube_with_name_and_id(
        cube_name, None, URIStyle[uri_style]
    )

def assertURIStyle(uri_style: URIStyle, temp_dir: Path, csv_file_name: str):

    baseUri = "file://relative-uris/"
    metadataFilePath = temp_dir.joinpath(f"{csv_file_name}-metadata.json")
    g = Graph()
    g.parse(metadataFilePath, publicID=baseUri)

    for (s, p, o) in g:
        if s.startswith(baseUri):
            assert_uri_style_for_uri(uri_style, s, (s, p, o))
        if p.startswith(baseUri):
            assert_uri_style_for_uri(uri_style, p, (s, p, o))

def assert_uri_style_for_uri(uri_style: URIStyle, uri: str, node):
    path = urlparse(uri).path
    if uri_style == URIStyle.WithoutFileExtensions:
        assert not path.endswith(
            ".csv"
        ), f"expected {node} to end without a CSV file extension"
    else:
        assert path.endswith(".csv") or path.endswith(
            ".json"
        ), f"expected {node} to end with .csv or .json"


# 4.3
# List of Depths: Given a binary tree, design an algorithm which creates a linked list of all the nodes
# at each depth (e.g., if you have a tree with depth D, you'll have D linked lists).
import unittest

from Chapter_4_TreesAndGraphs import BinaryTree
from Chapter_4_TreesAndGraphs.Node import Node
from Chapter_4_TreesAndGraphs.ex_4_2_MinimalTree import minimal_tree

def list_of_depths(tree: BinaryTree):
    list_by_depths = [None] * tree.height()

    def add_tree_to_list_by_depths(current_tree: BinaryTree, current_depth: int):
        if list_by_depths[current_depth] is None:
            list_by_depths[current_depth] = Node(current_tree.data)
        else:
            n = list_by_depths[current_depth]
            while n.next is not None:
                n = n.next
            n.next = Node(current_tree.data)
        if current_tree.left is not None:
            add_tree_to_list_by_depths(current_tree.left, current_depth + 1)
        if current_tree.right is not None:
            add_tree_to_list_by_depths(current_tree.right, current_depth + 1)
    add_tree_to_list_by_depths(tree, 0)
    return list_by_depths

class TestCase(unittest.TestCase):
    def test(self):
        tree = minimal_tree(range(20))
        self.assertEqual('[15 -> None, 7 -> 19 -> None, 3 -> 11 -> 17 -> None, 1 -> 5 -> 9 -> 13 -> 16 -> 18 -> None, 0 -> 2 -> 4 -> 6 -> 8 -> 10 -> 12 -> 14 -> None]',\
                         list_of_depths(tree).__repr__())


def nearestValidPoint(x, y, points):
    # if x matches left side of any of the points its valid
    # if y matches right side of any of the points its valid
    # Manhatten distance is abs(x)
    smallest_seen = float('inf')
    index = -1
    for i, (a, b) in enumerate(points):
        print(i, a, b)
        print(points)
        if x == a or y == b:
            if abs(x - a) + abs(y - b) < smallest_seen:
                smallest_seen = abs(x - a) + abs(y - b)
                index = i
    return index

nearestValidPoint(3, 4, [[1,2],[3,1],[2,4],[2,3],[4,4]])


"""
This module implement the ServiceProxy class.

This class is used to provide a local proxy to a remote service for a ZeroRobot.
When a service or robot ask the creation of a service to another robot, a proxy class is created locally
so the robot see the service as if it as local to him while in reality the service is managed by another robot.
"""

import urllib

from requests.exceptions import HTTPError

from jose import jwt
from js9 import j
from zerorobot.task import (TASK_STATE_ERROR, TASK_STATE_NEW, TASK_STATE_OK,
                            TASK_STATE_RUNNING, Task, TaskNotFoundError)
from zerorobot.template.state import ServiceState

class ServiceProxy():
    """
    This class is used to provide a local proxy to a remote service for a ZeroRobot.
    When a service or robot ask the creation of a service to another robot, a proxy class is created locally
    so the robot see the service as if it as local to him while in reality the service is managed by another robot.
    """

    def __init__(self, name, guid, zrobot_client):
        """
        @param name: name of the service
        @param guid: guid of the service
        @param zrobot_client: Instance of ZeroRobotClient that talks to the robot on which the
                              service is actually running
        """
        self._zrobot_client = zrobot_client
        self.name = name
        self.guid = guid
        self.template_uid = None
        # a proxy service doesn't have direct access to the data of it's remote homologue
        # cause data are always only accessible  by the service itself and locally
        self._data = None
        self.task_list = TaskListProxy(self)

    def __repr__(self):
        # Provide a nice representation in tools like IPython / js9
        return "robot://%s/%s?%s" % (self._zrobot_client.instance, self.template_uid, urllib.parse.urlencode(dict(name=self.name, guid=self.guid)))

    @property
    def data(self):
        return self._data

    @property
    def state(self):
        # TODO: handle exceptions
        service, _ = self._zrobot_client.api.services.GetService(self.guid)
        s = ServiceState()
        for state in service.state:
            s.set(state.category, state.tag, state.state.value)
        return s

    @property
    def actions(self):
        """
        list available actions of the services
        """
        actions, _ = self._zrobot_client.api.services.ListActions(self.guid)
        return sorted([a.name for a in actions])

    @property
    def logs(self):
        try:
            logs, resp = self._zrobot_client.api.services.GetLogs(self.guid)
        except HTTPError as err:
            if err.response.status_code == 400:
                raise RuntimeError(err.response.json()['message'])
            raise err

        return logs.logs

    def schedule_action(self, action, args=None):
        """
        Do a call on a remote ZeroRobot to add an action to the task list of
        the corresponding service

        @param action: action is the name of the action to add to the task list
        @param args: dictionnary of the argument to pass to the action
        """
        req = {
            "action_name": action,
        }
        if args:
            req["args"] = args
        task, _ = self._zrobot_client.api.services.AddTaskToList(req, service_guid=self.guid)

        return _task_proxy_from_api(task, self)

    def delete(self):
        self._zrobot_client.api.services.DeleteService(self.guid)
        # clean up secret from zrobot client
        for secret in list(self._zrobot_client.config.data['secrets_']):
            try:
                claims = jwt.get_unverified_claims(secret)
            except:
                continue
            else:
                if claims.get('service_guid') == self.guid:
                    self._zrobot_client.config.data['secrets_'].remove(secret)
                    self._zrobot_client.config.save()
                    return

class TaskListProxy:

    def __init__(self, service_proxy):
        self._service = service_proxy

    def empty(self):
        tasks, _ = self._service._zrobot_client.api.services.getTaskList(service_guid=self._service.guid, query_params={'all': False})
        return len(tasks) <= 0

    def list_tasks(self, all=False):
        tasks, _ = self._service._zrobot_client.api.services.getTaskList(service_guid=self._service.guid, query_params={'all': all})
        return [_task_proxy_from_api(t, self._service) for t in tasks]

    def get_task_by_guid(self, guid):
        """
        return a task from the list by it's guid
        """
        try:
            task, _ = self._service._zrobot_client.api.services.GetTask(service_guid=self._service.guid, task_guid=guid)
            return _task_proxy_from_api(task, self._service)
        except HTTPError as err:
            if err.response.status_code == 404:
                raise TaskNotFoundError("no task with guid %s found" % guid)
            raise err

class TaskProxy(Task):
    """
    class that represent a task on a remote service

    the state attribute is an property that do an API call to get the
    actual state of the task on the remote ZeroRobot
    """

    def __init__(self, guid, service, action_name, args, created):
        super().__init__(func=None, args=args)
        self.action_name = action_name
        self.service = service
        self.guid = guid
        self._created = created

    def execute(self):
        raise RuntimeError("a TaskProxy should never be executed")

    @property
    def result(self):
        if self._result is None:
            task, _ = self.service._zrobot_client.api.services.GetTask(task_guid=self.guid, service_guid=self.service.guid)
            if task.result:
                self._result = j.data.serializer.json.loads(task.result)
        return self._result

    @property
    def duration(self):
        if self._duration is None:
            task, _ = self.service._zrobot_client.api.services.GetTask(task_guid=self.guid, service_guid=self.service.guid)
            self._duration = task.duration
        return self._duration

    @property
    def state(self):
        task, _ = self.service._zrobot_client.api.services.GetTask(task_guid=self.guid, service_guid=self.service.guid)
        return task.state.value

    @state.setter
    def state(self, value):
        raise RuntimeError("you can't change the statet of a TaskProxy")

    @property
    def eco(self):
        if self._eco is None:
            task, _ = self.service._zrobot_client.api.services.GetTask(task_guid=self.guid, service_guid=self.service.guid)
            if task.eco:
                d_eco = task.eco.as_dict()
                d_eco['_traceback'] = task.eco._traceback
                self._eco = j.core.errorhandler.getErrorConditionObject(ddict=d_eco)
        return self._eco

def _task_proxy_from_api(task, service):
    t = TaskProxy(task.guid, service, task.action_name, task.args, task.created)
    if task.duration:
        t._duration = task.duration
    if task.eco:
        d_eco = task.eco.as_dict()
        d_eco['_traceback'] = task.eco._traceback
        t._eco = j.core.errorhandler.getErrorConditionObject(ddict=d_eco)
    return t


from typing import DefaultDict
import sys
import os
import csv
sys.path.append(0, os.path.abspath('.'))
sys.path.append(0, os.path.abspath('./src'))
sys.path.append(0, os.path.abspath('./src/utilities'))
from src.utilities import SCRIPT_HOME
from src.utilities.post_process import post_proc_timeseries
from net_sim import Attack_Sim
from time import time
from sys import argv

def main(argv):
    if len(argv) == 1:
        network                 = 'Comcast'
        hosts                   = 149
        te_method               = "-semimcfraeke"
        traffic_type            = "FlashCrowd"
        iter_i                  = 1
        experiment_tag          = "NoFailures"
        network_instance        = network
        repeat                  = "False"
        traffic_file            = "/home/mhall/OLTE/data/traffic/{}_x100_10000_pareto-matrix.txt".format(network)
        n_fallow_transponders   = "20"

    else:
        _                      ,\
        network                ,\
        hosts                  ,\
        te_method              ,\
        traffic_type           ,\
        iter_i                 ,\
        experiment_tag         ,\
        network_instance       ,\
        repeat                 ,\
        traffic_file           ,\
        n_fallow_transponders  ,\
        optical_strategy       ,\
        fallow_tx_allocation   ,\
        ftx_file               = argv

        iter_i = int(iter_i)
        hosts = int(hosts)

    iterations = int(os.popen('wc -l ' + traffic_file).read().split()[0])
    data = DefaultDict(list)
    if 1: ########################## Baseline ########################
        t0_baseline_init = time()
        attack_sim = Attack_Sim(network_instance,
                                hosts,
                                "_".join([traffic_type,experiment_tag]),
                                iterations=iterations,
                                te_method=te_method,
                                method="none",
                                traffic_file=traffic_file,
                                # strategy="baseline",
                                strategy=optical_strategy,
                                use_heuristic='no',
                                fallow_transponders=n_fallow_transponders,
                                fallow_tx_allocation_strategy=fallow_tx_allocation,
                                fallow_tx_allocation_file=ftx_file,
                                salt=str(iter_i)
                                )
        if repeat == "repeat":
            result = attack_sim.perform_sim(circuits=1, start_iter=iter_i, end_iter=iter_i, repeat = True)
        else:
            result = attack_sim.perform_sim(circuits=1, start_iter=iter_i, end_iter=iter_i)

        for key in result:
            data[key].extend(result[key])

        # results_file = SCRIPT_HOME + "/data/results/{}_coremelt_every_link_{}_{}_{}".format(
        #     network, attack, net_iter.split('/')[1], iter_i) + ".csv"
        # print("writing results to: " + results_file)
        # with open(results_file, "w") as outfile:
        #     writer = csv.writer(outfile)
        #     writer.writerow(data.keys())
        #     writer.writerows(zip(*data.values()))

if __name__ == "__main__":
    main(argv)