import re
import warnings
from .ReplaceWrkAllocFunction import replace_wrkalloc
from .Functions import remove_comments, load_intrinsics, strip_call_arguments, close_brackets, split_declaration, \
    which_type, which_dimension, which_intent, in_which_block, is_optional, is_parameter, is_allocatable, \
    is_keyword_argument, function_type_from_header, type_of_contents, find_call_arguments, split_semicolon, \
    find_kind_of_parameter_exception, add_rpe_to_argument, add_rpe_var_to_arguments, find_called_routine, \
    calls_to_routines, calls_to_procedure, calls_to_functions, find_called_function, find_argument_intervals, \
    add_val_to_argument, add_real_to_argument, split_declaration_arguments, separate_line_container_and_arguments, \
    match_arguments_and_interface, has_operations, split_read_arguments, parse_attributes, use_temporal_variable, \
    remove_literals


# Source Database Classes
class Vault:
    def __init__(self):
        self.modules = []
        self.variables = []
        self.dictionary_of_variables = {}
        self.procedures = []
        self.routines = []
        self.functions = []
        self.interfaces = []
        self.derived_types = []
        self.intrinsics = load_intrinsics()

    def add_module(self, module):
        self.modules.append(module)

    def __getitem__(self, item):
        for module in self.modules:
            if module.name == item:
                return module

    def process_procedures(self):
        # Purpose: Add the procedures defined in the modules to the list of procedures and classify by type.
        for module in self.modules:
            for procedure in module.procedures:
                self.procedures.append(procedure)
        for procedure in self.procedures:
            if isinstance(procedure, Routine):
                self.routines.append(procedure)
            elif isinstance(procedure, Function):
                self.functions.append(procedure)
            elif isinstance(procedure, Interface):
                self.interfaces.append(procedure)
            elif isinstance(procedure, DerivedType):
                self.derived_types.append(procedure)

    def process_variables(self):
        # Purpose: Add the variables defined in the modules to the list of variables.
        if not self.procedures:
            self.process_procedures()
        for procedure in self.procedures:
            if isinstance(procedure, ModuleMain) or isinstance(procedure, Function) or isinstance(procedure, Routine):
                for variable in procedure.variables:
                    self.variables.append(variable)
                    if not variable.name.lower() in self.dictionary_of_variables:
                        self.dictionary_of_variables[variable.name.lower()] = [variable]
                    else:
                        self.dictionary_of_variables[variable.name.lower()] += [variable]

    def get_variable(self, item):
        # Purpose: Retrieve a variable by name
        if not self.variables:
            self.process_variables()
        try:
            for variable in self.dictionary_of_variables[item.lower()]:
                yield variable
        except KeyError:
            pass

    def get_variable_by_id(self, var_id):
        # Purpose: Retrieve a variable by name
        if not self.variables:
            self.process_variables()
        for variable in self.variables:
            if variable.id == var_id:
                return variable

    def get_function(self, item):
        # Purpose: Retrieve a function by name
        return self.get_procedure(item, Function)

    def get_procedure(self, item, procedure_type=None):
        # Purpose: Retrieve a procedure by name
        # TODO: This routine is quite expensive (16%), could be boosted using dictionaries.
        if not self.procedures:
            self.process_procedures()

        for procedure in self.procedures:
            if procedure_type:
                if isinstance(procedure, procedure_type):
                    if procedure.name == item:
                        return procedure
            elif procedure.name == item:
                return procedure

    def get_derived_type(self, item):
        # Purpose: Retrieve a derived type by name
        for dt in self.derived_types:
            if dt.name.lower() == item.lower():
                    return dt

    def expand_used_modules(self):
        for module in self.modules:
            queue = []
            done = []
            for mod in module.uses:
                queue.append(mod)
            while len(queue) > 0:
                mod = queue.pop(0)
                for mod1 in mod.uses:
                    if mod1 not in module.uses and mod1 not in done:
                        module.uses.append(mod1)
                        queue.append(mod1)
                done.append(mod)

    def expand_used_by(self):
        for module in self.modules:
            for mod1 in module.uses:
                if module not in mod1.used_by:
                    mod1.used_by.append(module)

    def generate_namelist_precisions(self, filename="namelist_precisions", exclude_double_precision=True):
        rpe_vars = [var for var in self.variables if var.type == "rpe_var"]
        assigned_rpe_vars = [var for var in rpe_vars if var.id is not None]
        assigned_rpe_vars = sorted(assigned_rpe_vars, key=lambda x: x.id)
        line_text = "emulator_variable_precisions(%d) = %d\t! Variable:%24s\tRoutine:%22s\tModule:%16s\n"
        with open(filename, "w") as namelist:
            namelist.write("! namelist variable precisions\n&precisions\n")
            for var in assigned_rpe_vars:
                if var.precision == 52 and exclude_double_precision:
                    continue
                namelist.write(line_text % (var.id, var.precision, var.name, var.routine.name, var.routine.module.name))
            namelist.write("/")


class Module:
    def __init__(self, _name):
        self.name = _name
        self.repo = None
        self.procedures = []
        self.variables = []
        self.precision = None
        self.uses = []
        self.used_by = []

    def add_procedure(self, _routine):
        assert isinstance(_routine, Procedure)
        _routine.module = self
        self.procedures.append(_routine)

    def add_variable(self, _variable):
        assert isinstance(_variable, Variable)
        _variable.routine = self
        self.variables.append(_variable)

    def __getitem__(self, item):
        for _routine in self.procedures:
            if _routine.name == item:
                return _routine
        new_routine = Routine(None, item)
        new_routine.module = self
        self.repo.add_procedure(new_routine)
        self.procedures.append(new_routine)
        return self[item]

    def set_precision(self, precision):
        assert isinstance(precision, int), "Precision argument in set_precision must be an integer"
        for routine in self.procedures:
            routine.set_precision(precision)
        self.precision = precision


    def main(self):
        for procedure in self.procedures:
            if isinstance(procedure, ModuleMain):
                return procedure
        return None


class Procedure(object):
    def __init__(self, _name, module):
        self.name = _name
        self.module = module
        self.variables = []
        self.precision = None
        self.interface = None
        self.dummy_arguments = None
        self.uses = []

    def add_variable(self, _variable):
        try:
            assert isinstance(_variable, Variable)
        except AssertionError:
            pass
        _variable.routine = self
        self.variables.append(_variable)

    def get_variable_by_position(self, position):
        for variable in self.variables:
            if variable.is_dummy_argument:
                if variable.position == position:
                    return variable

    def get_variable_by_name(self, name):
        for variable in self.variables:
            if variable.name.lower() == name.lower():
                return variable

    def accessible_variables(self):
        variables = self.variables
        if self.module:
            variables += self.module.main().variables
            if self.module.uses:
                for module in self.module.uses:
                    variables += module.main().variables

        return variables

    def get_dummy_arguments(self):
        if self.dummy_arguments is None:
            number_of_dummy_arguments = len([True for var in self.variables if var.is_dummy_argument])
            self.dummy_arguments = [self.get_variable_by_position(i) for i in range(number_of_dummy_arguments)]
        return self.dummy_arguments


class Routine(Procedure):
    def __init__(self, _name, module):
        super(Routine, self).__init__(_name, module)


class Function(Procedure):
    def __init__(self, _name, module):
        super(Function, self).__init__(_name, module)
        self.type = None


class DerivedType(Procedure):
    def __init__(self, _name, module):
        super(DerivedType, self).__init__(_name, module)

    def get_variable(self, item):
        for variable in self.variables:
            if variable.name.lower() == item.lower():
                return variable


class ModuleMain(Procedure):
    def __init__(self, module):
        super(ModuleMain, self).__init__("main", module)
        self.module = module


class Interface:
    def __init__(self, _name):
        self.name = _name
        self.module = None
        self.procedures = []

    def add_procedure(self, _procedure):
        assert isinstance(_procedure, Routine) or isinstance(_procedure, Function)

        self.procedures.append(_procedure)
        # Don't know it will be useful yet
        if not _procedure.interface:
            _procedure.interface = self
        elif isinstance(_procedure.interface, list):
            _procedure.interface.append(self)
        else:
            _procedure.interface = [_procedure.interface, self]


class Variable:
    def __init__(self, _routine, _name, _type, _dimension):
        self.id = None
        self.module = None
        self.routine = _routine
        self.name = _name
        self.type = _type
        self.dimension = _dimension
        self.is_dummy_argument = False
        self.intent = None
        self.is_optional = None
        self.position = None
        self.allocatable = False
        self.precision = None
        self.is_parameter = None

    def set_precision(self, precision):
        assert isinstance(precision, int), "Precision argument in set_precision must be an integer"
        self.precision = precision


# File manipulation Classes

class ReadSourceFile(object):
    def __init__(self, _filename):
        # Init variables
        self.file_path = _filename
        self.filename = self.file_path.split("/")[-1]
        self.text = None
        self.lines = None

        # Read file
        self.read_file(_filename)

        # Pre-Process file
        self.remove_all_comments()
        self.remove_empty_lines()
        self.merge_all()
        self.split_lines()
        self.split_declarations()
        self.rebuild_text()

    def read_file(self, _filename):
        try:
            with open(_filename) as f:
                self.text = f.read()
                self.lines = self.text.split("\n")
        except UnicodeDecodeError:
            with open(_filename, encoding="latin1") as f:
                self.text = f.read()
                self.lines = self.text.split("\n")

    def remove_comments_in_interval(self, start, end):
        # Definition: The comments of the lines between start and end are removed
        # Input:
        #       -start: integer with the starting line number.
        #       -end: integer with the ending line number.
        for _index in range(start, end):
            self.lines[_index] = remove_comments(self.lines[_index])

    def merge_all(self):
        self.merge_lines_in_interval(0, len(self.lines))

    def merge_lines_in_interval(self, start, end):
        # Definition: Given the number of the starting and ending lines of an interval,
        # the split lines are merged.
        # Input:
        #       -r_start: integer with the starting line number.
        #       -r_end: integer with the ending line number.
        for _index in range(start, end):
            line = self.lines[_index]
            if line.count("&"):
                i = 1
                while line and line.strip()[-1] == "&":
                    line1 = line
                    line2 = self.lines[_index + i]
                    line = line1.replace("&", "").rstrip() + line2

                    i += 1
                line = line.replace("&", "")
                line = re.sub(", +", ", ", line)
                line = re.sub(":  +", ":  ", line)
                self.lines[_index] = line
                for j in range(1, i):
                    self.lines[_index + j] = ""

    def split_lines(self):
        # TODO: We should be sure that we are not spliting by characters in literals

        _index = 0
        length = len(self.lines)
        while _index < length:
            line = self.lines[_index]
            if line.count(";"):
                parts = split_semicolon(line)
                self.lines[_index] = parts[0]
                for i in range(1, len(parts)):
                    self.lines.insert(_index + 1, parts[i])
                    length += 1
                    _index += 1
            _index += 1
        self.rebuild_text()

    def split_declarations(self):
        # This method should split declarations of variables putting a single variable in each line
        _index = 0
        length = len(self.lines)
        while _index < length:
            line = self.lines[_index]
            line = remove_literals(line)
            if line.count("::"):
                attributes, arguments = line.split("::")
                arguments = find_call_arguments("c(%s)" % arguments)
                if len(arguments) > 1:
                    new_line = "%s :: %s" % (attributes, arguments[0])
                    self.lines[_index] = new_line
                    for argument in arguments[1:]:
                        new_line = "%s :: %s" % (attributes, argument)
                        self.lines.insert(_index + 1, new_line)
                        length += 1
                        _index += 1

            _index += 1
        self.rebuild_text()

    def remove_all_comments(self):
        self.remove_comments_in_interval(0, len(self.lines))
        self.remove_empty_lines()

    def remove_empty_lines(self):
        lines = [line for line in self.lines if line.strip()]
        self.lines = lines

    def rebuild_text(self):
        self.text = "".join([l + "\n" for l in self.lines[:]])

    def save_modified_file(self, filepath):
        with open(filepath, "w") as output_file:
            output_file.write(self.text)


# Classes used in ParseRoutines.py
class ObtainSourcefileInfo(ReadSourceFile):
    # Definition: This class is used to load a sourcefile and parse the subroutines contained there
    # Input: string with path to sourcefile
    def __init__(self, _filename):
        super(ObtainSourcefileInfo, self).__init__(_filename)
        # Definition: Initializing the object reading the sourcefile
        # Input: string with path to sourcefile
        # I think that it is save to collect the information using all the code in lower case.
        self.text = self.text.lower()
        self.objects = []
        self.module = None

    def find_next_routine_end(self, _index):
        # Definition: Given a line number, looks for next line with "end subroutine"
        # Input: integer with line number
        # Output: integer with the line number of the next "end subroutine"
        return self.find_next_end(_index, "end subroutine")

    def find_next_function_end(self, _index):
        # Definition: Given a line number, looks for next line with "end subroutine"
        # Input: integer with line number
        # Output: integer with the line number of the next "end subroutine"
        return self.find_next_end(_index, "end function")

    def find_next_interface_end(self, _index):
        # Definition: Given a line number, looks for next line with "end interface"
        # Input: integer with line number
        # Output: integer with the line number of the next "end interface"
        return self.find_next_end(_index, "end interface")

    def find_next_end(self, _index, end_statement):
        # Definition: Given a line number, looks for next line with the string contained in end_statement argument.
        # Input:
        #       _index: integer with line number
        #       end_statement: string that indicates next end_statement

        # Output: integer with the line number of the next line containing the end_statement
        _i = _index
        while _i < len(self.lines):
            _l = self.lines[_i]
            if _l.lower().count(end_statement):
                return _i
            _i += 1
        return len(self.lines)

    def get_dummy_arguments(self, _index):
        # Definition: Given a line number, returns the subroutine name and the dummy arguments (in case they exist)
        # Input: integer with line number
        # Output: tuple with routine name and list of dummy arguments
        _line = self.lines[_index]
        # Workaround to solve the problem that appears when there's a function declaration with (kind=...)
        if _line.lower().count("function"):
            if _line.find("(") < _line.lower().find("function"):
                _line = _line[_line.lower().find("function"):]

        # If the subroutine declaration does not have any parenthesis, this routine does not have any argument
        if not _line.count("("):
            _name = _line.split()[1].strip()
            _dummy_arguments = []
        # If otherwise it has dummy arguments, find them
        elif _line.lower().count("result") or _line.lower().count("bind("):
            _name = _line[:_line.find("(")].split()[1]
            _dummy_arguments = []
            _dummy_arguments += strip_call_arguments(close_brackets(_line))
            # I don't know if we should record the result variable with the other variables
            # result_start = _line.lower().find("result")
            # _dummy_arguments += strip_call_arguments(_line[result_start:])
        else:
            _name = _line[:_line.find("(")].split()[-1]
            _dummy_arguments = strip_call_arguments(_line)
        _dummy_arguments = [_x for _x in _dummy_arguments if _x.strip()]
        return _name, _dummy_arguments

    def find_routine_declarations(self, _routine_start, _routine_end):
        # Definition: Given the line numbers where a routine starts and ends,
        # look for variable declarations.
        # Input:
        #       -_routine_start: integer with line number where the routine starts
        #       -_routine_end: integer with line number where the routine ends
        # Output: list of strings containing declarations

        # Lines inside the routine
        _routine_lines = self.lines[_routine_start:_routine_end + 1]
        # Lines that are variable declarations
        _declarations = [_l for _l in _routine_lines if _l.count("::")]
        return _declarations

    def find_procedure_variables(self, routine_name, dummy_arguments, declarations, header):
        # Definition: Given the routine name, the name of the dummy arguments and the lines containing declarations,
        # this method finds the attributes of each dummy arguments and adds all the information
        # into the object variable "routines".
        # Input:
        #       -routine_name: string with the routine name
        #       -dummy_arguments: list of strings with argument names
        #       -declarations: list of strings with variable declarations

        if header.count("function"):
            routine_or_function = "function"
        elif header.count("subroutine"):
            routine_or_function = "routine"
        else:
            raise ValueError("Header does not correspond to function or routine")

        if routine_or_function == "routine":
            obj_type = Routine
        elif routine_or_function == "function":
            obj_type = Function
        else:
            raise ValueError("routine_or_function must be 'routine' or 'function'")

        ob = obj_type(routine_name, self.module)

        # Reverse approach
        dummy_arguments = [x.lower() for x in dummy_arguments]
        for declaration in declarations:
            pattern = "(.*)::(.*)"
            m = re.search(pattern, declaration)
            _attributes, variable = m.groups()
            # Check if the dummy argument is inside the declaration arguments
            # If it is, take the type, dimension, intent and optional
            _var_type, _dimension, _intent, _optional, _allocatable, _is_parameter = parse_attributes(_attributes)
            # Workaround for variables that have the dimension implicitly specified.
            if variable.count("("):
                _dimension = 1 + variable.count(",")

            variable_name = split_declaration_arguments(variable)

            if len(variable_name) != 1:
                raise AssertionError("WTF?")
            variable_name = variable_name[0]

            _v = Variable(ob, variable_name, _var_type, _dimension)
            _v.allocatable = _allocatable
            _v.is_parameter = _is_parameter

            if variable_name.lower() in dummy_arguments:
                _v.intent = _intent
                _v.is_optional = _optional
                _v.is_dummy_argument = True
            ob.add_variable(_v)

        # Save the position of the argument
        found_vars = [variable.name.lower() for variable in ob.variables]
        for index, dummy_argument in enumerate(dummy_arguments):
            if dummy_argument in found_vars:
                # TODO: save position of already found vars
                ob.get_variable_by_name(dummy_argument).position = index  # variable.position = index
                continue
                # If it's not found its an implicitly declared variable
            if dummy_argument[0] in "ijklmn":
                var_type = "integer"
            else:
                var_type = "real"
            dimension = 0
            intent = None
            optional = None
            _v = Variable(ob, dummy_argument, var_type, dimension)
            _v.intent = intent
            _v.is_optional = optional
            _v.is_dummy_argument = True
            _v.position = index
            _v.is_parameter = False
            ob.add_variable(_v)
        # Save object
        if routine_or_function == "function":

            type_found, type_or_var = function_type_from_header(header)

            if type_found:
                ob.type = type_or_var
            else:
                for variable in ob.variables:
                    if variable.name.lower() == type_or_var.lower():
                        ob.type = variable.type
                        break
        self.objects.append(ob)

    def find_routines(self):
        # Definition: Looks into the sourcefile for routine declarations, storing the name, type,
        # dimensions and optionality of the arguments.

        # Look into each line
        for _index, line in enumerate(self.lines):
            # Look if routine starts with subroutine
            if re.search("subroutine", line, re.IGNORECASE):
                if line.lower().strip().find("end") == 0:
                    continue
                routine_start = _index
                # Look for end subroutine statements
                routine_end = self.find_next_routine_end(_index)

                # Find name and dummy arguments
                routine_name, dummy_arguments = self.get_dummy_arguments(_index)
                # Find declarations
                declarations = self.find_routine_declarations(routine_start, routine_end)
                header = self.lines[routine_start].lower()
                # Parse everything and store
                self.find_procedure_variables(routine_name.lower(), dummy_arguments, declarations, header)

    def find_types(self):
        # Definition: Looks into the sourcefile for routine declarations, storing the name, type,
        # dimensions and optionality of the arguments.

        # Look into each line
        in_type_declaration = False
        types = {}
        type_name = None
        variable_counter = None
        for _index, line in enumerate(self.lines):
            # Look if routine starts with subroutine
            if not in_type_declaration:
                pattern = "^ *type[^()]*[ \t](\w+) *$"
                m = re.search(pattern, line, re.IGNORECASE)
                if m:
                    in_type_declaration = True
                    variable_counter = 0
                    type_name = m.groups(0)[0]
                    types[type_name] = {}
                    ob = DerivedType(type_name, self.module)
                    self.objects.append(ob)
            else:
                pattern = "^ *END TYPE"
                m = re.search(pattern, line, re.IGNORECASE)
                if m:
                    in_type_declaration = False
                else:
                    pattern = "(.*)::(.*)"
                    m = re.search(pattern, line)
                    if m:
                        attributes, variables = m.groups()
                        var_type, dimension, intent, optional, allocatable, is_parameter = parse_attributes(attributes)
                        # Workaround for variables that have the dimension implicitly specified.
                        if variables.count("("):
                            dimension = 1 + variables.count(",")

                        variables = split_declaration_arguments(variables)
                        for var in variables:
                            types[type_name][var] = var_type
                            v = Variable(ob, var, var_type, dimension)
                            v.position = variable_counter
                            v.is_dummy_argument = True
                            v.allocatable = allocatable
                            v.is_parameter = is_parameter
                            ob.add_variable(v)
                            variable_counter += 1
        return types

    def find_module_variables(self):
        main_routine = ModuleMain(self.filename)

        in_type = False
        self.objects.append(main_routine)
        pattern = "(.*)::(.*)"
        for line in self.lines:

            if not in_type:
                # Check if we are entering a type declaration zone
                type_pattern = "^ *type[^()]*[ \t](\w+) *$"
                m = re.search(type_pattern, line, re.IGNORECASE)
                if m:
                    in_type = True
                    continue
            else:
                # Check if we are exiting a type declaration zone
                type_pattern = "^ *END TYPE"
                m = re.search(type_pattern, line, re.IGNORECASE)
                if m:
                    in_type = False
                # Even if we are inside or exeting a type declaration zone, we have to jump to next line
                continue

            if line.lower().count("contains"):
                break
            if line.lower().count("end program"):
                break
            match = re.search(pattern, line)
            if match:
                attributes, variables = match.groups()
                var_type, dimension, intent, optional, allocatable, is_parameter = parse_attributes(attributes)
                variables = split_declaration_arguments(variables)
                if var_type:
                    for variable in variables:
                        v = Variable(main_routine, variable, var_type, dimension)
                        v.allocatable = allocatable
                        v.is_parameter = is_parameter
                        main_routine.add_variable(v)

    def is_start_of_function_definition(self, line_number):
        line = self.lines[line_number]
        match = re.search("(^ *[a-z\(\)0-9=]* *function.*)", line, re.I)
        if match:
            if match.group().lower().count("end function"):
                return False
            return True
        return False

    def find_functions(self):
        # Definition: Looks into the sourcefile for routine declarations, storing the name, type,
        # dimensions and optionality of the arguments.

        # Look into each line
        for _index, line in enumerate(self.lines):
            if self.is_start_of_function_definition(_index):
                function_start = _index
                # Look for end subroutine statements
                function_end = self.find_next_function_end(_index)

                # Find name and dummy arguments
                function_name, dummy_arguments = self.get_dummy_arguments(_index)
                # Find declarations
                declarations = self.find_routine_declarations(function_start, function_end)
                # Parse everything and store
                header = self.lines[function_start].lower()
                self.find_procedure_variables(function_name.lower(), dummy_arguments, declarations, header)

    def find_implicit_functions(self, vault):
        try:
            routine = self.module.main()
        except AttributeError:
            return None
        # Definition: Looks into the sourcefile for routine declarations, storing the name, type,
        # dimensions and optionality of the arguments.
        pattern = "^ *(\w+) *\([\w, ]*\) *= *(.*)"
        # Look into each line
        for _index, line in enumerate(self.lines):
            m = re.search(pattern, line)
            if m:

                call = close_brackets(line[m.start(1):])
                call_arguments = find_call_arguments(call)
                is_function = False
                for argument in call_arguments:
                    argument_type = type_of_contents(argument, routine=routine, vault=vault)
                    if argument_type != "integer":
                        is_function = True
                        break

                if is_function:
                    function_name = find_called_function(call)
                    new_function = Function(function_name, module=self.module)
                    new_function.type = type_of_contents(function_name, routine=routine, vault=vault)
                    for argument_index, argument in enumerate(call_arguments):
                        var = routine.get_variable_by_name(argument)
                        var.is_dummy_argument = True
                        var.position = argument_index
                        var.intent = "in"
                        new_function.add_variable(var)
                    self.objects.append(new_function)

            else:
                routine = in_which_block(line, _routine=routine, vault=vault)

    def find_interfaces(self, object_procedures):
        # Definition: Looks into the sourcefile for interface declarations, storing the name.
        for _index, _line in enumerate(self.lines):
            if _line.strip().lower().find("interface") == 0:
                interface_start = _index
                interface_end = self.find_next_interface_end(_index)
                interface_name = _line.split()[1].strip()
                routines = []
                line_routines = None
                for interface_line in self.lines[interface_start:interface_end + 1]:
                    if interface_line.strip().lower().find("module procedure") == 0:
                        line_routines = interface_line.lower().replace("module procedure", "")
                        line_routines = [_r.strip() for _r in line_routines.split(",")]
                        routines += line_routines
                    elif interface_line.strip().lower().strip().find("subroutine") == 0:
                        routine_name = interface_line[:interface_line.find("(")].split()[1]
                        routines += [routine_name]

                interface_procedures = [object_procedures[_routine] for _routine in routines]
                ob = Interface(interface_name)
                for procedure in interface_procedures:
                    ob.add_procedure(procedure)
                if not ob.procedures:
                    raise AssertionError("No routine found for interface %s" % interface_name)
                self.objects.append(ob)

    def find_used_modules(self, vault):
        current_block = self.module.main()
        for line in self.lines:
            current_block = in_which_block(line, current_block, vault=vault)
            line = line.strip().lower()
            if line.find("use ") == 0:
                module_name = line.split()[1]
                if module_name.count(","):
                    module_name = module_name.replace(",", "")
                for module in vault.modules:
                    if module.name == module_name:
                        self.module.uses.append(module)

                # Look for
                pattern = "(\w+) *=> *(\w+)"
                matches = re.findall(pattern, line)
                if matches:
                    for match in matches:
                        pointer, target = match
                        target_var = self.module.uses[-1].main().get_variable_by_name(target)
                        new_var = Variable(current_block, pointer, target_var.type, target_var.dimension)
                        current_block.add_variable(new_var)


class ImplementRPE(ReadSourceFile):
    def __init__(self, _filename, vault):
        super(ImplementRPE, self).__init__(_filename)

        # Dictionaries and lists about source info
        self.source_info = vault
        if self.source_info:
            module_name = _filename.replace(".f90", "")
            if module_name.count("/"):
                module_name = module_name.split("/")[-1]
            self.module = self.source_info[module_name]
        else:
            self.module = None

    def pre_process(self, replace_wrk_alloc=True, replace_reals=True):
        # Replace wrk_alloc
        # TODO: This is only useful with NEMO and probably for old versions
        if replace_wrk_alloc:
            self.replace_wrk_alloc()
        if replace_reals:
            # Replace real declarations with type(rpe_var)
            self.replace_real_declaration()
            # Workaround for parameters
            self.replace_parameter_declaration()

    def post_process(self):
        # If we don't have source info there's nothing to process
        if self.source_info is None:
            raise AssertionError("It is needed to have a vault database to postprocess the code")

        # Process intrinsic calls
        self.fix_intrinsic_calls()
        # Process routine calls
        self.fix_routines()
        # Process function calls
        self.fix_functions()

        self.fix_type_conversion()

        self.fix_data_statement()

        # Process read and write calls
        self.fix_read_and_write_calls()
        # Fix pointer assignations
        self.fix_pointer_assignations()
        # Add use rp_emulator statement
        self.add_use_rpe_statement()

    def fix_pointer_assignations(self):
        current_block = self.module.main()
        pattern = "^[^:'\n\w]*([\w%\(\):,]+) *=> *([\w%\(\):, -]*)"

        for line_index, line in enumerate(self.lines):
            if not re.search(pattern, line):
                current_block = in_which_block(line, current_block, vault=self.source_info)
            else:
                m = re.search(pattern, line)
                pointer, target = m.groups(0)
                pointer_type = type_of_contents(pointer, routine=current_block, vault=self.source_info)
                target_type = type_of_contents(target, routine=current_block, vault=self.source_info)
                if pointer_type == "real" and target_type == "rpe_var":
                    self.lines[line_index] = line.replace(target, "%s%%val" % target)

    def replace_wrk_alloc(self):
        self.text = self.text.replace("USE wrk_nemo", "")
        self.text = replace_wrkalloc(self.text)
        self.lines = self.text.split("\n")

    def replace_real_declaration(self):
        # Look at each line for REAL variable declarations and replace it with type(rpe_var)
        for _index, line in enumerate(self.lines):
            _match = re.search('[^\!]*real.*::', line, flags=re.I)
            if _match:
                _match = re.search('[^\!]*(real *\([\w= ]*\)).*::', line, flags=re.I)
                if _match:
                    real_type = _match.groups(0)[0]
                    line = line.replace(real_type, "type(rpe_var)")
                    self.lines[_index] = line
                else:
                    _match = re.search('[^\!]*(real).*::', line, flags=re.I)
                    real_type = _match.groups(0)[0]
                    line = line.replace(real_type, "type(rpe_var)")
                    self.lines[_index] = line
        self.rebuild_text()

    def replace_parameter_declaration(self):
        # If there's an assignation whe have to find a workaround
        for _index, line in enumerate(self.lines):
            if re.search('^[^\!]*type\(rpe_var\).*::.*=', line, flags=re.I):
                arguments = line.split("::")[1]

                type_of_exception = find_kind_of_parameter_exception(arguments)

                if type_of_exception == "regular":
                    piece_to_replace = arguments.split("=")[1].strip()

                    replacement = " rpe_var( 52, %s ) " % piece_to_replace
                    self.lines[_index] = line.replace(piece_to_replace, replacement)
                elif type_of_exception == "multiple_variables":
                    for argument in arguments.split(","):
                        piece_to_replace = argument.split("=")[1].strip()
                        replacement = " rpe_var(  52, %s ) " % piece_to_replace
                        line = line.replace(piece_to_replace, replacement)
                    self.lines[_index] = line
                elif type_of_exception == "array_initialization":
                    piece_to_replace = arguments
                    replacement = add_rpe_var_to_arguments(arguments)
                    self.lines[_index] = line.replace(piece_to_replace, replacement)
                elif type_of_exception == "already_fixed":
                    continue
        self.rebuild_text()

    def fix_data_statement(self):
        changed_something = False
        current_block = self.module.main()
        # If there's an assignation whe have to find a workaround
        for _index, line in enumerate(self.lines):
            match = re.search(r'^( *)data +(.*) +/(.*)/', line, flags=re.I)
            if match:
                spaces, var_name, values = match.groups(0)
                if var_name.count(","):
                    var_type = type_of_contents(var_name.split(",")[0], current_block, vault=self.source_info)
                else:
                    var_type = type_of_contents(var_name, current_block, vault=self.source_info)
                if var_type != "rpe_var":
                    continue
                values = ",".join(["rpe_var(52, %s)" % x for x in values.split(",")])
                self.lines[_index] = "%sdata %s /%s/" % (spaces, var_name, values)
                changed_something = True
            else:
                current_block = in_which_block(line, current_block, vault=self.source_info)
        if changed_something:
            self.rebuild_text()

    def add_use_rpe_statement(self):
        # Adding module rp_emulator (in theory adding this to par_kind.f90 should be enough,
        # but theory fails and compiler complains so I'll add it everywhere
        self.lines.insert(1, "USE rp_emulator")
        self.rebuild_text()

    def fix_read_and_write_calls(self):
        self.fix_read_calls()
        self.fix_write_calls()

    def fix_read_calls(self):
        current_block = self.module.main()
        pattern = "[^\w](read *\(.*)"
        for line_index, line in enumerate(self.lines):
            matches = re.search(pattern, line, re.IGNORECASE)
            if matches:

                to_remove = close_brackets(matches.groups(1)[0])
                read_arguments = line[matches.start(1):].replace(to_remove, "")

                arguments = split_read_arguments(read_arguments)

                for argument_index, argument in enumerate(arguments):
                    argument_type = type_of_contents(argument, routine=current_block, vault=self.source_info)
                    if argument_type == "rpe_var":
                        arguments[argument_index] = "%s%%val" % argument
                replacement = ",".join(arguments)
                self.lines[line_index] = line.replace(read_arguments, replacement)
            else:
                current_block = in_which_block(line, current_block, vault=self.source_info)

    def fix_write_calls(self):
        current_block = self.module.main()
        pattern = "^[^\!]* +write\([^()]*\) (.*)"

        for line_index, line in enumerate(self.lines):
            if re.search("WRITE *\(", line):
                wc = close_brackets(line[line.find("WRITE"):])

                line = line.replace(wc, "WRITE()")
            matches = re.search(pattern, line, re.IGNORECASE)
            if matches:
                line_container, arguments = separate_line_container_and_arguments(self.lines[line_index])
                if not arguments:
                    continue

                for argument_index, argument in enumerate(arguments):
                    argument_type = type_of_contents(argument, routine=current_block, vault=self.source_info)
                    if argument_type == "rpe_var":
                        arguments[argument_index] = "real( %s )" % argument
                replacement = ",".join(arguments)
                self.lines[line_index] = line_container.replace("###TOREPLACE###", replacement)
            else:
                current_block = in_which_block(line, current_block, vault=self.source_info)

    def fix_routines(self):
        current_block = self.module.main()
        exceptions = {}
        temporal_variable_counter = Counter()
        for _index, _line in enumerate(self.lines):
            # Find routine calls in line
            routine_calls = calls_to_routines(_line)
            if routine_calls is None:
                # If not, look if the code is entering or exiting a routine block
                current_block = in_which_block(_line, current_block, vault=self.source_info)
            else:
                temporal_line = _line
                for call in routine_calls:
                    is_external = False
                    called_routine = find_called_routine(call)
                    called_arguments = find_call_arguments(call)
                    if not called_arguments:
                        continue
                    procedure = self.source_info.get_procedure(called_routine)
                    if isinstance(procedure, Routine):
                        pass
                    elif isinstance(procedure, Interface):
                        procedure = match_arguments_and_interface(called_arguments, interface=procedure,
                                                                  current_block=current_block, vault=self.source_info)
                    else:
                        warnings.warn("Routine not found in vault database %s" % called_routine)
                        is_external = True
                        # continue

                    temporal_call = call
                    if not is_external:
                        for argument_index, argument in enumerate(called_arguments):
                            if is_keyword_argument(argument):
                                keyword = argument.split("=")[0].strip()
                                argument_type = type_of_contents(argument, routine=current_block,
                                                                 vault=self.source_info)
                                dummy_argument = procedure.get_variable_by_name(keyword)
                                if not dummy_argument:
                                    raise AssertionError("Variable not found: %s" % keyword)
                                if argument_type == dummy_argument.type:
                                    continue
                                elif argument_type == "real" and dummy_argument.type == "rpe_var":
                                    if dummy_argument.intent == "in":
                                        temporal_call = add_rpe_to_argument(temporal_call, argument_index)
                                    else:
                                        raise AssertionError("Unknwon intent: %s" % dummy_argument.intent)
                                elif argument_type == "rpe_var" and dummy_argument.type == "real":
                                    if dummy_argument.intent == "in":
                                        temporal_call = add_real_to_argument(temporal_call, argument_index)
                                    elif dummy_argument.intent == "out":
                                        temporal_call = add_val_to_argument(temporal_call, argument_index)
                                    else:
                                        raise AssertionError("Unknwon intent: %s" % dummy_argument.intent)
                            else:
                                argument_type = type_of_contents(argument, routine=current_block,
                                                                 vault=self.source_info)
                                dummy_argument = procedure.get_variable_by_position(argument_index)
                                if not dummy_argument:
                                    raise AssertionError("Variable not found in position: %i" % argument_index)
                                if argument_type == dummy_argument.type:
                                    continue
                                elif argument_type == "real" and dummy_argument.type == "rpe_var":
                                    if dummy_argument.intent == "in":
                                        temporal_call = add_rpe_to_argument(temporal_call, argument_index)
                                    else:
                                        try:
                                            exceptions[_index].append((argument, argument_index,dummy_argument, current_block))
                                        except KeyError:
                                            exceptions[_index] = [(argument, argument_index,dummy_argument, current_block)]
                                elif argument_type == "rpe_var" and dummy_argument.type == "real":
                                    if dummy_argument.intent == "in":
                                        temporal_call = add_real_to_argument(temporal_call, argument_index)
                                    elif dummy_argument.intent == "out":
                                        temporal_call = add_val_to_argument(temporal_call, argument_index)
                                    elif dummy_argument.intent == "inout":
                                        temporal_call = add_val_to_argument(temporal_call, argument_index)
                                    else:
                                        raise AssertionError("Unknwon intent: %s" % dummy_argument.intent)
                        temporal_line = temporal_line.replace(call, temporal_call)
                    else:
                        for argument_index, argument in enumerate(called_arguments):
                            # TODO: Probably should find a better solution, the code should not identify an array as a
                            # Todo: ... function even if the name coincides.
                            # if argument == ":":
                            #     continue
                            argument_type = type_of_contents(argument, routine=current_block, vault=self.source_info)
                            if argument_type == "rpe_var":
                                # TODO: Probably I should check first if the argument is a single variable and can be used as an output argument or not
                                if re.match("^\w+$", argument):
                                    temporal_call = add_val_to_argument(temporal_call, argument_index)
                                elif re.match("^\w+ *\([^\(\)]*\)$", argument):
                                    temporal_call = add_val_to_argument(temporal_call, argument_index)
                                elif not has_operations(argument):
                                    temporal_call = add_val_to_argument(temporal_call, argument_index)
                                else:
                                    temporal_call = add_real_to_argument(temporal_call, argument_index)
                        temporal_line = temporal_line.replace(call, temporal_call)
                self.lines[_index] = temporal_line
        self.rebuild_text()

        # Manage exceptions
        added_lines = 0
        for line in sorted(exceptions):
            for argument, argument_index, dummy_argument, routine in exceptions[line]:
                current_line = line+added_lines
                use_temporal_variable(self, current_line, argument, argument_index, routine=routine,
                                      dummy=dummy_argument, counter=temporal_variable_counter)
                added_lines += 3
            added_lines += 2 * len(exceptions[line])
        self.rebuild_text()

    def fix_functions(self):
        current_block = self.module.main()
        for line_index, line in enumerate(self.lines):
            function_calls = calls_to_functions(line, self.source_info)
            if not function_calls:
                # If not, look if the code is entering or exiting a routine block
                current_block = in_which_block(line, current_block, vault=self.source_info)
            else:
                # Fix function calls (line_index
                temporal_line = line
                for call in function_calls:
                    # Small workaround to avoid identifying routines as functions.
                    patt = "call +%s" % call
                    patt = patt.replace("(", "\(")
                    patt = patt.replace(")", "\)")
                    m = re.search(patt, line, re.I)
                    if m:
                        continue
                    is_external = False
                    called_function = find_called_function(call)

                    if called_function.lower() == current_block.name.lower():
                        continue
                    called_arguments = find_call_arguments(call)
                    if not called_arguments:
                        continue

                    procedure = self.source_info.get_procedure(called_function)
                    if isinstance(procedure, Function):
                        pass
                    elif isinstance(procedure, Interface):
                        procedure = match_arguments_and_interface(called_arguments, interface=procedure,
                                                                  current_block=current_block, vault=self.source_info)
                    else:
                        warnings.warn(
                            "Function not found in vault database %s\nWill be treated as external." % called_function)
                        is_external = True
                        # continue

                    if not is_external:
                        temporal_call = call
                        for argument_index, argument in enumerate(called_arguments):
                            # TODO: Probably should find a better solution, the code should not identify an array as a
                            # Todo: ... function even if the name coincides. sbcice_lim and lib_fortran fail without it
                            if argument == ":":
                                continue
                            if is_keyword_argument(argument):
                                keyword = argument.split("=")[0].strip()
                                argument_type = type_of_contents(argument, routine=current_block,
                                                                 vault=self.source_info)
                                dummy_argument = procedure.get_variable_by_name(keyword)
                                if not dummy_argument:
                                    raise AssertionError("Variable not found: %s" % keyword)
                                if argument_type == dummy_argument.type:
                                    continue
                                elif argument_type == "real" and dummy_argument.type == "rpe_var":
                                    if dummy_argument.intent == "in":
                                        temporal_call = add_rpe_to_argument(temporal_call, argument_index)
                                    else:
                                        raise AssertionError("Unknwon intent: %s" % dummy_argument.intent)
                                elif argument_type == "rpe_var" and dummy_argument.type == "real":
                                    if dummy_argument.intent == "in":
                                        temporal_call = add_real_to_argument(temporal_call, argument_index)
                                    elif dummy_argument.intent == "out":
                                        temporal_call = add_val_to_argument(temporal_call, argument_index)
                                    else:
                                        raise AssertionError("Unknwon intent: %s" % dummy_argument.intent)
                            else:
                                argument_type = type_of_contents(argument, routine=current_block,
                                                                 vault=self.source_info)
                                dummy_argument = procedure.get_variable_by_position(argument_index)
                                if not dummy_argument:
                                    raise AssertionError("Variable not found in position: %i" % argument_index)
                                if argument_type == dummy_argument.type:
                                    continue
                                elif argument_type == "real" and dummy_argument.type == "rpe_var":
                                    if dummy_argument.intent == "in":
                                        temporal_call = add_rpe_to_argument(temporal_call, argument_index)
                                    else:
                                        raise AssertionError("Unknwon intent: %s" % dummy_argument.intent)
                                elif argument_type == "rpe_var" and dummy_argument.type == "real":
                                    if dummy_argument.intent == "in":
                                        temporal_call = add_real_to_argument(temporal_call, argument_index)
                                    elif dummy_argument.intent == "out":
                                        temporal_call = add_val_to_argument(temporal_call, argument_index)
                                    elif dummy_argument.intent == "inout":
                                        temporal_call = add_val_to_argument(temporal_call, argument_index)
                                    else:
                                        raise AssertionError("Unknwon intent: %s" % dummy_argument.intent)
                        temporal_line = temporal_line.replace(call, temporal_call)
                    else:
                        temporal_call = call
                        for argument_index, argument in enumerate(called_arguments):
                            # TODO: Probably should find a better solution, the code should not identify an array as a
                            # Todo: ... function even if the name coincides.
                            # if argument == ":":
                            #     continue
                            argument_type = type_of_contents(argument, routine=current_block, vault=self.source_info)
                            if argument_type == "rpe_var":
                                # TODO: Probably I should check first if the argument is a single variable and can be used as an output argument or not
                                temporal_call = add_val_to_argument(temporal_call, argument_index)
                        temporal_line = temporal_line.replace(call, temporal_call)
                self.lines[line_index] = temporal_line
        self.rebuild_text()

    def fix_type_conversion(self):
        current_block = self.module.main()
        for line_index, line in enumerate(self.lines):
            function_calls = calls_to_procedure(line, procedure_type=DerivedType, vault=self.source_info)
            if not function_calls:
                # If not, look if the code is entering or exiting a routine block
                current_block = in_which_block(line, current_block, vault=self.source_info)
            else:
                # Fix function calls (line_index
                temporal_line = line
                for call in function_calls:
                    called_function = find_called_function(call)
                    called_arguments = find_call_arguments(call)
                    if not called_arguments:
                        continue

                    procedure = self.source_info.get_procedure(called_function)
                    if isinstance(procedure, DerivedType):
                        pass
                    else:
                        raise TypeError("The %s procedure does not have the DerivedType type" % procedure.name)

                    temporal_call = call
                    for argument_index, argument in enumerate(called_arguments):
                        # TODO: Probably should find a better solution, the code should not identify an array as a
                        # Todo: ... function even if the name coincides. sbcice_lim and lib_fortran fail without it
                        if argument == ":":
                            continue
                        argument_type = type_of_contents(argument, routine=current_block,
                                                         vault=self.source_info)
                        dummy_argument = procedure.get_variable_by_position(argument_index)
                        if not dummy_argument:
                            raise AssertionError("Variable not found in position: %i" % argument_index)
                        if argument_type == dummy_argument.type:
                            continue
                        elif argument_type == "real" and dummy_argument.type == "rpe_var":
                            temporal_call = add_rpe_to_argument(temporal_call, argument_index)
                        else:
                            raise AssertionError("Types do not coincide and the case is not considered %s %s" % (
                                argument_type, dummy_argument.type))
                    temporal_line = temporal_line.replace(call, temporal_call)
                self.lines[line_index] = temporal_line
        self.rebuild_text()

    def fix_intrinsic_calls(self):
        # todo: this intrinsic calls only need to be corrected if there's a hardcoded real
        # list_of_intrinsics = ["max", "min", "maxloc", "minloc", "cmplx", "sum"]
        list_of_intrinsics = ["max", "min", "cmplx", "sum"]
        for intrinsic in list_of_intrinsics:
            for _index, _line in enumerate(self.lines):
                # This may seem redundant but as we are modifying the lines multiple times
                # we need to be sure that the line is up to date
                _line = self.lines[_index]
                fun = "[ ,(+-](%s *)\(" % intrinsic

                matches = re.finditer(fun, _line, flags=re.I)
                matches = [m for m in matches]

                for _i in range(matches.__len__()):
                    matches = re.finditer(fun, _line, flags=re.I)
                    matches = [m for m in matches]
                    m = matches[_i]

                    start = m.start(0)
                    piece_to_replace = _line[start:]

                    # Bug fix: first char should'nt be a bracket
                    start = piece_to_replace.lower().find(intrinsic)

                    piece_to_replace = piece_to_replace[start:]

                    piece_to_replace = close_brackets(piece_to_replace)
                    # if there's no point (dirty way to look for a hardcoded real) we can skip this transformation
                    # Very ugly workaround, the workaround should be applied only if the arguments are rpe_vars
                    if not piece_to_replace.count("."):
                        continue

                    number_of_arguments, intervals = find_argument_intervals(piece_to_replace)
                    replacement = piece_to_replace
                    if intrinsic == "cmplx":
                        number_of_arguments = 2
                    for __i in range(number_of_arguments):
                        replacement = add_real_to_argument(replacement, __i)
                    _line = _line.replace(piece_to_replace, replacement)

                self.lines[_index] = _line


# Miscellaneous Utils
class Counter:
    def __init__(self):
        self.count = 0

    def up(self, how_much=1):
        self.count += how_much
