#!/bin/python3
# This is Dwarfguard Python-CGI lib.
# Used primarily for testing and DG SNMP gateway agent
# Copyright (c) 2022-2025 Jan Otte, Dwarf Technologies s.r.o. All rights reserved.
import os
import re
import sys
import subprocess
import string
import random
import logging
from pathlib import Path
import datetime

# initialization defs file (fixed)
initfile_default = "base_defs"
# brand file (default)
# other stuff fixed at global level
di_allowed_chars = 'ABCDFGOPRSTUVXYZ'
di_free_prefix = "free_"
di_taken_prefix = "taken_"
version_ok_chars = set(string.digits + '.')
log = None
md_vars = [ 'name', 'bindir', 'srvdir', 'aerr_invid', 'aerr_notex', 'aerr_regpr', 'aerr_serfu', 'aerr_serve', 'aerr_invda', 'aerr_datal', 'aerr_datap', 'aerr_comme', 'aerr_neser', 'srvdir_srv', 'srvdir_cli', 'srvdir_clidata', 'srvdir_evgui', 'srvdir_evreg', 'srvdir_evdata', 'servid', 'use_ssl', 'srv_liclock', 'form_devid', 'form_fdata', 'form_ddata', 'form_bdata', 'form_devt', 'form_protv', 'devt_advr', 'wmgroup', 'devt_linb', 'devt_owrt', 'devt_telt', 'devt_tegw', 'devt_snmp', 'pylog', 'logdir', 'sfn_data', 'fn_cfgmerge', 'fn_defaults', 'fn_backarch', 'dwarfg_siteconf', 'dwarfg_dbn' ]

class LibVars(object):

    def __init__(self, defs_fn = None, defs_dict = None, brand_fn = "brand_selection", init_brand = False, verbose = False):
        mydefs = defs_fn
        self.devtypes = []
        self.md = {}
        self.md_env_read = False
        self.mp_brand_short = ""
        self.mp_brand_long = ""
        if defs_dict is not None and len(defs_dict) > 0:
            if verbose: print("Reading definitions from the provided dictionary, not attempting base_defs read...")
            for potkey in defs_dict.keys():
                if potkey in md_vars:
                    self.md[potkey] = defs_dict[potkey]
                else:
                    pass # potentially log extraneous key in incoming dictionary
        else:
            if mydefs is None:
                file = open(sys.path[0]+"/"+initfile_default) # try neighboring base_defs first
                for line in file:
                    if re.search("^SHORTNAME", line):
                        if re.search("DWARFG_SHORT", line):
                            if verbose: print("DEV environment encountered, ignoring DEV base_defs...")
                            break # DEV env, yuck!
                        else:
                            mydefs=(sys.path[0]+"/"+initfile_default)
            if mydefs is None: # still no base_defs, attempt to get it from first deployment
                proc = subprocess.Popen(["dwarfg_deplist", "--getfirst"], stdout=subprocess.PIPE)
                proc.wait()
                if 0 == proc.returncode:
                    mydefs = "/opt/" + str(proc.communicate()[0], 'utf-8').rstrip() + "/" + initfile_default
            if (mydefs is None or len(mydefs) == 0) and defs_dict is None:
                raise ValueError('Basic defs file cannot be found/sourced.')
            else:
                if verbose: print("Sourcing base_defs file from", mydefs)
                self.source_bash_file(mydefs)
                for lc_key in md_vars:
                    self.md[lc_key] = os.getenv(lc_key.upper())
                    self.md_env_read = True
        self.devtypes = [ "Any device", self.md['devt_linb'], self.md['devt_advr'], self.md['devt_owrt'], self.md['devt_telt'], self.md['devt_tegw'], self.md['devt_snmp'] ]
        if brand_fn is not None and len(brand_fn) > 0 and init_brand:
            self.source_bash_file(brand_fn)
            self.load_brand(os.getenv('BRAND_SHORT', ""), os.getenv('BRAND_LONG', ""), os.getenv('BRAND_OPT', ""), os.getenv('BRAND_EMAIL', ""), os.getenv('BRAND_WEB', ""), os.getenv('BRAND_MANUF', ""))
        try:
            log = logging.getLogger()
            setlevel(logging.WARNING)
            fh = logging.FileHandler(self.md['logdir'] + "/" + self.md['pylog'], "a+")
            log.addHandler(fh)
        except:
            #print("CGI Logging intialization failure. No logging will be done to MAMAS CGI log.")
            pass

    def find_devtype(self, dt):
        try:
            ret = self.devtypes.index(dt)
        except: return -1
        if 0 == ret: return -1 # "any device" device type not allowed during registration
        return ret

    def get_siteconf(self):
        if self.md['dwarfg_siteconf'] is None or 0 == len(self.md['dwarfg_siteconf']):
            raise Exception("Site config file is not defined.")
        self.source_bash_file(self.md['dwarfg_siteconf'])
        self.md_dbpwd = os.getenv('DBPASS', "")

    def source_bash_file(self, filename):
        command = "env -i bash -c 'set -a && . " + filename + " && env'"
        with open(filename): pass
        if os.path.isfile(filename):
            for line in subprocess.getoutput(command).split("\n"):
                key, value = line.split("=")
                os.environ[key] = value
        else:
            raise Exception("File to be sourced ("+str(filename)+") was not found")

    def load_brand(self, brand_short, brand_long, brand_opt, brand_email, brand_web, brand_manuf):
        self.mp_brand_short = brand_short
        self.mp_brand_long = brand_long
        self.mp_brand_opt = brand_opt
        self.mp_brand_email = brand_email
        self.mp_brand_web = brand_web
        self.mp_brand_manuf= brand_manuf

    def valid_devtype(self, devt):
        if devt == self.md['devt_advr'] or devt == self.md['devt_linb'] or devt == self.md['devt_owrt'] or devt == self.md['devt_telt'] or devt == self.md['devt_tegw'] or devt == self.md['devt_snmp']:
            return True
        return False


# global variables beginning with md (Dwarfguard defs, filled on init file read)


def stamp():
    return datetime.datetime.now().strftime("[%H%M:%S.%f]")

def pathstring(inint = -1, depth = 4, take = False, free = False):
    if (8<depth): return ''
    while True:
        if 0 > inint: nmbr = random.randrange(16 ** depth)
        else: nmbr = inint % (16 ** depth)
        if 0 < nmbr: # do not accept 0 as ID
            break
    path = ''
    for i in range(depth):
        res = nmbr >> (4*(depth-1-i)) & 0b1111
        if i+1 == depth:
            if take: path += di_taken_prefix
            elif free: path += di_free_prefix
        path = path + di_allowed_chars[res]
    return path

def checkversion(x):
    return set(x) <= version_ok_chars

def vtuple(x):
    nt = []
    for i in x.split('.'):
        nt.append(i)
    return tuple(nt)

def pathstringtonum(ps = ''):
    num = 0
    exp = 0
    for i in range(len(ps)):
        if ps[-1-i] == 'A':
            val = 0
        elif ps[-1-i] == 'B':
            val = 1
        elif ps[-1-i] == 'C':
            val = 2
        elif ps[-1-i] == 'D':
            val = 3
        elif ps[-1-i] == 'F':
            val = 4
        elif ps[-1-i] == 'G':
            val = 5
        elif ps[-1-i] == 'O':
            val = 6
        elif ps[-1-i] == 'P':
            val = 7
        elif ps[-1-i] == 'R':
            val = 8
        elif ps[-1-i] == 'S':
            val = 9
        elif ps[-1-i] == 'T':
            val = 10
        elif ps[-1-i] == 'U':
            val = 11
        elif ps[-1-i] == 'V':
            val = 12
        elif ps[-1-i] == 'X':
            val = 13
        elif ps[-1-i] == 'Y':
            val = 14
        elif ps[-1-i] == 'Z':
            val = 15
        else: continue
        num += val * 16 ** exp;
        exp += 1
    return num

def expand_path(path, depth = 3):
    np = ''
    if depth > len(path): return ''
    for i in range(depth): np += path[i] + '/'
    np += path[i+1:]
    return np

def create_dir(dname):
    try:
        os.mkdir(dname)
        os.chmod(dname, 0o775)
    except OSError: sys.exit("Unable to create directory "+dname)

def setup_dir_hier(whereto, wherefrom = '', depth = 0):
    if 0 == depth:
        if '' == wherefrom:
            depth = 3
        else:
            depth = 4
    if 1 > depth: return
    if not os.path.isdir(whereto):
        create_dir(whereto)
    cwd = os.getcwd()
    os.chdir(whereto)
    for i in range(len(di_allowed_chars)):
        if 0 == len(wherefrom): # asked to build the persistent tree
            if not os.path.isdir(di_allowed_chars[i]): os.mkdir(di_allowed_chars[i])
        else: # asked to copy tree from peristent to ramdisk
            if depth > 1: # not the last level, simply create the tree
                if not os.path.isdir(di_allowed_chars[i]): os.mkdir(di_allowed_chars[i])
            else: # last level containing dirs and links
                if os.path.isdir(wherefrom + '/' + di_allowed_chars[i]): # if device registered in persist., create in ramdisk
                    if not os.path.isdir(whereto + '/' + di_allowed_chars[i]): os.mkdir(whereto + '/' + di_allowed_chars[i])
                    if os.path.isfile(whereto + '/' + di_free_prefix + di_allowed_chars[i]):
                        os.unlink(whereto + '/' + di_free_prefix + di_allowed_chars[i]) # remove dangling free files
                else: # otherwise it is free, create appropriate file
                    if not os.path.isfile(whereto + '/' + di_free_prefix + di_allowed_chars[i]):
                        Path(whereto + '/' + di_free_prefix + di_allowed_chars[i]).touch()
        follow_wherefrom = wherefrom
        if '' != wherefrom: follow_wherefrom += '/' + di_allowed_chars[i]
        if 1 < depth: setup_dir_hier(whereto + '/' + di_allowed_chars[i], follow_wherefrom, depth-1)
    os.chdir(cwd)

def setlevel(level):
    global log
    log.setLevel(level)

