from typing import Union, Any, List, Set, Dict, Tuple, Optional
import json

from prosto.utils import *

from prosto.Prosto import *
from prosto.Table import *
from prosto.Column import *


class Operation:
    """The class represents one operation."""

    operation_no = 0

    def __init__(self, prosto, definition):
        """
        Create a new operation object using its definition.

        :param prosto: Prosto context object this operation belongs to
        :param definition: Operation definition as a dict
        """

        # Assign id
        self.id = definition.get("id", None)
        if self.id is None:
            self.id = "___operation___" + str(Operation.operation_no)
            definition["id"] = self.id
            Operation.operation_no += 1

        self.prosto = prosto
        self.definition = definition
        self.operation = definition.get("operation")

    def __repr__(self):
        return "["+self.id+"::"+self.operation+"]"

    def get_columns(self) -> List[str]:
        """Get a list of input column names specified in this definition."""
        definition = self.definition
        columns = definition.get("columns", [])

        # If a single name is provided (not a list), then we wrap it into a list
        if isinstance(columns, str):
            columns = [columns]

        return columns

    def get_tables(self) -> List[str]:
        """Get a list of input table names specified in this definition."""
        definition = self.definition
        tables = definition.get("tables", [])

        # If a single name is provided (not a list), then we wrap it into a list
        if isinstance(tables, str):
            tables = [tables]

        return tables

    def get_outputs(self) -> List[str]:
        """Get a list of output column names generated by this definition."""
        definition = self.definition
        outputs = definition.get("outputs", [])

        # If a single name is provided (not a list), then we wrap it into a list
        if isinstance(outputs, str):
            outputs = [outputs]

        if not outputs:  # If outputs are not specified then use id
            outputs.append(definition.get("id"))

        return outputs

    def get_dependency_objects(self) -> List[Union[Table, Column]]:
        """
        Get dependencies as a list of objects of Table or Column resolved from the corresponding names.
        Note that not all names can be resolved to objects, for example, attributes, column paths or expressions.
        """
        deps = self.get_dependencies_names()

        dependencies = []

        # Tables
        for table_name in deps.keys():
            # Resolve this table name into a table object
            table = self.prosto.get_table(table_name)
            if table not in dependencies:
                dependencies.append(table)

        # Columns
        for table_name, column_names in deps.items():
            for column_name in column_names:
                # Resolve column name
                column = self.prosto.get_column(table_name, column_name)
                if column and column not in dependencies:
                    dependencies.append(column)
                else:
                    pass  # It could be an attribute or column path

        return dependencies


if __name__ == "__main__":
    pass
