#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2019-2024 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from pathlib import Path
import stat
from enum import Enum

from xdg.DesktopEntry import DesktopEntry
import json

from util.windows import transform_windows_path
from util.xml import get_xml_root
from util.paths import get_desktop_files_directory
from .dynamic_attributes import DynamicAttributes

class TargetType(Enum):
    FILESYSTEM = 'FILESYSTEM'
    URL = 'URL'

    def __str__(self):
        return self.value

def get_ttype(targetstr):
    '''
    Validation function for targetType property

    :targetstr: String representing link type.

    :returns: Object of type TargetType.
    '''
    ttype = TargetType.FILESYSTEM

    if targetstr == 'URL'or targetstr == TargetType.URL:
        ttype = TargetType.URL

    return ttype

def ttype2str(ttype):
    '''
    Transform TargetType to string for JSON serialization

    :param ttype: TargetType object
    '''
    result = 'FILESYSTEM'

    if ttype == TargetType.URL:
        result = 'URL'

    return result

def read_shortcuts(shortcuts_file):
    '''
    Read shortcut objects from GPTs XML file

    :shortcuts_file: Location of Shortcuts.xml
    '''
    shortcuts = list()

    for link in get_xml_root(shortcuts_file):
        props = link.find('Properties')
        # Location of the link itself
        dest = props.get('shortcutPath')
        # Location where link should follow
        path = transform_windows_path(props.get('targetPath'))
        # Arguments to executable file
        arguments = props.get('arguments')
        # URL or FILESYSTEM
        target_type = get_ttype(props.get('targetType'))

        sc = shortcut(dest, path, arguments, link.get('name'), props.get('action'), target_type)
        sc.set_changed(link.get('changed'))
        sc.set_clsid(link.get('clsid'))
        sc.set_guid(link.get('uid'))
        sc.set_usercontext(link.get('userContext', False))
        sc.set_icon(props.get('iconPath'))
        if props.get('comment'):
            sc.set_comment(props.get('comment'))

        shortcuts.append(sc)

    return shortcuts

def merge_shortcuts(storage, sid, shortcut_objects, policy_name):
    for shortcut in shortcut_objects:
        storage.add_shortcut(sid, shortcut, policy_name)


def find_desktop_entry(binary_path):
    desktop_dir = get_desktop_files_directory()
    binary_name = ''.join(binary_path.split('/')[-1])
    desktop_file_path = Path(f"{desktop_dir}/{binary_name}.desktop")

    if desktop_file_path.exists():
        desktop_entry = DesktopEntry()
        desktop_entry.parse(desktop_file_path)
        return desktop_entry

    return None


class shortcut(DynamicAttributes):
    def __init__(self, dest, path, arguments, name=None, action=None, ttype=TargetType.FILESYSTEM):
        '''
        :param dest: Path to resulting file on file system
        :param path: Path where the link should point to
        :param arguments: Arguemnts to eecutable file
        :param name: Name of the application
        :param type: Link type - FILESYSTEM or URL
        '''
        self.dest = self.replace_slashes(dest)
        self.path = path
        self.expanded_path = None
        self.arguments = arguments
        self.name = self.replace_name(name)
        self.action = action
        self.changed = ''
        self.icon = None
        self.comment = ''
        self.is_in_user_context = self.set_usercontext()
        self.type = ttype
        self.desktop_file_template = None

    def replace_slashes(self, input_path):
        if input_path.startswith('%'):
            index = input_path.find('%', 1)
            if index != -1:
                replace_path = input_path[:index + 2] + input_path[index + 2:].replace('/','-')
                return replace_path
        return input_path.replace('/','-')

    def replace_name(self, input_name):
        if input_name.startswith('%'):
            index = input_name.find('%', 1)
            if index != -1:
                replace_name = input_name[index + 2:]
                return replace_name
        return input_name

    def __str__(self):
        result = self.to_json()
        return result

    def set_changed(self, change_date):
        '''
        Set object change date
        '''
        self.changed = change_date

    def set_clsid(self, clsid):
        self.clsid = clsid

    def set_guid(self, uid):
        self.guid = uid

    def set_icon(self, icon_name):
        self.icon = icon_name

    def set_comment(self, comment):
        self.comment = comment

    def set_type(self, ttype):
        '''
        Set type of the hyperlink - FILESYSTEM or URL

        :ttype: - object of class TargetType
        '''
        self.type = ttype

    def set_usercontext(self, usercontext=False):
        '''
        Perform action in user context or not
        '''
        ctx = False

        if usercontext in [1, '1', True]:
            ctx = True

        self.is_in_user_context = ctx

    def set_expanded_path(self, path):
        '''
        Adjust shortcut path with expanding windows variables
        '''
        self.expanded_path = path

    def is_usercontext(self):
        return self.is_in_user_context

    def desktop(self, dest=None):
        '''
        Returns desktop file object which may be written to disk.
        '''
        if dest:
            self.desktop_file = DesktopEntry(dest)
        else:
            self.desktop_file_template = find_desktop_entry(self.path)
            self.desktop_file = DesktopEntry()
            self.desktop_file.addGroup('Desktop Entry')
            self.desktop_file.set('Version', '1.0')
        self._update_desktop()

        return self.desktop_file

    def _update_desktop(self):
        '''
        Update desktop file object from internal data.
        '''
        if get_ttype(self.type) == TargetType.URL:
            self.desktop_file.set('Type', 'Link')
        else:
            self.desktop_file.set('Type', 'Application')

        self.desktop_file.set('Name', self.name)

        desktop_path = self.path
        if self.expanded_path:
            desktop_path = self.expanded_path
        if get_ttype(self.type) == TargetType.URL:
            self.desktop_file.set('URL', desktop_path)
        else:
            str2bool_lambda = (lambda boolstr: boolstr if isinstance(boolstr, bool)
                               else boolstr and boolstr.lower() in ['True', 'true', 'yes', '1'])
            if self.desktop_file_template:
                terminal_state = str2bool_lambda(self.desktop_file_template.get('Terminal'))
                self.desktop_file.set('Terminal', 'true' if terminal_state else 'false')
            self.desktop_file.set('Exec', '{} {}'.format(desktop_path, self.arguments))
            self.desktop_file.set('Comment', self.comment)

        if self.icon:
            self.desktop_file.set('Icon', self.icon)
        elif self.desktop_file_template and self.desktop_file_template.get('Icon', False):
            self.desktop_file.set('Icon', self.desktop_file_template.get('Icon'))

    def _write_desktop(self, dest, create_only=False, read_firstly=False):
        '''
        Write .desktop file to disk using path 'dest'. Please note that
        .desktop files must have executable bit set in order to work in
        GUI.
        '''
        sc = Path(dest)
        if sc.exists() and create_only:
            return

        if sc.exists() and read_firstly:
            self.desktop(dest).write(dest)
        else:
            self.desktop().write(dest)

        sc.chmod(sc.stat().st_mode | stat.S_IEXEC)

    def _remove_desktop(self, dest):
        '''
        Remove .desktop file fromo disk using path 'dest'.
        '''
        sc = Path(dest)
        if sc.exists():
            sc.unlink()

    def apply_desktop(self, dest):
        '''
        Apply .desktop file by action.
        '''
        if self.action == 'U':
            self._write_desktop(dest, read_firstly=True)
        elif self.action == 'D':
            self._remove_desktop(dest)
        elif self.action == 'R':
            self._remove_desktop(dest)
            self._write_desktop(dest)
        elif self.action == 'C':
            self._write_desktop(dest, create_only=True)