import rnftools.rnfformat
import re

reg_lrn = re.compile(r"^([!-?A-^`-~]*)__([0-9a-f]+)__([!-?A-^`-~]+)__([!-?A-^`-~]*)$")
reg_prefix_part = re.compile(r"^[!-?A-^`-~]*$")
reg_id_part = re.compile(r"^[0-9a-f]+$")
reg_segmental_part = re.compile(r"^(?:(\([0-9FRN,]*\))(?:,(?!$)|$))+$")
reg_suffix_part = re.compile(r"^(?:((?:[a-zA-Z0-9]+:){0,1})\[([!-?A-Z\\^`-~]*)\](?:,(?!$)|$))+$")
reg_segment = re.compile(r"^\(([0-9]+),([0-9]+),([FRN]),([0-9]+),([0-9]+)\)$")
reg_comment = re.compile(r"^\[([!-?A-Z\\^`-~]*)\]$")
reg_extension = re.compile(r"^\[([!-?A-Z\\^`-~]*)\]$")


class Validator:
    """Class for validation of RNF.

	Args:
		initial_read_tuple_name (str): Initial read tuple name to detect profile (widths).
		report_only_first (bool): Report only first occurrence of every error.
		warnings_as_errors (bool): Treat warnings as errors (error code).
	"""

    def __init__(
        self,
        initial_read_tuple_name,
        report_only_first=True,
        warnings_as_errors=False,
    ):
        self.report_only_first = report_only_first
        self.reported_errors = set()
        self.error_has_been_reported = False
        self.warning_has_been_reported = False
        self.warnings_as_errors = warnings_as_errors

        self.rnf_profile = rnftools.rnfformat.RnfProfile(read_tuple_name=initial_read_tuple_name)

    def validate(self, read_tuple_name):
        """Check RNF validity of a read tuple.

		Args:
			read_tuple_name (str): Read tuple name to be checked.s
		"""
        if reg_lrn.match(read_tuple_name) is None:
            self.report_error(
                read_tuple_name=read_tuple_name,
                error_name="wrong_read_tuple_name_structure",
                message="'{}' is not matched".format(reg_lrn),
            )
        else:
            parts = read_tuple_name.split("__")

            if reg_prefix_part.match(parts[0]) is None:
                self.report_error(
                    read_tuple_name=read_tuple_name,
                    error_name="wrong_prefix_part",
                    message="'{}' is not matched".format(reg_prefix_part),
                )

            if reg_id_part.match(parts[1]) is None:
                self.report_error(
                    read_tuple_name=read_tuple_name,
                    error_name="wrong_id_part",
                    message="'{}' is not matched".format(reg_id_part),
                )

            if reg_segmental_part.match(parts[2]) is None:
                self.report_error(
                    read_tuple_name=read_tuple_name,
                    error_name="wrong_segmental_part",
                    message="'{}' is not matched".format(reg_segmental_part),
                )

            if reg_suffix_part.match(parts[3]) is None:
                self.report_error(
                    read_tuple_name=read_tuple_name,
                    error_name="wrong_suffix_part",
                    message="'{}' is not matched".format(reg_suffix_part),
                )

            if not self.rnf_profile.check(read_tuple_name):
                self.report_error(
                    read_tuple_name=read_tuple_name,
                    error_name="wrong_profile",
                    message="Read has a wrong profile (wrong widths). It should be: {} but it is: {}.".format(
                        self.rnf_profile,
                        rnftools.rnfformat.RnfProfile(read_tuple_name=read_tuple_name),
                    ),
                    warning=True,
                )

    def get_return_code(self):
        """Get final return code (0 = ok, 1=error appeared).
		"""
        if self.error_has_been_reported:
            return 1
        if self.warning_has_been_reported and self.warnings_as_errors:
            return 1

    def report_error(self, read_tuple_name, error_name, wrong="", message="", warning=False):
        """Report an error.

		Args:
			read_tuple_name (): Name of the read tuple.
			error_name (): Name of the error.
			wrong (str): What is wrong. 
			message (str): Additional msessage to be printed.
			warning (bool): Warning (not an error).
		"""
        if (not self.report_only_first) or (error_name not in self.reported_errors):
            print("\t".join(["error" if warning == False else "warning", read_tuple_name, error_name, wrong, message]))
        self.reported_errors.add(error_name)
        if warning:
            self.warning_has_been_reported = True
        else:
            self.error_has_been_reported = True
