#!/usr/bin/env python3

from xdg.BaseDirectory import xdg_config_home
from enum import Enum
import subprocess
import platform
import distro
import json
import re
import os
import wmctrl

colors = [
    # Regular colors.
    "\u001b[30m",
    "\u001b[31m",
    "\u001b[32m",
    "\u001b[33m",
    "\u001b[34m",
    "\u001b[35m",
    "\u001b[36m",
    "\u001b[37m",

    # Bright colors.
    "\u001b[30;1m",
    "\u001b[31;1m",
    "\u001b[32;1m",
    "\u001b[33;1m",
    "\u001b[34;1m",
    "\u001b[35;1m",
    "\u001b[36;1m",
    "\u001b[37;1m",

    # Reset.
    "\u001b[0m"
]

decorations = [
    "\u001b[1m", # Bold.
    "\u001b[4m", # Underline.
    "\u001b[7m"  # Reversed.
]

# Creates a copy of the specified string with color and decorations added.
def colored(string, colorIndex, decorationIndices=[]):
    newString = colors[colorIndex]
    for decorationIndex in decorationIndices:
        newString += decorations[decorationIndex]
    newString += string + colors[len(colors)-1]
    return newString

# Enum for the different data types.
class Type(str, Enum):
    os = 'os'
    kernel = 'kernel'
    wm = 'wm'
    packages = 'packages'
    uptime = 'uptime'

# Enum for the different align modes.
class AlignMode(str, Enum):
    spaces = 'spaces'
    center = 'center'

# Loads the settings from the configuration file.
# First checks for a configuration file in ~/.config/vfetch/vfetch.conf,
# else it defaults to the configuration file in the same folder as the script.
def loadSettings():
    try:
        file = open(xdg_config_home + '/vfetch/vfetch.conf', 'r')
    except FileNotFoundError:
        file = open(os.path.dirname(os.path.realpath(__file__)) + '/vfetch.conf', 'r')
    content = file.read()
    settings = json.loads(content)
    file.close()
    return settings

# Prints string without ending with a new line.
def printn(string):
    print(string, end="")

# Prints string at a specified position.
def printAt(string, *position):
    if len(position) == 1:
        x = position[0][0]
        y = position[0][1]
    else:
        x = position[0]
        y = position[1]
    printn("\x1b7\x1b[%d;%df%s\x1b8" % (y+1, x+1, string))

# Prints the data lines.
def printLines(lines, colorIndex, offsetX, offsetY, alignMode, alignSpace):
    longestName = 0
    dataPosition = 0

    if alignMode is AlignMode.spaces:
        for line in lines:
            position = len(line[0]) + alignSpace
            if position > dataPosition:
                dataPosition = position
    else:
        # Finds the length of the longest name.
        longestName = len(max(lines, key = lambda data: len(data[0]))[0])

    y = 0
    x = offsetX
    # Prints the lines and formats them accordingly.
    for line in lines:
        if alignMode is AlignMode.spaces:
            printAt(line[1], x + dataPosition, y+offsetY)
        elif alignMode is AlignMode.center:
            line[0] = ' ' * (longestName - len(line[0])) + line[0]

        printAt(colored(line[0], colorIndex, [0]), x, y+offsetY)
        if alignMode is AlignMode.center:
            printAt(' ~ ' + line[1], x+len(line[0]), y+offsetY)
        y += 1

# Sets the cursor position.
def setCursorPosition(*position, newLine=False):
    if len(position) == 1:
        x = position[0][0]
        y = position[0][1]
    else:
        x = position[0]
        y = position[1]
    string = '\033[%d;%dH' % (y, x)
    if newLine:
        print(string)
    else:
        printn(string)

# Runs the specified terminal command.
def termRun(command, arguments):
    output = subprocess.run([command, arguments], text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    return output.stdout

# Prints ascii image.
def printAscii(position, asciiImage):
    setCursorPosition(position)
    lines = asciiImage.split('\n')
    for line in lines:
        print(line)

# Gets the operating system.
def getOS(architecture=False, removeLinux=False):
    os = distro.linux_distribution()[0]
    if removeLinux:
        os = re.sub('linux', '', os, flags=re.IGNORECASE)
    os = os.rstrip()
    if architecture:
        os += ' ' + platform.machine()
    return os

# Gets the kernel.
def getKernel(fullName=True):
    kernel = platform.release()
    if not fullName:
        kernel = kernel.split('-')[0]
    return kernel

# Gets the window manager.
def getWM():
    try:
        return wmctrl.os.environ.get('DESKTOP_SESSION')
    except:
        pass
    try:
        return wmctrl.os.environ.get('XDG_SESSION_DESKTOP')
    except:
        return None

# Gets the number of packages.
def getPackages(displayPackageManager=False):
    try:
        packages = termRun('pacman', '-Qq')
        string = str(len(packages.split('\n')))
        if displayPackageManager:
            string += ' (pacman)'
        return string
    except:
        return None

# Gets the machine uptime.
def getUptime():
    with open('/proc/uptime', 'r') as f:
        uptime_seconds = float(f.readline().split()[0])
        hours = uptime_seconds / 3600
        minutes = (hours - int(hours)) * 60
        hours = int(hours)
        minutes = int(minutes)
        string = ''
        if hours != 0:
            string += str(hours) + 'h '
        if minutes != 0 or hours == 0:
            string += str(minutes) + 'm'
    return string

# Gets the data for the specified data type.
def getData(type, settings):
    data = {
        Type.os: getOS(settings['displayArchitecture'], settings['removeLinux']),
        Type.kernel: getKernel(settings['kernelFullName']),
        Type.wm: getWM(),
        Type.packages: getPackages(settings['displayPackageManager']),
        Type.uptime: getUptime()
    }.get(type, None)

    if data is None:
        return None

    name = {
        Type.os: [ 'OS', '' ],
        Type.kernel: [ 'Kernel', '' ],
        Type.wm: [ 'WM', '缾' ],
        Type.packages: [ 'Packages', '' ],
        Type.uptime: [ 'Uptime', '' ]
    }.get(type, None)[int(settings['iconMode'])]

    if settings['lowercase']:
        name = name.lower()
        data = data.lower()

    return [name, data]

# Gets the size of the specified ascii image.
def asciiSize(asciiImage):
    x = 0
    split = asciiImage.split('\n')
    for line in split:
        if len(line) > x:
            x = len(line)
    return [x, len(split)]

# Trims the specified ascii image of empty lines and trailing whitespaces.
def trimAscii(asciiImage):
    lines = asciiImage.split('\n')
    string = ''
    for line in lines:
        trimmedString = line.rstrip()
        if len(trimmedString) != 0:
            string += trimmedString + '\n'
    string = string[:-1] # Removes last newline.
    return string

# Loads the ascii image at the specified path.
def loadAsciiImage(path):
    file = open(path, 'r')
    asciiImage = trimAscii(file.read())
    file.close()
    return asciiImage

settings = loadSettings()

displayAscii = settings['displayAscii']
offset = settings['offset']

# Loads the data lines. If the data is invalid (None) it does not get added.
lines = []
for dataType in settings['data']:
    data = getData(dataType, settings)
    if data is not None:
        lines.append(data)

# Loads the ascii image if the option is set for it.
if displayAscii:
    asciiImage = loadAsciiImage(settings['asciiImage'])
    size = asciiSize(asciiImage)
    offset[0] += size[0]
    finalPosition = [0, size[1]]
else:
    finalPosition = [0, len(lines)+offset[1]]

# Makes the prompt after the script finishes have a blank line before it.
finalPosition[1] += 1

os.system('clear')

if displayAscii:
    printAscii([0,0], asciiImage)

alignMode = AlignMode(settings['alignMode'])

printLines(lines, settings['colorIndex'], offset[0], offset[1], alignMode, settings['alignSpace'])

# Sets the final cursor position for the prompt to end up at.
setCursorPosition(finalPosition, newLine=True)