''' ANSI TEXT
Ansi escape codes CSI library by DaSpider

refs.
https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
https://www.bit-101.com/2017/2022/11/ansi-escape-codes/
https://en.wikipedia.org/wiki/ANSI_escape_code
'''

# Special codes
RST = '\033[0m' # Reset any style and colour when printed
CSI = '\033[' # Control Sequence Introducer = octal escape code <ESC>+[
DEL	= '\177' #	Delete character
ESC	= '\033' #	\e*	^[	Escape character
BEL = '\007' #	\a	^G	Terminal bell - not in all terms have sound
BS  = '\010' #	\b	^H	Backspace
HT  = '\011' #	\t	^I	Horizontal TAB
VT	= '\013' #	\v	^K	Vertical TAB
LF	= '\012' #	\n	^J	Linefeed (newline)
FF	= '\014' #	\f	^L	Formfeed (also: New page NP) AZ
CR	= '\015' #	\r	^M	Carriage return - move to left

# Format codes
BOLD,NO_BOLD = '\033[1m', '\033[22m'
DIM, NO_DIM = '\033[2m', '\033[22m'
ITALIC,NO_ITALIC = '\033[3m', '\033[23m'
UL, NO_UL = '\033[4m', '\033[24m'
BLINK, NO_BLINK = '\033[5m', '\033[25m'
INVERSE, NO_INVERSE = '\033[7m', '\033[27m'
INVISIBLE, NO_INVISIBLE = '\033[8m', '\033[28m'
DASH, NO_DASH = '\033[9m', '\033[29m'

BRIGHT=+6 # bright text colors 90-97

def bright(sw=-1):
    ''' Switch bright mode, default on '''
    global BRIGHT
    if sw==1 or sw==0: BRIGHT= 6*sw
    elif type(sw)==str: BRIGHT = 6 if sw.lower()=='on' else 0
    else: BRIGHT = 6 if BRIGHT==0 else 0

def reset():
    ''' Reset text format to default '''
    t('\033[0m')

def home():
    ''' Move cursor to position (0,0) '''
    print(CSI+'H',end='')

def cls():
    ''' Clears the screen '''
    print(ESC+'c')

def getCode(style=1, color_fore=9, color_back=9):
    ''' Returns a string with specific format for reuse '''
    codes = {'plain':0,'normal':0,'bold':1,'dim':2,'faint':2,
             'italic':3,'underline':4,'ul':4,'blink_slow':5,
             'blink':6,'inverse':7,'invisible':8,'dash':9,
             'strike':9,'font0':10,'font1':11,'font2':12,
             'font3':13,'font4':14,'font5':15,'font6':16,
             'font7':17,'font8':18,'font9':19,'gothic':20,
             'fractur':20,'double_underline':21,'default':22,
             'framed':51,'encircled':51,'overlined':51}
    if type(style) == str: style=codes.get(style.lower(), 1)
    if type(color_fore) == str: color_fore=pick(color_fore)
    if type(color_back) == str: color_back=pick(color_back)
    return f"{CSI}{style};{3+BRIGHT}{color_fore};4{color_back}m"

def move(n, m=''):
    ''' Move cursor to position (line, column) or <n> in a given direction '''
    codes = {'up':'A','down':'B', 'right':'C', 'left':'D',
             'lines down':'E', 'lines up':'F', 'column':'G'}
    if type(n) == str and m=='': t(CSI + codes.get(n.lower(), n))
    elif type(m) == str and n>0: t(CSI + str(n) + codes.get(m.lower(), m))
    else:  print(f'{CSI}{n};{m}H',end='')

def pick(color):
    # Define ANSI color codes
    c = color.split(':')
    codes = {'black':0, 'red':1,'green':2,'yellow':3, 'blue':4, 'magenta':5,
             'cyan':6, 'white':7,  'rgb':'8;2;', 'code':'8;5;', 'default':9}

    # Get the color code or default to white if not found
    return str(codes.get(c[0].lower(), '7'))+(c[1] if len(c)>1 else '')

def fg(color, text=''):
    ''' Print optional text with the specified color '''
    if type(color) == str: color=pick(color)
    t(f"{CSI}{3+BRIGHT}{color}m{text}")

def bg(color, text=''):
    ''' Sets background to color, print optional text '''
    if type(color) == str: color=pick(color)
    t(f"{CSI}4{color}m{text}")

def color(color_fore, color_back=0):
    ''' Set terminal color from color names or number '''
    if type(color_fore) == str: color_fore=pick(color_fore)
    if type(color_back) == str: color_back=pick(color_back)
    t(f"{CSI}{3+BRIGHT}{color_fore};4{color_back}m")

def t(text=''):
    ''' Just outputs text in stencil, no newline '''
    print(text, end='')

def tn(text=''):
    ''' Output text and newline '''
    print(text)

def examples():
    ''' Examples of usage '''
    cls(); help(cls)  # clears the screen

    # Colors array in different modes, white and black excluded
    c = ['red','green','yellow','blue','magenta','cyan']
    bright('on'); fg("white",BOLD+"Background: " +HT)
    for i in range(6): bg(c[i],c[i]+'  ')
    fg("white",RST+BOLD+"\nBright bold: " +HT)
    for i in range(6): fg(c[i],c[i]+'  ')
    fg("white",NO_BOLD+"\nBright: " +HT)
    for i in range(6): fg(c[i],c[i]+'  ')
    bright('off'); fg("white","\nDim:     " +HT)
    for i in range(6): fg(c[i],c[i]+'  ')
    
    # Color text and background
    bg('code:17'); bright(1) # Dark blue background
    fg("yellow", f"\n{BOLD}This text is bright bold yellow on blue\n")
    fg("yellow", f"{NO_BOLD}This text is bright yellow on blue!\n")
    bright(0); fg("yellow", "This text is dim yellow on blue!\n"+RST)
    bright(1); fg("red", "This text is bright red!\n")
    bright(0); fg("red", "This text is dim red!\n")
    bright(1); fg("green", "This text is bright green!\n")
    bright(0); fg("green", "This text is dim green!\n")
    tn(f'This too, {RST} but not this')
    bright(1); fg("blue", "This text is bright blue!\n")
    bright(0); fg("blue", "This text is dim blue!\n")
    bg("yellow",'Blue on yellow background'+RST+LF)

    # Text style format strings
    tn(f'Change {BOLD}{ITALIC}styles with {UL}format{NO_UL} strings:{NO_ITALIC}')
    t(f'{BOLD}bold{NO_BOLD}, {ITALIC}italic{NO_ITALIC}, {UL}underline{NO_UL} and ')
    t(f'{DASH}strikethrough{NO_DASH}, while ')
    t(f'{BLINK}BLINK{NO_BLINK}, {INVERSE}INVERSE{NO_INVERSE} and\n')
    t(f'{INVISIBLE}INVISIBLE{NO_INVISIBLE} (invisible) may not be')
    tn("available in some consoles.\nYou can also use special codes like ")    

    # special characters
    tn(f'TAB & Backspace:{HT}XYZ{BS}-')

    # move cursor around
    move(35,'right'); move(9,'up')
    move('up'); tn("<print text anywhere on the screen>")
    move(55,'lines down') # max bottom of the screen
    tn()

    # Shows formats in the array:
    fmt = ['plain','normal','bold','dim','faint','italic','underline',
           'ul','blink_slow','blink','inverse','invisible','dash',
           'strike','font0','font1','font2','font3','font4','font5',
           'font6','font7','font8','font9','gothic','double_underline',
           'default','framed','encircled','overlined']
    tn('Styles in this terminal:')
    for i in range(len(fmt)):
        print(getCode(fmt[i])+fmt[i]+RST+HT,end='' if (i+1)%5 else '\n')
    
    # 256 color palette codes
    tn(f"\nPick colors from {BOLD}{ITALIC}color('code:<n>'){RST}")
    for i in range(256):
        bg(f'code:{i}'); t(f'{i:3} ' + (f'{RST}\n' if (i+1)%16==0 else ''))
    tn(RST+f"RGB code with {BOLD}{ITALIC}bg('rgb:<R>;<G>;<B>'){RST}")

    # RGB 16 & 24-bit colors
    for i in range(256):
        bg(f'rgb:{i};{(256-i)%128*2};{(64-i)%64*4}')
        t(f' ' + (f'{RST}\n' if (i+1)%64==0 else ''))
    reset()
    tn(RST+f"Or RGB code with {BOLD}{ITALIC}color('rgb:<R>;<G>;<B>', 'rgb:<R>;<G>;<B>'){RST}")
    for i in range(256):
        color(f'rgb:{i};{(256-i)%128*2};{(64-i)%64*4}',f'rgb:{256-i};{(256-i)%128*2};{(64-i)%64*4}');
        t(f'@' + (f'{RST}\n' if (i+1)%32==0 else ''))
    reset()

# run examples if executed in standalone mode
if __name__ == "__main__":
    examples()