import csv


class Writer:
    """
    This class saves Numpy array-of-arrays data to various output formats:

    * CSV
    * JSON
    * Tiled JSON
    """

    def __init__(self, data, identifier):
        self.data = data
        self.id = identifier
        self.residues1 = []
        self.residues2 = []
        self.distances = []

    def __repr__(self):
        return f"{type(self).__name__}(data={type(self.data)}, identifier={self.id})"

    def save_to_csv(self):
        """
        Write out the contents of the data into a CSV file

        :return: None
        """
        csv_output = open("%s_distogram.csv" % self.id, "w", newline='')
        writer = csv.writer(csv_output)
        # These are the expected headers
        writer.writerow(["residue1", "residue2", "distance"])
        n = len(self.data)
        for i in range(n):
            for j in range(n):
                self.conditional_write(i, j, writer)
        csv_output.close()

    def conditional_write(self, i, j, writer):
        """
        Only write out distance values <21
        This is because the interactive data visualisation would break with too many data points
        Luckily, values >21 are meaningless in terms of usability and so can be safely removed

        :param i: Number
        :param j: Number
        :param writer: CSV writer
        :return: None
        """
        if self.data[i][j] < 21:
            writer.writerow([i + 1, j + 1, "%.2f" % self.data[i][j]])

    def save_to_tiled_json(self):
        """
        Write out tiled JSON data into the expected JSON format

        Note: This requires the self.data to be a filed data which is
        generated by loader.create_tiled_data()
        :return: None
        """
        with open("%s_distogram_tiled.json" % self.id, "w") as json_output:
            json_output.write('[')
            num_of_levels = len(self.data)
            for m in range(num_of_levels):
                self.save_sub_tile(json_output, m, num_of_levels)
                self.reset_arrays()
            json_output.write(']')

    def reset_arrays(self):
        """
        Reset the arrays to empty

        :return: None
        """
        self.residues1 = []
        self.residues2 = []
        self.distances = []

    def save_sub_tile(self, json_output, m, num_of_levels):
        """
        Save an individual data tile from a tiled data array.

        :param json_output: Output file
        :param m: Number
        :param num_of_levels: Number
        :return:
        """
        json_output.write('{')
        # The sub-matrixes have n x n dimensions
        n = len(self.data[m])
        # values = []
        for i in range(n):
            for j in range(n):
                self.conditional_append(i, j, m)
        json_output.write('"residue1": [%s],' % ",".join(self.residues1))
        json_output.write('"residue2": [%s],' % ",".join(self.residues2))
        json_output.write('"distance": [%s]' % ",".join(self.distances))
        json_output.write('}')
        if m < num_of_levels - 1:
            json_output.write(',')

    def conditional_append(self, i, j, m):
        """
        Only write out distance values <21
        This is because the interactive data visualisation would break with too many data points
        Luckily, values >21 are meaningless in terms of usability and so can be safely removed

        :param i: Number
        :param j: Number
        :param m: Number
        :return: None
        """
        if self.data[m][i][j] < 21 and i >= j:
            self.residues1.append(str(i + 1))
            self.residues2.append(str(j + 1))
            self.distances.append(str(self.data[m][i][j]))

    def save_to_json(self):
        """
        Write out the contents of the data into a JSON file after filtering
        the data

        :return: None
        """
        self.data = [self.data]
        with open("%s_distogram.json" % self.id, "w") as json_output:
            json_output.write('[')
            self.save_sub_tile(json_output, 0, 1)
            json_output.write(']')

    @staticmethod
    def turn_to_zero(value):
        """
        Simple method for filtering data
        :param value: Number
        :return: Number
        """
        if value >= 21:
            return 0
        return value

import csv


class Writer:
    """
    This class saves Numpy array-of-arrays data to various output formats:

    * CSV
    * JSON
    * Tiled JSON
    """

    def __init__(self, data, identifier):
        self.data = data
        self.id = identifier
        self.residues1 = []
        self.residues2 = []
        self.distances = []

    def __repr__(self):
        return f"{type(self).__name__}(data={type(self.data)}, identifier={self.id})"

    def save_to_csv(self):
        """
        Write out the contents of the data into a CSV file

        :return: None
        """
        csv_output = open("%s_distogram.csv" % self.id, "w", newline='')
        writer = csv.writer(csv_output)
        # These are the expected headers
        writer.writerow(["residue1", "residue2", "distance"])
        n = len(self.data)
        for i in range(n):
            for j in range(n):
                self.conditional_write(i, j, writer)
        csv_output.close()

    def conditional_write(self, i, j, writer):
        """
        Only write out distance values <21
        This is because the interactive data visualisation would break with too many data points
        Luckily, values >21 are meaningless in terms of usability and so can be safely removed

        :param i: Number
        :param j: Number
        :param writer: CSV writer
        :return: None
        """
        if self.data[i][j] < 21:
            writer.writerow([i + 1, j + 1, "%.2f" % self.data[i][j]])

    def save_to_tiled_json(self):
        """
        Write out tiled JSON data into the expected JSON format

        Note: This requires the self.data to be a filed data which is
        generated by loader.create_tiled_data()
        :return: None
        """
        with open("%s_distogram_tiled.json" % self.id, "w") as json_output:
            json_output.write('[')
            num_of_levels = len(self.data)
            for m in range(num_of_levels):
                self.save_sub_tile(json_output, m, num_of_levels)
                self.reset_arrays()
            json_output.write(']')

    def reset_arrays(self):
        """
        Reset the arrays to empty

        :return: None
        """
        self.residues1 = []
        self.residues2 = []
        self.distances = []

    def save_sub_tile(self, json_output, m, num_of_levels):
        """
        Save an individual data tile from a tiled data array.

        :param json_output: Output file
        :param m: Number
        :param num_of_levels: Number
        :return:
        """
        json_output.write('{')
        # The sub-matrixes have n x n dimensions
        n = len(self.data[m])
        # values = []
        for i in range(n):
            for j in range(n):
                self.conditional_append(i, j, m)
        json_output.write('"residue1": [%s],' % ",".join(self.residues1))
        json_output.write('"residue2": [%s],' % ",".join(self.residues2))
        json_output.write('"distance": [%s]' % ",".join(self.distances))
        json_output.write('}')
        if m < num_of_levels - 1:
            json_output.write(',')

    def conditional_append(self, i, j, m):
        """
        Only write out distance values <21
        This is because the interactive data visualisation would break with too many data points
        Luckily, values >21 are meaningless in terms of usability and so can be safely removed

        :param i: Number
        :param j: Number
        :param m: Number
        :return: None
        """
        if self.data[m][i][j] < 21 and i >= j:
            self.residues1.append(str(i + 1))
            self.residues2.append(str(j + 1))
            self.distances.append(str(self.data[m][i][j]))

    def save_to_json(self):
        """
        Write out the contents of the data into a JSON file after filtering
        the data

        :return: None
        """
        self.data = [self.data]
        with open("%s_distogram.json" % self.id, "w") as json_output:
            json_output.write('[')
            self.save_sub_tile(json_output, 0, 1)
            json_output.write(']')

    @staticmethod
    def turn_to_zero(value):
        """
        Simple method for filtering data
        :param value: Number
        :return: Number
        """
        if value >= 21:
            return 0
        return value

This class saves Numpy array-of-arrays data to various output formats:      * CSV     * JSON     * Tiled JSON Write out the contents of the data into a CSV file          :return: None # These are the expected headers Only write out distance values <21         This is because the interactive data visualisation would break with too many data points         Luckily, values >21 are meaningless in terms of usability and so can be safely removed          :param i: Number         :param j: Number         :param writer: CSV writer         :return: None Write out tiled JSON data into the expected JSON format          Note: This requires the self.data to be a filed data which is         generated by loader.create_tiled_data()         :return: None Reset the arrays to empty          :return: None Save an individual data tile from a tiled data array.          :param json_output: Output file         :param m: Number         :param num_of_levels: Number         :return: # The sub-matrixes have n x n dimensions # values = [] Only write out distance values <21         This is because the interactive data visualisation would break with too many data points         Luckily, values >21 are meaningless in terms of usability and so can be safely removed          :param i: Number         :param j: Number         :param m: Number         :return: None Write out the contents of the data into a JSON file after filtering         the data          :return: None Simple method for filtering data         :param value: Number         :return: Number