import os
try:
    from os import sep
except:
    sep = os.getcwd()[0]
from time import localtime,struct_time
from sys import stdin,implementation,path
try:
    from traceback import print_exception,format_exception
except:
    from sys import print_exception as sysprexcept
    print_exception = lambda err,value=None,tb=None: sysprexcept(err)
    format_exception = lambda err,value=None,tb=None: [err]
if not sep+'lib' in path:
    path.insert(1,sep+'lib')
path.append(f'{sep}apps{sep}PyBasic')
try:
    from pydos_ui import Pydos_ui
except ImportError:
    Pydos_ui = None
try:
    from pydos_ui import input
except ImportError:
    pass
try:
    from rtc import RTC
    clockavail = True
except ImportError:
    clockavail = False

import gc
imp = "B"
if implementation.name.upper() == "MICROPYTHON":
    from micropython import mem_info
    readonly = False
    imp = "M"
elif implementation.name.upper() == "CIRCUITPYTHON":
    from supervisor import runtime, reload
    import storage
    import microcontroller
    import displayio
    from adafruit_argv_file import argv_filename
    readonly = storage.getmount("/").readonly

    imp = "C"

gc.collect()
if 'threshold' in dir(gc):
    gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())

# The first string may contain wildcards
def _match(first, second):

    # If we reach end of both strings, we are done
    if len(first) == 0 and len(second) == 0:
        return True

    # Make sure that the characters after '*' are present
    # in second string. Can't contain two consecutive '*'
    if len(first) > 1 and first[0] == '*' and  len(second) == 0:
        return False

    if (len(first) > 1 and first[0] == '?') or (len(first) != 0
        and len(second) !=0 and first[0] == second[0]):
        return _match(first[1:],second[1:])

    if first[:1] == '*':
        return _match(first[1:],second) or _match(first,second[1:])

    return False

def calcWildCardLen(wldCLen,recursiveFail):
    wldCLen += 1
    if not recursiveFail and wldCLen < 115:
        try:
            (wldCLen,recursiveFail) = calcWildCardLen(wldCLen,recursiveFail)
        except:
            recursiveFail = True

    return (wldCLen,recursiveFail)

def PyDOS():

    global envVars
    if "envVars" not in globals().keys():
        envVars = {}
    _VER = "1.56-fruitjam"
    prmpVals = ['>','(',')','&','|','\x1b','\b','<','=',' ',_VER,'\n','$','']

    print("Starting Py-DOS... Type 'help' for help.")
    if readonly:
        print("Warning: Py-DOS is running in read-only mode, some commands may not work.")
        ans = 'N'
        while ans[:1].upper() != "C" and ans[:1].upper() != "R":
            ans = input("Press 'C' to continue or 'R' to restart in read-write mode: ")
            if ans[:1].upper() == "R":
                print("\nNote: You can not modify files using the CIRCUITPY drive from a")
                print("connected host computer in read-write mode. After restarting,")
                print("you can use the 'readonly' command in PyDOS to return to read-only mode.\n")
                if input('Are you sure? (Y/N): ').upper() == 'Y':
                    boot_args_file = argv_filename("/boot.py") 
                    with open(boot_args_file, "w") as f: 
                        f.write('[false, "/code.py"]')
                    microcontroller.reset()
                else: 
                    break
            elif ans[:1].upper() == "C":
                break
            else:
                print("\nInvalid entry",ans[:1])
            

    envVars["PATH"] = f'{sep};{sep}apps{sep}PyDOS;{sep}apps{sep}PyBasic'
    envVars["PROMPT"] = "$P$G"
    envVars["LIB"] = ";".join(path[1:])
    envVars["DIRSEP"] = sep
    if Pydos_ui:
        (envVars["_scrHeight"],envVars["_scrWidth"]) = Pydos_ui.get_screensize()
    else:
        try:
            envVars["_scrHeight"] = runtime.display.root_group[0].height
            envVars["_scrWidth"] = runtime.display.root_group[0].width - 1
        except:
            envVars["_scrHeight"] = 24
            envVars["_scrWidth"] = 89
    scrWdth = int(envVars["_scrWidth"])

    wldCLen = 0
    recursiveFail = False

    (wldCLen,recursiveFail) = calcWildCardLen(wldCLen,recursiveFail)
    wldCAdj = int(1+.2*wldCLen)
    if imp == "C":
        wldCAdj += 5
    wldCLen = max(1,wldCLen-wldCAdj)

    if wldCLen < 60:
        print("Wild card length set to: ",wldCLen)
    gc.collect()

    aFile = lambda dPth: bool(os.stat(dPth)[0]&(32768))

    def setdate(newDate):
        mdays = [0,31,29,31,30,31,30,31,31,30,31,30,31]

        if newDate != "":
            inDate = newDate.split('-')

            if len(inDate) != 3:
                print("Bad Date format")
            elif int(inDate[0]) not in range(1,13):
                print("Invalid month entered (1-12)")
            elif int(inDate[1]) not in range(1,mdays[int(inDate[0])]+1):
                print("invalid day entered (1-",mdays[int(inDate[0])],")")
            elif int(inDate[2]) not in range(21,32):
                print("invalid year entered (21-31)")
            else:
                RTC().datetime = struct_time((2000+int(inDate[2]),int(inDate[0]),int(inDate[1]), \
                    RTC().datetime[3],RTC().datetime[4],RTC().datetime[5],RTC().datetime[6],-1,-1))

    def settime(newTime):
        if newTime != "":
            inTime = newTime.split(':')

            if len(inTime) != 3:
                print("Bad time format")
            elif int(inTime[0]) not in range(0,25):
                print("Invalid hour entered (0-24)")
            elif int(inTime[1]) not in range(0,61):
                print("invalid minute entered (0-60)")
            elif int(inTime[2]) not in range(0,61):
                print("invalid seconds entered (0-60)")
            else:
                RTC().datetime = struct_time((RTC().datetime[0],RTC().datetime[1],RTC().datetime[2], \
                    int(inTime[0]),int(inTime[1]),int(inTime[2]),RTC().datetime[6],-1,-1))

    def anyKey():
        print("Press any key to continue . . . ."[:scrWdth],end="")
        if Pydos_ui:
            while not Pydos_ui.serial_bytes_available():
                pass
            keyIn = Pydos_ui.read_keyboard(1)
        else:
            if imp == "C":
                while not runtime.serial_bytes_available:
                    pass
            keyIn = stdin.read(1)
        print("")
        return(keyIn)

    def scrnPause(swPause,nLines,scrnLine,scrnEnd=None):
        quit = False
        i = 0
        for sLine in scrnLine:
            i += 1
            if swPause and nLines >= int(envVars["_scrHeight"])-1:
                key = anyKey()
                nLines = 0
                if key in "QqCc":
                    quit = True
                    break
            if sLine is not None:
                if scrnEnd is None or i<len(scrnLine):
                    print(sLine)
                    nLines += 1
                else:
                    print(sLine,end="")
        return (quit,nLines)

    def exCmd(cFile,passedIn):
        try:
            with open(cFile) as cf:
                if passedIn.find("'") > -1:
                    exec(f'passedIn = "{passedIn}"\n{cf.read()}')
                else:
                    exec(f"passedIn = '{passedIn}'\n{cf.read()}")
        except Exception as err:
            print_exception(err,err, \
                err.__traceback__ if hasattr(err,'__traceback__') else None)
            envVars['lasterror'] = format_exception(err,err, \
                err.__traceback__ if hasattr(err,'__traceback__') else None)
        except KeyboardInterrupt:
            print("^C")

        if imp == "C":
            if "_display" not in envVars.keys():
                if "display" in dir(Pydos_ui):
                    envVars["_display"] = Pydos_ui.display
                elif "display" in dir(runtime) and runtime.display is not None:
                    envVars["_display"] = runtime.display
                elif "display" in dir(board):
                    envVars["_display"] = board.display

            # If _displayTerm is set to "N", do not restore terminal to display
            if "_display" in envVars.keys() and envVars["_display"] is not None \
                and envVars.get("_displayTerm","Y")[0].upper() != "N":

                if envVars["_display"].root_group != displayio.CIRCUITPYTHON_TERMINAL:
                    envVars["_display"].root_group = displayio.CIRCUITPYTHON_TERMINAL
            
                envVars["_display"].auto_refresh = True
                envVars["_scrHeight"] = envVars["_display"].root_group[0].height
                envVars["_scrWidth"] = envVars["_display"].root_group[0].width - 1

        return

    def chkPath(tstPath):
        validPath = True
        simpPath = ""

        if tstPath != []:
            savDir = os.getcwd()
            for path in tstPath:
                if path[1:2] == ":":
                    path = path[2:]
                if path == "":
                    os.chdir(sep)
                elif os.getcwd() == sep and path == "..":
                    validPath = False
                    break
                elif path == ".":
                    continue
                elif path == ".." and len(os.getcwd().split(sep)) == 2:
                    os.chdir(sep)
                elif path == "..":
                    os.chdir("..")
                elif path in os.listdir() and not aFile(path):
                    os.chdir(path)
                else:
                    validPath = False
                    break

            if validPath:
                simpPath = os.getcwd()
            os.chdir(savDir)

        return((validPath,simpPath))

    def pFmt(dPath,trailsep=True):
        if dPath == "":
            return sep
        elif dPath == sep:
            return dPath
        elif trailsep:
            return dPath+(sep if dPath[-1]!=sep else "")
        else:
            return dPath[:(-1 if dPath[-1] == sep else None)]

    def absolutePath(argPath,currDir):

        if argPath[:1] == sep:
            fullPath = argPath
        elif currDir == sep:
            fullPath = sep+argPath
        else:
            fullPath = currDir+sep+argPath

        fullPath = pFmt(fullPath,False)

        return(fullPath)

    srtFnc = lambda v,dP: f"{str(os.stat(dP+v)[0]&(32768))[0]}{v.lower()}*{v}"

    def dirLoop(tmpDir,lastDir,isFile,swPause,swWide,swRecur,prSum, \
        nLines=0,nFiles=0,tFSize=0,nDirs=0):

        wideCols = scrWdth//16
        wideCount = 0
        dirHeadPrntd = False
        quit = False

        if "*" in lastDir or "?" in lastDir or isFile:
            dirPat = lastDir
            lastDir = ""
        else:
            dirPat = None

        dPath = pFmt(pFmt(tmpDir)+lastDir,False)
        if dPath == sep+".":
            dPath = sep
            lastDir = ""

        if dirPat is None:
            (quit,nLines) = scrnPause(swPause,nLines,["","Directory of "+dPath.replace('/',('\\' if envVars.get('DIRSEP','/') == '\\' else '/'))])
            dirHeadPrntd = True
            nDirs += 2
            if swWide:
                if wideCols > 1:
                    (quit,nLines) = scrnPause(swPause,nLines, \
                        ["[.]             [..]            "],"")
                    wideCount += 2
                else:
                    (quit,nLines) = scrnPause(swPause,nLines,["[.]","[..]"],"")
                    wideCount = 1
            else:
                scrAdj1 = 52 - min(scrWdth,52)
                (quit,nLines) = scrnPause(swPause,nLines, \
                    [f'.{" "*(23-scrAdj1)}<DIR>',f'..{" "*(22-scrAdj1)}<DIR>'])

        for _dir in sorted([srtFnc(x,pFmt(dPath)) for x in os.listdir(dPath)]):
            _dir = _dir.split('*')[1]

            if (dirPat is None or _match(dirPat,_dir[:wldCLen])) and not quit:
                dStat = os.stat(f"{pFmt(dPath)}{_dir}")
                ForD = aFile(f"{pFmt(dPath)}{_dir}")

                if not dirHeadPrntd:
                    (quit,nLines) = scrnPause(swPause,nLines,["","Directory of "+dPath.replace('/',('\\' if envVars.get('DIRSEP','/') == '\\' else '/'))])
                    if quit:
                        break
                    dirHeadPrntd = True

                if not ForD:
                    fSize = 0
                    nDirs += 1
                else:
                    fSize = str(dStat[6])
                    tFSize += int(fSize)
                    nFiles += 1

                if swWide:
                    if wideCount >= wideCols:
                        wideCount = 0
                        print()
                        nLines += 1
                    wideCount += 1
                    if not ForD:
                        (quit,nLines) = scrnPause(swPause,nLines, \
                            [f'[{_dir[:13]}]{" "*(14-len(_dir[:13]))}'],"")
                    else:
                        (quit,nLines) = scrnPause(swPause,nLines, \
                            [_dir[:15]+" "*(16-len(_dir[:15]))],"")
                else:

                    fTime = localtime(max(min(2145916800,dStat[9]),946684800))
                    if not ForD:
                        scrAdj1 = 52 - min(scrWdth,52)
                        scrAdj2 = min(13,65-min(scrWdth,65))
                        (quit,nLines) = scrnPause(swPause,nLines, \
                            [f'{_dir[:max(8,scrWdth-26)]}{" "*(24-len(_dir)-scrAdj1)}<DIR>{" "*(18-scrAdj2)}{fTime[1]:02}-{fTime[2]:02}-{fTime[0]:04} {fTime[3]:02}:{fTime[4]:02}'])
                    else:
                        scrAdj1 = 65 - min(scrWdth,65)
                        (quit,nLines) = scrnPause(swPause,nLines, \
                            [f'{_dir[:max(8,scrWdth-20-len(fSize))]}{" "*(36-len(_dir)+10-len(fSize)-scrAdj1)}{fSize} {fTime[1]:02}-{fTime[2]:02}-{fTime[0]:04} {fTime[3]:02}:{fTime[4]:02}'])

                if quit:
                    break

        if not quit:
            if swWide:
                if dirHeadPrntd:
                    print()
                    nLines += 1

            if swRecur:
                for _dir in sorted(os.listdir(dPath), key=str.upper):
                    dStat = pFmt(dPath)+_dir
                    if not aFile(dStat):
                        try:
                            (nLines,nFiles,tFSize,nDirs,quit) = \
                                dirLoop(dStat,(dirPat if dirPat is not None else ""), \
                                    isFile,swPause,swWide,swRecur,False, \
                                    nLines,nFiles,tFSize,nDirs)
                        except:
                            print("Recursion limit exceeded, Pystack too small")
                            quit = True
                    if quit:
                        break

            if prSum and not quit:
                try:
                    availDisk = os.statvfs(dPath)[1]*os.statvfs(dPath)[4]
                except:
                    availDisk = 0

                scrAdj1 = 65 - min(scrWdth,65)
                (quit,nLines) = scrnPause(swPause,nLines, \
                    [(f'{" "*(4-len(str(nFiles)))} {nFiles} File(s){" "*(32-len(str(tFSize))-scrAdj1)} {tFSize} Bytes.')[:scrWdth], \
                    (f'{" "*(4-len(str(nDirs)))} {nDirs} Dir(s){" "*(33-len(str(availDisk))-scrAdj1)} {availDisk} Bytes free.')[:scrWdth],""],"")

        return (nLines,nFiles,tFSize,nDirs,quit)

    def prDir(dirPath,swBits):
        if swBits & (swAllB-int('010110',2)):
            print("Illegal switch, Command Format: DIR[/p][/w][/s] [path][file]")
            return

        savDir = os.getcwd()
        fullPath = absolutePath(dirPath,savDir)

        pathDirs = fullPath.split(sep)
        lastDir = pathDirs.pop(-1)

        (validPath, tmpDir) = chkPath(pathDirs)

        if validPath:

            os.chdir(tmpDir)

            if tmpDir == sep:
                pathDirs = [""]
            else:
                pathDirs = tmpDir.split(sep)

            # Check for relative directory from possible mount point root
            if len(pathDirs) == 2:
                if lastDir == ".":
                    os.chdir(sep)
                    lastDir = tmpDir[1:]
                elif lastDir == "..":
                    os.chdir(sep)
                    lastDir = ""

            tmpDir = os.getcwd()

            if lastDir in os.listdir() or lastDir in ".." or "*" in lastDir or "?" in lastDir or \
                swBits & int('000010',2):

                if lastDir in os.listdir():
                    if aFile(pFmt(lastDir,False)):
                        isFile = True
                    else:
                        isFile = False
                else:
                    if lastDir in ".." or (tmpDir == sep and lastDir == ""):
                        isFile = False
                    else:
                        isFile = True

                dirLoop(tmpDir,lastDir,isFile,bool(swBits&int('000100',2)),bool(swBits&int('010000',2)),bool(swBits&int('000010',2)),True)
            else:
                print("File",dirPath,"not found. (1)")

            os.chdir(savDir)
        else:
            print("File",dirPath,"not found. (2)")

        return

    def filecpy(file1,file2):
        gc.collect()
        with open(file2, "wb") as fCopy:
            with open(file1, 'rb') as fOrig:
                for line in fOrig:
                    fCopy.write(line)
        return

    def delFiles(Dir,File,Recurs,removDirs):
        for _dir in os.listdir(pFmt(Dir,False)):
            if _match(File,_dir[:wldCLen]):
                if File == "*" or File == "*.*" or _dir == _dir[:wldCLen]:
                    if aFile(Dir+_dir):
                        try:
                            os.remove(Dir+_dir)
                            print((Dir+_dir).replace('/',('\\' if envVars.get('DIRSEP','/') == '\\' else '/')),"deleted.")
                        except Exception as err:
                            print(f"Unable to delete: {Dir}{_dir}, Exception: {err}")
                            break
                    elif Recurs:
                        delFiles(Dir+_dir+sep,'*',Recurs,removDirs)
                        if removDirs:
                            if os.getcwd() == Dir+_dir:
                                os.chdir("..")
                            try:
                                os.rmdir(Dir+_dir)
                            except Exception as err:
                                print(f"Unable to remove: {Dir}{_dir}, Exception: {err}")
                                break
                else:
                    print("Unable to delete: "+Dir+_dir+". Filename too long for wildcard operation.")
            elif Recurs and not removDirs and not aFile(Dir+_dir):
                delFiles(Dir+_dir+sep,File,Recurs,removDirs)

    def setCondCmd(args,i,condResult):
        condCmd = ""
        foundElse = False

        for _ in args[i:]:

            if condResult:
                if _.upper() == "ELSE":
                    break
                condCmd += (_+" ")
            else:
                if foundElse:
                    condCmd += (_+" ")
                else:
                    if _.upper() == "ELSE":
                        foundElse = True

        return condCmd

    def readBATFile(BATfile):
        batIndex = [0]
        batLabels = {}
        batLineNo = 0
        for batLine in BATfile:
            batIndex.append(batIndex[batLineNo]+len(batLine))
            batLineNo += 1
            if batLine.strip()[:1] == ":" and len(batLine.strip().split(" ")[0]) > 1:
                batLabels[batLine.strip().split(" ")[0][1:]] = [batLineNo,batIndex[batLineNo]]
        BATfile.seek(0)
        del batIndex,batLineNo

        return batLabels

    batEcho = True
    cmd = ""
    condCmd = ""
    os.chdir(sep)
    if "autoexec.bat" in ",".join(os.listdir()).lower().split(','):
        activeBAT = True
        BATfile = open(os.listdir()[(",".join(os.listdir()).lower().split(",")).index("autoexec.bat")])
        batLabels = readBATFile(BATfile)
        batLineNo = 0
        batParams = []
    else:
        activeBAT = False
        gc.collect()

    while True:
        scrWdth = int(envVars["_scrWidth"])
        if condCmd != "":
            cmdLine = condCmd
            condCmd = ""
        else:
            if activeBAT:
                cmdLine = BATfile.readline()
                i=1
                for param in batParams:
                    cmdLine = cmdLine.replace(f'%{i}',param)
                    i+=1
                    if i>9:
                        break

                batLineNo += 1
                if cmdLine == "":
                    activeBAT = False
                    batEcho = True
                    BATfile.close()
                    gc.collect()
                elif batEcho and cmdLine[0] !="@":
                    print(cmdLine,end="")
                elif cmdLine[0] == "@":
                    cmdLine = cmdLine[1:]

            else:
                prompt = "\n"
                prmpLitrl = True
                for prmpToken in envVars.get('PROMPT','$P$G').replace("$$","$."):
                    if prmpToken == '$':
                        prmpLitrl = False
                        continue
                    if prmpLitrl:
                        prompt += prmpToken
                    else:
                        prmpToken = prmpToken.upper()
                        prmpLitrl = True
                        if prmpToken == 'R':
                            if 'mem_free' in dir(gc):
                                prompt += str(gc.mem_free())
                        elif prmpToken == 'D':
                            prompt += f"{localtime()[1]:02}/{localtime()[2]:02}/{localtime()[0]:04}"
                        elif prmpToken == 'T':
                            prompt += f"{localtime()[3]:02}:{localtime()[4]:02}:{localtime()[5]:02}"
                        elif prmpToken == 'P':
                            prompt += os.getcwd().replace('/',('\\' if envVars.get('DIRSEP','/') == '\\' else '/'))
                        else:
                            prompt += prmpVals['GCFABEHLQSV_.'.find(prmpToken)]
                cmdLine = input(prompt)

        cmdLine = cmdLine.strip()

        envFound = False
        fndVar = ""
        newCmdLine = ""
        for _ in cmdLine:
            if not envFound:
                if _ == "%":
                    envFound = True
                    doublepct = True
                else:
                    newCmdLine += _
            else:
                if _ == "%":
                    if doublepct:
                        newCmdLine += "%"
                    envFound = False
                    newCmdLine += str(envVars.get(fndVar,""))
                    fndVar = ""
                else:
                    doublepct = False
                    fndVar += _

        if envVars.get('DIRSEP','/') == '\\':
            switches = []
            switch = ""
            nxt = False
            cmdLine = newCmdLine[:1].replace('\\','/')

            for _ in newCmdLine[1:]:
                if nxt and _ not in " /":
                    switch += _.upper()
                elif _ == '/':
                    nxt = True
                    if switch != "":
                        switches.append(switch)
                        switch = ""
                elif nxt and _ == " ":
                    cmdLine += _
                    nxt = False
                    switches.append(switch)
                    switch = ""
                elif _ == "\\":
                    cmdLine += sep
                else:
                    cmdLine += _
            if switch != "":
                switches.append(switch)
        else:
            cmdLine = newCmdLine

        args = cmdLine.split(" ")

        quotedArg = False
        if len(args) > 1:
            i = 0
            iEnd = len(args)
            for _ in range(0, iEnd):
                if args[i].strip() == "":
                    args.pop(i)
                elif quotedArg:
                    if args[i].find('"') > -1 and args[i].find('"') != len(args[i])-1:
                        break
                    elif args[i][-1] == '"':
                        args[i-1] = f"{args[i-1]} {args.pop(i)[:-1]}"
                        quotedArg = False
                    else:
                        args[i-1] = f"{args[i-1]} {args.pop(i)}"
                elif args[i][0] == '"':
                    if args[i][-1] != '"':
                        args[i] = args[i][1:]
                        i += 1
                        quotedArg = True
                    else:
                        args[i] = args[i][1:-1]
                        i += 1
                else:
                    i += 1

        if cmdLine != "" and cmdLine[0] != '/':
            tmp = (args[0].upper()).split('/')
            if envVars.get('DIRSEP','/') != '\\':
                switches = tmp
            cmd = tmp.pop(0)
        else:
            if envVars.get('DIRSEP','/') != '\\':
                switches = []
            cmd = args[0]

        # Error=1, (S)Recur=2, (P)Pause=4, (Y)Conf=8, (W)Wide=16, (D)debug=32, (Q)uiet=64
        # (V)erify=128
        swBits = 0
        swAllB = int('11111111',2)
        for i in range(len(switches)):
            swBits = swBits | (2**('SPYWDQV'.find(switches[i])+1))

        if quotedArg:
            print("Mismatched quotes.")
            cmd = ""

        if cmd in ["DELETE","DEL","TYPE","MORE","MKDIR","MD","RMDIR","RD","COPY", \
                   "CHDIR","CD","RENAME","REN","MOVE","DELTREE"]:
            if len(args) > 1:
                savDir = os.getcwd()
                args[1] = absolutePath(args[1],savDir)
                aPath = args[1].split(sep)
                if cmd not in ["RMDIR","RD","CHDIR","CD","DELTREE"]:
                    newdir = aPath.pop(-1)
                (validPath,tmpDir) = chkPath(aPath)
                if cmd in ["DELETE","DEL","TYPE","MORE","MKDIR","MD"]:
                    tmpDir = pFmt(tmpDir)
                else:
                    tmpDir = pFmt(tmpDir,False)

        if cmd == "" or cmd == "REM":
            continue
        elif cmd == "HELP":
            print("File Commands: DIR[/p][/w][/s], RENAME, DEL[/s], TYPE[/p], CD, MKDIR, RMDIR[/s], COPY[/y]")
            print("Environment Commands: HELP, SET[/p][/a], PROMPT, PATH, READONLY")
            print("Operating System Commands: EXIT, VER, MEM, DATE [mm-dd-yy], TIME [hh:mm:ss]")
            print("Batch Commands: GOTO, IF, ECHO, PAUSE")
            print("Command to execute a single Python command: PEXEC [command]")
            print("Run a Python program: [path]program[.py]")
            print("Run a DOS batch file: [path]program[.bat]")
        elif cmd == "DIR":
            if len(args) == 1:
                prDir(os.getcwd()[(2 if os.getcwd()[1:2]==":" else 0):],swBits)
            elif len(args) == 2:
                prDir(args[1],swBits)
            else:
                print("Too many arguments. Command Format: DIR/p/w/s [path][file]")

        elif cmd == "DATE":
            if len(args) <=2:
                if len(args) == 2:
                    if clockavail:
                        setdate(args[1])
                    else:
                        print("Real Time Clock (rtc) not available on board")
                i = localtime()[6]*3
                print(f'The current date is: {"MonTueWedThuFriSatSun"[i:i+3]} {localtime()[1]:02}/{localtime()[2]:02}/{localtime()[0]:04}')
            else:
                print("Too many arguments. Command Format: DATE [mm-dd-yy]")

        elif cmd == "TIME":
            if len(args) <= 2:
                if len(args) == 2:
                    if clockavail:
                        settime(args[1])
                    else:
                        print("Real Time Clock (rtc) not available on board")
                print(f'The current time is: {localtime()[3]%12}:{localtime()[4]:02}:{localtime()[5]:02} {["AM","PM"][localtime()[3]//12]}')
            else:
                print("Too many arguments. Command Format: TIME [hh:mm:ss]")

        elif cmd == "MEM":
            gc.collect()
            print(f"\n{gc.mem_free()/1024:10.1f} Kb free conventional memory")
            print(f"{gc.mem_alloc()/1024:10.1f} Kb used conventional memory")
            if imp == "M":
                if 'threshold' in dir(gc):
                    print(f"{gc.threshold()/1024:10.1f} Kb current threshold value")

                if swBits & int('010000',2):
                    print(mem_info(1))
                elif swBits:
                    print("Illegal switch, Command Format: mem[/d]")

        elif cmd == "VER":
            print(f"PyDOS [Version {_VER}]")

        elif cmd == "ECHO":
            if len(args) == 1:
                print("Echo is "+("on." if batEcho else "off."))
            else:
                if args[1].upper() == 'ON':
                    batEcho = True
                elif args[1].upper() == 'OFF':
                    batEcho = False
                else:
                    print(cmdLine[5:])

        elif cmd == "PAUSE":
            anyKey()

        elif cmd[0] == ":" and activeBAT:
            if len(args[0]) <= 1 or len(args) != 1:
                print("Invalid batch label")
                condCmd = "exit"

        elif cmd == "GOTO" and activeBAT:
            if len(args) == 2:
                try:
                    BATfile.seek(batLabels[args[1]][1])
                except:
                    print("Invalid Goto label:",args[1])
                    condCmd = "exit"

                batLineNo = batLabels.get(args[1],[batLineNo+1,0])[0]
            else:
                print("Invalid Goto label:",cmdLine)
                condCmd = "exit"

        elif cmd == "IF" and activeBAT:
            condResult = False

            if len(args) < 3:
                print("Invalid command format:",cmdLine)
                condCmd = "exit"
            else:
                i = 1
                notlogic = False
                if args[1].upper() == "NOT":
                    notlogic = True
                    i = 2

                if args[i].strip().upper() == 'ERRORLEVEL':
                    i += 1
                    if len(args) > i and args[i].isdigit():
                        if str(envVars.get('errorlevel')).isdigit() and int(envVars.get('errorlevel')) == int(args[i]):
                            condResult = True

                        if notlogic:
                            condResult = not condResult

                        i += 1
                        condCmd = setCondCmd(args,i,condResult)
                    else:
                        print("Invalid conditional ERRORLEVEL:",cmdLine)
                        condCmd = "exit"

                elif args[i].strip().upper() == 'EXIST':
                    i += 1
                    if len(args) > i:
                        savDir = os.getcwd()
                        args[i] = absolutePath(args[i],savDir)

                        aPath = args[i].split(sep)
                        newdir = aPath.pop(-1)
                        (validPath, tmpDir) = chkPath(aPath)
                        tmpDir = pFmt(tmpDir)

                        if validPath and newdir in os.listdir(pFmt(tmpDir,False)):
                            condResult = True
                        if notlogic:
                            condResult = not condResult

                        i += 1
                        condCmd = setCondCmd(args,i,condResult)

                    else:
                        print("Invalid conditional EXIST:",cmdLine)
                        condCmd = "exit"
                else:
                    # string comparison
                    if len(args) > i:
                        string1 = args[i]
                        if "==" in args[i]:
                            string1 = args[i].split("==")[0]
                            if args[i][-1] != "=":
                                string2 = args[i].split("==")[-1]
                            else:
                                i += 1
                                if len(args) > i:
                                    string2 = args[i]
                                else:
                                    print("Invalid string conditional:",cmdLine)
                                    condCmd = "exit"
                        elif len(args) > i+1:
                            i += 1
                            if "==" in args[i]:
                                if len(args[i]) > 2:
                                    string2 = args[i].split("==")[-1]
                                else:
                                    i+=1
                                    if len(args) > i:
                                        string2 = args[i]
                                    else:
                                        print("Invalid string conditional:",cmdLine)
                                        condCmd = "exit"
                            else:
                                print("Invalid string conditional:",cmdLine)
                                condCmd = "exit"
                        else:
                            print("Invalid string conditional:",cmdLine)
                            condCmd = "exit"


                        if condCmd != "exit":
                            if string1 == string2:
                                condResult = True
                            if notlogic:
                                condResult = not condResult

                            i += 1
                            condCmd = setCondCmd(args,i,condResult)
                    else:
                        print("Invalid string conditional:",cmdLine)
                        condCmd = "exit"

        elif cmd == "SET":
            if len(args) == 1:
                for _ in sorted(envVars):
                    print(f"{_}={envVars[_]}")
            else:
                args = cmdLine.split(" ")
                args.pop(0)
                envCmd = (" ".join(args)).split("=")
                envCmdVar = envCmd.pop(0).strip()

                if len(switches) <= 1:
                    if len(switches) == 0:
                        tmp = "=".join(envCmd).strip()
                    elif switches[0] == 'A':
                        # Replace all possible environment variables with their values
                        envCmd = "=".join(envCmd)
                        for _ in " %*()-+/":
                            envCmd = envCmd.replace(_," ")
                        envCmd = envCmd.split(" ")

                        for _ in envCmd:
                            if _[:1].isalpha() or _[:1] == "_":
                                cmdLine = cmdLine.replace(_.strip(),str(envVars.get(_,0)))

                        # Evaluate right sight of = after value substituion
                        args = cmdLine.split(" ")
                        args.pop(0)
                        envCmd = (" ".join(args)).split("=")
                        envCmdVar = envCmd.pop(0).strip()
                        try:
                            envVars[envCmdVar] = str(eval("=".join(envCmd).strip()))
                        except:
                            envVars[envCmdVar] = "0"
                    elif switches[0] == "P":
                        tmp = input("=".join(envCmd).strip()+" ")
                    else:
                        print("Illegal switch, Command Format: SET[/a|/p] [variable = [string|expression]]")

                    if len(switches) == 0 or switches[0] == "P":
                        if tmp != "":
                            envVars[envCmdVar] = tmp
                        elif envCmdVar == "_scrHeight" or envCmdVar == "_scrWidth":
                            if Pydos_ui:
                                (tHeight,tWidth) = Pydos_ui.get_screensize(envVars.get('_display'))
                            else:
                                tHeight = 29
                                tWidth = 79
                            if envCmdVar == "_scrWidth":
                                envVars[envCmdVar] = tWidth
                            else:
                                envVars[envCmdVar] = tHeight
                        elif envVars.get(envCmdVar) != None:
                            envVars.pop(envCmdVar)

                    scrWdth = int(envVars["_scrWidth"])
                    if envCmdVar == 'LIB':
                        path.clear()
                        path.extend(['']+envVars.get("LIB","").split(';'))
                else:
                    print("Illegal switch, Command Format: SET[/a|/p] [variable = [string|expression]]")

        elif cmd in ["PROMPT","PATH"]:
            if len(args) == 1:
                print(cmd+"="+envVars.get(cmd,""))
            else:
                envVars[cmd] = args[1]

        elif cmd in ["RENAME","REN","MOVE"]:
# todo: allow source to be file and target directory?
# Wildcard renames
# renames across sd mount points

            if len(args) == 3:
# Check that first argument has a valid path and exists
                if validPath and newdir in os.listdir(tmpDir) and args[1][-1] != sep:

                    args[2] = absolutePath(args[2],savDir)
                    aPath2 = args[2].split(sep)
                    newdir2 = aPath2.pop(-1)
                    (validPath, tmpDir2) = chkPath(aPath2)

                    if newdir2 == '*':
                        newdir2 = newdir
# Second argument has valid path
                    if validPath and args[2][-1] != sep and '?' not in newdir2 and \
                        '*' not in newdir2 and newdir2 !="." and newdir2 != "..":

# second argument doesn't specify an existing target
                        if newdir2 not in os.listdir(tmpDir2):
                            currDRen = False
                            if not aFile(tmpDir+sep+newdir):
                                if tmpDir+sep+newdir == os.getcwd():
                                    currDRen = True
                            os.rename(pFmt(tmpDir)+newdir,pFmt(tmpDir2)+newdir2)
                            if currDRen:
                                os.chdir(tmpDir2)

                        else:
                            print("Target file exists")
                    else:
                        print("Invalid target:",args[2])
                else:
                    print("Invalid source:",args[1])
            else:
                print("Wrong number of arguments")

        elif cmd in ["DELETE","DEL"]:

            if len(args) == 2:
                if validPath:
                    if not (swAllB-int('000010',2)) & swBits:
                        if "*" in newdir or "?" in newdir:
                            ans = "Y"
                            if newdir == "*" or newdir == "*.*":
                                ans = input(tmpDir+newdir+", Are you sure (y/n)? ").upper()

                            if ans == "Y":
                                delFiles(tmpDir,newdir,bool(swBits&int('000010',2)),False)
                        else:
                            if newdir in os.listdir(pFmt(tmpDir,False)):
                                if aFile(tmpDir+newdir):
                                    os.remove(tmpDir+newdir)
                                    print((tmpDir+newdir).replace('/',('\\' if envVars.get('DIRSEP','/') == '\\' else '/')),"deleted.")
                                else:
                                    ans = input(tmpDir+newdir+sep+"*, Are you sure (y/n)? ").upper()
                                    if ans == "Y":
                                        delFiles(tmpDir+newdir+sep,'*',bool(swBits&int('000010',2)),False)
                            else:
                                if swBits & int('000010',2):
                                    ans = input(tmpDir+newdir+sep+"*, Are you sure (y/n)? ").upper()
                                    if ans == "Y":
                                        delFiles(tmpDir,newdir,True,False)
                                else:
                                    print(f"Unable to delete: {tmpDir}{newdir}. File not found.")
                    else:
                        print("Illegal switch, Command Format: DEL[/s] [path][file]")
                else:
                    print(f"Unable to delete: {tmpDir}{newdir}. File not found.")
            else:
                print("Illegal Path.")

        elif cmd in ["TYPE","MORE"]:

            if len(args) == 2:
                if validPath and newdir in os.listdir(pFmt(tmpDir,False)) and aFile(tmpDir+newdir):
                    if cmd == "MORE":
                        swBits = swBits | int('000100',2)

                    if not ((swAllB-int('000100',2)) & swBits):
                        swPause = bool(swBits & int('000100',2))
                        key = " "
                        with open(tmpDir+newdir, "rb") as f:
                            nLines = 0
                            for line in f:
                                istrt = 0
                                while istrt+scrWdth < len(line):
                                    (quit,nLines) = scrnPause(swPause,nLines,[None])
                                    try:
                                        print(line[istrt:istrt+scrWdth].decode())
                                    except:
                                        print(chr(65534)*scrWdth)
                                    nLines += 1
                                    istrt += scrWdth
                                (quit,nLines) = scrnPause(swPause,nLines,[None])
                                if quit:
                                    break
                                try:
                                    print(line[istrt:len(line)].decode(),end="")
                                except:
                                    print(chr(65534)*(len(line)-istrt))
                                nLines += 1
                    else:
                        print("Illegal switch, Command Format: TYPE[/p] [path][file]")
                else:
                    print(f"Unable to display: {tmpDir}{newdir}. File not found.")
            else:
                print("Illegal Path.")


        elif cmd in ["CHDIR","CD"]:

            if len(args) == 1:
                print(os.getcwd().replace('/',('\\' if envVars.get('DIRSEP','/') == '\\' else '/')))
            else:
                if validPath:
                    os.chdir(tmpDir)
                else:
                    print("Unable to change to:",args[1])

        elif cmd in ["MKDIR","MD"]:
            if len(args) == 1:
                print("Unable to make .")
            elif len(args) > 2:
                print("Too many arguments")
            else:
                if validPath:
                    if newdir not in os.listdir(pFmt(tmpDir,False)):
                        os.mkdir(tmpDir+newdir)
                    else:
                        print("Target name already exists")
                else:
                    print("Invalid path")

        elif cmd in ["RMDIR","RD","DELTREE"]:
            if len(args) == 2 and validPath:
                if cmd == "DELTREE":
                    swBits = int('000010',2)

                if not (swBits & (swAllB-int('000010',2))):
                    if tmpDir != sep:
                        if swBits & int('000010',2):
                            ans = input(tmpDir+" Are you sure (y/n)? ").upper()
                            if ans == "Y":
                                delFiles(tmpDir+sep,'*',bool(swBits&int('000010',2)),True)

                        if os.listdir(tmpDir) == []:
                            if os.getcwd() == tmpDir:
                                os.chdir("..")
                            if len(tmpDir.split(sep)) > 2:
                                os.chdir(sep+tmpDir.split(sep)[1])
                            try:
                                os.rmdir(tmpDir)
                            except Exception as err:
                                print(f"Unable to remove: {tmpDir}, Exception: {err}")
                            try:
                                os.chdir(savDir)
                            except:
                                pass
                        else:
                            print("The directory is not empty")
                    else:
                        print("Can not remove root directory")
                else:
                    print("Illegal switch, Command Format: RD[/s] path")
            else:
                print("Invalid path")

        elif cmd == "COPY":

            if (len(args) == 3 or len(args) == 2) and not swBits & (swAllB-int('00001000',2)):
                if len(args) == 2:
                    args.append('.')

                nFiles = 0
                earlyError = False
                trailingSlash = False
                if args[1][-1] == sep:
                    print("The source file cannot be a directory")
                    earlyError = True
                if args[2][-1] == sep:
                    trailingSlash = True

# Check that first argument has a valid path, exists and is not a directory file
                if not earlyError and validPath and ("*" in newdir or "?" in newdir or \
                    (newdir in os.listdir(tmpDir) and aFile(pFmt(tmpDir)+newdir))):

                    sourcePath = tmpDir
                    if "*" in newdir or "?" in newdir:
                        wildCardOp = True
                    else:
                        wildCardOp = False

                    enteredArg = args[2]
                    args[2] = absolutePath(args[2],savDir)
                    aPath2 = args[2].split(sep)
                    newdir2 = aPath2.pop(-1)
                    (validPath, tmpDir) = chkPath(aPath2)
                    if newdir2 == "*":
                        newdir2 = "."
                    if "*" in newdir2 or "?" in newdir2:
                        validPath = False

# Second argument has valid path
                    if validPath:
                        gc.collect()
                        targetPath = tmpDir

                        if newdir2 in "..":
                            (validPath,tmpDir) = chkPath((pFmt(targetPath)+newdir2).split(sep))
                            aPath2 = tmpDir.split(sep)
                            newdir2 = aPath2.pop(-1)
                            targetPath = chkPath(aPath2)[1]
                            if not validPath:
                                print("Impossible Target Error:",args[2])

# Second argument specifies an existing target
                        if newdir2 in os.listdir(targetPath) or (targetPath == sep and newdir2 == ""):

# Second argument target is a file
                            if aFile(pFmt(targetPath)+newdir2):
                                if trailingSlash:
                                    print("Cannot find the specified path: ",enteredArg)
                                elif wildCardOp:
                                    print("Target must be directory for wildcard copy ",enteredArg)

                                elif sourcePath == targetPath and newdir == newdir2:
                                    print("The file cannot be copied onto itself")

                                elif swBits & int('001000',2) or input("Overwrite "+args[2]+"? (y/n): ").upper() == "Y":
                                    os.remove(pFmt(targetPath)+newdir2)
                                    filecpy(pFmt(sourcePath)+newdir,pFmt(targetPath)+newdir2)
                                    nFiles += 1

# Second argument target is a directory
                            else:
                                sourcePath = pFmt(sourcePath)
                                targetPath = pFmt(targetPath)

                                if wildCardOp:
                                    ans = ""
                                    for _dir in os.listdir(pFmt(sourcePath,False)):
                                        if aFile(sourcePath+_dir) and _match(newdir,_dir[:wldCLen]):
                                            print("copy",sourcePath+_dir,"to",pFmt(targetPath+newdir2)+_dir)
                                            if _dir in os.listdir(targetPath+newdir2):
                                                if sourcePath == pFmt(targetPath+newdir2):
                                                    print("The file cannot be copied onto itself")
                                                    break
                                                else:
                                                    if ans != "A" and not swBits & int('001000',2):
                                                        ans = input("Overwrite "+pFmt(targetPath+newdir2)+_dir+"? (y/n/(q)uit/(a)ll): ").upper()
                                                    if ans  == "Y" or ans == "A" or swBits & int('001000',2):
                                                        filecpy(sourcePath+_dir,pFmt(targetPath+newdir2)+_dir)
                                                        nFiles += 1
                                                    elif ans == "Q":
                                                        break
                                            else:
                                                filecpy(sourcePath+_dir,pFmt(targetPath+newdir2)+_dir)
                                                nFiles += 1
                                            gc.collect()

                                elif newdir in os.listdir(targetPath+newdir2):
                                    if sourcePath == pFmt(targetPath+newdir2):
                                        print("The file cannot be copied onto itself")
                                    elif swBits & int('001000',2) or input("Overwrite "+pFmt(targetPath+newdir2)+newdir+"? (y/n): ").upper() == "Y":
                                        os.remove(pFmt(targetPath+newdir2)+newdir)
                                        filecpy(sourcePath+newdir,pFmt(targetPath+newdir2)+newdir)
                                        nFiles += 1

                                else:
                                    filecpy(sourcePath+newdir,pFmt(targetPath+newdir2)+newdir)
                                    nFiles += 1
# Second argument is a new file
                        else:
                            if trailingSlash or wildCardOp:
                                if wildCardOp:
                                    print("Target must be directory for wildcard copy ",enteredArg)
                                else:
                                    print("Cannot find the specified path: ",enteredArg)
                            else:
                                filecpy(pFmt(sourcePath)+newdir,pFmt(targetPath)+newdir2)
                                nFiles += 1

                        print(" "*7,nFiles,"files(s) copied.")
                    else:
                        print("Invalid destination:",args[2])
                else:
                    print("No such file:",args[1])
            else:
                if swBits & (swAllB-int('001000',2)):
                    print("Illegal switch, Command Format: COPY[/y] [path]file [path][file]")
                else:
                    print("Wrong number of arguments")

        elif cmd == "PEXEC":
            if not (swAllB-int('1000000',2)) & swBits:
                if len(args) == 1:
                    pcmd = input("=>> ")
                else:
                    pcmd = " ".join(args[1:])
                try:
                    exec(pcmd)
                    envVars['errorlevel'] = '0'
                except Exception as err:
                    if not swBits & int('1000000',2):
                        print("*ERROR* Exception:",str(err))
                    envVars['errorlevel'] = '1'
                del pcmd
            else:
                print("Illegal switch, Command Format: PEXEC[/q] python command")

        elif cmd == "READONLY":
            if imp != "C":
                print("READONLY command is only available when running on CirucitPython.")
            elif readonly:
                print("The system is already set to read-only.")
            else:
                if input("Setting the system to read-only will cause the Fruit Jam to restart? (y/n): ").upper() == "Y":
                    boot_args_file = argv_filename("/boot.py") 
                    with open(boot_args_file, "w") as f: 
                        f.write('[true, "/code.py"]')
                    microcontroller.reset()

        elif cmd == "EXIT":
            if activeBAT:
                if len(args) > 1:
                    envVars['errorlevel'] = args[1]
                activeBAT = False
                batEcho = True
                BATfile.close()
            else:
                threadFound = True
                try:
                    testthreadLock = threadLock
                except:
                    threadFound = False

                if threadFound:
                    import _thread

                    if threadLock.locked():
                        threadLock.release()
                    else:
                        threadLock.acquire()

                break

        else:
            savDir = os.getcwd()
            args[0] = absolutePath(args[0],savDir)

            aPath = args[0].split(sep)
            if 'xcopy' in aPath:
                envVars["_switches"] = [i.upper() for i in aPath[aPath.index('xcopy')+1:]]
                aPath = aPath[0:aPath.index('xcopy')+1]
            newdir = aPath.pop(-1)
            (validPath, tmpDir) = chkPath(aPath)
            tmpDir = pFmt(tmpDir)

            args = [('"'+i+'"' if i.find(" ") != -1 else i) for i in args]
            if len(args) == 1:
                passedIn = ""
                batParams = []
            elif len(args) > 1:
                passedIn = " ".join(args[1:])
                batParams = args[1:]

            gc.collect()
            batFound = ""

            cmdFound = False
            for tmpDir in [tmpDir]+(envVars.get('PATH',"").split(";") if envVars.get('PATH',"") != "" else []):
                tmpDir = pFmt(tmpDir)
                try:
                    curDLst = os.listdir(pFmt(tmpDir,False))
                except:
                    curDLst = []
                curDLst_LC = ",".join(curDLst).lower().split(",")
                if  ((newdir.split("."))[-1]).upper() == "PY":
                    if validPath and newdir in curDLst and aFile(tmpDir+newdir):

                        exCmd(tmpDir+newdir,passedIn)
                        cmdFound = True
                        break
                elif ((newdir.split("."))[-1]).upper() == "BAT":
                    if validPath and newdir in curDLst and aFile(tmpDir+newdir):
                        batFound = newdir
                        break
                elif validPath:
                    if newdir.lower()+".py" in curDLst_LC:
                        curDLst_indx = curDLst_LC.index(newdir.lower()+".py")
                        if aFile(tmpDir+curDLst[curDLst_indx]):
                            exCmd(tmpDir+curDLst[curDLst_indx],passedIn)
                            cmdFound = True
                            break
                    elif newdir.lower()+".bat" in curDLst_LC:
                        curDLst_indx = curDLst_LC.index(newdir.lower()+".bat")
                        if aFile(tmpDir+curDLst[curDLst_indx]):
                            batFound = curDLst[curDLst_indx]
                            break

            if '_switches' in envVars.keys():
                envVars.pop('_switches')

            if batFound != "":
                if activeBAT:
                    BATfile.close()
                BATfile = open(tmpDir+batFound)
                activeBAT = True
                batEcho = True
                batLabels = readBATFile(BATfile)
                batLineNo = 0
            elif not cmdFound:
                print("Illegal command:",cmdLine.split(" ")[0])

            gc.collect()

PyDOS()
reload()

