from __future__ import annotations
from DataStructure.CounterHashMap import CounterHashMap
from Dictionary.Word import Word

from Corpus.LanguageChecker import LanguageChecker
from Corpus.Paragraph import Paragraph
from Corpus.Sentence import Sentence
from Corpus.SentenceSplitter import SentenceSplitter

import random

class Corpus:

    """
    Constructor of Corpus class which takes a file name as an input. Then reads the input file line by line
    and calls addSentence method with each read line.

    PARAMETERS
    ----------
    fileName : str
        String file name input that will be read.
    """
    def __init__(self, fileName = None):
        self.sentences = []
        self.paragraphs = []
        self.wordList = CounterHashMap()
        if fileName is not None:
            self.fileName = fileName
            file = open(fileName, "r")
            lines = file.readlines()
            for line in lines:
                self.addSentence(Sentence(line))

    """
    Another constructor of Corpus class which takes SentenceSplitter  as an input besides the file name.
    It reads input file line by line and calls the sentenceSplitter method with each line, then calls addSentence method
    with each sentence.

    PARAMETERS
    ----------
    fileName : str        
        String file name input that will be read.
    sentenceSplitter : SentenceSplitter
        SentenceSplitter type input.
    """
    def initWithSentenceSplitter(self, fileName: str, sentenceSplitter: SentenceSplitter):
        self.sentences = []
        self.paragraphs = []
        self.wordList = CounterHashMap()
        self.fileName = fileName
        file = open(fileName, "r")
        lines = file.readlines()
        for line in lines:
            sentences = sentenceSplitter.split(line)
            paragraph = Paragraph()
            for sentence in sentences:
                paragraph.addSentence(sentence)
            self.addParagraph(paragraph)

    """
    Another constructor of Corpus class which also takes languageChecker input besides file name input.
    It reads input file line by line and add each sentence also by using the languageChecker input which simply checks
    the validity of the sentence.

    PARAMETERS
    ----------
    fileName : str       
        String file name input that will be read.
    languageChecker : LanguageChecker
        LanguageChecker type input.
    """
    def initWithLanguageChecker(self, fileName: str, languageChecker: LanguageChecker):
        self.sentences = []
        self.paragraphs = []
        self.wordList = CounterHashMap()
        self.fileName = fileName
        if fileName is not None:
            file = open(fileName, "r")
            lines = file.readlines()
            for line in lines:
                sentence = Sentence()
                self.addSentence(sentence.initWithLanguageChecker(line, languageChecker))

    """
    The combine method takes a Corpus as an input and adds each sentence of sentences list.

    PARAMETERS
    ----------
    corpus : Corpus
        Corpus type input.
    """
    def combine(self, corpus: Corpus):
        for sentence in corpus.sentences:
            self.addSentence(sentence)

    """
    The addSentence method takes a Sentence as an input. It adds given input to sentences list and loops
    through the each word in sentence and puts these words into wordList CounterHashMap.

    PARAMETERS
    ----------
    s : Sentence
        Sentence type input that will be added to sentences list and its words will be added to wordList CounterHashMap.
    """
    def addSentence(self, s: Sentence):
        self.sentences.append(s)
        for i in range(s.wordCount()):
            w = s.getWord(i)
            self.wordList.put(w)

    """
    The numberOfWords method loops through the sentences list and accumulates the number of words in sentence.

    RETURNS
    -------
    int
        size which holds the total number of words.
    """
    def numberOfWords(self) -> int:
        size = 0
        for s in self.sentences:
            size += s.wordCount()
        return size

    """
    The contains method takes a String word as an input and checks whether wordList CounterHashMap has the
    given word and returns true if so, otherwise returns false.

    PARAMETERS
    ----------
    word : str
        String input to check.
        
    RETURNS
    -------
    bool
        True if wordList has the given word, False otherwise.
    """
    def contains(self, word: str) -> bool:
        return Word(word) in self.wordList

    """
    The addParagraph method takes a Paragraph type input. It gets the sentences in the given paragraph and
    add these to the sentences list and the words in the sentences to the wordList CounterHashMap.

    PARAMETERS
    ----------
    p : Paragraph
        Paragraph type input to add sentences and wordList.
    """
    def addParagraph(self, p: Paragraph):
        self.paragraphs.append(p)
        for i in range(p.sentenceCount()):
            self.addSentence(p.getSentence(i))

    """
    Getter for the file name.

    RETURNS
    -------
    str
        file name.
    """
    def getFileName(self) -> str:
        return self.fileName

    """
    Getter for the wordList.

    RETURNS
    -------
    set
        The keySet of wordList.
    """
    def getWordList(self) -> set:
        return self.wordList.keys()

    """
    The wordCount method returns the size of the wordList CounterHashMap.

    RETURNS
    -------
    int
        The size of the wordList CounterHashMap.
    """
    def wordCount(self) -> int:
        return len(self.wordList)

    """
    The getCount method returns the count value of given word.

    PARAMETERS
    ----------
    word : Word
        Word type input to check.
        
    RETURNS
    -------
    int
        The count value of given word.
    """
    def getCount(self, word: Word) -> int:
        return self.wordList[word]

    """
    The sentenceCount method returns the size of the sentences list.

    RETURNS
    -------
    int
        The size of the sentences list.
    """
    def sentenceCount(self) -> int:
        return len(self.sentences)

    """
    Getter for getting a sentence at given index.

    PARAMETERS
    ----------
    index : int
        index to get sentence from.
        
    RETURNS
    -------
    Sentence
        The sentence at given index.
    """
    def getSentence(self, index: int) -> Sentence:
        return self.sentences[index]

    """
    The paragraphCount method returns the size of the paragraphs list.

    RETURNS
    -------
    int
        The size of the paragraphs list.
    """
    def paragraphCount(self) -> int:
        return len(self.paragraphs)

    """
    Getter for getting a paragraph at given index.

    PARAMETERS
    ----------
    index : int
        index to get paragraph from.
        
    RETURNS
    -------
    Paragraph
        The paragraph at given index.
    """
    def getParagraph(self, index: int) -> Paragraph:
        return self.paragraphs[index]

    """
    The maxSentenceLength method finds the sentence with the maximum number of words and returns this number.

    RETURNS
    -------
    int
        maximum length.
    """
    def maxSentenceLength(self) -> int:
        maxLength = 0
        for s in self.sentences:
            if s.wordCount() + 1 > maxLength:
                maxLength = s.wordCount() + 1
        return maxLength

    """
    The getAllWordsAsList method creates new list of lists and adds each word in each sentence of sentences
    list into new list.

    RETURNS
    -------
    list
        Newly created and populated list.
    """
    def getAllWordsAsList(self) -> list:
        allWords = []
        for i in range(self.sentenceCount()):
            allWords.append(self.getSentence(i).getWords())
        return allWords

    """
    The shuffleSentences method randomly shuffles sentences list with given seed value.

    PARAMETERS
    ----------
    seed : int
        value to randomize shuffling.
    """
    def shuffleSentences(self, seed: int):
        random.shuffle(self.sentences, seed)

    """
    The getTrainCorpus method takes two integer inputs foldNo and foldCount for determining train data size and count 
    of fold respectively. Initially creates a new empty Corpus, then finds the sentenceCount as N. Then, starting from 
    the index 0 it loops through the index (foldNo * N) / foldCount and add each sentence of sentences list to new 
    Corpus. Later on, starting from the index ((foldNo + 1) * N) / foldCount, it loops through the index N and add each 
    sentence of sentences list to new Corpus.

    PARAMETERS
    ----------
    foldNo : int   
        Integer input for train set size.
    foldCount : int
        Integer input for counting fold.
        
    RETURNS
    -------
    Corpus
        The newly created and populated Corpus.
    """
    def getTrainCorpus(self, foldNo: int, foldCount: int) -> Corpus:
        trainCorpus = Corpus()
        N = self.sentenceCount()
        for i in range((foldNo * N) // foldCount):
            trainCorpus.addSentence(self.sentences[i])
        for i in range(((foldNo + 1) * N) // foldCount, N):
            trainCorpus.addSentence(self.sentences[i])
        return trainCorpus

    """
    The getTestCorpus method takes two integer inputs foldNo and foldCount for determining test data size and count of
    fold respectively. Initially creates a new empty Corpus, then finds the sentenceCount as N. Then, starting from the 
    index (foldNo * N) / foldCount it loops through the index ((foldNo + 1) * N) / foldCount and add each sentence of 
    sentences list to new Corpus.

    PARAMETERS
    ----------
    foldNo : int   
        Integer input for test size.
    foldCount : int
        Integer input counting fold.
        
    RETURNS
    -------
    Corpus
        The newly created and populated Corpus.
    """
    def getTestCorpus(self, foldNo: int, foldCount: int) -> Corpus:
        testCorpus = Corpus()
        N = self.sentenceCount()
        for i in range((foldNo * N) // foldCount, ((foldNo + 1) * N) // foldCount):
            testCorpus.addSentence(self.sentences[i])
        return testCorpus
