# comment for testing gitlab pipeline, no use in code
import numpy as np
import matrizen as mzn
import os
import sys # for debug print
from collections import defaultdict as defDict
from math import log10, floor

def updateMasse(array1, array2):
    # chatGPT code verbatim
    if array1.dtype != mzn.LCAdType:
        raise ValueError("array1 must have LCAdType [('Parameter', object), ('Wert', 'f8')]")
    if array2.dtype != mzn.LCAdType:
        raise ValueError("array2 must have LCAdType [('Parameter', object), ('Wert', 'f8')]")
    update_dict = {param: masse for param, masse in array2}
    updated_masse = np.array([update_dict.get(param, masse) for param, masse in array1], dtype='f8')
    return np.array(list(zip(array1['Parameter'], updated_masse)), dtype=array1.dtype)

def get_data_dir():
    # Get the directory where this script is located
    script_dir = os.path.dirname(os.path.realpath(__file__))
    # Go up one level and then into data
    return os.path.join(os.path.dirname(script_dir), 'data')

def findInputKeys(data, rechnertyp='npkDefinition', results=None, keys=None): # recursive
    if results is None:
        results = {}
    if keys is None:
        keys = []
    if isinstance(data, dict):
        for key, value in data.items():
            if isinstance(value, dict):
                if 'selectUsedAsInputIn' in value and rechnertyp in value['selectUsedAsInputIn']:
                    for colName in mzn.input_meta['rechnertyp']['options'][rechnertyp]['selColNames']:
                        results[f"{key}_{colName}"] = keys + [key]
                if 'valUsedAsInputIn' in value and rechnertyp in value['valUsedAsInputIn']:
                    for colName in mzn.input_meta['rechnertyp']['options'][rechnertyp]['valColNames']:
                        results[f"{key}_{colName}"] = keys + [key]
                findInputKeys(value, rechnertyp, results, keys + [key])
    return results

def setStdInputValues(data, rechnertyp, transStdStrArray, transStdBahnArray, results=None): # recursive
    # for debug prints
    #import sys
    if results is None:
        results = {}
    if isinstance(data, dict):
        for key, value in data.items():
            #print (f"key: {key}, value: {value}", file=sys.stderr)
            if isinstance(value, dict):
                if 'selectUsedAsInputIn' in value and rechnertyp in value['selectUsedAsInputIn']:
                    for idx, colName in enumerate(
                        mzn.input_meta['rechnertyp']['options'][rechnertyp]['selColNames']):
                        if idx < len(value['selectStdValues']):
                            results[f"{key}_{colName}"] = value['selectStdValues'][idx]
                        else:
                            results[f"{key}_{colName}"] = 0
                if 'valUsedAsInputIn' in value and rechnertyp in value['valUsedAsInputIn']:
                    # TODO change to reading in from stdValTable
                    for idx, colName in enumerate(
                        mzn.input_meta['rechnertyp']['options'][rechnertyp]['valColNames']):
                        if colName == "lkw":
                            #print(f"colName: {colName}", file=sys.stderr)
                            results[f"{key}_{colName}"] = (
                                transStdStrArray['Wert'][(transStdStrArray['Parameter'] ==
                            mzn.input_meta['bestandteile'][key]['resultName'])][0])
                            #print(f"{key}_{colName}: {mzn.input_meta['bestandteile'][key]['resultName']}", file=sys.stderr)
                        elif "bahn" in colName:
                            results[f"{key}_{colName}"] = (
                                transStdBahnArray['Wert'][(transStdBahnArray['Parameter'] ==
                            mzn.input_meta['bestandteile'][key]['resultName'])][0])
                            #print(f"{key}_{colName}: {mzn.input_meta['bestandteile'][key]['resultName']}", file=sys.stderr)
                        else:  # TODO masse still read from matrizen.py
                            if idx < len(value['valStdValues']):
                                results[f"{key}_{colName}"] = value['valStdValues'][idx]
                            else:
                                results[f"{key}_{colName}"] = 0
                setStdInputValues(value, rechnertyp, transStdStrArray, transStdBahnArray, results)
    return results

def inputCheck(inputData, rechnertyp):
    keysDict = findInputKeys(mzn.input_meta, rechnertyp)
    #print(f"keysDict: {keysDict}", file=sys.stderr)
    for inputKey, inputValue in inputData.items():
        #print(f"testing {inputKey}, {inputValue}", file=sys.stderr)
        if inputKey not in keysDict.keys():
             return False, f"Der input name \"{input_key}\" ist nicht erlaubt, bitte korrigieren."
        dictPath = keysDict[inputKey]
        #print(f"dictPath: {dictPath}", file=sys.stderr)
        curDict = mzn.input_meta
        for metaKey in dictPath:
            curDict = curDict[metaKey]
        #print(f"checking curDict: {curDict}", file=sys.stderr)
        if 'selectUsedAsInputIn' not in curDict and 'valUsedAsInputIn' not in curDict:
            return False, f'Keine gültige Definition für {inputKey} gefunden. Wir würden uns freuen über eine Meldung dieses Fehlers an team@pawis.ch'
        if inputKey.endswith("_value"):
            if 'options' in curDict and inputValue not in curDict['options']:
                return False, f'Die Auswahl "{curDict['label']['text']}" enthält keinen Wert aus der Auswahlliste.'
        if inputKey.endswith("_masse") or inputKey.endswith("_lkw") or inputKey.endswith("_bahn"):
            #print(f"Testing {inputKey}:{inputData[inputKey]}", file=sys.stderr)
            if inputData[inputKey] is None or inputData[inputKey] == "":
                inputData[inputKey] = 0.0
            else:
                try:
                    float(inputData[inputKey])
                except:
                    return False, f'Bitte eine Zahl eingeben im Feld "{curDict['label']['text']}".'
    return True, ''

def isValidArray4MatrixMult(arr):
    if not isinstance(arr, np.ndarray) or arr.dtype.names is None:
        raise ValueError("Input is not a NumPy array.")
    if arr.dtype.names is None:
        raise ValueError("Input is not a structured NumPy array (missing field names).")
    fieldNames = arr.dtype.names
    if arr.dtype.fields[fieldNames[0]][0].kind != 'O':
        raise ValueError(
            f"First field '{fieldNames[0]}' must be of object type (kind='O'), got kind='{first_field_kind}'."
        )
    for name in fieldNames[1:]:
        kind = arr.dtype.fields[name][0].kind
        if kind not in 'fi':  # 'f' = float, 'i' = int
            raise ValueError(
                f"Field '{name}' must be of type int or float (kind='f' or 'i'), got kind='{kind}'."
            )
    return True

def matrixMult(npStructArray1, npStructArray2):
    # print(f"npStructArray2.dtype.names[1:]= {npStructArray2.dtype.names[1:]}", file = sys.stderr)
    if not isValidArray4MatrixMult(npStructArray1) or not isValidArray4MatrixMult(npStructArray2):
        return False
    common_params = [name for name in npStructArray1.dtype.names[1:]
                      if name in npStructArray2[npStructArray2.dtype.names[0]]]
    if not common_params:
        return "functions::matrixMult(npStructArray1, npStructArray2): no common params found"
    matrix1 = np.vstack([npStructArray1[name] for name in common_params]).T # shape: (n_rows, n_common)
    array2_ordered = npStructArray2[[
        np.where(npStructArray2[npStructArray2.dtype.names[0]] == name)[0][0]
        for name in common_params]]
    matrix2 = np.vstack([ [row[name] for name in array2_ordered.dtype.names[1:]]
        for row in array2_ordered ])  # shape: (n_common, m)
    # Matrix multiplication
    result_matrix = matrix1 @ matrix2  # shape: (n_rows, m)
    # Create structured array output
    output_field_names = ['Parameter'] + list(array2_ordered.dtype.names[1:])
    output_dtype = [('Parameter', 'O')] + [(name, '<f8') for name in array2_ordered.dtype.names[1:]]
    num_rows = result_matrix.shape[0]
    structured_result = np.empty(num_rows, dtype=output_dtype)
    structured_result['Parameter'] = npStructArray1[npStructArray1.dtype.names[0]]
    for i, name in enumerate(array2_ordered.dtype.names[1:]):
        structured_result[name] = result_matrix[:, i]
    return structured_result

def vectMatrix(npStructArrayVect, npStructArrayMatrix):
    # only works with npStructArrayVect of dType LCAdType
    # import sys # for debug print
    scale_dict = {row['Parameter']: row['Wert'] for row in npStructArrayVect}
    for param in npStructArrayMatrix.dtype.names[1:]:
        if param not in scale_dict:
            # print(f"param: {param}", file=sys.stderr)
            scale_dict[param] =  0
    for col_name in npStructArrayMatrix.dtype.names[1:]:
        npStructArrayMatrix[col_name] *= scale_dict[col_name]
    return npStructArrayMatrix

def addAdditionalLCIAData(lciaArray, addLciaArray):
    origDtype = lciaArray.dtype
    param2Idx = {row['Parameter']: idx for idx, row in enumerate(lciaArray)}
    newCols = addLciaArray.dtype.names[1:]  # exclude 'Parameter'
    newDtypes = [(name, '<f8') for name in newCols]
    existingCols = set(lciaArray.dtype.names)
    combiDtypes = [dt for dt in lciaArray.dtype.descr if dt[0] not in newCols]
    combiDtypes += [(name, '<f8') for name in newCols]
    extendedLciaArray = np.zeros(lciaArray.shape, dtype=combiDtypes)
    for name in lciaArray.dtype.names:
        if name not in newCols:
            extendedLciaArray[name] = lciaArray[name]
    for row in addLciaArray:
        param = row['Parameter']
        if param in param2Idx:
            idx = param2Idx[param]
            for col in newCols:
                extendedLciaArray[col][idx] = row[col]
    return extendedLciaArray

def transposeNamedArray(arr):
    orig_names = arr.dtype.names
    row_labels = arr['Parameter']
    col_labels = [name for name in orig_names if name != 'Parameter']
    new_dtype = [('Parameter', object)] + [(label, '<f8') for label in row_labels]
    transposed_arr = np.zeros(len(col_labels), dtype=new_dtype)
    for i, col in enumerate(col_labels):
        transposed_arr[i]['Parameter'] = col
        for j, label in enumerate(row_labels):
            transposed_arr[i][label] = arr[j][col]
    return transposed_arr

# create lookup dict for umwindikator
def createUmwindLookup():
    umwindLookup = {}
    for key, value in mzn.input_meta['umwindikator']['options'].items():
        umwindLookup[value['Datensatz']] = key
    return umwindLookup

def lciaSum(npNamedArrayDict):
    # create lookup dict for umwindikator
    umwindLookup = createUmwindLookup()
    #print(f"functions::lciaSum: umwindLookup {umwindLookup}", file=sys.stderr)
    sumArr = np.zeros(len(npNamedArrayDict['Herstellung']), dtype=mzn.ResdType)
    removeParamList = []
    showEntsList = []
    for i, row in enumerate(npNamedArrayDict['Herstellung']):
        param = row['Parameter']
        try:
            sumArr[i]['LCIA-Faktor'] = mzn.input_meta['umwindikator']['options'][umwindLookup[param]]['menutext']
            sumArr[i]['Einheit'] = mzn.input_meta['umwindikator']['options'][umwindLookup[param]]['Einheit']
            if mzn.input_meta['umwindikator']['options'][umwindLookup[param]]['showEnts']:
                showEntsList.append(sumArr[i]['LCIA-Faktor'])
        except:
            # print(f"functions::lciaSum: failed key {param}", file=sys.stderr)
            removeParamList.append(param)
            sumArr[i]['LCIA-Faktor'] = param
        values = [row[name] for name in npNamedArrayDict['Herstellung'].dtype.names[1:]]
        sumArr[i]['Herstellung'] = np.sum(values)
    #print(f"functions::lciaSum: showEntsList = {showEntsList}", file=sys.stderr)
    sumArr = sumArr[~np.isin(sumArr['LCIA-Faktor'], removeParamList)]
    for i, row in enumerate(npNamedArrayDict['Entsorgung']):
        try:
            npNamedArrayDict['Entsorgung'][i]['Parameter'] = (
                mzn.input_meta['umwindikator']['options'][umwindLookup[row['Parameter']]]['menutext'] )
        except:
            pass
            # print(f"functions::lciaSum: failed key {row['Parameter']}", file=sys.stderr)
    for i, row in enumerate(npNamedArrayDict['Entsorgung']):
        values = [row[name] for name in npNamedArrayDict['Entsorgung'].dtype.names[1:]]
        if row['Parameter'] in sumArr['LCIA-Faktor']:
            if row['Parameter'] in showEntsList:
                sumArr['Entsorgung'][sumArr['LCIA-Faktor'] == row['Parameter']] = np.sum(values)
                sumArr['Total'][sumArr['LCIA-Faktor'] == row['Parameter']] = (
                    sumArr['Herstellung'][sumArr['LCIA-Faktor'] == row['Parameter']] +
                    sumArr['Entsorgung'][sumArr['LCIA-Faktor'] == row['Parameter']] )
            else:
                sumArr['Entsorgung'][sumArr['LCIA-Faktor'] == row['Parameter']] = np.nan
                sumArr['Total'][sumArr['LCIA-Faktor'] == row['Parameter']] = np.nan
    return sumArr

def lciaDetails(npNamedArrayDict, inData):
    curLCIACol = mzn.input_meta['umwindikator']['options'][inData['umwindikator_select']]['Datensatz']
    curLCIAEinh = mzn.input_meta['umwindikator']['options'][inData['umwindikator_select']]['Einheit']
    internalDict = {}
    internalDict['Herstellung'] = np.empty(0, dtype=mzn.ResdType)
    internalDict['Herstellung'] = npNamedArrayDict['Herstellung'][npNamedArrayDict['Herstellung']['Parameter'] == curLCIACol]
    internalDict['Herstellung'] = transposeNamedArray(internalDict['Herstellung'])
    retArr = np.zeros(len(internalDict['Herstellung']), dtype=mzn.ResDetailsdType)
    for i, row in enumerate(internalDict['Herstellung']):
        retArr[i]['Bestandteil / Prozess'] = row['Parameter']
        retArr[i]['Herstellung'] = row[curLCIACol]
        retArr[i]['Einheit'] = curLCIAEinh
    internalDict['Entsorgung'] = np.empty(0, dtype=mzn.ResdType)
    internalDict['Entsorgung'] = npNamedArrayDict['Entsorgung'][npNamedArrayDict['Entsorgung']['Parameter'] == curLCIACol]
    internalDict['Entsorgung'] = transposeNamedArray(internalDict['Entsorgung'])
    #print(f"internalDict['Entsorgung'].dtype =  {internalDict['Entsorgung'].dtype}", file=sys.stderr)
    #print(f"internalDict['Entsorgung'] =  {internalDict['Entsorgung']}", file=sys.stderr)
    for i, row in enumerate(internalDict['Entsorgung']):
        if row['Parameter'] in retArr['Bestandteil / Prozess']:
            retArr['Entsorgung'][retArr['Bestandteil / Prozess'] == row['Parameter']] = row[curLCIACol]
            retArr['Total'][retArr['Bestandteil / Prozess'] == row['Parameter']] = (
                retArr['Herstellung'][retArr['Bestandteil / Prozess'] == row['Parameter']] +
                retArr['Entsorgung'][retArr['Bestandteil / Prozess'] == row['Parameter']] )
    return retArr

def gruppiereBestandteile(npNamedArray):
    mappingDict = {}
    for value in mzn.input_meta['bestandteile'].values():
        mappingDict[value['resultName']] = value['Zuordnung Bestandteile']
    #print(f"mappingDict = {mappingDict}", file = sys.stderr)
    from collections import defaultdict as defDict
    groupSums = defDict(lambda: {'Herstellung': 0.0, 'Entsorgung': 0.0, 'Total': 0.0})
    groupTotals = {'Herstellung': 0.0, 'Entsorgung': 0.0, 'Total': 0.0}
    for row in npNamedArray:
        param = row['Bestandteil / Prozess']
        if param not in mappingDict:
            groupKey = 'Übriges Betonherstellung'
        else:
            groupKey = mappingDict[param]
        groupSums[groupKey]['Herstellung'] += row['Herstellung']
        groupTotals['Herstellung'] += row['Herstellung']
        groupSums[groupKey]['Entsorgung'] += row['Entsorgung']
        groupTotals['Entsorgung'] += row['Entsorgung']
        groupSums[groupKey]['Total'] += row['Total']
    groupSums['Total']['Herstellung'] = groupTotals['Herstellung']
    groupSums['Total']['Entsorgung'] += groupTotals['Entsorgung']
    groupSums['Total']['Total'] += groupSums['Total']['Herstellung'] + groupSums['Total']['Entsorgung']
    resArr = np.array( [(group, vals['Herstellung'], vals['Entsorgung'], vals['Total'], '')
                            for group, vals in groupSums.items()], dtype=mzn.ResDetailsdType )
    resArr['Einheit'] = npNamedArray[0]['Einheit']
    return resArr

def calcConcreteNormLabel(resultDict):
    # sorte bestimmen aus Gesteinskoernung
    aggregateAll = 0
    if 'Betongranulat C' in resultDict:
        aggregateC = float(resultDict['Betongranulat C'])
    else:
        aggregateC = 0
    aggregateAll += aggregateC
    if 'Mischgranulat M' in resultDict:
        aggregateM = float(resultDict['Mischgranulat M'])
    else:
        aggregateM = 0
    aggregateAll += aggregateM
    if 'Kies rund' in resultDict:
            aggregateGravelRound = float(resultDict['Kies rund'])
    else:
            aggregateGravelRound = 0
    aggregateAll += aggregateGravelRound
    if 'Kies gebrochen' in resultDict:
            aggregateGravelBroken = float(resultDict['Kies gebrochen'])
    else:
            aggregateGravelBroken = 0
    aggregateAll += aggregateGravelBroken
    if 'Sand' in resultDict:
            aggregateSand = float(resultDict['Sand'])
    else:
            aggregateSand = 0
    aggregateAll += aggregateSand
    if aggregateAll == 0:
        aggregateAll = 1
    shareC = aggregateC / aggregateAll
    shareM = aggregateM / aggregateAll
    if shareM == 0:
        if shareC >= 0.5:
            return 'RCbC50'
        if shareC >= 0.25:
            return 'RCbC25'
        return 'Pb'
    if shareM >= 0.4:
        return 'RCbM40'
    if shareM >= 0.1:
        return 'RCbM10'
    if shareC + shareM >= 0.25:
        return 'nNormRec'
    return 'Pb'


def createInput4Change(inData, inRechnertyp, testRechnertyp):
    retDict = {}
    retDict['rechnertyp_select'] = mzn.input_meta['rechnertyp']['selectStdValues'][0]
    retDict['anwendung_select'] = inData['anwendung_select']
    retDict['cemtyp_select'] = inData['cemtyp_select']
    retDict['cemtyp2_select'] = inData['cemtyp_select']
    retDict['bewehrung_select'] = inData['bewehrung_select']
    retDict['bewehrung_masse'] = inData['bewehrung_masse']
    retDict['einspeicherungKohle_masse'] = inData['einspeicherungKohle_masse']
    retDict['einspeicherungGranulat_masse'] = inData['einspeicherungGranulat_masse']
    retDict['umwindikator_select'] = inData['umwindikator_select']
    retDict['actTabInput_select'] = inData['actTabInput_select']
    import betonrechner_main as brMain
    inputDataTmp, tabDict  = brMain.rechneBilanz(inData, testRechnertyp)
    resultDict = {t[0]: t[1] for t in tabDict['tabZusBetonUnbewehrt']}
    if inRechnertyp == mzn.input_meta['rechnertyp']['selectStdValues'][0]: # npkDefinition
        #retDict['sorte_select'] = 'Pb'
        retDict['sorte_select'] = calcConcreteNormLabel(resultDict)
    elif inRechnertyp == mzn.input_meta['rechnertyp']['selectStdValues'][1]: # detailedDefinition
        try:
            #print(f"resultDict {resultDict}", file=sys.stderr)
            retDict['cemtyp_masse'] = resultDict['Zement']
            retDict['cemtyp2_masse'] = ''
            if 'Betongranulat C' in resultDict:
                retDict['betongranulat_masse'] = resultDict['Betongranulat C']
            else:
                retDict['betongranulat_masse'] = ''
            if 'Mischgranulat M' in resultDict:
                retDict['mischgranulat_masse'] = resultDict['Mischgranulat M']
            else:
                retDict['mischgranulat_masse'] = ''
            if 'Kies rund' in resultDict:
                retDict['kies_rund_masse'] = resultDict['Kies rund']
            else:
                retDict['kies_rund_masse'] = ''
            if 'Kies gebrochen' in resultDict:
                retDict['kies_gebrochen_masse'] = resultDict['Kies gebrochen']
            else:
                retDict['kies_gebrochen_masse'] = ''
            if 'Sand' in resultDict:
                retDict['sand_masse'] = resultDict['Sand']
            else:
                retDict['sand_masse'] = ''
            if 'Fliessmittel' in resultDict:
                retDict['fliessmittel_masse'] = resultDict['Fliessmittel']
            else:
                retDict['fliessmittel_masse'] = ''
            if 'Wasser' in resultDict:
                retDict['wasser_masse'] = resultDict['Wasser']
            else:
                retDict['wasser_masse'] = ''
            retDict.update( {
                'cemtyp_lkw': '20.0', 'cemtyp_bahn': '100.0',
                'cemtyp2_lkw': '20.0', 'cemtyp2_bahn': '100.0',
                'betongranulat_lkw': '20.0', 'betongranulat_bahn': '0.0',
                'mischgranulat_lkw': '20.0', 'mischgranulat_bahn': '0.0',
                'kies_rund_lkw': '20.0', 'kies_rund_bahn': '0.0',
                'kies_gebrochen_lkw': '20.0', 'kies_gebrochen_bahn': '0.0',
                'sand_lkw': '20.0', 'sand_bahn': '0.0',
                'kalksteinmehl_masse': '0', 'kalksteinmehl_lkw': '20.0', 'kalksteinmehl_bahn': '0.0',
                'flugasche_masse': '0', 'flugasche_lkw': '20.0', 'flugasche_bahn': '0.0',
                'fliessmittel_lkw': '50.0', 'fliessmittel_bahn': '600.0',
                'weitere_zusaetze_masse': '0', 'weitere_zusaetze_lkw': '50.0', 'weitere_zusaetze_bahn': '600.0',
                'wasser_lkw': '0.0', 'wasser_bahn': '0.0',
                'bewehrung_lkw': '50.0', 'bewehrung_bahn': '600.0',
                'einspeicherungKohle_lkw': '70.0', 'einspeicherungKohle_bahn': '0.0',
                'einspeicherungGranulat_lkw': '0.0', 'einspeicherungGranulat_bahn': '0.0'
            } )

        except:
            #print("brMain.rechneBilanz failed", file=sys.stderr)
            #import traceback
            #print(f"<pre>Python error:\n{traceback.format_exc()}</pre>", file=sys.stderr)
            retDict.update( {
                'cemtyp_masse': '', 'cemtyp_lkw': '20.0', 'cemtyp_bahn': '100.0',
                'cemtyp2_masse': '', 'cemtyp2_lkw': '20.0', 'cemtyp2_bahn': '100.0',
                'betongranulat_masse': '', 'betongranulat_lkw': '20.0', 'betongranulat_bahn': '0.0',
                'mischgranulat_masse': '', 'mischgranulat_lkw': '20.0', 'mischgranulat_bahn': '0.0',
                'kies_rund_masse': '', 'kies_rund_lkw': '20.0', 'kies_rund_bahn': '0.0',
                'kies_gebrochen_masse': '', 'kies_gebrochen_lkw': '20.0', 'kies_gebrochen_bahn': '0.0',
                'sand_masse': '', 'sand_lkw': '20.0', 'sand_bahn': '0.0',
                'kalksteinmehl_masse': '', 'kalksteinmehl_lkw': '20.0', 'kalksteinmehl_bahn': '0.0',
                'flugasche_masse': '', 'flugasche_lkw': '20.0', 'flugasche_bahn': '0.0',
                'fliessmittel_masse': '', 'fliessmittel_lkw': '50.0', 'fliessmittel_bahn': '600.0',
                'weitere_zusaetze_masse': '', 'weitere_zusaetze_lkw': '50.0', 'weitere_zusaetze_bahn': '600.0',
                'wasser_masse': '', 'wasser_lkw': '0.0', 'wasser_bahn': '0.0',
                'bewehrung_lkw': '50.0', 'bewehrung_bahn': '600.0',
                'einspeicherungKohle_lkw': '70.0', 'einspeicherungKohle_bahn': '0.0',
                'einspeicherungGranulat_lkw': '0.0', 'einspeicherungGranulat_bahn': '0.0'
            } )
    return retDict

def npArray2HTMLResultTab(array, alignLeftList, stellenRunden):
    # kleinste Zahl mit den meisten Nachkommastellen feststellen
    stellenDict = {}     # in dict abspeichern key=Spaltennummer, value=Anzahl Stellen
    if array.size > 0:
        #print(array.dtype.names, file=sys.stderr)
        #print(array, file=sys.stderr)
        floatColInArray = np.array([
        dtype[0] == np.float64
        for dtype in array.dtype.fields.values() ])
        for row in array:
            for i, cell in enumerate(row):
                if (floatColInArray[i]):
                    if np.isnan(cell):
                        stellenDict[i] = max(stellenDict.get(i,0), 0)
                    else:
                        test = signif(cell,stellenRunden)
                        test = str(test)
                        if '.' in test:
                            testParts = test.split('.')
                            stellenDict[i] = max(stellenDict.get(i,0), len(testParts[1]))
                        else:
                            stellenDict[i] = max(stellenDict.get(i,0), 0)
                else:
                    stellenDict[i] = 0
        #print(stellenDict)
        html_table = "<table class=\"table shiny-table table-striped spacing-s\" style=\"width:auto;\">\n<thead><tr>\n"
        for colIdx, colName in enumerate(array.dtype.names):
            if colIdx in alignLeftList:
                html_table += "<th style=\"text-align: left;\">{}</th>".format(colName)
            else:
                html_table += "<th style=\"text-align: right;\">{}</th>".format(colName)
        html_table += "  </tr></thead>\n<tbody>\n"
        for row in array:
            html_table += "<tr>"
            for i, cell in enumerate(row):
                if i in alignLeftList:
                    html_table += "<td>{}</td>".format(signifPretty(cell,stellenRunden,stellenDict[i]))
                else:
                    html_table += "<td align=\"right\">{}</td>".format(signifPretty(cell,stellenRunden,stellenDict[i]))
            html_table += "</tr>\n"
        html_table += "</tbody></table>\n"
    else:
        html_table = "<table class=\"table shiny-table table-striped spacing-s\" style=\"width:auto;\">\n</table>"
    return html_table



# round x to n significant digits, n must be >0
def roundKg2g(x):
    return np.round(x, 3)

def signif(x, num_fig):
    try:
        x = float(x)
    except ValueError:
        return x
    if np.isnan(x):
        return ''
    if x == 0:
        return 0
    else:
        retX = round(x, num_fig-int(floor(log10(abs(x))))-1)
        if retX % 1 == 0:
            retX = int(retX)
        return '{:,}'.format(retX).replace(',','\'')

def signifPretty(x, numFig, decPlaces):
    signX = signif(x, numFig)
    signX = str(signX)
    splitX = signX.split('.')
    decNums = 0
    spanAdded = False
    if len(splitX) > 1:
        decNums = len(splitX[1])
    else:
        if decPlaces > 0:
            signX += '<span style="color:rgba(0,0,0,0);">.' #adding a space for the dot
            spanAdded = True
    for i in range(decPlaces-decNums):
        if not spanAdded:
            signX += '<span style="color:rgba(0,0,0,0);">'
            spanAdded = True
        signX += '0' #adding a space for missing decimal numbers
    if spanAdded:
        signX += '</span>'
    return signX
