#!/usr/bin/env python3

import sys, os, time
from bullet import Bullet, colors
from yaspin import yaspin
from yaspin.spinners import Spinners

from masgent import tools, schemas
from masgent.ai_mode import ai_backend
from masgent.utils.utils import (
    color_print, 
    color_input, 
    print_help, 
    global_commands, 
    start_new_session,
    clear_and_print_entry_message,
    )

COMMANDS = {}

def register(code, func):
    def decorator(func):
        COMMANDS[code] = {
            'function': func,
            'description': func.__doc__ or ''
        }
        return func
    return decorator

def run_command(code):
    cmd = COMMANDS.get(code)
    if cmd:
        cmd['function']()
    else:
        color_print(f'[Error] Invalid command code: {code}\n', 'red')

def check_poscar():
    while True:
        runs_dir = os.environ.get('MASGENT_SESSION_RUNS_DIR')

        if os.path.exists(os.path.join(runs_dir, 'POSCAR')):
            use_default = True
        else:
            use_default = False

        if use_default:
            runs_dir_name = os.path.basename(runs_dir)
            clear_and_print_entry_message()
            choices = [
                'Yes  ->  Use POSCAR file in current runs directory',
                'No   ->  Provide a different POSCAR file path',
            ] + global_commands()

            prompt = f'\nUse POSCAR file in current runs directory: {runs_dir_name}/POSCAR ?\n'
            cli = Bullet(prompt=prompt, choices=choices, margin=1, bullet=' ●', word_color=colors.foreground['green'])
            user_input = cli.launch()

            if user_input.startswith('AI'):
                ai_backend.main()
            elif user_input.startswith('New'):
                start_new_session()
            elif user_input.startswith('Back'):
                return
            elif user_input.startswith('Main'):
                run_command('0')
            elif user_input.startswith('Help'):
                print_help()
            elif user_input.startswith('Exit'):
                color_print('\nExiting Masgent... Goodbye!\n', 'green')
                sys.exit(0)
            elif user_input.startswith('Yes'):
                poscar_path = os.path.join(runs_dir, 'POSCAR')
            elif user_input.startswith('No'):
                poscar_path = color_input('\nEnter path to POSCAR file: ', 'yellow').strip()
            else:
                continue
        else:
            poscar_path = color_input('\nEnter path to POSCAR file: ', 'yellow').strip()
        
        if not poscar_path:
            continue
        
        try:
            schemas.CheckPoscar(poscar_path=poscar_path)
            return poscar_path
        except Exception:
            color_print(f'[Error] Invalid POSCAR: {poscar_path}, please double check and try again.\n', 'red')

#############################################
#                                           #
# Below are implementations of sub-commands #
#                                           #
#############################################

@register('1.1.1', 'Generate POSCAR from chemical formula.')
def command_1_1_1():
    try: 
        while True:
            formula = color_input('\nEnter chemical formula (e.g., NaCl): ', 'yellow').strip()

            if not formula:
                continue

            try:
                schemas.GenerateVaspPoscarSchema(formula=formula)
                break
            except Exception:
                color_print(f'[Error] Invalid formula: {formula}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    input = schemas.GenerateVaspPoscarSchema(formula=formula)
    result = tools.generate_vasp_poscar(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.1.2', 'Convert POSCAR coordinates (Direct <-> Cartesian).')
def command_1_1_2():
    try:
        while True:
            clear_and_print_entry_message()
            choices = [
                'Direct coordinates     —>  Cartesian coordinates',
                'Cartesian coordinates  —>  Direct coordinates',
            ] + global_commands()
            cli = Bullet(prompt='\n', choices=choices, margin=1, bullet=' ●', word_color=colors.foreground['green'])
            user_input = cli.launch()

            if user_input.startswith('AI'):
                ai_backend.main()
            elif user_input.startswith('New'):
                start_new_session()
            elif user_input.startswith('Back'):
                return
            elif user_input.startswith('Main'):
                run_command('0')
            elif user_input.startswith('Help'):
                print_help()
            elif user_input.startswith('Exit'):
                color_print('\nExiting Masgent... Goodbye!\n', 'green')
                sys.exit(0)
            elif user_input.startswith('Direct coordinates'):
                to_cartesian = True
                break
            elif user_input.startswith('Cartesian coordinates'):
                to_cartesian = False
                break
            else:
                continue
    
    except (KeyboardInterrupt, EOFError):
        color_print('\nExiting Masgent... Goodbye!\n', 'green')
        sys.exit(0)

    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    input = schemas.ConvertPoscarCoordinatesSchema(poscar_path=poscar_path, to_cartesian=to_cartesian)
    result = tools.convert_poscar_coordinates(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.1.3', 'Convert structure file formats (CIF, POSCAR, XYZ).')
def command_1_1_3():
    try:
        while True:
            clear_and_print_entry_message()
            choices = [
                'POSCAR  ->  CIF',
                'POSCAR  ->  XYZ',
                'CIF     ->  POSCAR',
                'CIF     ->  XYZ',
                'XYZ     ->  POSCAR',
                'XYZ     ->  CIF',
            ] + global_commands()
            cli = Bullet(prompt='\n', choices=choices, margin=1, bullet=' ●', word_color=colors.foreground['green'])
            user_input = cli.launch()

            if user_input.startswith('AI'):
                ai_backend.main()
            elif user_input.startswith('New'):
                start_new_session()
            elif user_input.startswith('Back'):
                return
            elif user_input.startswith('Main'):
                run_command('0')
            elif user_input.startswith('Help'):
                print_help()
            elif user_input.startswith('Exit'):
                color_print('\nExiting Masgent... Goodbye!\n', 'green')
                sys.exit(0)
            elif user_input.startswith('POSCAR') and user_input.endswith('CIF'):
                input_format, output_format = 'POSCAR', 'CIF'
                break
            elif user_input.startswith('POSCAR') and user_input.endswith('XYZ'):
                input_format, output_format = 'POSCAR', 'XYZ'
                break
            elif user_input.startswith('CIF') and user_input.endswith('POSCAR'):
                input_format, output_format = 'CIF', 'POSCAR'
                break
            elif user_input.startswith('CIF') and user_input.endswith('XYZ'):
                input_format, output_format = 'CIF', 'XYZ'
                break
            elif user_input.startswith('XYZ') and user_input.endswith('POSCAR'):
                input_format, output_format = 'XYZ', 'POSCAR'
                break
            elif user_input.startswith('XYZ') and user_input.endswith('CIF'):
                input_format, output_format = 'XYZ', 'CIF'
                break
            else:
                continue
    
    except (KeyboardInterrupt, EOFError):
        color_print('\nExiting Masgent... Goodbye!\n', 'green')
        sys.exit(0)

    try:
        while True:
            input_path = color_input('\nEnter path to input structure file: ', 'yellow').strip()

            if not input_path:
                continue
            
            try:
                schemas.ConvertStructureFormatSchema(input_path=input_path, input_format=input_format, output_format=output_format)
                break
            except Exception:
                color_print(f'[Error] Invalid input: {input_path}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    input = schemas.ConvertStructureFormatSchema(input_path=input_path, input_format=input_format, output_format=output_format)
    result = tools.convert_structure_format(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.1.4', 'Generate structure with defects (Vacancy, Interstitial, Substitution).')
def command_1_1_4():
    try:
        while True:
            clear_and_print_entry_message()
            choices = [
                'Vacancy                 ->  Randomly remove atoms of a selected element',
                'Substitution            ->  Randomly substitute atoms of a selected element with defect element',
                'Interstitial (Voronoi)  ->  Add atom at interstitial sites using Voronoi method',
            ] + global_commands()
            cli = Bullet(prompt='\n', choices=choices, margin=1, bullet=' ●', word_color=colors.foreground['green'])
            user_input = cli.launch()

            if user_input.startswith('AI'):
                ai_backend.main()
            elif user_input.startswith('New'):
                start_new_session()
            elif user_input.startswith('Back'):
                return
            elif user_input.startswith('Main'):
                run_command('0')
            elif user_input.startswith('Help'):
                print_help()
            elif user_input.startswith('Exit'):
                color_print('\nExiting Masgent... Goodbye!\n', 'green')
                sys.exit(0)
            elif user_input.startswith('Vacancy'):
                run_command('vacancy')
                break
            elif user_input.startswith('Interstitial (Voronoi)'):
                run_command('interstitial')
                break
            elif user_input.startswith('Substitution'):
                run_command('substitution')
                break
            else:
                continue
    
    except (KeyboardInterrupt, EOFError):
        color_print('\nExiting Masgent... Goodbye!\n', 'green')
        sys.exit(0)

@register('vacancy', 'Generate structure with vacancy defects.')
def command_vacancy():
    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    try:
        while True:
            original_element = color_input('\nEnter the element to remove (e.g., Na): ', 'yellow').strip()
            if not original_element:
                continue
            
            try:
                schemas.CheckElement(element_symbol=original_element)
                schemas.CheckElementExistence(poscar_path=poscar_path, element_symbol=original_element)
                break
            except Exception:
                color_print(f'[Error] Invalid element {original_element}, please double check and try again.\n', 'red')
                continue

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    try:
        while True:
            defect_amount_str = color_input('\nEnter the defect amount (fraction between 0 and 1, or atom count >=1): ', 'yellow').strip()
            if not defect_amount_str:
                continue

            try:
                if '.' in defect_amount_str:
                    defect_amount = float(defect_amount_str)
                else:
                    defect_amount = int(defect_amount_str)
                
                schemas.GenerateVaspPoscarWithVacancyDefects(poscar_path=poscar_path, original_element=original_element, defect_amount=defect_amount)
                break

            except Exception:
                color_print(f'[Error] Invalid defect amount: {defect_amount_str}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    input = schemas.GenerateVaspPoscarWithVacancyDefects(poscar_path=poscar_path, original_element=original_element, defect_amount=defect_amount)
    result = tools.generate_vasp_poscar_with_vacancy_defects(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('substitution', 'Generate structure with substitution defects.')
def command_substitution():
    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            original_element = color_input('\nEnter the target element to be substituted (e.g., Na): ', 'yellow').strip()
            if not original_element:
                continue
            
            try:
                schemas.CheckElement(element_symbol=original_element)
                schemas.CheckElementExistence(poscar_path=poscar_path, element_symbol=original_element)
                break
            
            except Exception:
                color_print(f'[Error] Invalid element {original_element}, please double check and try again.\n', 'red')
                
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            defect_element = color_input('\nEnter the defect element to substitute in (e.g., K): ', 'yellow').strip()
            if not defect_element:
                continue

            try:
                schemas.CheckElement(element_symbol=defect_element)
                break
            except Exception:
                color_print(f'[Error] Invalid element {defect_element}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    try:
        while True:
            defect_amount_str = color_input('\nEnter the defect amount (fraction between 0 and 1, or atom count >=1): ', 'yellow').strip()
            if not defect_amount_str:
                continue
            
            try:
                if '.' in defect_amount_str:
                    defect_amount = float(defect_amount_str)
                else:
                    defect_amount = int(defect_amount_str)
                schemas.GenerateVaspPoscarWithSubstitutionDefects(poscar_path=poscar_path, original_element=original_element, defect_element=defect_element, defect_amount=defect_amount)
                break

            except Exception:
                color_print(f'[Error] Invalid defect amount: {defect_amount_str}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    input = schemas.GenerateVaspPoscarWithSubstitutionDefects(poscar_path=poscar_path, original_element=original_element, defect_element=defect_element, defect_amount=defect_amount)
    result = tools.generate_vasp_poscar_with_substitution_defects(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('interstitial', 'Generate structure with interstitial (Voronoi) defects.')
def command_interstitial():
    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            defect_element = color_input('\nEnter the defect element to add (e.g., Na): ', 'yellow').strip()
            if not defect_element:
                continue

            try:
                schemas.GenerateVaspPoscarWithInterstitialDefects(poscar_path=poscar_path, defect_element=defect_element)
                break
            except Exception:
                color_print(f'[Error] Invalid element {defect_element}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    input = schemas.GenerateVaspPoscarWithInterstitialDefects(poscar_path=poscar_path, defect_element=defect_element)
    print('')
    with yaspin(Spinners.dots, text='Generating SQS... See details in the log file.', color='cyan') as sp:
        result = tools.generate_vasp_poscar_with_interstitial_defects(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.1.5', 'Generate supercell from POSCAR with specified scaling matrix.')
def command_1_1_5():
    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            sm = color_input('\nEnter the scaling matrix (e.g., "2 0 0; 0 2 0; 0 0 2" for 2x2x2 supercell): ', 'yellow').strip()

            if not sm:
                continue
            
            try:
                schemas.GenerateSupercellFromPoscar(poscar_path=poscar_path, scaling_matrix=sm)
                break

            except Exception:
                color_print(f'[Error] Invalid scaling matrix: {sm}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    input = schemas.GenerateSupercellFromPoscar(poscar_path=poscar_path, scaling_matrix=sm)
    result = tools.generate_supercell_from_poscar(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.1.6', 'Generate special quasi-random structure (SQS) from POSCAR.')
def command_1_1_6():
    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            target_configurations_str = color_input('\nEnter target configurations (e.g., "La:La=0.5,Y=0.5; Co:Al=0.75,Co=0.25"): ', 'yellow').strip()
            
            if not target_configurations_str:
                continue

            try:
                # parse target configurations to {'La': {'La': 0.5, 'Y': 0.5}, 'Co': {'Al': 0.75, 'Co': 0.25}}
                target_configurations = {}
                for sublattice_str in target_configurations_str.split(';'):
                    sublattice_str = sublattice_str.strip()
                    if not sublattice_str:
                        continue
                    element, conc_str = sublattice_str.split(':')
                    element = element.strip()
                    conc_pairs = conc_str.split(',')
                    conc_dict = {}
                    for pair in conc_pairs:
                        species, conc = pair.split('=')
                        conc_dict[species.strip()] = float(conc.strip())
                    target_configurations[element] = conc_dict
                schemas.GenerateSqsFromPoscar(poscar_path=poscar_path, target_configurations=target_configurations)
                break
            except Exception:
                color_print(f'[Error] Invalid target configurations: {target_configurations_str}, please double check and try again.\n', 'red')
    
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            cutoffs_str = color_input('\nEnter cluster cutoffs in Å (e.g., "8.0 4.0 4.0" for pairs, triplets, and quadruplets): ', 'yellow').strip()

            if not cutoffs_str:
                continue

            try:
                cutoffs = [float(x) for x in cutoffs_str.split()]
                schemas.GenerateSqsFromPoscar(poscar_path=poscar_path, target_configurations=target_configurations, cutoffs=cutoffs)
                break
            except Exception:
                color_print(f'[Error] Invalid cutoffs: {cutoffs_str}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            max_supercell_size_str = color_input('\nEnter maximum supercell size (e.g., "8"): ', 'yellow').strip()

            if not max_supercell_size_str:
                continue

            try:
                max_supercell_size = int(max_supercell_size_str)
                schemas.GenerateSqsFromPoscar(poscar_path=poscar_path, target_configurations=target_configurations, cutoffs=cutoffs, max_supercell_size=max_supercell_size)
                break
            except Exception:
                color_print(f'[Error] Invalid maximum supercell size: {max_supercell_size_str}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            mc_steps_str = color_input('\nEnter number of Monte Carlo steps (e.g., ">=1000"): ', 'yellow').strip()

            if not mc_steps_str:
                continue

            try:
                mc_steps = int(mc_steps_str)
                schemas.GenerateSqsFromPoscar(poscar_path=poscar_path, target_configurations=target_configurations, cutoffs=cutoffs, max_supercell_size=max_supercell_size, mc_steps=mc_steps)
                break
            except Exception:
                color_print(f'[Error] Invalid number of Monte Carlo steps: {mc_steps_str}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    input = schemas.GenerateSqsFromPoscar(poscar_path=poscar_path, target_configurations=target_configurations, cutoffs=cutoffs, max_supercell_size=max_supercell_size, mc_steps=mc_steps)
    print('')
    with yaspin(Spinners.dots, text='Generating SQS... See details in the log file.', color='cyan') as sp:
        result = tools.generate_sqs_from_poscar(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.1.7', 'Generate surface slab from POSCAR with specified Miller indices, vacuum thickness, and slab layers.')
def command_1_1_7():
    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            miller_indices_str = color_input('\nEnter the Miller indices (e.g., "1 1 1"): ', 'yellow').strip()
            
            if not miller_indices_str:
                continue

            try:
                miller_indices = [int(x) for x in miller_indices_str.split()]
                schemas.GenerateSurfaceSlabFromPoscar(poscar_path=poscar_path, miller_indices=miller_indices)
                break
            except Exception:
                color_print(f'[Error] Invalid Miller indices: {miller_indices_str}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            vacuum_thickness_str = color_input('\nEnter the vacuum thickness in Å (e.g., "15.0"): ', 'yellow').strip()

            if not vacuum_thickness_str:
                continue

            try:
                vacuum_thickness = float(vacuum_thickness_str)
                schemas.GenerateSurfaceSlabFromPoscar(poscar_path=poscar_path, miller_indices=miller_indices, vacuum_thickness=vacuum_thickness)
                break
            except Exception:
                color_print(f'[Error] Invalid vacuum thickness: {vacuum_thickness_str}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            slab_layers_str = color_input('\nEnter the number of slab layers (e.g., "4"): ', 'yellow').strip()

            if not slab_layers_str:
                continue

            try:
                slab_layers = int(slab_layers_str)
                schemas.GenerateSurfaceSlabFromPoscar(poscar_path=poscar_path, miller_indices=miller_indices, vacuum_thickness=vacuum_thickness, slab_layers=slab_layers)
                break
            except Exception:
                color_print(f'[Error] Invalid slab layers: {slab_layers_str}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    input = schemas.GenerateSurfaceSlabFromPoscar(poscar_path=poscar_path, miller_indices=miller_indices, vacuum_thickness=vacuum_thickness, slab_layers=slab_layers)
    result = tools.generate_surface_slab_from_poscar(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.1.8', 'Generate interface structure from two POSCAR files with specified parameters.')
def command_1_1_8():
    try:
        while True:
            lower_poscar_path = color_input('\nSelect the lower POSCAR file: ', 'yellow').strip()

            if not lower_poscar_path:
                continue

            try:
                schemas.CheckPoscar(poscar_path=lower_poscar_path)
                break
            except Exception:
                color_print(f'[Error] Invalid POSCAR: {lower_poscar_path}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            upper_poscar_path = color_input('\nSelect the upper POSCAR file: ', 'yellow').strip()

            if not upper_poscar_path:
                continue

            try:
                schemas.CheckPoscar(poscar_path=upper_poscar_path)
                break
            except Exception:
                color_print(f'[Error] Invalid POSCAR: {upper_poscar_path}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            hkl_str = color_input('\nEnter the Miller indices for the lower and upper surfaces (e.g., "1 0 0; 1 0 0"): ', 'yellow').strip()

            if not hkl_str:
                continue

            try:
                lower_hkl = [int(x) for x in hkl_str.split(';')[0].strip().split()]
                upper_hkl = [int(x) for x in hkl_str.split(';')[1].strip().split()]
                schemas.GenerateInterfaceFromPoscars(lower_poscar_path=lower_poscar_path, upper_poscar_path=upper_poscar_path, lower_hkl=lower_hkl, upper_hkl=upper_hkl)
                break
            except Exception:
                color_print(f'[Error] Invalid Miller indices: {hkl_str}, please double check and try again.\n', 'red')
    
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            slab_layers_str = color_input('\nEnter the number of slab layers for the lower and upper slabs (e.g., "4 4"): ', 'yellow').strip()

            if not slab_layers_str:
                continue

            try:
                lower_slab_layers = int(slab_layers_str.split()[0].strip())
                upper_slab_layers = int(slab_layers_str.split()[1].strip())
                schemas.GenerateInterfaceFromPoscars(lower_poscar_path=lower_poscar_path, upper_poscar_path=upper_poscar_path, lower_hkl=lower_hkl, upper_hkl=upper_hkl, lower_slab_layers=lower_slab_layers, upper_slab_layers=upper_slab_layers)
                break
            except Exception:
                color_print(f'[Error] Invalid slab layers: {slab_layers_str}, please double check and try again.\n', 'red')
    
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            slab_vacuum_str = color_input('\nEnter the vacuum thickness in Å (e.g., "15.0"): ', 'yellow').strip()

            if not slab_vacuum_str:
                continue

            try:
                slab_vacuum = float(slab_vacuum_str)
                schemas.GenerateInterfaceFromPoscars(lower_poscar_path=lower_poscar_path, upper_poscar_path=upper_poscar_path, lower_hkl=lower_hkl, upper_hkl=upper_hkl, lower_slab_layers=lower_slab_layers, upper_slab_layers=upper_slab_layers, slab_vacuum=slab_vacuum)
                break
            except Exception:
                color_print(f'[Error] Invalid vacuum thickness: {slab_vacuum_str}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            area_str = color_input('\nEnter the minimum and maximum interface area to search in Å² (e.g., "50.0 500.0"): ', 'yellow').strip()

            if not area_str:
                continue

            try:
                min_area = float(area_str.split()[0].strip())
                max_area = float(area_str.split()[1].strip())
                schemas.GenerateInterfaceFromPoscars(lower_poscar_path=lower_poscar_path, upper_poscar_path=upper_poscar_path, lower_hkl=lower_hkl, upper_hkl=upper_hkl, lower_slab_layers=lower_slab_layers, upper_slab_layers=upper_slab_layers, slab_vacuum=slab_vacuum, min_area=min_area, max_area=max_area)
                break
            except Exception:
                color_print(f'[Error] Invalid interface area: {area_str}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            interface_gap_str = color_input('\nEnter the interface gap in Å (e.g., "2.0"): ', 'yellow').strip()

            if not interface_gap_str:
                continue

            try:
                interface_gap = float(interface_gap_str)
                schemas.GenerateInterfaceFromPoscars(lower_poscar_path=lower_poscar_path, upper_poscar_path=upper_poscar_path, lower_hkl=lower_hkl, upper_hkl=upper_hkl, lower_slab_layers=lower_slab_layers, upper_slab_layers=upper_slab_layers, slab_vacuum=slab_vacuum, min_area=min_area, max_area=max_area, interface_gap=interface_gap)
                break
            except Exception:
                color_print(f'[Error] Invalid interface gap: {interface_gap_str}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            tolerance_str = color_input('\nEnter the lattice vector tolerance (%) and angle tolerance (degrees) (e.g., "5.0 5.0"): ', 'yellow').strip()

            if not tolerance_str:
                continue

            try:
                uv_tolerance = float(tolerance_str.split()[0].strip())
                angle_tolerance = float(tolerance_str.split()[1].strip())
                schemas.GenerateInterfaceFromPoscars(lower_poscar_path=lower_poscar_path, upper_poscar_path=upper_poscar_path, lower_hkl=lower_hkl, upper_hkl=upper_hkl, lower_slab_layers=lower_slab_layers, upper_slab_layers=upper_slab_layers, slab_vacuum=slab_vacuum, min_area=min_area, max_area=max_area, interface_gap=interface_gap, uv_tolerance=uv_tolerance, angle_tolerance=angle_tolerance)
                break
            except Exception:
                color_print(f'[Error] Invalid lattice vector tolerance: {tolerance_str}, please double check and try again.\n', 'red')
    
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            shape_filter_str = color_input('\nDo you want to apply shape filtering to only keep the most square-like interface? [y/n]: ', 'yellow').strip().lower()

            if not shape_filter_str:
                continue

            if shape_filter_str == 'y':
                shape_filter = True
                break
            elif shape_filter_str == 'n':
                shape_filter = False
                break
            else:
                continue

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    input = schemas.GenerateInterfaceFromPoscars(lower_poscar_path=lower_poscar_path, upper_poscar_path=upper_poscar_path, lower_hkl=lower_hkl, upper_hkl=upper_hkl, lower_slab_layers=lower_slab_layers, upper_slab_layers=upper_slab_layers, slab_vacuum=slab_vacuum, min_area=min_area, max_area=max_area, interface_gap=interface_gap, uv_tolerance=uv_tolerance, angle_tolerance=angle_tolerance, shape_filter=shape_filter)
    print('')
    with yaspin(Spinners.dots, text='Generating interface structure... See details in the log file.', color='cyan') as sp:
        result = tools.generate_interface_from_poscars(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.2.1', 'Prepare full VASP input files (INCAR, KPOINTS, POTCAR, POSCAR).')
def command_1_2_1():
    try:
        while True:
            clear_and_print_entry_message()
            choices = [
                'MPRelaxSet       ->   suggested for structure relaxation',
                'MPStaticSet      ->   suggested for static calculations',
                'MPNonSCFSet      ->   suggested for non-self-consistent field calculations',
                'MPScanRelaxSet   ->   suggested for structure relaxation with r2SCAN functional',
                'MPScanStaticSet  ->   suggested for static calculations with r2SCAN functional',
                'MPMDSet          ->   suggested for molecular dynamics simulations',
                'NEBSet           ->   suggested for nudged elastic band calculations',
                'MVLElasticSet    ->   suggested for elastic constant calculations',
            ] + global_commands()
            cli = Bullet(prompt='\n', choices=choices, margin=1, bullet=' ●', word_color=colors.foreground['green'])
            user_input = cli.launch()

            if user_input.startswith('AI'):
                ai_backend.main()
            elif user_input.startswith('New'):
                start_new_session()
            elif user_input.startswith('Back'):
                return
            elif user_input.startswith('Main'):
                run_command('0')
            elif user_input.startswith('Help'):
                print_help()
            elif user_input.startswith('Exit'):
                color_print('\nExiting Masgent... Goodbye!\n', 'green')
                sys.exit(0)
            elif user_input.startswith('MPRelaxSet'):
                vasp_input_sets = 'MPRelaxSet'
                break
            elif user_input.startswith('MPStaticSet'):
                vasp_input_sets = 'MPStaticSet'
                break
            elif user_input.startswith('MPNonSCFSet'):
                vasp_input_sets = 'MPNonSCFSet'
                break
            elif user_input.startswith('MPScanRelaxSet'):
                vasp_input_sets = 'MPScanRelaxSet'
                break
            elif user_input.startswith('MPScanStaticSet'):
                vasp_input_sets = 'MPScanStaticSet'
                break
            elif user_input.startswith('MPMDSet'):
                vasp_input_sets = 'MPMDSet'
                break
            elif user_input.startswith('NEBSet'):
                vasp_input_sets = 'NEBSet'
                break
            elif user_input.startswith('MVLElasticSet'):
                vasp_input_sets = 'MVLElasticSet'
                break
            else:
                continue
    
    except (KeyboardInterrupt, EOFError):
        color_print('\nExiting Masgent... Goodbye!\n', 'green')
        sys.exit(0)
    
    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    input = schemas.GenerateVaspInputsFromPoscar(poscar_path=poscar_path, vasp_input_sets=vasp_input_sets, only_incar=False)
    result = tools.generate_vasp_inputs_from_poscar(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.2.2', 'Generate INCAR templates (relaxation, static, MD, etc.).')
def command_1_2_2():
    try:
        while True:
            clear_and_print_entry_message()
            choices = [
                'MPRelaxSet       ->   suggested for structure relaxation',
                'MPStaticSet      ->   suggested for static calculations',
                'MPNonSCFSet      ->   suggested for non-self-consistent field calculations',
                'MPScanRelaxSet   ->   suggested for structure relaxation with r2SCAN functional',
                'MPScanStaticSet  ->   suggested for static calculations with r2SCAN functional',
                'MPMDSet          ->   suggested for molecular dynamics simulations',
                'NEBSet           ->   suggested for nudged elastic band calculations',
                'MVLElasticSet    ->   suggested for elastic constant calculations',
            ] + global_commands()
            cli = Bullet(prompt='\n', choices=choices, margin=1, bullet=' ●', word_color=colors.foreground['green'])
            user_input = cli.launch()

            if user_input.startswith('AI'):
                ai_backend.main()
            elif user_input.startswith('New'):
                start_new_session()
            elif user_input.startswith('Back'):
                return
            elif user_input.startswith('Main'):
                run_command('0')
            elif user_input.startswith('Help'):
                print_help()
            elif user_input.startswith('Exit'):
                color_print('\nExiting Masgent... Goodbye!\n', 'green')
                sys.exit(0)
            elif user_input.startswith('MPRelaxSet'):
                vasp_input_sets = 'MPRelaxSet'
                break
            elif user_input.startswith('MPStaticSet'):
                vasp_input_sets = 'MPStaticSet'
                break
            elif user_input.startswith('MPNonSCFSet'):
                vasp_input_sets = 'MPNonSCFSet'
                break
            elif user_input.startswith('MPScanRelaxSet'):
                vasp_input_sets = 'MPScanRelaxSet'
                break
            elif user_input.startswith('MPScanStaticSet'):
                vasp_input_sets = 'MPScanStaticSet'
                break
            elif user_input.startswith('MPMDSet'):
                vasp_input_sets = 'MPMDSet'
                break
            elif user_input.startswith('NEBSet'):
                vasp_input_sets = 'NEBSet'
                break
            elif user_input.startswith('MVLElasticSet'):
                vasp_input_sets = 'MVLElasticSet'
                break
            else:
                continue
    
    except (KeyboardInterrupt, EOFError):
        color_print('\nExiting Masgent... Goodbye!\n', 'green')
        sys.exit(0)
    
    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    input = schemas.GenerateVaspInputsFromPoscar(poscar_path=poscar_path, vasp_input_sets=vasp_input_sets, only_incar=True)
    result = tools.generate_vasp_inputs_from_poscar(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.2.3', 'Generate KPOINTS with specified accuracy.')
def command_1_2_3():
    try:
        while True:
            clear_and_print_entry_message()
            choices = [
                'Low     ->  Suitable for preliminary calculations, grid density = 1000 / number of atoms',
                'Medium  ->  Balanced accuracy and computational cost, grid density = 3000 / number of atoms',
                'High    ->  High accuracy for production runs, grid density = 5000 / number of atoms',
            ] + global_commands()
            cli = Bullet(prompt='\n', choices=choices, margin=1, bullet=' ●', word_color=colors.foreground['green'])
            user_input = cli.launch()

            if user_input.startswith('AI'):
                ai_backend.main()
            elif user_input.startswith('New'):
                start_new_session()
            elif user_input.startswith('Back'):
                return
            elif user_input.startswith('Main'):
                run_command('0')
            elif user_input.startswith('Help'):
                print_help()
            elif user_input.startswith('Exit'):
                color_print('\nExiting Masgent... Goodbye!\n', 'green')
                sys.exit(0)
            elif user_input.startswith('Low'):
                accuracy_level = 'Low'
                break
            elif user_input.startswith('Medium'):
                accuracy_level = 'Medium'
                break
            elif user_input.startswith('High'):
                accuracy_level = 'High'
                break
            else:
                continue
    
    except (KeyboardInterrupt, EOFError):
        color_print('\nExiting Masgent... Goodbye!\n', 'green')
        sys.exit(0)

    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    input = schemas.CustomizeVaspKpointsWithAccuracy(poscar_path=poscar_path, accuracy_level=accuracy_level)
    result = tools.customize_vasp_kpoints_with_accuracy(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.2.4', 'Generate HPC Slurm job submission script for VASP calculations.')
def command_1_2_4():
    try:
        partition = color_input('\nEnter the HPC partition/queue name (default: normal): ', 'yellow').strip() or 'normal'
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    try:
        while True:
            nodes = color_input('\nEnter the number of nodes (default: 1): ', 'yellow').strip()
            try:
                if not nodes:
                    nodes = 1
                else:
                    nodes = int(nodes)
                schemas.GenerateVaspInputsHpcSlurmScript(nodes=nodes)
                break
            except ValueError:
                color_print(f"\n[Error] Invalid number of nodes: {nodes}. Please enter a positive integer.\n", 'red')
                continue
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            ntasks = color_input('\nEnter the number of tasks per node (default: 8): ', 'yellow').strip()
            try:
                if not ntasks:
                    ntasks = 8
                else:
                    ntasks = int(ntasks)
                schemas.GenerateVaspInputsHpcSlurmScript(ntasks=ntasks)
                break
            except ValueError:
                color_print(f"\n[Error] Invalid number of tasks: {ntasks}. Please enter a positive integer.\n", 'red')
                continue
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    try:
        while True:
            walltime = color_input('\nEnter the job walltime in format HH:MM:SS (default: 01:00:00): ', 'yellow').strip()
            try:
                if not walltime:
                    walltime = '01:00:00'
                schemas.GenerateVaspInputsHpcSlurmScript(walltime=walltime)
                break
            except ValueError:
                color_print(f"\n[Error] Invalid walltime format: {walltime}. Please use 'HH:MM:SS' format.\n", 'red')
                continue
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    try:
        job_name = color_input('\nEnter the job name (default: masgent_job): ', 'yellow').strip()
        if not job_name:
            job_name = 'masgent_job'
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    input = schemas.GenerateVaspInputsHpcSlurmScript(
        partition=partition,
        nodes=nodes,
        ntasks=ntasks,
        walltime=walltime,
        job_name=job_name,
        command='srun vasp_std > vasp.out'
    )
    result = tools.generate_vasp_inputs_hpc_slurm_script(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.3.1', 'Generate VASP workflow for convergence tests for k-points and energy cutoff based on given POSCAR.')
def command_1_3_1():
    try:
        while True:
            clear_and_print_entry_message()
            choices = [
                'All      ->  Convergence tests for both energy cutoff and k-points',
                'ENCUT    ->  Convergence test for energy cutoff only',
                'KPOINTS  ->  Convergence test for k-points only',
            ] + global_commands()

            cli = Bullet(prompt='\n', choices=choices, margin=1, bullet=' ●', word_color=colors.foreground['green'])
            user_input = cli.launch()

            if user_input.startswith('AI'):
                ai_backend.main()
            elif user_input.startswith('New'):
                start_new_session()
            elif user_input.startswith('Back'):
                return
            elif user_input.startswith('Main'):
                run_command('0')
            elif user_input.startswith('Help'):
                print_help()
            elif user_input.startswith('Exit'):
                color_print('\nExiting Masgent... Goodbye!\n', 'green')
                sys.exit(0)
            elif user_input.startswith('All'):
                test_type = 'all'
                break
            elif user_input.startswith('ENCUT'):
                test_type = 'encut'
                break
            elif user_input.startswith('KPOINTS'):
                test_type = 'kpoints'
                break
            else:
                continue

    except (KeyboardInterrupt, EOFError):
        color_print('\nExiting Masgent... Goodbye!\n', 'green')
        sys.exit(0)
    
    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    if test_type == 'encut':
        try:
            while True:
                encut_levels_str = color_input('\nEnter the energy cutoff levels you want to test (e.g., "300 400 500 600 700"): ', 'yellow').strip()

                if not encut_levels_str:
                    continue

                try:
                    encut_levels = [int(x) for x in encut_levels_str.split()]
                    schemas.GenerateVaspWorkflowOfConvergenceTests(poscar_path=poscar_path, test_type=test_type, encut_levels=encut_levels)
                    break
                except Exception:
                    color_print(f'[Error] Invalid energy cutoff levels: {encut_levels_str}, please double check and try again.\n', 'red')
        
        except (KeyboardInterrupt, EOFError):
            color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
            time.sleep(1)
            return
        
    elif test_type == 'kpoints':
        try:
            while True:
                kpoint_levels_str = color_input('\nEnter the k-point grid density levels you want to test (e.g., "1000 2000 3000 4000 5000"): ', 'yellow').strip()

                if not kpoint_levels_str:
                    continue

                try:
                    kpoint_levels = [int(x) for x in kpoint_levels_str.split()]
                    schemas.GenerateVaspWorkflowOfConvergenceTests(poscar_path=poscar_path, test_type=test_type, kpoint_levels=kpoint_levels)
                    break
                except Exception:
                    color_print(f'[Error] Invalid k-point levels: {kpoint_levels_str}, please double check and try again.\n', 'red')
        
        except (KeyboardInterrupt, EOFError):
            color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
            time.sleep(1)
            return
        
    else:
        try:
            while True:
                encut_levels_str = color_input('\nEnter the energy cutoff levels you want to test (e.g., "300 400 500 600 700"): ', 'yellow').strip()

                if not encut_levels_str:
                    continue

                try:
                    encut_levels = [int(x) for x in encut_levels_str.split()]
                    schemas.GenerateVaspWorkflowOfConvergenceTests(poscar_path=poscar_path, test_type=test_type, encut_levels=encut_levels)
                    break
                except Exception:
                    color_print(f'[Error] Invalid energy cutoff levels: {encut_levels_str}, please double check and try again.\n', 'red')
        
        except (KeyboardInterrupt, EOFError):
            color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
            time.sleep(1)
            return
        
        try:
            while True:
                kpoint_levels_str = color_input('\nEnter the k-point grid density levels you want to test (e.g., "1000 2000 3000 4000 5000"): ', 'yellow').strip()

                if not kpoint_levels_str:
                    continue

                try:
                    kpoint_levels = [int(x) for x in kpoint_levels_str.split()]
                    schemas.GenerateVaspWorkflowOfConvergenceTests(poscar_path=poscar_path, test_type=test_type, kpoint_levels=kpoint_levels)
                    break
                except Exception:
                    color_print(f'[Error] Invalid k-point levels: {kpoint_levels_str}, please double check and try again.\n', 'red')
        
        except (KeyboardInterrupt, EOFError):
            color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
            time.sleep(1)
            return
    
    if test_type == 'encut':
        input = schemas.GenerateVaspWorkflowOfConvergenceTests(poscar_path=poscar_path, test_type=test_type, encut_levels=encut_levels)
    elif test_type == 'kpoints':
        input = schemas.GenerateVaspWorkflowOfConvergenceTests(poscar_path=poscar_path, test_type=test_type, kpoint_levels=kpoint_levels)
    else:
        input = schemas.GenerateVaspWorkflowOfConvergenceTests(poscar_path=poscar_path, test_type=test_type, encut_levels=encut_levels, kpoint_levels=kpoint_levels)
    result = tools.generate_vasp_workflow_of_convergence_tests(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.3.2', 'Generate VASP workflow of equation of state (EOS) calculations based on given POSCAR.')
def command_1_3_2():
    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    try:
        while True:
            scale_factors_str = color_input('\nEnter the volume scale factors for EOS calculations (e.g., "0.94 0.96 0.98 1.00 1.02 1.04 1.06"): ', 'yellow').strip()

            if not scale_factors_str:
                continue

            try:
                scale_factors = [float(x) for x in scale_factors_str.split()]
                schemas.GenerateVaspWorkflowOfEos(poscar_path=poscar_path, scale_factors=scale_factors)
                break
            except Exception:
                color_print(f'[Error] Invalid scale factors: {scale_factors_str}, please double check and try again.\n', 'red')

    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return
    
    input = schemas.GenerateVaspWorkflowOfEos(poscar_path=poscar_path, scale_factors=scale_factors)
    result = tools.generate_vasp_workflow_of_eos(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)

@register('1.3.3', 'Generate VASP workflow for elastic constants calculations based on given POSCAR.')
def command_1_3_3():
    try:
        poscar_path = check_poscar()
    except (KeyboardInterrupt, EOFError):
        color_print('\n[Error] Input cancelled. Returning to previous menu...\n', 'red')
        time.sleep(1)
        return

    input = schemas.GenerateVaspWorkflowOfElasticConstants(poscar_path=poscar_path)
    result = tools.generate_vasp_workflow_of_elastic_constants(input=input)
    color_print(result['message'], 'green')
    time.sleep(1)