#!python

import sys
import subprocess
import os


class PackageInstaller:

    def install_required_packages(self):
        print(">>> Installing Dependencies...\n\n")

        # install curses
        self.__install('windows-curses')

        # clear screen and inform that the process is finished
        self.__clear_screen()
        print("--- INSTALLATIONS FINISHED ---")
        

    def __install(self, package: str):
        # install package
        subprocess.check_call([sys.executable, "-m", "pip", "install", package])


    def __clear_screen(self):
        if os.name == 'nt':
            os.system('cls')
        else:
            os.system('clear')


try:
    # check if curses is installed or not
    import curses
    from curses import wrapper
except:
    # if not yet install, the automatically install the module
    PackageInstaller().install_required_packages()
    import curses
    from curses import wrapper

from typing import Tuple, List
import time
import random




# the code for curses color pairs
ERROR_CODE = 1
CORRECT_CODE = 2
HIGHLIGHT_CODE = 3
PROMPT_CODE = 4


# the sentences that can be used for the users to type
SENTENCES = [
    "Babies have around a hundred more bones than adults",
    "The Eiffel Tower can be fifteen centimeters taller during the summer",
    "twenty percent of oxygen on Earth is produced by the Amazon rainforest",
    "Some metals are so reactive that they explode on contact with water",
    "A teaspoonful of neutron star would weigh six billion tons",
    "Hawaii moves seven point five centimeters closer to Alaska every year",
    "Chalk is made from trillions of microscopic plankton fossils",
    "In 2.3 billion years it will be too hot for life to exist on Earth",
    "Polar bears are nearly undetectable by infrared cameras",
    "It takes about 8 minutes, 19 seconds for light to travel from the Sun to the Earth",
    "Stomach acid is strong enough to dissolve stainless steel",
    "The Earth is a giant magnet",
    "Venus is the only planet to spin clockwise",
    "A flea can accelerate faster than the Space Shuttle",
    "The average human body carries ten times more bacterial cells than human cells",
    "There are actually over two dozen states of matter (that we know of)",
    "Killer whales are actually dolphins",
    "Grasshoppers have ears in their bellies",
    "Humans cannot taste food without saliva",
    "Octopuses have three hearts, nine brains, and blue blood",
    "An individual blood cell takes about 60 seconds to make a complete circuit of the body",
    "The known universe is made up of fifty billion galaxies",
    "Water can boil and freeze at the same time",
    "Lasers can get trapped in a waterfall",
    "We have spacecraft hurtling towards the edge of our Solar System really, really fast",
    "An egg looks like a crazy jellyfish underwater",
    "Female sharks have thicker skins than males",
    "The ocean is eight Empire State Buildings deep",
    "Your hand has a built-in snuff box",
    "Pompeii plumbing was ahead of its time",
    "Some tornadoes can be faster than Formula One race cars",
    "There are 2,000 thunderstorms on Earth every minute",
    "The wind is silent until it blows against something",
    "There are ice caves in Iceland that have hot springs",
    "The fastest recorded raindrop was 18 mph",
    "The US gets over 1200 tornadoes a year",
    "Lightning can, in fact, strike twice",
    "Clouds look white because they are reflecting sunlight from above them",
    "Rain contains vitamin B12",
    "A bolt of lightning is five times hotter than the sun",
    "A hurricane releases enough energy in one second to equal that of 10 atomic bombs",
    "It can be too warm to snow but never too cold",
    "One teaspoon of a neutron star would weigh six-billion tons",
    "Sally Ride was the first American woman to fly in space, on June 18, 1983",
    "One million Earths could fit inside the sun",
    "Even in an airplane, a trip to Pluto would take about 800 years",
    "Ham the Astrochimp was the first hominid in space, launched on January 31st, 1961",
    "Days on Neptunes are 16 hours long",
    "It takes eight minutes and 19 seconds for light to travel from the sun to Earth",
    "The footprints on the moon will be there for 100 million years",
    "A neutron star can spin 600 times in one second",
    "Sound does not carry in space",
    "The core of the Earth is as hot as the surface of the sun",
    "Every year over one million earthquakes shake the Earth",
    "The largest ever hailstone weighed over 1kg and fell in Bangladesh in 1986",
    "Every year lightning kills 1000 people",
    "In 10/1999, an Iceberg the size of London broke free from the Antarctic ice shelf",
    "If you could drive your car straight up you would arrive in space in just over an hour",
    "Human tapeworms can grow up to 22.9m",
    "The Earth is 4.56 billion years old, the same age as the Moon and the Sun",
    "The dinosaurs became extinct before the Rockies or the Alps were formed",
    "Female black widow spiders eat their males after mating",
    "If our Sun were just inch in diameter, the nearest star would be 445 miles away",
    "The Australian billygoat plum contains 100 times more vitamin C than an orange",
    "DNA was first discovered in 1869 by Swiss Friedrich Miescher",
    "The molecular structure of DNA was first determined by Watson and Crick in 1953",
    "The first synthetic human chromosome was constructed by US scientists in 1997",
    "The thermometer was invented in 1607 by Galileo",
    "Englishman Roger Bacon invented the magnifying glass in 1250",
    "Alfred Nobel invented dynamite in 1866",
    "Wilhelm Rontgen won the first Nobel Prize for physics for discovering X-rays in 1895",
    "An electric eel can produce a shock of up to 650 volts",
    "The earliest wine makers lived in Egypt around 2300 BC",
    "The Ebola virus kills 4 out of every 5 humans it infects",
    "In 5 billion years the Sun will run out of fuel and turn into a Red Giant",
    "Without its lining of mucus your stomach would digest itself",
    "Humans have 46 chromosomes, peas have 14 and crayfish have 200",
    "There are 60,000 miles of blood vessels in the human body",
    "Utopia is a large, smooth lying area of Mars",
    "Each person sheds 40lbs of skin in his or her lifetime",
    "At 15 inches the eyes of giant squids are the largest on the planet",
    "The largest galaxies contain a million, million stars",
    "The Universe contains over 100 billion galaxies",
    "More germs are transferred shaking hands than kissing",
    "The longest glacier in Antarctica is 250 miles long and 40 miles wide",
    "A healthy person has 6,000 million, million, million haemoglobin molecules",
    "The grey whale migrates 12,500 miles from the Artic to Mexico and back every year",
    "Each rubber molecule is made of 65,000 individual atoms",
    "Quasars emit more energy than 100 giant galaxies",
    "Light would take 0.13 seconds to travel around the Earth",
    "Males produce one thousand sperm cells each second",
    "One in every 2000 babies is born with a tooth",
    "Every hour the Universe expands by a billion miles in all directions",
    "The temperature in Antarctica plummets as low as -35 degrees Celsius",
    "A thimbleful of a neutron star would weigh over 100 million tons",
    "The African Elephant gestates for 22 months",
    "The short-nosed Bandicoot has a gestation period of only 12 days",
    "The mortality rate if bitten by a Black Mamba snake is over 95%",
    "A dog's sense of smell is 1,000 times more sensitive than a humans",
    "A typical hurricane produces the energy equivalent to 8,000 one megaton bombs",
    "90% of those who die from hurricanes die from drowning",
    "To escape the Earth's gravity a rocket need to travel at 7 miles a second",
    "Microbial life can survive on the cooling rods of a nuclear reactor",
    "The human stomach can actually dissolve razor blades",
    "A laser can get trapped in water",
    "Earth's oxygen is mostly produced by the ocean",
    "Animals use Earth's magnetic field for orientation",
    "A cloud can weigh around a million pounds",
    "Rats can laugh when they're tickled",
    "Bananas are radioactive since they contain potassium",
    "There are more trees on Earth than stars in our galaxy",
    "It can rain diamonds on other planets like Neptune, Uranus, and Saturn",
    "There were roughly 2.5 billion T. rexes on Earth, but not all at the same time",
    "Water can exist in three states at once",
    "Helium can work against gravity",
    "About half of your body is bacteria",
    "We have no idea what most of the universe looks like",
    "Bats don't get sick from most viruses",
    "Beer is twice as fizzy as champagne",
    "There are also earthquakes (or moonquakes) on Moon",
]




def typing_test(stdscr) -> None:
    ''' 
    main function
    '''
    init_color(stdscr)
    stdscr.clear()
    curses.noecho()

    num_of_sentences = prompt_input(stdscr)
    sentences = get_random_sentences(num_of_sentences)


    ''' start the typing test '''

    start_time = time.time()
    total_words = 0
    words_typed = 0
    wrong_words = 0
    wpm = 0

    curses.noecho()


    for i in range(len(sentences)):
        user_sentence = ""
        sentence = sentences[i]
        total_words += len(sentence.split())

        centralize_text(stdscr, sentence)

        stdscr.clear()
        stdscr.refresh()

        while True:

            # calculate the current wpm
            time_passed = (time.time() - start_time) / 60
            if time_passed > 0:
                wpm = (len(user_sentence.split()) + words_typed - wrong_words) // time_passed

            # show the remaining sentences and the current wpm
            stdscr.move(0, 0)
            stdscr.addstr(f"Remaining Sentences: ")
            stdscr.addstr(f"{len(sentences) - i - 1}\n", curses.color_pair(HIGHLIGHT_CODE))
            stdscr.addstr(f"Words per Minute: ")
            stdscr.addstr(f"{wpm:.0f}\n", curses.color_pair(HIGHLIGHT_CODE))
            
            # print the target sentence
            x, y = centralize_text(stdscr, sentence)

            # print the user sentence on top of the target sentence
            stdscr.move(x, y)
            for j in range(len(user_sentence)):

                color = curses.color_pair(CORRECT_CODE)
                if j >= len(sentence) or user_sentence[j] != sentence[j]:
                    color = curses.color_pair(ERROR_CODE)

                stdscr.addstr(user_sentence[j], color)

            # get the user key entered
            ch = stdscr.getch()

            if ch == 10 or ch == curses.KEY_ENTER:      # if enter/newline
                break
            elif ch == 27 or ch == curses.KEY_EXIT:     # if escape
                exit(0)
            elif ch == curses.KEY_BACKSPACE:            # if backspace
                user_sentence = user_sentence[:-1]
            elif 32 <= ch <= 126:                       # if text
                user_sentence += chr(ch)

        
        user_sentence = user_sentence.split()
        words_typed += len(user_sentence)
        target = sentence.split()

        # find the number of wrong words
        for j in range(len(user_sentence)):
            if j < len(target) and user_sentence[j] != target[j]:
                wrong_words += 1
            elif j >= len(target):
                wrong_words += 1
                

    ''' end the typing test and calculate and output the result '''

    duration = time.time() - start_time
    accuracy = (total_words - wrong_words) / total_words
    wpm = total_words // (duration / 60)
    wpm *= accuracy

    stdscr.clear()
    stdscr.addstr("RESULT:\n\n", curses.color_pair(HIGHLIGHT_CODE))
    stdscr.addstr(f"Total Words:\t")
    stdscr.addstr(f"{total_words:.0f}\n", curses.color_pair(HIGHLIGHT_CODE))
    stdscr.addstr(f"Total Errors:\t")
    stdscr.addstr(f"{wrong_words:.0f}\n", curses.color_pair(HIGHLIGHT_CODE))
    stdscr.addstr(f"Accuracy:\t")
    stdscr.addstr(f"{accuracy*100:.2f}%\n", curses.color_pair(HIGHLIGHT_CODE))
    stdscr.addstr(f"Final WPM:\t")
    stdscr.addstr(f"{wpm:.0f}\n\n", curses.color_pair(HIGHLIGHT_CODE))
    
    # prompt the user if they want to replay
    prompt_replay(stdscr)






def centralize_text(stdscr, text:str, color:int=0) -> Tuple[int,int]:
    '''
    put a line of text in the middle of the curse
    args:
        stdscr: the standard screen
        text:   the text to be displayed
    return:
        the coordinate of the text on the curse
    '''
    scrh, scrw = stdscr.getmaxyx()

    x = scrh // 2
    y = (scrw // 2 - len(text) // 2) if (len(text) < scrw) else 0

    stdscr.addstr(x, y, text, color)
    return (x, y)






def prompt_input(stdscr) -> int:
    '''
    prompt the user for the number of sentences to be typed
    args:
        stdscr: the standard screen
    return:
        number of sentences to type
    '''
    MIN = 1
    MAX = 50

    usr_input = ""
    while True:
        stdscr.addstr(0, 0, f"Please enter the number of sentences to type ({MIN}-{MAX}):\n",
                      curses.color_pair(PROMPT_CODE))
        stdscr.move(1, len(usr_input))
        c = stdscr.getch()

        if c == 10:                             # if enter/newline
            break
        elif c == 27 or c == curses.KEY_EXIT:   # if escape
            exit(0)
        elif c == curses.KEY_BACKSPACE:         # if backspace
            usr_input = usr_input[:-1]
        elif 48 <= c <= 57:                     # if number
            usr_input += chr(c)
            # input must not exceed 50
            if int(usr_input) > 50:
                usr_input = usr_input[:-1]

        stdscr.clear()
        stdscr.addstr(1, 0, usr_input)

    return int(usr_input)






def prompt_replay(stdscr) -> None:
    '''
    prompt if user wants to replay, if not then quit immediately
    '''
    stdscr.addstr(f"Do you want to replay? (Y/N) ", curses.color_pair(PROMPT_CODE))
    while True:
        response = stdscr.getkey()
        if response == "Y" or response == "y":
            return
        elif response == "N" or response == "n":
            exit(0)
    





def init_color(stdscr) -> None:
    '''
    initialize the color pairs that will be used throughout the application
    '''
    stdscr.clear()
    if not curses.has_colors():
        stdscr.addstr("Warning!!! Your terminal does not support color!\n")
        stdscr.addstr("Press any key to continue...")
        stdscr.getch()
        return
    curses.start_color()
    curses.init_pair(ERROR_CODE, curses.COLOR_WHITE, curses.COLOR_RED)
    curses.init_pair(CORRECT_CODE, curses.COLOR_GREEN, curses.COLOR_BLACK)
    curses.init_pair(HIGHLIGHT_CODE, curses.COLOR_YELLOW, curses.COLOR_BLACK)
    curses.init_pair(PROMPT_CODE, curses.COLOR_BLUE, curses.COLOR_BLACK)
    
    
    
    
    
    
def get_random_sentences(amount) -> List[str]:
    result = list()
    added = set()
    for _ in range(amount):
        rand_idx = random.randint(0, len(SENTENCES) - 1)
        while rand_idx in added:
            rand_idx = random.randint(0, len(SENTENCES) - 1)
        result.append(SENTENCES[rand_idx])
        added.add(rand_idx)
    return result






def mainloop():
    try:
        while True:
            wrapper(typing_test)
    except KeyboardInterrupt:
        print("Program has ended due to key board interupt")





if __name__ == "__main__":
    mainloop()
