#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""
IMM - Internet Media Manager: Play internet media in desktop media
player just like local files and optionally save them offline. IMM can
function as conventional media downloaders as well.
Copyright (C) 2018-2020  IMM@radii.dev

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""

"""
NOTE: IMPROVE CODE, FIX REPETIONS AND MESSES. GO THROUGH ENTIRE CODE. REFACTOR CODEBASE.
Use tuple where list is not required scuh as constant & non-global data.

Below, first several functions are defined. To read squentially, go to
the bottom and start reading from execution/calling of Main() function.

VD is VariablesDictionary. see GenerateVariablesDictionary() function.
"""

# Python standard library imports
import subprocess
import sys
import time
import os
import threading
import glob
import shlex
import pathlib

# Third-party imports
# import youtube-dl
try:
    import pyperclip
except:
    print(
        "WARNING: pyperclip module not found. Please install pyperclip"
        " module https://pypi.org/project/pyperclip/ in your python "
        "setup. It is required to automatically fetch urls from "
        "clipboard. Loading pyperclip module provided in programme but"
        " it might not work", file=sys.stderr)
    try:
        from Code.PythonModules import pyperclip
    except:
        # symbolic function if failed to import
        class pyperclip:
            def paste():
                return ''

# Current programme imports
from Code.IMM.GeneralFunctions.InputOutput import (
    ReadTextFile,
    ReadlinesTextFile,
    WriteGeneralTextFiles)

from Code.IMM.GeneralFunctions.DataStructureManupulations import (
    ConvertToStandardPathFormat,
    GetTextAfter,
    DoubleQuoteString,
    SingleQuoteString,
    ListIntoString,
    StandardVariableName)


class FindMediaURLs:
    """ First look for media urls in programme arguments than clipboard
    and finally ask for user input if still not found.
    """
    def __init__(self):
        MediaURLs = self.ExtractingMediaUrls(Arguments)
        if len(MediaURLs) != 0:
            self.MediaURLs = MediaURLs
            return
        
        # Causing Gtk-WARNING **: Theme parsing error:
        Clipboard = pyperclip.paste()
        Clipboard = Clipboard.split('\n')
        MediaURLs = self.ExtractingMediaUrls(Clipboard)
        if len(MediaURLs) != 0:
            self.MediaURLs = MediaURLs
            return
        
        MediaURLs = self.ProcessUserInput()
        self.MediaURLs = MediaURLs

    def ProcessUserInput(self):
        print(
            "No media url found in aguments and clipboard. You can "
            "paste media urls and imm created m3u files location "
            "below. Paste one url per line (empty input will break the"
            " url collection loop). This way you can paste list of "
            "mutiple urls with one url per line (bulk url paste will "
            "work)")
        MediaURLList = ''
        while len(MediaURLList) == 0:
            UserInputList = []
            while True:
                UserInput = input()
                UserInputList.append(UserInput)
                if len(UserInput) == 0:
                    break
            MediaURLList = self.ExtractingMediaUrls(UserInputList)
            if len(MediaURLList) != 0:
                return MediaURLList
            print("No valid media url or m3u file cointaing valid "
                "media url found. Try again!")

    def ExtractingMediaUrls(self, Source):
        MediaURLs = []
        for i in range(len(Source)):
            Item = Source[i].strip(Quotes + ' ')
            if Item.casefold().startswith(VD['validmediaurlstart']):
                MediaURL = DoubleQuoteString(Item)
                MediaURLs.append(MediaURL)
            elif Item.casefold().endswith(
                VD['validextensioncontaningmediaurl1']):
                
                M3UFile = ConvertToStandardPathFormat(Item)
                MediaAvailableOffline = \
                    self.M3UFile_IsMediaAvailableOffline(M3UFile)
                if len(MediaAvailableOffline) > 0:
                    MediaURLs.append(MediaAvailableOffline)
                else:
                    MediaURL = self.GetMediaURLFromM3UFile(M3UFile)
                    if MediaURL != None:
                        MediaURL = DoubleQuoteString(MediaURL)
                        MediaURLs.append(MediaURL)
        return MediaURLs

    def GetMediaURLFromM3UFile(self, M3UFile):
        if os.path.isfile(M3UFile) == True:
            FileLines = ReadlinesTextFile(M3UFile)
            MediaURL = GetTextAfter('#Source: ', FileLines).strip(' ')
            if len(MediaURL) > 0:
                return MediaURL

    def M3UFile_IsMediaAvailableOffline(self, M3UFile):
        Return = ''
        FileLines = ReadlinesTextFile(M3UFile)
        ChangeWorkingDirectoryTo = os.path.split(M3UFile)[0]
        os.chdir(ChangeWorkingDirectoryTo)
        for i in range(len(FileLines)):
            Line = FileLines[i].strip('\n' + Quotes)
            if Line.startswith('#') or\
                Line.casefold().startswith(VD['validmediaurlstart']):
                    
                continue
            else:
                Line = os.path.abspath(Line)
                if os.path.isfile(Line) == True:
                    Return ='Offline://' + Line
        os.chdir(ProgrammeDirectory)
        return Return


class CalculateMediaQualityPreference:
    def __init__(self, DownloadOnly = False):
        InternetSpeed = VD['internetspeed']
        MediaQualityHeight = VD['mediaqualityheight']
        PreferAudioOnly = VD['preferaudioonly']
        self.AllowSeprateVideoAndAudioSource = True
        if DownloadOnly == False:
            if (len(MediaPlayerMultipleSourcePlayCommand) <= 0
                or VD['allowseprateaudiosource'] != "yes"):
                
                self.AllowSeprateVideoAndAudioSource = False
        CustomFormatOptions = VD['customformatoptions'].strip(' "\'/')
        if len(CustomFormatOptions) > 0:
            CustomFormatOptions += '/'
        SpeedBasedPreference = ''
        QualityBasedPreference = ''
        TryAudioFirst = ''
        if InternetSpeed != None:
            MaxAverageBitrate = 0.8 * int(InternetSpeed)
            MaxAverageBitrateString = str(MaxAverageBitrate)
            MaxAverageBitrate2 = MaxAverageBitrate - 320
            MaxAverageBitrate2String = str(MaxAverageBitrate2)
            SpeedBasedPreference = (
                "bestvideo[tbr<="
                + MaxAverageBitrate2String
                + "]+bestaudio/worstvideo[tbr>="
                + MaxAverageBitrate2String
                + "]+bestaudio/best[tbr<="
                + MaxAverageBitrateString
                + "]/worst[tbr>="
                + MaxAverageBitrateString
                + "]/")
        if MediaQualityHeight != None:
            MediaQualityHeight = str(MediaQualityHeight)
            QualityBasedPreference = (
                "bestvideo[height<="
                + MediaQualityHeight
                + "]+bestaudio/worstvideo[height>="
                + MediaQualityHeight
                + "]+bestaudio/best[height<="
                + MediaQualityHeight
                + "]/worst[height>="
                + MediaQualityHeight
                + "]/")
        if (PreferAudioOnly == 'yes'
            or Arguments.count('--preferaudioonly') != 0):
            
            if InternetSpeed != None:
                TryAudioFirst = (
                    "bestaudio[tbr<="
                    + MaxAverageBitrateString
                    +"']/worst[tbr>="
                    + MaxAverageBitrateString
                    + "]/")
            else:
                TryAudioFirst = "bestaudio/"
        MediaPreference = (
            TryAudioFirst
            + CustomFormatOptions
            + SpeedBasedPreference
            + QualityBasedPreference
            + "best")
        if self.AllowSeprateVideoAndAudioSource == False:
            MediaPreference = self.RemoveMutipleSourceFormats(
                MediaPreference)
        self.MediaPreference = MediaPreference

    def RemoveMutipleSourceFormats(self, MediaPreference):
        MediaPreferencelist = MediaPreference.split('/')
        MediaPreference = []
        for i in range(len(MediaPreferencelist)):
            if MediaPreferencelist[i].find('+') == -1:
                MediaPreference.append(MediaPreferencelist[i])
        MediaPreference = '/'.join(MediaPreference)
        return MediaPreference

    def ManuallySelectFormat(self, MediaURL):
        ManuallySelectFormat = VD['manuallyselectformat']
        if (ManuallySelectFormat == 'yes'
            or Arguments.count('--manuallyselectformat') != 0):
            
            print('Finding available formats...')
            GetAvailableFormatsCommand = ' '.join((
                YoutubeDL,
                "--list-formats --no-playlist",
                MediaURL))
            OutputString = RunSubprocessAndReturnOutputString(
                GetAvailableFormatsCommand)
            FoundFormats, FormatStartAt = self.DidFoundAnyFormat(
                OutputString)
            if FoundFormats == False:
                print(
                    "Could not find any format. Press return key to "
                    " process next url in queue if any, else to exit. "
                    "Possible reasons: \n"
                    "1. Probably bug in IMM, report issue to us.\n"
                    "2. Unsupported website or supported but still not"
                    " working, report it to youtube-dl developers\n"
                    "3. Incorrect url or url has no media. check "
                    "again.\n"
                    "below is the output from youtube-dl:\n"
                    + OutputString)
                input()
                return 'Failed'
            InvalidInputTryAgainOrSkip = (
                "Invalid format code. Try again or type 's' or 'skip'"
                " to process media with automatic format selection.")
            print(
                "Type code of one (example 'videoandaudio') or two "
                "(example 'videos+audio') of the available formats "
                "below to process it. You can add a blank space ' ' "
                "before to keep manuallyselectformat turned on, by "
                "default its automaically turned off. Add blank space "
                "' ' after code to add it to customformatoptions so "
                "that you do not have to manully select it again and "
                "again.\n"
                + OutputString[FormatStartAt:])
            VerificationCode = -1
            while VerificationCode == -1:
                UserInput = input()
                if len(UserInput) == 0 :
                    print(InvalidInputTryAgainOrSkip)
                    continue
                if UserInput.startswith(' '):
                    KeepManuallySelectFormatON = True
                else:
                    KeepManuallySelectFormatON = False
                if UserInput.endswith(' '):
                    AddToCustomFormatOptions = True
                else:
                    AddToCustomFormatOptions = False
                UserInput = UserInput.strip(' ')
                MultipleFormatSelected = UserInput.find('+')
                if MultipleFormatSelected == -1:
                    Verification = '\n' + UserInput
                    VerificationCode = OutputString.find(Verification)
                else:
                    if self.AllowSeprateVideoAndAudioSource == True:
                        FirstVerification = (
                            '\n'
                            + UserInput[:MultipleFormatSelected])
                        FirstVerificationCode = OutputString.find(
                            FirstVerification)
                        LastVerification = (
                            '\n'
                            + UserInput[(MultipleFormatSelected + 1):])
                        LastVerificationCode = OutputString.find(
                            LastVerification)
                        if FirstVerification != -1 and LastVerificationCode != -1:
                            VerificationCode = 1
                    else:
                        print(
                            "Media player do not support seprate "
                            "video+audio source playback. Select single"
                            " videoandaudio or audioonly format")
                if VerificationCode != -1:
                    UserSelectedFormat = UserInput + '/'
                    if KeepManuallySelectFormatON == False:
                        Configuration.EditConfigurationFile(
                            'manuallyselectformat', 'no')
                    if AddToCustomFormatOptions == True:
                        AlreadyInConfiguration = \
                            VD['CustomFormatOptions'].strip('/ ')
                        UpdatedCustomFormatOptions = (
                            UserSelectedFormat
                            + AlreadyInConfiguration).strip('/ ')
                        Configuration.EditConfigurationFile(
                            'CustomFormatOptions',
                            UpdatedCustomFormatOptions)
                    break
                if UserInput == 's' or UserInput == 'skip':
                    UserSelectedFormat = ''
                    break
                print(InvalidInputTryAgainOrSkip)
        else:
            UserSelectedFormat = ''
        MediaPreference = DoubleQuoteString(
            UserSelectedFormat + self.MediaPreference)
        MediaPreference = "--format " + MediaPreference
        return MediaPreference

    def DidFoundAnyFormat(self, OutputString):
        """ When ManuallySelectFormat is enabled, it tests if found any
        format/media source links or not.
        """
        FindTheseInSequence = (
            'format', 'code', 'extension', 'resolution', 'note')
        b = 0
        for Item in range(len(FindTheseInSequence)):
            a = OutputString[b:].find(FindTheseInSequence[Item])
            if a == -1:
                return False, 0
            b += a
        FormatStartAt = OutputString[:b].rfind('\n') + 1
        return True, FormatStartAt


def ParseDomainName(MediaURL):
    """ Parse domain name from url for naming m3u files in Previous
    list and Watch Later list.
    """
    DomainStart = MediaURL.find("//") + 2
    MediaURLFactor2 = MediaURL[DomainStart:].find('/')
    DomainEnd = DomainStart + MediaURLFactor2
    MediaDomain = MediaURL[DomainStart : DomainEnd]
    if MediaDomain.startswith("www."):
        MediaDomain = MediaDomain[4:]
    return MediaDomain

def ConvertDurationIntoSeconds(MediaDuration):
    """ Example: Input:12:05 => Output: 725 """
    DurationsList = MediaDuration.split(':')
    DurationsListLength = len(DurationsList)
    MutiplyingFactor = 1
    DurationInSeconds = 0
    for i in range(len(DurationsList) - 1, -1, -1):
        Add = int(DurationsList[i]) * MutiplyingFactor
        DurationInSeconds += Add
        MutiplyingFactor *= 60
    return str(DurationInSeconds)

class PlayOnly:
    def __init__(self, MediaURL, MediaPreference):
        self.MediaURL = MediaURL
        self.MediaPreference = MediaPreference
        """ This won't work it won't have custom configuration like
        audio only, it will have seprate configuration, duplication
        etc. and therefore defeating the purpose of IMM. Find how to
        pass Arguments in gnome-mpv and how to send additonal media
        urls to already playing gnome-mpv instance. No need of it any
        since mpv itself don't have instance control either (not sure
        it gnome-mpv have instance control or not yet. read
        documentation on website or talk on IRC).
         
        POSSIBLE FIX: read path of gnome-mpv's mpv configuration path
        in gnome-mpv config folder and copy it and add arguments as
        commands in copied file and specify that in argument of
        execution of gnome-mpv. If no option to specify in arguments
        than specify in configuration file and than execute it, than
        after playing, restore gnome-mpv configuration file.
        if no config file found than create it and than delete after
        playing.
        THIS CAN WORK.
        Push changes upstream to support this feature.
        """
        IsMediaPlayedAlready = self.DirectlyPlayForSomeMediaPlayers()
        if IsMediaPlayedAlready != True:
            self.MediaDomain = ParseDomainName(self.MediaURL)
            PrimaryMethod = self.PrimaryPlayingMethod()
            if PrimaryMethod == 'failed':
                self.AlternatePlayingMethod()

    # Kill this function
    def DirectlyPlayForSomeMediaPlayers(self):
        if (MediaPlayer.endswith('gnome-mpv')
            or MediaPlayer.endswith('gnome-mpv.exe')):
            
                PlayMedia(MediaURLListString)
                return True
        return False

    def AlternatePlayingMethod(self):
        print('Wait... extracting media url')
        ExtractURL = ' '.join((
            YoutubeDL,
            "--get-url --no-playlist",
            self.MediaPreference,
            self.MediaURL))
        ExtractedURL = RunSubprocessAndReturnOutputString(
            ExtractURL).strip('\n')
        QuoteExtractedURL = DoubleQuoteString(ExtractedURL)
        StartProcessing.ProcessNextURL = True
        PlayMedia(QuoteExtractedURL)

    def PrimaryPlayingMethod(self):
        print('Wait... extracting media url')
        ExtractURLTitleAndDuration =  ' '.join((
            YoutubeDL,
            "--get-title --get-url --get-duration --no-playlist",
            AdditionalYoutubeDLArguments,
            self.MediaPreference,
            self.MediaURL))
        ExtractedURLTitleAndDurationList = RunSubprocessAndReturnOutputString(
            ExtractURLTitleAndDuration).strip('\n').split('\n')
        LenOfExtractedURLTitleAndDurationList = \
            len(ExtractedURLTitleAndDurationList)
        ExtractedAudioURL = None
        PrintErrorMessageIfFails = (
            "Something went wrong with extraction process. Below "
            "(or above) is the extracted string:\n"
            + ('\n').join(ExtractedURLTitleAndDurationList))
        if LenOfExtractedURLTitleAndDurationList == 4:
            if ExtractedURLTitleAndDurationList[2].find(
                ExtractedURLTitleAndDurationList[1][:4]) != -1:
                
                MediaTitle = ExtractedURLTitleAndDurationList[0]
                ExtractedURL = ExtractedURLTitleAndDurationList[1]
                ExtractedAudioURL = ExtractedURLTitleAndDurationList[2]
                ExtractedAudioURL = DoubleQuoteString(ExtractedAudioURL)
                MediaDuration = ExtractedURLTitleAndDurationList[3]
                MediaDurationInSeconds = ConvertDurationIntoSeconds(MediaDuration)
            else:
                print(PrintErrorMessageIfFails)
                return 'failed'
        elif LenOfExtractedURLTitleAndDurationList == 3:
            """ Test if both start with same protocol like http. Than
            both are media urls (one is video source and other is audio
            source), otherwise other one is MediaDuration.
            """
            if ExtractedURLTitleAndDurationList[2].find(
                ExtractedURLTitleAndDurationList[1][:4]) != -1:
                
                MediaTitle = ExtractedURLTitleAndDurationList[0]
                ExtractedURL = ExtractedURLTitleAndDurationList[1]
                ExtractedAudioURL = ExtractedURLTitleAndDurationList[2]
                ExtractedAudioURL = DoubleQuoteString(ExtractedAudioURL)
            else:
                MediaTitle = ExtractedURLTitleAndDurationList[0]
                ExtractedURL = ExtractedURLTitleAndDurationList[1]
                MediaDuration = ExtractedURLTitleAndDurationList[2]
                MediaDurationInSeconds = ConvertDurationIntoSeconds(
                    MediaDuration)
        elif LenOfExtractedURLTitleAndDurationList == 2:
            MediaTitle = ExtractedURLTitleAndDurationList[0]
            ExtractedURL = ExtractedURLTitleAndDurationList[1]
            MediaDurationInSeconds = ''
        else:
            print(PrintErrorMessageIfFails)
            return 'failed'
        M3UFileName = self.MediaDomain + ' → ' + MediaTitle + '.m3u'
        for Symbols in range(len(VD['ReplaceTheseSymbols'])):
            M3UFileName = M3UFileName.replace(
                VD['ReplaceTheseSymbols'][Symbols],
                VD['ReplaceWithSymbols'][Symbols])
        AudioSourceLine = ''
        if ExtractedAudioURL != None:
            AudioSourceLine = '#Audio:' + ExtractedAudioURL
        """ Known Issue: for now, atleast 1 ASCII character would be
        required in name to work in Windows since can't get unicode
        output in Windows.
        """
        DirectoryAndM3UFileName = os.path.join(VD['previouslist'], M3UFileName)
        WriteToM3UFile(
            DirectoryAndM3UFileName,
            MediaDuration = MediaDurationInSeconds,
            MediaTitle = MediaTitle,
            MediaLocation = ExtractedURL,
            AddtionalText = AudioSourceLine,
            MediaURL = self.MediaURL)
        DirectoryAndM3UFileName2 = os.path.join(VD['previouslist2'], M3UFileName)
        WriteToM3UFile(
            DirectoryAndM3UFileName2,
            MediaDuration = MediaDurationInSeconds,
            MediaTitle = MediaTitle,
            MediaLocation = ExtractedURL,
            AddtionalText = AudioSourceLine,
            MediaURL = self.MediaURL)
        StartProcessing.ProcessNextURL = True
        DirectoryAndM3UFileName2 = DoubleQuoteString(
            DirectoryAndM3UFileName2)
        # M3UFileName2 = '"' + M3UFileName + '"'
        if (MediaPlayer.endswith('vlc')
            or MediaPlayer.endswith('vlc.exe')):
            
            quotedExtractedURL = DoubleQuoteString(ExtractedURL)
            PlayMedia(
                DirectoryAndM3UFileName2,
                SeprateAudio = True,
                WorkaroundForVLC = quotedExtractedURL,
                VLCWorkAroundTitle = MediaTitle)
        else:
            PlayMedia(DirectoryAndM3UFileName2, SeprateAudio = True)
        #threading.Thread(target = PlayMedia(DirectoryAndM3UFileName2)).start()


# REWRITE FUNCTION WITH BETTER CLASSES-OBJECTS UNDERSTANDING
class PlayAndSave:
    def __init__(self, MediaNumber, MediaURL, MediaPreference):
        self.MediaNumber = MediaNumber
        self.MediaURL = MediaURL
        self.MediaPreference = MediaPreference
        self.FileNameExtracted = False
        self.FinalFileName = False
        self.AudioSource = None
        self.DeleteFilesTuple = ()
        self.MediaTitle = ''
        self.FormatsMerged = False
        self.DirectoryAndFile = ''
        self.FileName = ''
        self.PlayingSeprateFile = False
        self.MediaDomain = ParseDomainName(MediaURL)
        
        threading.Thread(
            target = self.GetAudioUrlIfSeprateAudioSource).start()
        threading.Thread(target = self.Play).start()
        #threading.Thread(target = self.DeleteFiles).start()
        self.Save()

    def GetAudioUrlIfSeprateAudioSource(self):
        IsSeprateAudioStreamCommand = ' '.join((
            YoutubeDL,
            "-e -g --no-playlist",
            AdditionalYoutubeDLArguments,
            self.MediaPreference,
            self.MediaURL))
        IsSeprateAudioStream = RunSubprocessAndReturnOutputString(
            IsSeprateAudioStreamCommand).strip('\n').split('\n')
        self.MediaTitle = DoubleQuoteString(
            IsSeprateAudioStream[0]).strip('"')
        if len(IsSeprateAudioStream) > 2:
            print('\nSeprate Audio Source')
            self.AudioSource = DoubleQuoteString(
                IsSeprateAudioStream[2]) #1=vdeo 2=audio
        else:
            self.AudioSource = ''

    def Save(self):
        print('Saving media... Will play as soon as its playable')
        SaveCommand = ' '.join((
            YoutubeDL,
            "--no-part --keep-video --no-playlist",
            AdditionalYoutubeDLArguments,
            self.MediaPreference,
            self.MediaURL))
        SaveCommand = shlex.split(SaveCommand)
        SaveMedia = subprocess.Popen(
            SaveCommand,
            cwd = VD['offlinemedia'],
            stdout = subprocess.PIPE,
            universal_newlines = True)
        while SaveMedia.poll() is None:
            Output = SaveMedia.stdout.readline()
             # Do not end with '\n' newline. Stay on same line.
            print(Output.strip('\n'), end = '', flush = True)
            """ Backspace/clear output to print next in same line """
            print('\r', end = '')
            self.ExtractFileName(Output)
        StartProcessing.ProcessNextURL = True

    def ExtractFileName(self, Output):
        if Output.startswith('[download] Destination: '):
            self.FileName = Output[24:-1]
            self.AfterFileNameExtracted()
        elif Output.startswith('[ffmpeg] Merging formats into "'):
            self.FileName = Output[31:-2]
            self.AfterFileNameExtracted(ThisIsFinalFileName = True)
            self.FinalFileName = True
            self.FormatsMerged = True
        elif Output.endswith(' has already been downloaded and merged\n'):
            self.FileName = Output[11:-40]
            self.AfterFileNameExtracted(ThisIsFinalFileName = True)
            self.FinalFileName = True
            self.FormatsMerged = True
        elif Output.endswith(' has already been downloaded\n'):
            self.FileName = Output[11:-29]
            self.AfterFileNameExtracted()

    def AfterFileNameExtracted(self, ThisIsFinalFileName = False):
        self.DirectoryAndFile = VD['offlinemedia'] + self.FileName
        if ThisIsFinalFileName == False:
            self.DeleteFilesTuple += self.DirectoryAndFile,
        self.FileNameExtracted = True
        
    def Play(self):
        global PlayingMediaNumber, PlayingSeprateFile
        while PlayingMediaNumber < self.MediaNumber:
            time.sleep(1/10)
        while self.AudioSource == None:
            time.sleep(1/10)
        while self.FileNameExtracted == False:
            time.sleep(1/10)
        while os.path.isfile(self.DirectoryAndFile) is False:
            time.sleep(1/10)
        MinimumRequiredSize = (1/2) * 1048576 # 0.5MB
        while os.path.getsize(self.DirectoryAndFile) < MinimumRequiredSize:
            time.sleep(1/10)
        threading.Thread(target = self.CreateM3UFile).start()
        DirectoryAndFile = DoubleQuoteString(self.DirectoryAndFile)
        if len(self.AudioSource) > 0 and self.FormatsMerged == False:
            PlayingSeprateFile = True
            PlayMedia(
                DirectoryAndFile,
                SeprateAudio = True,
                AudioSource = self.AudioSource)
            PlayingSeprateFile = False
        else:
            PlayMedia(DirectoryAndFile)
        PlayingMediaNumber += 1
        """ This is temporary until fix PlayingSeprateFile variable
        issue in DeleteFiles()
        """
        self.DeleteFiles()

    def DeleteFiles(self):
        while self.AudioSource == None:
            time.sleep(1)
        if len(self.AudioSource) == 0:
            return
        while self.FormatsMerged == False:
            time.sleep(1)
        while os.path.isfile(self.DirectoryAndFile) == False:
            time.sleep(1)
        # Do not delete if file is still being merged i.e. increasing size
        while True:
            a = os.path.getsize(self.DirectoryAndFile)
            time.sleep(5)
            b = os.path.getsize(self.DirectoryAndFile)
            if a == b:
                break
        while self.PlayingSeprateFile == True: #why this not working?
            #print(self.PlayingSeprateFile)
            time.sleep(1)
        for File in range(len(self.DeleteFilesTuple)):
            FileToBeDeleted = self.DeleteFilesTuple[File]
            print(
                'Files are Merged, Deleting Seprate File '
                + FileToBeDeleted)
            os.remove(FileToBeDeleted)

    def CreateM3UFile(self):
        if len(self.AudioSource) > 0:
            while self.FinalFileName == False:
                time.sleep(1)
        M3UFileName = self.MediaDomain + ' → ' + self.MediaTitle + '.m3u'
        DirectoryAndM3UFileName = os.path.join(VD['previouslist'], M3UFileName)
        SavingMediaPath = RelativeIfProgrammeSubpathElseAbsolutePath(
            self.DirectoryAndFile,
            RelativeTo = VD['previouslist'],
            DirectoryOrFile = 'file')
        WriteToM3UFile(
            DirectoryAndM3UFileName,
            MediaLocation = SavingMediaPath,
            MediaURL = self.MediaURL)


def DownloadOnly(MediaURL, MediaPreference):
    FileName = ''
    MediaDomain = ParseDomainName(MediaURL)
    print('Downloading media. Will auto-exit once download completed.')
    DownloadCommand = ' '.join((
        YoutubeDL,
        "--no-playlist",
        AdditionalYoutubeDLArguments,
        MediaPreference,
        MediaURL))
    DownloadCommand = shlex.split(DownloadCommand)
    DownloadMedia = subprocess.Popen(
        DownloadCommand,
        cwd = VD['offlinemedia'],
        stdout = subprocess.PIPE,
        universal_newlines = True)
    while DownloadMedia.poll() is None:
        Output = DownloadMedia.stdout.readline()
        print(Output.strip('\n'), end = '', flush = True) #do not end with '\n'
        print('\r', end = '') #backspace/clear output to print next in same line
        if Output.startswith('[download] Destination: '):
            FileName = Output[24:-1]
        elif Output.startswith('[ffmpeg] Merging formats into "'):
            FileName = Output[31:-2]
        elif Output.endswith(' has already been downloaded and merged\n'):
            FileName = Output[11:-40]
        elif Output.endswith(' has already been downloaded\n'):
            FileName = Output[11:-29]
    MediaTitleUpto = FileName.rfind('-')
    MediaTitle = FileName[:MediaTitleUpto]
    DirectoryAndFile = os.path.join(VD['offlinemedia'], FileName)
    M3UFileName = MediaDomain + ' → ' + MediaTitle + '.m3u'
    DirectoryAndM3UFileName = os.path.join(VD['previouslist'], M3UFileName)
    DownloadedMediaPath = RelativeIfProgrammeSubpathElseAbsolutePath(
        DirectoryAndFile,
        RelativeTo = VD['previouslist'],
        DirectoryOrFile = 'file')
    WriteToM3UFile(
        DirectoryAndM3UFileName,
        MediaLocation = DownloadedMediaPath,
        MediaURL = MediaURL)
    print('Download complete')

def WatchLater(MediaURL):
    MediaDomain = ParseDomainName(MediaURL)
    ExtractTitleCommand = ' '.join((
        YoutubeDL,
         "--get-title --no-playlist",
         AdditionalYoutubeDLArguments,
         MediaURL))
    ExtractedTitle = RunSubprocessAndReturnOutputString(
        ExtractTitleCommand).strip('\n')
    if len(ExtractedTitle) > 0:
        MediaTitle = ExtractedTitle
        M3UFileName = MediaDomain + ' → ' + MediaTitle + '.m3u'
    else:
        MediaTitle = MediaURL
        M3UFileName = MediaTitle+'.m3u'
    for Symbols in range(len(VD['ReplaceTheseSymbols'])):
        M3UFileName = M3UFileName.replace(
            VD['ReplaceTheseSymbols'][Symbols],
            VD['ReplaceWithSymbols'][Symbols])
    DirectoryAndM3UFileName = os.path.join(VD['watchlaterlist'], M3UFileName)
    WatchLaterComment = "#You have marked this media to watch later"
    WriteToM3UFile(
        DirectoryAndM3UFileName,
        MediaTitle = MediaTitle,
        MediaLocation = WatchLaterComment,
        MediaURL = MediaURL)
    DirectoryAndM3UFileName2 = VD['watchlaterlist2'] + M3UFileName
    WriteToM3UFile(
        DirectoryAndM3UFileName2,
        MediaTitle = MediaTitle,
        MediaLocation = WatchLaterComment,
        MediaURL = MediaURL)
    print('Saved url to watch later list')

def WriteToM3UFile(
    FilePathAndName,
    MediaDuration = '',
    MediaTitle = '',
    MediaLocation = '',
    AddtionalText = '',
    MediaURL = ''):
    
    if len(MediaTitle) > 0:
        Start = '#EXTINF:'
        LineBreak = '\n'
    else:
        Start = ''
        LineBreak = ''
    if len(MediaDuration) > 0:
        MediaDuration = MediaDuration + ','
    if len(AddtionalText) > 0:
        AddtionalText = AddtionalText + '\n'
    WriteContent = (
        Start
        + MediaDuration
        + MediaTitle
        + LineBreak
        + MediaLocation
        + '\n'
        + AddtionalText
        + '#Source: '
        + MediaURL)
    WriteGeneralTextFiles(WriteContent, FilePathAndName)

def RelativeIfProgrammeSubpathElseAbsolutePath(
    Path,
    RelativeTo = None,
    DirectoryOrFile = 'Directory'):
    
    Path = os.path.realpath(Path)
    if Path.startswith(ProgrammeDirectory) == False:
        return Path
    if RelativeTo == None:
        RelativeTo = ProgrammeDirectory
    RelativePath = os.path.relpath(Path, RelativeTo)
    return RelativePath
 
def PlayMedia(
    FileLocation,
    SeprateAudio = False,
    AudioSource = None,
    WorkaroundForVLC = None,
    VLCWorkAroundTitle = None):
    
    # THIS FUNCTION IS A MESS, FIX IT.
    if SeprateAudio == True and AudioSource == None:
        M3UFileText = ReadlinesTextFile(FileLocation)
        AudioSource = GetTextAfter('#Audio:', M3UFileText).strip(' ')
    QuotedMediaPlayer = DoubleQuoteString(MediaPlayer)
    PlayCommand = (MediaPlayerOptions + ' ' + FileLocation).strip(' ')
    if SeprateAudio == True and AudioSource != '':
        MultipleSourcePlayCommand = \
            MediaPlayerMultipleSourcePlayCommand
        if len(MultipleSourcePlayCommand) > 0:
            if WorkaroundForVLC != None:
                MultipleSourcePlayCommand = \
                    MultipleSourcePlayCommand.replace(
                        'videosource', WorkaroundForVLC)
                if VLCWorkAroundTitle != None:
                    MediaTitle = DoubleQuoteString(VLCWorkAroundTitle)
                    MultipleSourcePlayCommand += ' :meta-title=' + MediaTitle
            else:
                MultipleSourcePlayCommand = \
                    MultipleSourcePlayCommand.replace(
                        'videosource', FileLocation)
            MultipleSourcePlayCommand = \
                MultipleSourcePlayCommand.replace(
                    'audiosource', AudioSource)
            PlayCommand = (
                MediaPlayerOptions + ' ' + MultipleSourcePlayCommand)
        else:
            print(
                "Error: Have to play seprate video+audio source but "
                "'MediaPlayerMultipleSourcePlayCommand' is empty. "
                "This is an unexpected programming bug. Please report "
                "issue to IMM. Press return key to quit programme")
            input()
            sys.exit()
    ExecuteMediaPlayerCommand = QuotedMediaPlayer + ' ' + PlayCommand
    print('Now playing in media player')
    #GnomeMPVExp(ExecuteMediaPlayerCommand)
    #print("Gnome-MPV")
    #input()
    PlayingMedia = subprocess.Popen(ExecuteMediaPlayerCommand, shell = True)
    """ Below method of stdout=subprocess.PIPE and readlines() is
    causing perfomance issues.
    """
    #PlayingMedia = subprocess.Popen(ExecuteMediaPlayerCommand, stderr = subprocess.PIPE, stdout = subprocess.PIPE, shell = True)
    while PlayingMedia.poll() is None:
        #PlayingMedia.stdout.readline() #Flushing by reading. Maybe otherwise output will fill the memory?
        #PlayingMedia.stderr.readline()
        time.sleep(1/60)
    #if sys.platform == 'win32':
        #subprocess.Popen(ExecuteMediaPlayerCommand, shell = True)
    #else:
        #os.system(ExecuteMediaPlayerCommand) #find solution to it like communicate() or wait()?

class StartProcessing:
    ProcessNextURL = True
    
    def __init__(self):
        for i in range(len(Arguments)):
            if Arguments[i].startswith('-'):
                if Arguments[i] == '-s' or Arguments[i] == '--playandsave':
                    self.Do('playandsave')
                    return
                elif Arguments[i] == '-p' or Arguments[i] == '--play':
                    self.Do('play')
                    return
                elif Arguments[i] == '-d' or Arguments[i] == '--downloadonly':
                    self.Do('downloadonly')
                    return
                elif Arguments[i] == '-w' or Arguments[i] == '--watchlater':
                    self.Do('watchlater')
                    return
        DefaultChoice = VD['defaultchoice']
        if (DefaultChoice == 'playandsave'
            or DefaultChoice == 'play'
            or DefaultChoice == 'downloadonly'
            or DefaultChoice == 'watchlater'):
            
            self.Do(DefaultChoice)
        else:
            print('Type code number of one of the options and press enter\n'
                '1 - play media in desktop media player\n'
                '2 - play media in desktop media player and save as well to drive\n'
                '3 - only download media (don\'t play in desktop media player)\n'
                '4 - save url in watch later list')
            while True:
                UserInput = input()
                if UserInput == '1':
                    self.Do('play')
                    break
                elif UserInput == '2':
                    self.Do('playandsave')
                    break
                elif UserInput == '3':
                    self.Do('downloadonly')
                    break
                elif UserInput == '4':
                    self.Do('watchlater')
                    break
                print('Invalid option, Try Again')

    """ Do what? Play, PlayAndSave, DownloadOnly, WatchLater """
    def Do(self, What):
        global MediaURLListString, MediaURLList, ClassMediaPreference
        MediaURLList = FindMediaURLs().MediaURLs
        MediaURLListString = ListIntoString(
            MediaURLList, QuoteItems = 2)
        if What == 'play':
            ClassMediaPreference = CalculateMediaQualityPreference()
            QueueInIMM = VD['queueinimm']
            for i in range(len(MediaURLList)):
                if QueueInIMM == 'yes':
                    print('Press return key to process next media url')
                    input()
                MediaURL = MediaURLList[i]
                if self.DirectlyPlayIfAvailableOffline(MediaURL) == True:
                    continue
                MediaPreference = \
                    ClassMediaPreference.ManuallySelectFormat(MediaURL)
                print('Processing ' + MediaURL)
                threading.Thread(
                    target = PlayOnly,
                    args = (MediaURL, MediaPreference)).start()
                self.WaitForProcessToComplete()
                continue #is this required or will work correctly?
        elif What == 'playandsave':
            global PlayingMediaNumber
            ClassMediaPreference = CalculateMediaQualityPreference()
            PlayingMediaNumber = 0
            for i in range(len(MediaURLList)):
                MediaURL = MediaURLList[i]
                if self.DirectlyPlayIfAvailableOffline(MediaURL) == True:
                    continue
                MediaPreference =\
                    ClassMediaPreference.ManuallySelectFormat(MediaURL)
                print('Processing ' + MediaURL)
                threading.Thread(
                    target = PlayAndSave,
                    args = (i, MediaURL, MediaPreference)).start()
                self.WaitForProcessToComplete()
                continue #is this required or will work correctly?
        elif What == 'downloadonly':
            ClassMediaPreference = \
                CalculateMediaQualityPreference(DownloadOnly = True)
            ApplyORNotMediaPreference = \
                VD['applymediapreferencetodownloadonlyalso']
            for i in range(len(MediaURLList)):
                MediaURL = MediaURLList[i]
                if ApplyORNotMediaPreference == 'yes':
                    MediaPreference = \
                        ClassMediaPreference.ManuallySelectFormat(MediaURL)
                    if MediaPreference == 'Failed':
                        return
                else:
                    MediaPreference = ''
                print('Processing ' + MediaURL)
                DownloadOnly(MediaURL, MediaPreference)
        elif What == 'watchlater':
            for i in range(len(MediaURLList)):
                MediaURL = MediaURLList[i]
                print('Processing ' + MediaURL)
                WatchLater(MediaURL)

    def WaitForProcessToComplete(self):
        StartProcessing.ProcessNextURL = False
        while StartProcessing.ProcessNextURL == False:
            time.sleep(1)

    def DirectlyPlayIfAvailableOffline(self, MediaURL):
        OfflineProtocol = 'Offline://'
        LenOfflineProtocol = len(OfflineProtocol)
        OfflineFile = MediaURL.strip(Quotes)
        if OfflineFile.startswith(OfflineProtocol):
            OfflineFile = DoubleQuoteString(
                OfflineFile[LenOfflineProtocol:])
            PlayMedia(OfflineFile)
            return True
        return False


class ProgrammeArguments:
    def __init__(self):
        self.GetArguments()
        self.SplitOneLetterArguments()

    def GetArguments(self):
        global Arguments
        if len(sys.argv) > 1:
            Arguments = sys.argv[1:]
        else:
            Arguments = []

    """ Can below function be written in a better way? """
    def SplitOneLetterArguments(self):
        ArgumentsHavingValues = ['c']
        ArgumentsAndValuesDictionary = {}
        ItemNumberOfValue = 1
        DeleteList = []
        for i in range(len(Arguments)):
            if Arguments[i].startswith('-') == True:
                if Arguments[i].startswith('--') == False:
                    if len(Arguments[i]) > 2: #eg. len(-p) == 2, len(-pc) == 3
                        if Arguments[i].find('=') == -1: #eg. -c=xyz
                            SplitString = Arguments[i][1:]
                            for s in range(len(SplitString)):
                                NewItem = '-' + SplitString[s]
                                NewItemValue = ''
                                if NewItem[1:] in ArgumentsHavingValues:
                                    NewItemValueNumber = i + ItemNumberOfValue
                                    ItemNumberOfValue += 1
                                    # index out of range risk
                                    NewItemValue = Arguments[NewItemValueNumber]
                                    DeleteList.append(NewItemValueNumber)
                                ArgumentsAndValuesDictionary[NewItem] = NewItemValue
                            DeleteList.append(i)
        DeleteList = sorted(DeleteList)
        for i in range(len(DeleteList)):
            """ Since an i is deleted from list, next to be deleted
            will be itemtobedeleted - 1. for next it will be
            itemtobedeleted - 2 etc.
            """
            del Arguments[DeleteList[i] - i]
        for keys, values in ArgumentsAndValuesDictionary.items():
            Arguments.append(keys)
            if len(values) > 0:
                Arguments.append(values)


class RunInTerminal:
    """ Reexecute programme in terminal with received arguments if not
    already running in terminal.
    """
    def __init__(self):
        if sys.stdout.isatty() == True:
            return
        else:
            #find command for osx (Mac)
            SupportedPlatorm = (
                'linux',
                'freebsd',
                'openbsd',
                'netbsd',
                'trueos',
                'darwin')
            if sys.platform.startswith(SupportedPlatorm):
                Arguments2 = ''
                for i in range(len(Arguments)):
                    Argument = Arguments[i]
                    if Argument.startswith('-') == False:
                        Arguments2 += ' ' + DoubleQuoteString(Argument)
                    else:
                        Arguments2 += ' ' + Argument
                Arguments2=Arguments2.replace('\'','\'\\\'\'')
                """ Programme = os.path.dirname(os.path.realpath(
                    sys.argv[0])) + sys.argv[0][1:]
                    """
                Programme = sys.argv[0]
                Programme = DoubleQuoteString(Programme)
                ProgrammeAndArguments = SingleQuoteString(
                    Programme + Arguments2)
                TerminalEmulator = self.FindTerminalEmulator()
                if TerminalEmulator == None:
                    sys.exit()
                ReexecuteCommand = (
                    TerminalEmulator + ' ' + ProgrammeAndArguments)
                os.system(ReexecuteCommand)
                sys.exit()

    def FindTerminalEmulator(self):
        SpecifiedTerminal = VD['terminalemulator']
        """ Likely bug - execution command for different terminals 
        would be different like -e or something else. Should have a
        tuple of execution command as well and return Terminal Emulator
        including execution command.
        """
        TerminalEmulators = (
            SpecifiedTerminal,
            'x-terminal-emulator',
            'gnome-terminal',
            'konsole',
            'konsole5',
            'mate-terminal',
            'xfce4-terminal',
            'terminal',
            'qterminal',
            'lxterminal',
            'roxterm', 'vt',
            'vte',
            'vte3',
            'tilix',
            'terminology',
            'guake',
            'anyterm',
            'eterm',
            'yakuake',
            'fbterm',
            'shellinabox',
            'sakura',
            'xterm',
            'xterm-256color',
            'uxterm',
            'vt52',
            'vt100',
            'vt102',
            'vt220',
            'ansi',
            'dumb',
            'qodem',
            'termit',
            'x3270-text',
            'mrxvt',
            'rxvt',
            'x3270-x11',
            'x3270',
            'wterm',
            'rxvt-unicode ',
            'rxvt-unicode-256color', 
            'aterm',
            'terminator',
            'gtk30',
            'tilda',
            'yakuake',
            'urxvt',
            'roxterm',
            'evilvte',
            'sakura',
            'i3',
            'tmux',
            'xcompmgr',
            'yeahconsole',
            'stjerm',
            'dwm',
            'sterm',
            'altyo',
            'mlterm',
            'koi8rxterm',
            'lxterm')
        for i in range(len(TerminalEmulators)):
            TerminalPath = FindFile(TerminalEmulators[i])
            if TerminalPath != None:
                    TerminalPath = DoubleQuoteString(TerminalPath)
                    return TerminalPath + ' -e'
        if sys.platform == 'darwin':
            return 'terminal -e' 


def GetPathsList():
    FindPaths = 'echo $PATH'
    PathsString = RunSubprocessAndReturnOutputString(
        FindPaths, Shell = True)
    PathsList = PathsString.split(Configuration.PathListSeprator)
    return PathsList

def FindFile(FileNameOrPath, ReturnFullPath=True):
    """ Find if specified file like media player exist in $PATH or
    refering to a file that exist.
    NOTE: should add current directory as well?
    """
    if FileNameOrPath == None: #temporary fix
        return
    FileNameOrPath = FileNameOrPath.strip(Quotes)
    PathsList = GetPathsList()
    for i in range(len(PathsList)):
        FilePath = os.path.join(PathsList[i], FileNameOrPath)
        FileFound = os.path.isfile(FilePath)
        if FileFound == True:
            if ReturnFullPath == True:
                return FilePath
            return FileNameOrPath
    FileFound = os.path.isfile(FileNameOrPath)
    if FileFound == True:
        if ReturnFullPath == True:
            return os.path.abspath(FileNameOrPath)
        return FileNameOrPath

def RunSubprocessAndReturnOutputString(RunCommand, Shell=False):
    if Shell==False:
        RunCommand=shlex.split(RunCommand)
    Execute=subprocess.Popen(
        RunCommand, stdout=subprocess.PIPE, shell=Shell)
    Byte=Execute.stdout.read()
    String=Byte.decode(Encoding)
    return String


class Configuration:
    """ Configure variables used in programme based on OS,
    configuration file, programme arguments, working directory etc.
    """
    global Quotes, PathSlash, FileProtocol
    Quotes = '"\''
    if sys.platform == 'win32':
        PathSlash = '\\'
        PathListSeprator = ';'
        UserConfigurationDirectory = (
            'AppData' + PathSlash + 'Roaming' + PathSlash)
    else:
        PathSlash = '/'
        PathListSeprator = ':'
        UserConfigurationDirectory = '.config' + PathSlash
    
    FileProtocol = 'file:' + 2*PathSlash
    OneDirectoryUp = '..' + PathSlash    
    
    def __init__(self):
        global Encoding
        self.FindVariousDirectories()
        os.chdir(ProgrammeDirectory)
        self.ReadConfigurationFile()
        self.GenerateVariablesDictionary()
        self.GlobalVariables()
        self.ModifyCheckCreateDirectories()
        """ This might be the problem in windows. it maybe decoding
        using codepage instead of unicode. Try as utf-8 and printing
        binary. if have crazy characters or numbers than solved the
        problem.
        """
        Encoding = sys.stdout.encoding

    def FindVariousDirectories(self):
        global\
            ProgrammeDirectory,\
            StartedWorkingDirectory,\
            HomeDirectory
        ProgrammeDirectory = os.path.dirname(
            os.path.realpath(sys.argv[0]))
        StartedWorkingDirectory = os.getcwd()
        HomeDirectory = str(pathlib.Path.home())
    
    def ReadConfigurationFile(self):
        global ConfigurationFileTextLines, ConfigurationFilePath
        SpecifiedConfigurationFilePath = ''
        ConfigurationFileName = "imm.conf"
        for i in range(len(Arguments)):
            if (Arguments[i].startswith("-c=")
                or Arguments[i].startswith("--configfile=")
                or Arguments[i].startswith("--configurationfilepath=")):
                
                Arg = Arguments[i]
                FilePathStartsAt = Arg.find('=')
                SpecifiedConfigurationFilePath = Arg[FilePathStartsAt + 1:]
        ConfigurationPaths = {
            "SpecifiedConfigurationFilePath " :
                SpecifiedConfigurationFilePath,
            "InProgrammeDirectory" : os.path.join(
                ProgrammeDirectory, ConfigurationFileName), 
            "InHomeDirectory" : HomeDirectory
                + PathSlash
                + Configuration.UserConfigurationDirectory
                + "imm"
                + PathSlash
                + ConfigurationFileName}
        for Keys, Paths in ConfigurationPaths.items():
            Path = os.path.abspath(Paths)
            if os.path.isfile(Path):
                ConfigurationFilePath = Path
                ConfigurationFileTextLines = ReadlinesTextFile(Path)
                return
        DefaultConfigurationTextLines = []
        ConfigurationFileTextLines = DefaultConfigurationTextLines
        ConfigurationFilePath = \
            ConfigurationPaths["InProgrammeDirectory"]
    
    def EditConfigurationFile(self, Variable, Value):
        global ConfigurationFileTextLines
        ReplaceWith = Variable +  '=' + Value
        Edited = False
        for Lines in range(len(ConfigurationFileTextLines)):
            Line = ConfigurationFileTextLines[Lines]
            VariableInLineAt = Line.find('=')
            VariableInLine = Line[:VariableInLineAt].strip(' ')
            if VariableInLine == Variable:
                ConfigurationFileTextLines[Lines] = ReplaceWith
                Edited = True
        if Edited == False:
            ConfigurationFileTextLines.append(ReplaceWith)
        WriteGeneralTextFiles(
            ConfigurationFileTextLines,
            ConfigurationFilePath)

    def GenerateVariablesDictionary(self):
        global VD #VD is VariablesDictionary
        """ These variables can be modified via arguments and 
        configuration file. unmodifiable variables are added/overwritten
        after processing configuration file and arguments.
        """
        VD = {
            'mediaplayeroptions' : '',
            'alternatemediaplayeroptions' : '', 
            "mediaplayermultiplesourcesplaycommand" : '',
            "alternatemediaplayermultiplesourcesplaycommand" : '',
            'queueinimm' : 'no', 
            'appendmedialist' : 'no', 
            'defaultchoice' : 'ask', 
            'allowseprateaudiosource' : 'yes', 
            # Do not use internet speed if not specified.
            'internetspeed':None,
            # By default accept upto 4K resolution 
            'mediaqualityheight':2200,
            'applymediapreferencetodownloadonlyalso' : 'no', 
            'manuallyselectformat' : 'no', 
            'preferaudioonly' : 'no', 
            'customformatoptions' : '', 
            'additionalyoutube-dlarguments' : '', 
            'previouslist' : 'Previous', 
            'watchlaterlist' : 'Watch Later', 
            'offlinemedia' : 'Offline', 
            'useterminal' : '', 
            'youtube-dl' : './Code/youtube-dl'}
        self.UpdateVariablesDictionaryFromConfigurationFile(
            ConfigurationFileTextLines)
        #RequiredVariables = ('mediaplayer')
        ExpandOneLetterArguments = {
            # 'm' -> play with alternate media player or media player
            # specified in argument.
            'm' : 'mediaplayer',
            'a' : 'preferaudioonly'}
        #Use default varibales value if value not specified in argument
        DefaultArgumentVariablesValue = {
            'preferaudioonly' : 'yes',
            'mediaplayer' : VD.get('alternatemediaplayer')}
        """ shouldn't both be considered:
        --variable=value & --variable value
        currently considering only former. Fix it.
        if i < len(Arguments):
            if (Argument[i+1].startswith('-') == False
                and Argument[i+1].strip(Quotes).casefold().startswith(VD['validmediaurlstart']) == False
                and (Argument[i+1].strip(Quotes).casefold().startswith(FileProtocol) == False):
                
                value = Argument[i+1]
        """
        for i in range(len(Arguments)):
            if Arguments[i].startswith('-'):
                Arg = Arguments[i]
                Seprator = Arg.find('=')
                if Seprator != -1:
                    Variable = Arg[:Seprator].strip('-')
                    if Variable in ExpandOneLetterArguments:
                        Variable = ExpandOneLetterArguments[Variable]
                    Value = Arg[Seprator + 1:]
                else:
                    Variable = Arg.strip('-')
                    if Variable in ExpandOneLetterArguments:
                        Variable = ExpandOneLetterArguments[Variable]
                    if Variable in DefaultArgumentVariablesValue:
                        Value = DefaultArgumentVariablesValue[Variable]
                    else:
                        Value = 'yes'
                VD[Variable] = Value
        UnmodifiableVD = {
            # Should instead do if str.lowercase() == VD[key][i]:
            'validmediaurlstart' :  ("http://", "https://"), 
            'validextensioncontaningmediaurl1' :  (".m3u", ".m3u8"), 
            # Find and replace possible conlicting symbols, atleast for
            # now, later either use youtube-dl without shell = True or
            # specify ASCII only characters in filenames to youtube-dl.
            # remove this when fix unicode issue fixed in windows but
            # if os=win32, specify *safe* characters only in youtube-dl
            'ReplaceTheseSymbols' : ('?', ' : ', '"', '/', '\\', '*', '<', '>', '|'), 
            'ReplaceWithSymbols' : ('¿', ';', '\'', '-', '-', '★', '{', '}', 'I')}
        VD.update(UnmodifiableVD)

    def UpdateVariablesDictionaryFromConfigurationFile(
        self, ConfigurationFileTextLines):
        
        VariableValueSeprator = '='
        for Lines in range(len(ConfigurationFileTextLines)):
            Line = ConfigurationFileTextLines[Lines]
            if len(Line) == 0 or Line.startswith('#') or Line.startswith('['):
                continue
            SepratorAt = Line.find(VariableValueSeprator)
            if SepratorAt != -1:
                VariableName = Line[:SepratorAt].strip(' ')
                VariableName = StandardVariableName(VariableName)
                VariableValue = Line[SepratorAt+1:].strip(' ')
                if len(VariableName) > 0 and len(VariableValue) > 0:
                    VD[VariableName] = VariableValue

    def GlobalVariables(self):
        """ Some global vairbales from variablesdictionary for
        neatness.
        """
        global AdditionalYoutubeDLArguments
        AdditionalYoutubeDLArguments = \
            VD['additionalyoutube-dlarguments']

    def ModifyCheckCreateDirectories(self):
        self.ChangePathSlashesIfWindowsOS()
        self.SetM3UFileAndOfflineMediaDirectory()
        Directories = (
            'offlinemedia',
            'previouslist',
            'watchlaterlist',
            'previouslist2',
            'watchlaterlist2')
        for i in range(len(Directories)):
            VD[Directories[i]] =(
                os.path.abspath(VD[Directories[i]]) + PathSlash)
        DirectoryUse = (
            'save media file(s) to this directory',
            'save media history to this directory',
            'save watch later list to this directory',
            'save media history to this directory',
            'save watch later list to this directory')
        self.CreateDirectories(
            Directories, DirectoryUse)

    def ChangePathSlashesIfWindowsOS(self):
        if sys.platform == 'win32':
            ModifyDirectories = (
                'previouslist',
                'watchlaterlist',
                'offlinemedia')
            for i in range(len(ModifyDirectories)):
                VD[ModifyDirectories[i]] = (
                    VD[ModifyDirectories[i]].replace('/', '\\'))

    def SetM3UFileAndOfflineMediaDirectory(self):
        """ Use StartedWorkingDirectory for saving/downloading media
        execept when same as HomeDirectory or sub directory of
        ProgrammeDirectory than use ProgrammeDirectory.
        """
        if (StartedWorkingDirectory.startswith(ProgrammeDirectory)
            or StartedWorkingDirectory == HomeDirectory):
            
            VD['previouslist2'] = VD['previouslist']
            VD['watchlaterlist2'] = VD['watchlaterlist']
        else:
            SetToStartedWorkingDirectory = (
                'previouslist2',
                'watchlaterlist2',
                'offlinemedia')
            for i in range(len(SetToStartedWorkingDirectory)):
                VD[SetToStartedWorkingDirectory[i]] = (
                    StartedWorkingDirectory)

    def CreateDirectories(
        self, Directories, DirectoryUse = ('unknown')):
        
        """ Directories which are in VD only. Not a generic function
        but specific
        ."""
        for i in range(len(Directories)):
            if os.path.isdir(VD[Directories[i]]) == False:
                NewDirectory = VD[Directories[i]]
                if NewDirectory.startswith(
                    ProgrammeDirectory) == True:
                    os.mkdir(NewDirectory)
                else:
                    print(
                        Directories[i]
                        + ' directory "'
                        + NewDirectory
                        +  '" does not exist. Type \'yes\' to create\
                        this directory and '
                        + DirectoryUse[i]
                        + '. Or simply press return key to exit.')
                    while True:
                        CreateOrNot = input()
                        if CreateOrNot == 'yes':
                            try:
                                os.mkdir(NewDirectory)
                                break
                            except:
                                print(
                                    "Could not create directory. "
                                    "Probably permission error. Still "
                                    "do not run as root/sudo/"
                                    "administrator. Instead try some "
                                    "other directory. Press return to "
                                    "exit.")
                                input()
                                sys.exit()
                        if CreateOrNot == '':
                            print('Goodbye!')
                            time.sleep(3)
                            sys.exit()
                        else:
                            print(
                                "Invalid input. Type either \'yes\' "
                                "or nothing.")
    
    def MediaPlayerVariables(self):
        global\
            MediaPlayer,\
                MediaPlayerOptions,\
                    MediaPlayerMultipleSourcePlayCommand
        if '--alternatemediaplayer' in Arguments or '-m' in Arguments:
            MediaPlayer = LookupInVD('alternatemediaplayer')
            if MediaPlayer != None:
                MediaPlayerOptions = VD['alternatemediaplayeroptions']
                MediaPlayerMultipleSourcePlayCommand = \
                    VD["alternatemediaplayermultiplesourcesplaycommand"]
                return
            else:
                print(
                    "Alternate media player not found in configuration file"
                    " or arguments. Trying to use primary media player.")
        MediaPlayer = self.LookupInVD("mediaplayer")
        if MediaPlayer != None:
            MediaPlayerOptions = VD["mediaplayeroptions"]
            MediaPlayerMultipleSourcePlayCommand = \
                VD["mediaplayermultiplesourcesplaycommand"]
        else:
            MediaPlayer = self.FindExecutable("Media Player")
            MediaPlayerOptions = ''
            self.EditConfigurationFile(
                "mediaplayeroptions", MediaPlayerOptions)
            MediaPlayerMultipleSourcePlayCommand =\
                self.LookInPreConfiguredCmdOfMediaPLayers()
            self.EditConfigurationFile(
                "mediaplayermultiplesourcesplaycommand",
                MediaPlayerMultipleSourcePlayCommand)
   
    def LookInPreConfiguredCmdOfMediaPLayers(self):
        CheckMediaPlayer = MediaPlayer
        PreConfiguredCmd = {
            "vlc" : "videosource :input-slave=audiosource",
            "mpv" : "videosource --audio-file=audiosource"}
        #Execeptions = ("gnome-mpv", "umpv")
        DoesNotSupportWarning = (
            "Media player may not support seprate audio source "
            "playback. Seprate video+audio formats will not be "
            "considered. If you know that specified media player "
            "supports seprate audio source than add that coommand to "
            "imm.conf file as:\n"
            "\n"
            "mediaplayermultiplesourcesplaycommand="
            "videosource --audio audiosource\n"
            "\n"
           "videosource and audiosource will be replaced with "
           "video url/file and audio url/file respectively.")
        CheckMediaPlayer = os.path.split(MediaPlayer)[1]
        if CheckMediaPlayer.endswith(".exe"):
            CheckMediaPlayer = CheckMediaPlayer[:-4]
        MediaPlayerCmd = PreConfiguredCmd.get(CheckMediaPlayer)
        if MediaPlayerCmd == None:
            print(DoesNotSupportWarning)
            return ''
        else:
            return MediaPlayerCmd
        #for Player, Cmd in PreConfiguredCmd.items():
            #if CheckMediaPlayer.endswith(Player):
                #if MediaPlayer.endswith(Execeptions):
                    #print(DoesNotSupportWarning)
                    #return ''
                #else:
                    #return Cmd
        #else:
            #print(DoesNotSupportWarning)
            #return ''
   
    def FindExecutable(self, ExecName):
        SampleExec = StandardVariableName(ExecName)
        Executable = self.LookupInVD(SampleExec)
        if Executable != None:
            return Executable
        print(
             ExecName + " not found in configuration nor specified in "
            "arguments. Enter " + ExecName + " run command (if any) "
            "like '" + SampleExec + "' or full path to executable like"
            " \"/usr/bin/" + SampleExec + "\" in GNU/Linux and other "
            "unix-like OS or \"c:\\Programme files\\" + ExecName + "\\"
            + SampleExec + ".exe\" in Windows")
        while True:
            Executable = input()
            Executable = FindFile(Executable)
            if Executable == None:
                print(
                    ExecName + " not found, try giving full path or "
                    "try again")
            else:
                self.EditConfigurationFile(SampleExec, Executable)
                break
        return Executable
    
    def LookupInVD(self, VDKey):
        Executable = VD.get(VDKey)
        return FindFile(Executable) #return None if not found


def Main():
    global MediaPlayer, YoutubeDL
    ProgrammeArguments()
    Config = Configuration()
    RunInTerminal()
    
    YoutubeDL = Config.FindExecutable("Youtube-DL")
    Config.MediaPlayerVariables()
    StartProcessing()

def GnomeMPVExp(PlayCommand):
    ConfigFile = HomeDirectory+"/.config/mpv/mpv.conf"
    ConfigList = ReadlinesTextFile(ConfigFile)
    ConfigListBak = tuple(ConfigList)
    print(PlayCommand)
    PlayCommand = shlex.split(PlayCommand)[1:]
    print(PlayCommand)
    input()
    for i in range(len(PlayCommand)):
        ConfigList.append(PlayCommand[i])
    WriteGeneralTextFiles(ConfigList, ConfigFile)
    time.sleep(5)
    os.system("gnome-mpv")
    time.sleep(50)
    WriteGeneralTextFiles(ConfigListBak, ConfigFile)
    
    

#==================================

Main()
