#  Copyright (C)  2023 Rage Uday Kiran
#
#      This program is free software: you can redistribute it and/or modify
#      it under the terms of the GNU General Public License as published by
#      the Free Software Foundation, either version 3 of the License, or
#      (at your option) any later version.
#
#      This program is distributed in the hope that it will be useful,
#      but WITHOUT ANY WARRANTY; without even the implied warranty of
#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#      GNU General Public License for more details.
#
#      You should have received a copy of the GNU General Public License
#      along with this program.  If not, see <https://www.gnu.org/licenses/>.

"""
        sequentialDatabaseStats is to get stats of database like avarage, minimun, maximum  and so on.

        Reference:
        ----------

        ----------
            :param inputFile: file :
               input file path
           :param database: dict :
               store time stamp and its ()separated by subsequence)
           :param seqLengthList: list :
               store size of all sequence
           :param subSeqLengthList: list :
               store size of all subsequence
           :param sep: str
               separator in file. Default is tab space.

        Methods:
        -------
            readDatabase():
                read sequential database from input file and store into database and size of each sequence and subsequences.
            getDatabaseSize(self):
                get the size of database
            getTotalNumberOfItems(self):
                get the number of items in database.
            getMinimumSequenceLength(self):
                get the minimum sequence length
            getAverageSubsequencePerSequenceLength(self):
                get the average subsequence length per sequence length. It is sum of all subsequence length divided by sequence length.
            getAverageItemPerSubsequenceLength(self):
                get the average Item length per subsequence. It is sum of all item length divided by subsequence length.
            getMaximumSequenceLength(self):
                get the maximum sequence length
            getStandardDeviationSubsequenceLength(self):
                get the standard deviation subsequence length
            getVarianceSequenceLength(self):
                get the variance Sequence length
            getSequenceSize(self):
                get the size of sequence
            getMinimumSubsequenceLength(self):
                get the minimum subsequence length
            getAverageItemPerSequenceLength(self):
                get the average item length per sequence. It is sum of all item length divided by sequence length.
            getMaximumSubsequenceLength(self):
                get the maximum subsequence length
            getStandardDeviationSubsequenceLength(self):
                get the standard deviation subsequence length
            getVarianceSubsequenceLength(self):
                get the variance subSequence length
            getSortedListOfItemFrequencies(self):
                get sorted list of item frequencies
            getFrequenciesInRange(self):
                get sorted list of item frequencies in some range
            getSequencialLengthDistribution(self):
                get Sequence length Distribution
            getSubsequencialLengthDistribution(self):
                get subSequence length distribution
            printStats(self):
                to print the all status of sequence database
            plotGraphs(self):
                to plot the  distribution about items, subsequences in sequence and items in subsequence

        Executing the code on terminal:
        -------------------------------
            Format:
            ------
                python3 sequentialDatabaseStats.py <inputFile>
            Examples:
            ---------
                python3 sequentialDatabaseStats.py sampleDB.txt
                python3 sequentialDatabaseStats.py sampleDB.txt
        Sample run of the importing code:
        ---------------------------------
            import PAMI.extra.DBstats.sequentialDatabaseStats as alg
            _ap=alg.sequentialDatabaseStats(inputfile,sep)
            _ap.readDatabase()
            _ap.printStats()
            _ap.plotGraphs()
        Credits:
        --------
            The complete program was written by Shota Suzuki  under the supervision of Professor Rage Uday Kiran.
"""

import statistics
import validators
from urllib.request import urlopen
import PAMI.extras.graph.plotLineGraphFromDictionary as plt
import sys
class sequentialDatabaseStats():
    """
           :Description:  sequentialDatabaseStats is class to get stats of database.


           :param inputFile: file :
               input file path
           :param database: dict :
               store time stamp and its ()separated by subsequence)
           :param seqLengthList: list :
               store size of all sequence
           :param subSeqLengthList: list :
               store size of all subsequence
           :param sep: str
               separator in file. Default is tab space.

              """

    def __init__(self, inputFile, sep='\t'):
        """
        :param inputFile: input file name or path
        :type inputFile: str
        """
        self.inputFile = inputFile
        self.seqLengthList = []
        self.subSeqLengthList = []
        self.sep = sep
        self.database = {}

    def readDatabase(self):
        """
                read sequential database from input file and store into database and size of each sequence and subsequences.
        """
        if isinstance(self.inputFile, str):
            if validators.url(self.inputFile):
                data = urlopen(self.inputFile)
                rowNum=0
                for line in data:
                    line.strip()
                    temp = [i.rstrip() for i in line.split('-1')]
                    temp = [x for x in temp if x]
                    temp.pop()
                    seq = []
                    self.seqLengthList.append(len(temp))
                    self.subSeqLengthList.append([len(i) for i in temp])
                    for i in temp:
                        if len(i) > 1:
                            tempSorted=list(sorted(set(i.split())))
                            seq.append(tempSorted)
                        else:
                            seq.append(i)
                    rowNum+=1
                    if seq:
                        self.database[rowNum]=seq
            else:
                with open(self.inputFile, 'r') as f:
                    rowNum = 0
                    for line in f:
                        temp = [i.rstrip(self.sep) for i in line.split('-1')]
                        temp = [x for x in temp if x]
                        temp.pop()
                        seq = []
                        self.seqLengthList.append(len(temp))
                        subseq=[]
                        for i in temp:
                            if len(i) > 1:
                                tempSorted = list(sorted(set(i.split())))
                                subseq.append(len(tempSorted))
                                seq.append(tempSorted)
                            else:
                                seq.append(i)
                                subseq.append(len(i))
                        if subseq!=[]:
                            self.subSeqLengthList.append(subseq)
                        rowNum += 1
                        if seq:
                            self.database[rowNum] = seq


    def getDatabaseSize(self):
        """
        get the size of database
        :return: database size
        """
        return len(self.database)

    def getTotalNumberOfItems(self):
        """
        get the number of items in database.
        :return: number of items
        """
        return len(self.getSortedListOfItemFrequencies())

    def getMinimumSequenceLength(self):
        """
        get the minimum sequence length
        :return: minimum sequence length
        """
        return min(self.seqLengthList)

    def getAverageSubsequencePerSequenceLength(self):
        """
        get the average subsequence length per sequence length. It is sum of all subsequence length divided by sequence length.
        :return: average subsequence length per sequence length
        """
        totalLength = sum(self.seqLengthList)
        return totalLength / len(self.database)

    def getAverageItemPerSubsequenceLength(self):

        """
        get the average Item length per subsequence. It is sum of all item length divided by subsequence length.
        :return: average Item length per subsequence
        """

        totalLength = sum(list(map(sum,self.subSeqLengthList)))
        return totalLength / sum(self.seqLengthList)

    def getMaximumSequenceLength(self):
        """
        get the maximum sequence length
        :return: maximum sequence length
        """
        return max(self.seqLengthList)

    def getStandardDeviationSequenceLength(self):
        """
        get the standard deviation sequence length
        :return: standard deviation sequence length
        """
        return statistics.pstdev(self.seqLengthList)

    def getVarianceSequenceLength(self):
        """
        get the variance Sequence length
        :return: variance Sequence length
        """
        return statistics.variance(self.seqLengthList)

    def getSequenceSize(self):
        """
        get the size of sequence
        :return: sequence size
        """
        return sum(self.seqLengthList)

    def getMinimumSubsequenceLength(self):
        """
        get the minimum subsequence length
        :return: minimum subsequence length
        """
        return min(list(map(min,self.subSeqLengthList)))

    def getAverageItemPerSequenceLength(self):
        """
        get the average item length per sequence. It is sum of all item length divided by sequence length.
        :return: average item length per sequence
        """
        totalLength = sum(list(map(sum,self.subSeqLengthList)))
        return totalLength / len(self.database)

    def getMaximumSubsequenceLength(self):
        """
        get the maximum subsequence length
        :return: maximum subsequence length
        """
        return max(list(map(max,self.subSeqLengthList)))

    def getStandardDeviationSubsequenceLength(self):
        """
        get the standard deviation subsequence length
        :return: standard deviation subsequence length
        """
        allList=[]
        for i in self.subSeqLengthList:
            allList=allList+i
        return statistics.pstdev(allList)

    def getVarianceSubsequenceLength(self):
        """
        get the variance subSequence length
        :return: variance subSequence length
        """
        allList = []
        for i in self.subSeqLengthList:
            allList = allList + i
        return statistics.variance(allList)

    def getSortedListOfItemFrequencies(self):
        """
        get sorted list of item frequencies
        :return: item frequencies
        """
        itemFrequencies = {}
        for seq in self.database:
            for sub in self.database[seq]:
                for item in sub:
                    itemFrequencies[item] = itemFrequencies.get(item, 0)
                    itemFrequencies[item] += 1
        return {k: v for k, v in sorted(itemFrequencies.items(), key=lambda x: x[1], reverse=True)}

    def getFrequenciesInRange(self):
        """
                get sorted list of item frequencies in some range
                :return: item separated by its frequencies
        """
        fre = self.getSortedListOfItemFrequencies()
        rangeFrequencies = {}
        maximum = max([i for i in fre.values()])
        values = [int(i * maximum / 6) for i in range(1, 6)]
        va = len({key: val for key, val in fre.items() if val > 0 and val < values[0]})
        rangeFrequencies[values[0]] = va
        for i in range(1, len(values)):
            va = len({key: val for key, val in fre.items() if val < values[i] and val > values[i - 1]})
            rangeFrequencies[values[i]] = va
        return rangeFrequencies

    def getSequencialLengthDistribution(self):
        """
        get Sequence length Distribution
        :return: Sequence length
        """
        transactionLength = {}
        for length in self.seqLengthList:
            transactionLength[length] = transactionLength.get(length, 0)
            transactionLength[length] += 1
        return {k: v for k, v in sorted(transactionLength.items(), key=lambda x: x[0])}

    def getSubsequencialLengthDistribution(self):
        """
        get subSequence length distribution
        :return: subSequence length
        """
        transactionLength = {}
        for sublen in self.subSeqLengthList:
            for length in sublen:
                transactionLength[length] = transactionLength.get(length, 0)
                transactionLength[length] += 1
        return {k: v for k, v in sorted(transactionLength.items(), key=lambda x: x[0])}



    def printStats(self):
        """
        to print the all status of sequence database
        Returns:

        """
        print(f'Database size (total no of sequence) : {self.getDatabaseSize()}')
        print(f'Number of items : {self.getTotalNumberOfItems()}')
        print(f'Minimum Sequence Size : {self.getMinimumSequenceLength()}')
        print(f'Average Sequence Size : {self.getAverageSubsequencePerSequenceLength()}')
        print(f'Maximum Sequence Size : {self.getMaximumSequenceLength()}')
        print(f'Standard Deviation Sequence Size : {self.getStandardDeviationSequenceLength()}')
        print(f'Variance in Sequence Sizes : {self.getVarianceSequenceLength()}')
        print(f'Sequence size (total no of subsequence) : {self.getSequenceSize()}')
        print(f'Minimum subSequence Size : {self.getMinimumSubsequenceLength()}')
        print(f'Average subSequence Size : {self.getAverageItemPerSubsequenceLength()}')
        print(f'Maximum subSequence Size : {self.getMaximumSubsequenceLength()}')
        print(f'Standard Deviation Sequence Size : {self.getStandardDeviationSubsequenceLength()}')
        print(f'Variance in Sequence Sizes : {self.getVarianceSubsequenceLength()}')

    def plotGraphs(self):
        """
        to plot the  distribution about items, subsequences in sequence and items in subsequence
        Returns:

        """
        itemFrequencies = self.getFrequenciesInRange()
        seqLen = self.getSequencialLengthDistribution()
        subLen=self.getSubsequencialLengthDistribution()
        plt.plotLineGraphFromDictionary(itemFrequencies, 100, 'Frequency', 'No of items', 'frequency')
        plt.plotLineGraphFromDictionary(seqLen, 100, 'sequence length', 'sequence length', 'frequency')
        plt.plotLineGraphFromDictionary(subLen, 100, 'subsequence length', 'subsequence length', 'frequency')

if __name__ == '__main__':
    _ap=str()
    if len(sys.argv)==3 or len(sys.argv)==2:
        if len(sys.argv)==3:
            _ap=sequentialDatabaseStats(sys.argv[1],sys.argv[2])
        if len(sys.argv) == 2:
            _ap = sequentialDatabaseStats(sys.argv[1])
        _ap.readDatabase()
        _ap.printStats()
        _ap.plotGraphs()
    else:
        print("Error! The number of input parameters do not match the total number of parameters provided")
