#!/bin/python3

import os
import sys
import json

RES_DIR = '.'

class color:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

def get_directory(filename):
    return filename[:-len(filename.split('/')[-1])]

def mkdir(filename, as_root = False):
    prefix = 'sudo ' if as_root else ''
    parts = filename.split('/')
    path = filename[:-len(parts[len(parts) - 1])]
    os.system(prefix + 'mkdir -p ' + path)

def copy(file_from, file_to, as_root = False):
    prefix = 'sudo ' if as_root else ''
    os.system(prefix + 'cp -r ' + file_from + ' ' + file_to)

def append(file_from, file_to, as_root = False):
    prefix = 'sudo ' if as_root else ''
    os.system(prefix + 'sh -c "cat ' + file_from + ' >> ' + file_to + '"')

def replace(file, search, repacement, as_root = False):
    prefix = 'sudo ' if as_root else ''
    os.system(prefix + 'sed -i "s/' + search.replace('/', '\\/').replace('[', '\\[').replace(']', '\\]') + '/' + repacement.replace('/', '\\/').replace('[', '\\[').replace(']', '\\]') + '/g" ' + file)

def get_path(base_path, path):
    if path.startswith('~'):
        return path
    if path.startswith('/'):
        return path
    return base_path + '/' + path                

def run_instructions(instructions, application_config_directory, variables = {}):
    for instruction in instructions:
        # terminal instruction
        if type(instruction) == str:
            for varname in variables:
                instruction = instruction.replace(varname, variables[varname])
            action = instruction.split(' ')[0]
            parameters = instruction.split(' ')[1:]

            # runs a bash command
            # run <command parameter parameter ...>
            if action == 'run':
                os.system(instruction[len(action) + 1:])

            # copies a file or directory
            # copy [--su] <from> <to>
            elif action == 'copy':
                base_idx = 0
                as_root = False
                if len(parameters) >= 3 and parameters[0] == '--su':
                    base_idx = 1
                    as_root = True

                file_from = get_path(application_config_directory, parameters[base_idx + 0])
                file_to = get_path(application_config_directory, parameters[base_idx + 1])
                
                mkdir(file_to, as_root)  
                copy(file_from, file_to, as_root)
            
            # appends the content of one file to the content of another file
            # append [--su] <fileToAppend> <fileToBeAppendedTo>
            elif action == 'append':
                base_idx = 0
                as_root = False
                if len(parameters) >= 3 and parameters[0] == '--su':
                    base_idx = 1
                    as_root = True

                file_from = get_path(application_config_directory, parameters[base_idx + 0])
                file_to = get_path(application_config_directory, parameters[base_idx + 1])

                mkdir(file_to, as_root) 
                append(file_from, file_to, as_root)

            # replaces a string with another
            # replace [--su] <filename> <stringToReplace> <stringToPutIn>
            elif action == 'replace':
                base_idx = 0
                as_root = False
                if len(parameters) >= 4 and parameters[0] == '--su':
                    base_idx = 1
                    as_root = True

                filename = get_path(application_config_directory, parameters[base_idx + 0])

                replace(filename, parameters[base_idx + 1], parameters[base_idx + 2].replace('%20', ' '), as_root)
            
            # evaluates a python program (e.g a mathematical formula like (10 + 42) / 1000)
            # eval <variable to write to> <python program string>
            elif action == 'eval':
                program = ''
                for param in parameters[1:]:
                    program += param + ' '
                variables.update({parameters[0]: str(eval(program))})
        
        # conditional instructions
        if type(instruction) == dict:
            waiting = True
            while waiting:
                if variables.get(instruction['variable']):
                    answer = variables.get(instruction['variable'])
                    print('\n' + instruction['question'])
                    print(' > Answer already given: ' + answer)
                else:
                    answer = input('\n' + instruction['question'] + '\n > ')
                    variables.update({instruction['variable']: answer})

                print()

                if instruction['answer'].get(answer) != None:
                    run_instructions(instruction['answer'][answer], application_config_directory, variables)
                    waiting = False
                    break

                if instruction['answer'].get('*') != None:
                    run_instructions(instruction['answer']['*'], application_config_directory, variables)
                    waiting = False
                    break
                
                if waiting:
                    print(color.FAIL + 'ERROR: illegal answer' + color.ENDC)
                    del variables[instruction['variable']]

def setup_application(application_directory, variables, ignore_deps):
    print('setting up "' + application_directory + '" ...')

    try:
        with open(application_directory + '/install.json', 'r') as f:
            instructions = json.load(f)

            # install dependencies
            if not ignore_deps:
                print(color.OKBLUE + '    installing dependencies ...' + color.ENDC)
                for dependency in instructions['dependencies']:
                    if len(os.popen('pacman -Qs ' + dependency).read()) == 0:
                        print(color.OKBLUE + '        installing dependency "' + dependency + '" ...' + color.ENDC)
                        os.system('pikaur -S ' + dependency)
                    else:
                        print(color.OKBLUE + '        dependency "' + dependency + '" already satisfied' + color.ENDC)

            # run installation instructions
            print(color.OKBLUE + '    running configuration ...' + color.ENDC)
            run_instructions(instructions['install'], application_directory, variables)

            print(color.OKGREEN + '    finished' + color.ENDC)

    except Exception as e:
        print(e)
        print(color.FAIL + '    ERROR: unable to open or handle install.json for "' + application + '"' + color.ENDC)

##########################
###   help and flags   ###
##########################
if '--help' in sys.argv:
    print()
    print('Usage:')
    print(' dotfiles_make [<application> ...]')
    print()
    print('Installs dotfiles and its dependencys for the current user.')
    print()
    print('Options:')
    print(' --ignore-deps   do not install any dependencies')
    print()
    print(' --help          show this message')
    exit(0)

ignore_deps = '--ignore-deps' in sys.argv
if ignore_deps:
    sys.argv.remove('--ignore-deps')

###################################
###   install pikaur from aur   ###
###################################
if not ignore_deps:
    if not os.path.isfile('/usr/bin/pikaur'):
        print(color.OKBLUE + 'installing pikaur from aur ...' + color.ENDC)
        try:
            os.mkdir('../tmp')
        except:
            pass
        os.system('cd ../tmp && git clone https://aur.archlinux.org/pikaur.git')
        os.system('cd ../tmp/pikaur && makepkg -is')
    else:
        print(color.OKBLUE + 'pikaur is installed on your system' + color.ENDC)

##################################
###   configure applications   ###
##################################
variables = {}

if len(sys.argv) == 1:
    for application in os.listdir(RES_DIR):
        setup_application(RES_DIR + '/' + application, variables, ignore_deps)
elif len(sys.argv) > 1:
    for application in sys.argv[1:]:
        if not application.startswith('--'):
            setup_application(RES_DIR + '/' + application, variables, ignore_deps)
