const os = require('os');

const fs = require('fs');

const path = require('path');

require('../config/config');

require('../models/subscribers');

const printf = require('util').format;

const Site = require('../models/site');

const CryptoJS = require('crypto-js');

const Url = require('url-parse');

const { ObjectID, ObjectId } = require('mongodb');

const shared_consts = require('./shared_nodejs');

const mongoose = require('mongoose').set('debug', false);
const Subscription = mongoose.model('subscribers');

const server_constants = require('./server_constants');

const download = require('image-downloader');

// SETTINGS WebPush Configuration
const webpush = require('web-push');

const i18n = require('i18n');


const FILELOG = 'filelog.txt';
const FILEEVENTS = 'logevents.txt';
const FILEMANAGERS = 'logmanagers.txt';
const FILETRANSACTION = 'logtrans.txt';
const FILEERRLOGIN = 'errlogin.txt';
const FILESOSTITUZIONI = 'log_sostituzioni.txt';
const FILEIP_TO_BAN = 'log_iptoban.txt';
const FILENAVE = 'logNave.txt';
const subject = process.env.VAPI_KEY_SUBJECT;
const publicVapidKey = process.env.PUBLIC_VAPI_KEY;
const privateVapidKey = process.env.PRIVATE_VAPI_KEY;

const FIELDS_REGEX = ['username', 'name', 'surname'];

// Code goes here
const keySize = 256;
const ivSize = 128;
const iterations = 100;

if (!!process.env.GCM_API_KEY && process.env.GCM_API_KEY !== '') {
  webpush.setGCMAPIKey(process.env.GCM_API_KEY);

  webpush.setVapidDetails(subject, publicVapidKey, privateVapidKey);
}
// console.log('setVapidDetails... config...');

// To Translate!
const textlang = {
  it: {
    'L\'Email è già stata Verificata': 'L\'Email è già stata Verificata',
    'Nuova Registrazione': 'Nuova Registrazione',
    'Effettuata una Nuova Registrazione': 'Effettuata una Nuova Registrazione',
    'partecipanti': 'partecipanti all\'Evento',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    'partecipanti a Cena Condivisa': 'partecipanti a Cena Condivisa',
    'TESTO_ASSISTENZA': '<strong><a href="%s">👉 Per entrare nel Sito</a></strong>\n\n' +
      '👉 <strong><a href="https://freeplanet.app/requestresetpwd">Hai dimenticato la password?</a></strong>\n\n',
    'BENVENUTO': 'Benvenut@',
    'TUE_NAVI': 'Ecco le tue Navi programmate',
    'HAI_I_7_REQUISITI': 'PRIMI PASSI OK!\nHai i Primi Requisiti per Entrare nella Lista !',
    'NON_HAI_I_7_REQUISITI': 'Attenzione!\nAncora non hai i 7 Requisiti per Entrare nella Lista !',
    'INFO_LA_MIA_LAVAGNA': '✨ Lista dei Passi: ✨ \n',
    'INFO_LAVAGNA_SITO_COMPLETARE': 'Per completare tutti i requisiti vai sul sito:\n%s\n👉🏻 <a href="%s">Entra nella tua Lavagna</a>.\n',
    'INFO_LAVAGNA_SITO': 'Per vedere in dettaglio lo <strong>STATO</strong>, sul sito \n 👉🏻 <a href="%s">Entra nella tua Lavagna</a>.\n',
    'INFO_LINK_DA_CONDIVIDERE': 'Link da condividere ai tuoi invitati per farli registrare al sito:\n\n%s',
    'INFO_LINK_ZOOM': 'Link da condividere per partecipare allo Zoom (Conferenza OnLine):\n%s',
    'ZOOM_CONFERENCE': 'Ecco il programma delle Conferenze (Zoom) aperti a TUTTI:',
    'NON_VERIF': 'Non Verificata',
    'VERIF': 'Verificata',
    'EMAIL': 'Email',
    'BOT': 'BOT',
    'EMAIL_NON_VERIF': 'Email Non Verificata\nleggi la tua casella email e trova **"Confermare la Registrazione"**\ne clicca sul bottone **"Verifica Registrazione"**',
    'TELEGRAM_NOT_CONNECTED': 'Telegram non associato al tuo account!',
    'NESSUN_INVITATO': 'Non hai invitato nessuno',
    'ZOOM_PARTECIPATO': 'Partecipazione ad almeno 1 Conferenza (Zoom)',
    'LINEE_GUIDA': 'Accettato le Linee Guida',
    'VIDEO_INTRO': 'Visto il Video',
    'SCRITTO_SOGNO': 'Hai scritto il tuo Sogno',
    'PAYMENTS': 'Modalità di Pagamento',
    'INVITATI': 'persone registrate che hai invitato',
    'INVITATI_ATTIVI': 'Invitati con i 7 Requisiti',
    'NONREG': 'Invitati non Registrati',
    'CLICCA_PER_ZOOM': 'AL GIORNO E ORA INDICATA, PER ENTRARE NELLA VIDEO-CONFERENZA, CLICCA SUL LINK INDICATO',
    'CLICCA_ENTRA': 'CLICCA QUI PER ENTRARE !',
    'ZOOM_INIZIATO': 'QUESTA CONFERENZA E\' INIZIATA!',
    'SCEGLI_VOCE': 'scegli una voce:',
    'INVITATI_LISTA': 'I Tuoi Invitati (in verde con almeno i primi 7 Requisiti)',
    'CIAO': 'Ciao',
    'ADDED_TOLISTAINGRESSO': '<strong>Complimenti! 🎊</strong>\nSei stato aggiunto alla <strong>Lista D\'Imbarco</strong> !\nNei prossimi giorni riceverai un messaggio qui quando la tua Nave sarà pronta a partire.\nRicorda che per salire di graduatoria, puoi condividere il Movimento con i tuoi amici ❤️.',
    'REGALATO_INVITANTE': 'da fare.... 😍🎊 Hai ricevuto in Regalo un Invitato {invitato} da parte di {mittente} !',
    'NO_PROG': 'Attualmente non sei ancora dentro alla Lista d\'Imbarco!',
    'SEND_LINK_CHAT_DONATORI': 'Ciao %s!\nLa tua NAVE sta finalmente Salpando!\nEntra nella Gift Chat della nave {%s} cliccando qui: %s',
    'SEND_LINK_CHAT_SOGNATORE': 'Ciao %s!\nOggi Diventi Sognatore !!! 🎊🎊🎊 Entra nella Gift Chat della nave {%s} cliccando qui: %s',
    'ENTRA_GIFT_CHAT': 'Entra nella Gift Chat',
    'DATA_PART_NAVE': 'data di Partenza della Nave:',
    'SOGNATORE': 'SOGNATORE',
    'INTERMEDIO': 'Intermedio',
    'MEDIATORE': 'MEDIATORE',
    'DONATORI': 'DONATORI',
    'RITESSITURA': 'RITESSITURA',
    'NAVE COMPLETATA': 'NAVE COMPLETATA',
    'Doni Effettuati': 'Doni Effettuati',
    'Facilitatore che affianchèra il Mediatore': 'Facilitatore che affianchèra il Mediatore',
    'APERTURA_CHIUSURA_GIFT_CHAT': '%s: Apertura GIFT CHAT\n%s Chiusura GIFT CHAT\nNota Bene: Hai tempo 3 giorni dall\'apertura per entrare in Chat ed effettuare il tuo Dono, dopodichè effettueremo la sostituzione.',
    'Giorno di Apertura GIFT CHAT': 'Giorno di Apertura GIFT CHAT',
    'Giorno in cui Inviare il DONO': 'Hai 3 giorni di tempo per inviare il tuo dono.\nGiorno di Chiusura',
    'SPOSTATO': 'Sei stato Spostato in una Nuova Nave !',
    'Note': 'Note',
    'TEMPORANEA': 'TEMPORANEA',
    'NAVE': 'NAVE',
    'MSG_SEND_FROM': 'Msg Inviato da',
    'ZOOM_CONFERMATO': 'Sei stato confermato ad aver visto la Video Conferenza di Benvenuto!',
    'RICHIESTA_AMICIZIA': '🌈 Richiesta d\'Amicizia da parte di %s',
    'RICHIESTA_HANDSHAKE': '🌈 %s ha comunicato che ti conosce personalmente e ha fiducia in te.',
    'ACCETTATO_SUL_GRUPPO': '✅ Sei stato accettato da %s a far parte del Gruppo %s',
    'CREATO_NUOVO_GRUPPO': '✅ Hai appena creato un nuovo Gruppo chiamato %s',
    'ACCETTATO_NOTIFICA_ADMINS': '✅ l\'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)',
    'GROUP_REQUEST': 'Richiesta di entrare nel Gruppo %s da parte di %s',
    "CLICCA_QUI": "CLICCA QUI",
  },
  si: {},
  es: {
    'L\'Email è già stata Verificata': 'El correo electrónico ya ha sido verificado',
    'Nuova Registrazione': 'Nuevo Registro',
    'Effettuata una Nuova Registrazione': 'Se ha realizado un nuevo registro',
    'partecipanti': 'participantes',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    'TESTO_ASSISTENZA': 'Para entrar en el sitio:\n%s\n\n' +
      '¿Olvidó su contraseña para acceder al sitio?\nhttps://freeplanet.app/requestresetpwd\n\n',
    'BENVENUTO': 'Bienvenido',
    'TUE_NAVI': 'Aquí están sus naves programadas',
    'HAI_I_7_REQUISITI': '¡LOS PRIMEROS PASOS ESTÁN BIEN!\nTiene los primeros 7 requisitos para entrar en la lista de embarque!',
    'NON_HAI_I_7_REQUISITI': '¡Atención!\nTodavía no tienes los 7 requisitos para entrar en la lista de embarque!',
    'HAI_I_9_REQUISITI': '¡FELICITACIONES!\n¡Has completado los 9 pasos de la Guía! Gracias por ayudar a expandirse!',
    'NON_HAI_I_9_REQUISITI': 'Recuerda que puedes ayudar a que el Movimiento crezca y se expanda compartiendo nuestro viaje con todos.!',
    'INFO_LA_MIA_LAVAGNA': '✨ Lista de pasos: ✨ \n',
    'INFO_LAVAGNA_SITO_COMPLETARE': 'Para completar todos los requisitos vaya al sitio:\n%s\nPara ver el estado de su nave y sus invitados,👉🏻 <a href="%s">Pizarra</a>.\n',
    'INFO_LAVAGNA_SITO': 'Para ver en detalle el estado de su nave, en el sitio web, haga clic en las 3 líneas de la parte superior izquierda y vaya a "Pizarra"..\n',
    'INFO_LINK_DA_CONDIVIDERE': 'Enlaces para compartir con sus invitados para que se registren en el sitio web:\n\n%s',
    'INFO_LINK_ZOOM': 'Enlaces para compartir para participar en el Zoom (Conferencia en línea):\n%s',
    'ZOOM_CONFERENCE': 'Aquí puedes encontrar las fechas de programación en el Zoom:',
    'NON_VERIF': 'No verificado',
    'VERIF': 'Verificado',
    'EMAIL': 'Email',
    'BOT': 'BOT',
    'EMAIL_NON_VERIF': 'Correo electrónico no verificado\nlea su buzón y encuentre **"Confirmar Registro "** <marca>y haga clic en el botón **"Verificar Registro "**.',
    'TELEGRAM_NOT_CONNECTED': 'Telegram no asociado a su cuenta!',
    'NESSUN_INVITATO': 'No invitaste a nadie',
    'ZOOM_PARTECIPATO': 'Participación en al menos 1 Conferencia (Zoom)',
    'LINEE_GUIDA': 'Directrices aceptadas',
    'VIDEO_INTRO': 'Ver el video',
    'SCRITTO_SOGNO': 'Escribiste tu sueño',
    'PAYMENTS': 'Métodos de pago',
    'INVITATI': 'las personas registradas que usted invitó',
    'INVITATI_ATTIVI': 'Invitado con los 7 requisitos',
    'NONREG': 'Invitados no registrados',
    'CLICCA_PER_ZOOM': 'AL DÍA Y HORA INDICADOS, PARA ENTRAR EN LA VIDEOCONFERENCIA, PULSE AQUÍ',
    'CLICCA_ENTRA': 'HAGA CLIC AQUÍ PARA ENTRAR !',
    'ZOOM_INIZIATO': 'ESTA CONFERENCIA HA COMENZADO!',
    'SCEGLI_VOCE': 'elegir una voz:',
    'INVITATI_LISTA': 'Sus invitados (en verde con al menos los 7 primeros requisitos)',
    'CIAO': 'Hola',
    'ADDED_TOLISTAINGRESSO': 'Has sido añadido a la lista de personas que entrarán en Lista de embarque!',
    'NO_PROG': 'Actualmente no está en la lista de embarque.!',
    'SEND_LINK_CHAT_DONATORI': 'Hola %s!\n¡Tu barco por fin está navegando!\nEntra en el Gift Chat haciendo clic aquí: %s',
    'SEND_LINK_CHAT_SOGNATORE': '¡Hola!\n¡Hoy te conviertes en un soñador! 🎊🎊🎊 Entre en el Chat de Regalos de la nave {%s} haciendo clic aquí: %s',
    'ENTRA_GIFT_CHAT': 'Entre en el Chat de Regalos',
    'DATA_PART_NAVE': 'fecha de salida de la nave:',
    'SOGNATORE': 'SOÑADOR',
    'INTERMEDIO': 'Intermedio',
    'MEDIATORE': 'MEDIATOR',
    'DONATORI': 'DONANTES',
    'RITESSITURA': 'RETEJIDO',
    'NAVE COMPLETATA': 'NAVE COMPLETADA',
    'Doni Effettuati': 'Regalos Realizados',
    'Facilitatore che affianchèra il Mediatore': 'Facilitatore de apoyo al Donante',
    'APERTURA_CHIUSURA_GIFT_CHAT': '%s: Apertura del CHAT DE REGALOS\n%s Cierre del CHAT DE REGALOS\nNota: Tienes 3 días desde la apertura para entrar en el Chat y hacer tu Regalo, después de lo cual haremos el reemplazo',
    'SPOSTATO': 'Has sido trasladado a una nueva nave !',
    'Note': 'Notas',
    'TEMPORANEA': 'TEMPORAL',
    'NAVE': 'NAVE',
    'MSG_SEND_FROM': 'Mensaje enviado por',
    'ZOOM_CONFERMATO': '¡Se ha confirmado que ha visto la Video Conferencia de Bienvenida!',
    'RICHIESTA_AMICIZIA': 'Richiesta d\'Amicizia da parte di %s',
    'ACCETTATO_SUL_GRUPPO': 'Sei stato accettato da %s a far parte del Gruppo %s',
    'ACCETTATO_NOTIFICA_ADMINS': 'l\'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)',
    'RICHIESTA_BLOCCO_GRUPPO': 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
  enUs: {
    'partecipanti': 'participants',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    'TESTO_ASSISTENZA': 'To enter the Site:\n%s\n\nForgot your password to access the site?\n' +
      'https://freeplanet.app/requestresetpwd\nChat AYNI BOT (this one):\nhttps://t.me/notevoleaynibot\n\n',
    'BENVENUTO': 'Welcome',
    'TUE_NAVI': 'Here are your programmed ships',
    'HAI_I_7_REQUISITI': 'FIRST STEPS OK!\nYou have the First 7 Requirements to Enter the Boarding List!',
    'NON_HAI_I_7_REQUISITI': 'Attention!\nYou still do not have the 7 requirements to enter the boarding list!',
    'INFO_LA_MIA_LAVAGNA': '✨ Step List: ✨ \n',
    'INFO_LAVAGNA_SITO_COMPLETARE': 'To complete all the requirements go to the site:%s\nTo see the status of your Ship and your guests\n👉🏻 <a href="%s">Dashboard</a>\n',
    'INFO_LAVAGNA_SITO': 'To see in detail the status of your ship, on the website, click on the 3 lines at the top left and go to "Blackboard".\n',
    'INFO_LINK_DA_CONDIVIDERE': 'Links to share with your guests to have them register on website:\n\n%s',
    'INFO_LINK_ZOOM': 'Links to share to participate in Zoom (Online Conference):\n%s',
    'ZOOM_CONFERENCE': 'Here you can find the Zoom Conference Planning:',
    'NON_VERIF': 'Not Verified',
    'VERIF': 'Verified',
    'EMAIL': 'Email',
    'BOT': 'BOT',
    'EMAIL_NON_VERIF': 'Email Not Verified\nread your mailbox and find **"Confirm Registration "**<br>and click on the button **"Verify Registration "**.',
    'TELEGRAM_NOT_CONNECTED': 'Telegram not associated with your account!',
    'NESSUN_INVITATO': 'You didn\'t invite anyone',
    'ZOOM_PARTECIPATO': 'Participation in at least 1 Conference (Zoom)',
    'LINEE_GUIDA': 'Guidelines Accepted',
    'VIDEO_INTRO': 'Seen the Video',
    'SCRITTO_SOGNO': 'You wrote your Dream',
    'PAYMENTS': 'Methods of Payment',
    'INVITATI': 'registered people you invited',
    'INVITATI_ATTIVI': 'Guests with the 7 Requirements',
    'NONREG': 'Non-registered Guests',
    'CLICCA_PER_ZOOM': 'TO THE DAY AND TIME INDICATED, TO ENTER THE VIDEO-CONFERENCE, CLICK HERE',
    'CLICCA_ENTRA': 'CLICK HERE TO ENTER !',
    'ZOOM_INIZIATO': 'THIS CONFERENCE HAS BEGUN!',
    'SCEGLI_VOCE': 'choose a menu item:',
    'INVITATI_LISTA': 'Your Guests (in green with at least the first 7 requirements)',
    'CIAO': 'Hi',
    'ADDED_TOLISTAINGRESSO': 'You have been added to the List of people who will enter the Boarding List!',
    'NO_PROG': 'You are not currently on the boarding list.!',
    'SEND_LINK_CHAT_DONATORI': 'Hi %s!\nYour ship is finally sailing!\nEnter the Gift Chat by clicking here: %s',
    'SEND_LINK_CHAT_SOGNATORE': 'Hello %s!\nToday you become a Dreamer! 🎊🎊🎊 Enter the ship\'s Gift Chat {%s} by clicking here: %s',
    'ENTRA_GIFT_CHAT': 'Enter the ship\'s Gift Chat',
    'DATA_PART_NAVE': 'date of departure of the ship:',
    'SOGNATORE': 'DREAMER',
    'INTERMEDIO': 'Intermediate',
    'MEDIATORE': 'MEDIATOR',
    'DONATORI': 'DONOR',
    'RITESSITURA': 'RE-ENTER',
    'NAVE COMPLETATA': 'SHIP COMPLETED',
    'Doni Effettuati': 'Gifts Made',
    'Facilitatore che affianchèra il Mediatore': 'Facilitatore supporting the Mediator',
    'APERTURA_CHIUSURA_GIFT_CHAT': '%s: Opening GIFT CHAT\n%s Closing GIFT CHAT\nNote: You have 3 days from the opening to enter the Chat and make your Gift, after which we will make the replacement.',
    'SPOSTATO': 'You\'ve been moved to a New Ship !',
    'Note': 'Note',
    'TEMPORANEA': 'TEMPORARY',
    'NAVE': 'SHIP',
    'MSG_SEND_FROM': 'Msg sent by',
    'ZOOM_CONFERMATO': 'You have been confirmed to have seen the Welcome Video Conference!',
    'RICHIESTA_AMICIZIA': 'Richiesta d\'Amicizia da parte di %s',
    'ACCETTATO_SUL_GRUPPO': 'Sei stato accettato da %s a far parte del Gruppo %s',
    'ACCETTATO_NOTIFICA_ADMINS': 'l\'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)',
    'RICHIESTA_BLOCCO_GRUPPO': 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
  fr: {
    'L\'Email è già stata Verificata': 'Le courrier électronique a déjà été vérifié',
    'Nuova Registrazione': 'Nouvelle inscription',
    'Effettuata una Nuova Registrazione': 'Un nouvel enregistrement a été effectué',
    'partecipanti': 'participants',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    'TESTO_ASSISTENZA': 'Pour entrer sur le site:\n%s\n\n' +
      'Vous avez oublié votre mot de passe pour accéder au site ?\nhttps://ayni.gifteconomy.app/requestresetpwd\n\n',
    'BENVENUTO': 'Bienvenue',
    'TUE_NAVI': 'Voici vos navires programmés',
    'HAI_I_7_REQUISITI': 'PREMIÈRES ÉTAPES OK!\nvous avez les 7 premiers Requis pour Entrer dans la liste d\'embarquement!',
    'NON_HAI_I_7_REQUISITI': '\'Attention!\nVous ne remplissez pas encore les 7 conditions pour figurer sur la liste d\'embarquement!',
    'HAI_I_9_REQUISITI': 'FÉLICITATIONS!\n!Vous avez franchi les 9 étapes de la conduite ! Merci d\'avoir aidé à se développer!',
    'NON_HAI_I_9_REQUISITI': 'N\'oubliez pas que vous pouvez aider le Mouvement à grandir et à se développer en partageant notre voyage avec tout le monde.!',
    'INFO_LA_MIA_LAVAGNA': '✨ Liste des étapes: ✨ \n',
    'INFO_LAVAGNA_SITO_COMPLETARE': 'Pour remplir toutes les conditions, rendez-vous sur le site:\n%s\nPour voir le statut de votre navire et de vos invités\n 👉🏻 <a href="%s">Tableau</a>.\n',
    'INFO_LAVAGNA_SITO': 'Pour voir en détail le statut de votre navire, sur le site, cliquez sur les 3 lignes en haut à gauche et allez sur "Tableau".\n',
    'INFO_LINK_DA_CONDIVIDERE': 'Liens à partager avec vos invités pour qu\'ils s\'inscrivent sur le site web:\n\n%s',
    'INFO_LINK_ZOOM': 'Liens à partager pour participer à Zoom (Conférence en ligne):\n%s',
    'ZOOM_CONFERENCE': 'Vous trouverez ici les dates de programmation sur Zoom:',
    'NON_VERIF': 'Non vérifié',
    'VERIF': 'Vérifié',
    'EMAIL': 'Courriel',
    'BOT': 'BOT',
    'EMAIL_NON_VERIF': 'Courriel non vérifié\nlisez votre boîte aux lettres et trouvez **"Confirmer l\'inscription "**<br>et cliquez sur le bouton **"Vérifier l\'inscription "**.',
    'TELEGRAM_NOT_CONNECTED': 'Télégramme non associé à votre compte!',
    'NESSUN_INVITATO': 'Vous n\'avez invité personne',
    'ZOOM_PARTECIPATO': 'Participation à au moins 1 conférence (Zoom)',
    'LINEE_GUIDA': 'Lignes directrices acceptées',
    'VIDEO_INTRO': 'Voir la vidéo',
    'SCRITTO_SOGNO': 'Vous avez écrit votre rêve',
    'PAYMENTS': 'Modes de paiement',
    'INVITATI': 'personnes inscrites que vous avez invitées',
    'INVITATI_ATTIVI': 'Invité avec les 7 exigences',
    'NONREG': 'Invités non enregistrés',
    'CLICCA_PER_ZOOM': 'AU JOUR ET À L\'HEURE INDIQUÉS, POUR ENTRER DANS LA VIDÉOCONFÉRENCE, CLIQUEZ ICI',
    'CLICCA_ENTRA': 'CLIQUEZ ICI POUR ENTRER !',
    'ZOOM_INIZIATO': 'CETTE CONFÉRENCE A COMMENCÉ!',
    'SCEGLI_VOCE': 'choisir une voix:',
    'INVITATI_LISTA': 'Vos invités (en vert avec au moins les 7 premières exigences)',
    'CIAO': 'Salut',
    'ADDED_TOLISTAINGRESSO': 'Vous avez été ajouté à la liste d\'embarquement !',
    'NO_PROG': 'Vous n\'êtes pas encore sur la liste d\'embarquement.!',
    'SEND_LINK_CHAT_DONATORI': 'Salut %s!\nVotre SHIP prend enfin la mer.!\nEntrez dans le Chat Cadeau en cliquant ici: %s',
    'SEND_LINK_CHAT_SOGNATORE': 'Bonjour %s !\nAujourd\'hui, vous devenez un Rêveur ! 🎊🎊🎊 Entrez dans le chat des cadeaux du navire {%s} en cliquant ici : %s',
    'ENTRA_GIFT_CHAT': 'Entrez dans le chat des cadeaux',
    'DATA_PART_NAVE': 'la date de départ du navire:',
    'SOGNATORE': 'Rêveur',
    'INTERMEDIO': 'Intermediaire',
    'MEDIATORE': 'Médiateur',
    'DONATORI': 'DONATEUR',
    'RITESSITURA': 'RETESSITURA',
    'NAVE COMPLETATA': 'NAVIRE COMPLÉTÉ',
    'Doni Effettuati': 'Don effectués',
    'Facilitatore che affianchèra il Mediatore': 'Le tuteur qui soutient le Mediateur',
    'APERTURA_CHIUSURA_GIFT_CHAT': '%s : Chat d\'ouverture\n%s Clôture du GIFT CHAT\nNote: Vous avez 3 jours à partir de l\'ouverture pour entrer dans le Chat et faire votre Cadeau, après quoi nous ferons le remplacement',
    'SPOSTATO': 'Vous avez été transféré sur un nouveau navire !',
    'Note': 'Notes',
    'TEMPORANEA': 'TEMPORAIRE',
    'NAVE': 'NAVIRE',
    'MSG_SEND_FROM': 'Message envoyé par',
    'ZOOM_CONFERMATO': 'Vous avez été confirmé comme ayant vu la vidéoconférence de bienvenue !',
    'RICHIESTA_AMICIZIA': 'Richiesta d\'Amicizia da parte di %s',
    'ACCETTATO_SUL_GRUPPO': 'Sei stato accettato da %s a far parte del Gruppo %s',
    'ACCETTATO_NOTIFICA_ADMINS': 'l\'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)',
    'RICHIESTA_BLOCCO_GRUPPO': 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
  pt: {
    'L\'Email è già stata Verificata': '',
    'Nuova Registrazione': 'Novo Registo',
    'Effettuata una Nuova Registrazione': 'Foi efectuado um novo registo',
    'partecipanti': 'participantes',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    'TESTO_ASSISTENZA': 'Para entrar no site:\n%s\n\n' +
      'Esqueceu sua senha para acessar o site?\nhttps://freeplanet.app/requestresetpwd\n\n',
    'BENVENUTO': 'Bem-vindo',
    'TUE_NAVI': 'Aqui estão os seus navios programados',
    'HAI_I_7_REQUISITI': 'PRIMEIROS PASSOS OK!\nVocê tem os 7 primeiros requisitos para entrar na lista de embarque!',
    'NON_HAI_I_7_REQUISITI': 'Atenção!\nVocê ainda não tem os 7 requisitos para entrar na lista de embarque!',
    'INFO_LA_MIA_LAVAGNA': '✨ Lista de etapas: ✨ \n',
    'INFO_LAVAGNA_SITO_COMPLETARE': 'Para completar todos os requisitos ir para o sitio:\n%s\nPara ver o estado do seu navio e dos seus convidados:\n 👉🏻 <a href="%s">Entre no seu Tablero</a>.\n',
    'INFO_LAVAGNA_SITO': 'Para ver em detalhe o estado do seu navio, no site, clique nas 3 linhas no canto superior esquerdo e vá para "DashBoard"..\n',
    'INFO_LINK_DA_CONDIVIDERE': 'Links para partilhar com os seus convidados para que se registem no website:\n\n%s',
    'INFO_LINK_ZOOM': 'Links para partilhar para participar na Zoom (Conferência Online):\n%s',
    'ZOOM_CONFERENCE': 'Aqui está o programa das Conferências (Zoom) aberto a TODOS:',
    'NON_VERIF': 'Não verificado',
    'VERIF': 'Verificado',
    'EMAIL': 'Email',
    'BOT': 'BOT',
    'EMAIL_NON_VERIF': 'Email Não verificado\nleia a sua caixa de correio e encontre **"Confirmar o registo"**<br>e clique no botão **"Verificação do registo"**',
    'TELEGRAM_NOT_CONNECTED': 'Telegrama não associado à sua conta!',
    'NESSUN_INVITATO': 'Você não convidou ninguém',
    'ZOOM_PARTECIPATO': 'Participação em pelo menos 1 Conferência (Zoom)',
    'LINEE_GUIDA': 'Directrizes Aceites',
    'VIDEO_INTRO': 'Ver o vídeo',
    'SCRITTO_SOGNO': 'Você escreveu o seu sonho',
    'PAYMENTS': 'Formas de pagamento',
    'INVITATI': 'pessoas registadas que convidou',
    'INVITATI_ATTIVI': 'Convidado com os 7 Requisitos',
    'NONREG': 'Convidados não registados',
    'CLICCA_PER_ZOOM': 'PARA O DIA E HORA INDICADOS, PARA ENTRAR NA VÍDEO-CONFERÊNCIA, CLIQUE AQUI',
    'CLICCA_ENTRA': 'CLIQUE AQUI PARA ENTRAR !',
    'ZOOM_INIZIATO': 'ESTA CONFERÊNCIA JÁ COMEÇOU!',
    'SCEGLI_VOCE': 'escolha uma voz:',
    'INVITATI_LISTA': 'Os seus convidados (em verde com, pelo menos, os 7 primeiros requisitos)',
    'CIAO': 'Olá',
    'ADDED_TOLISTAINGRESSO': 'Foi acrescentado à Lista de Embarque !',
    'NO_PROG': 'Actualmente, ainda não consta da lista de embarque.!',
    'SEND_LINK_CHAT_DONATORI': 'Olà %s!\nO seu SHIP está finalmente a zarpar.!\nEntre no Gift Chat, clicando aqui: %s',
    'SEND_LINK_CHAT_SOGNATORE': 'Olá %s!\nHoje você se torna um sonhador! 🎊🎊🎊 Entre no Gift Chat do navio {%s} clicando aqui: %s',
    'ENTRA_GIFT_CHAT': 'Entre no Gift Chat do navio',
    'DATA_PART_NAVE': 'data de partida do navio:',
    'SOGNATORE': 'SONHEIROS',
    'INTERMEDIO': 'Intermediar',
    'MEDIATORE': 'MEDIATOR',
    'DONATORI': 'DONATORES',
    'RITESSITURA': 'ENTRE EM',
    'NAVE COMPLETATA': 'NAVIO COMPLETADO',
    'Doni Effettuati': 'Regalo Feitos',
    'Facilitatore che affianchèra il Mediatore': 'Facilitatore que apoia o Mediator',
    'APERTURA_CHIUSURA_GIFT_CHAT': '%s: Abertura do GIFT CHAT\n%s Fechamento GIFT CHAT\nNota: Você tem 3 dias desde a abertura para entrar no Chat e fazer o seu Presente, depois do qual faremos o substituição.',
    'SPOSTATO': 'Você foi transferido para um novo navio !',
    'Note': 'Notas',
    'TEMPORANEA': 'TEMPORÁRIO',
    'NAVE': 'NAVE',
    'MSG_SEND_FROM': 'Mensagem enviada por',
    'ZOOM_CONFERMATO': 'Foi-lhe confirmado que assistiu à Conferência de Vídeo de Boas-vindas!',
    'RICHIESTA_AMICIZIA': 'Richiesta d\'Amicizia da parte di %s',
    'ACCETTATO_SUL_GRUPPO': 'Sei stato accettato da %s a far parte del Gruppo %s',
    'ACCETTATO_NOTIFICA_ADMINS': 'l\'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)',

    'RICHIESTA_BLOCCO_GRUPPO': 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
};

module.exports = {
  MYAPPS: [],
  INITDB_FIRSTIME: true,
  ACAPO: '\n',

  ENABLE_CRONTAB: 'CRONTAB',
  UPDATE_GRADUATORIA: 'UPDATE_GRAD',

  LANGADMIN: 'it',

  FREEPLANET: '1',
  AYNI: '7',
  CNM: '10',
  RISO: '13',
  FIOREDELLAVITA: '15',

  HELP_CHAT: '',
  TYPECONF_ZOOM: 'zoom',
  TYPECONF_JITSI: 'jitsi',

  APORTADOR_NONE: '------',

  SEP: '|',

  USER_ADMIN_CIRCUITS: 'paoloar77',

  TYPE_PROJECT: 1,
  TYPE_TODO: 2,

  TipoMsg: {
    SEND_LINK_CHAT_DONATORI: 1,
    SEND_MSG: 2,
    SEND_MSG_SINGOLO: 3,
    SEND_TO_ALL: 10,
    SEND_MSG_EFFETTUA_IL_DONO: 1000,
    SEND_MSG_SOLLECITO_DONATORI_NO_DONO: 1005,
    SEND_MSG_A_MEDIATORI: 1010,
    SEND_MSG_A_SOGNATORE: 1020,
    SEND_MSG_A_UTENTE_SOSTITUITO: 1030,
    SEND_MSG_DONO_RICEVUTO_CORRETTAMENTE: 1040,
    SEND_MSG_DONO_NON_RICEVUTO: 1050,
  },

  Perm: {
    PERM_NONE: 0,
    PERM_FRIEND: 1,
    PERM_ALL: 10,
  },

  Placca: {
    DONATORI: 1,
    TUTTI: 2,

    STR_SOGNATORE: '<strong>Sognatore</strong>',
    STR_MEDIATORE: '<strong>Mediatore</strong>',
    STR_MEDIATORI: '<strong>Mediatori</strong>',
    STR_DONATORI: '<strong>Donatori</strong>',
    SOGNATORE: 'E1 🌈 ',
    MEDIATORE: 'A1 💦 ',
    SONOFUOCO: 0,
    SONOACQUA: 3,
    SONOTERRA: 2,
    SONOARIA: 1,
  },

  FieldType: {
    boolean: 1,
    date: 2,
    string: 4,
    binary: 8,
    html: 16,
    select: 32,
    number: 64,
    typeinrec: 128,
    multiselect: 256,
    password: 512,
    listimages: 1024,
    exact: 2048,
    image: 3000,
    nationality: 4096,
    intcode: 5000,
    multioption: 6000,
    onlydate: 7000,
    hours: 8000,
    crypted: 9000,
  },

  MAX_PHASES: 5,
  FIRST_PROJ: '__PROJECTS',
  EXECUTE_CALCPROJ: true,

  gettranslate(text, lang) {
    try {
      return textlang[lang][text];
    } catch (e) {
      return textlang['it'][text];
    }
  },

  getlangbyres(res) {
    // ++Todo: res estrarre la lingua
    let lang = '';
    try {
      lang = res.lang;
    } catch (e) {

    }
    if (!lang) {
      lang = 'it';
    }
    return lang;
  },

  get__(text, lang) {
    try {
      return textlang[lang][text];
    } catch (e) {
      return textlang['it'][text];
    }
  },

  getres__(text, res) {
    let lang = this.getlangbyres(res);
    try {
      return textlang[lang][text];
    } catch (e) {
      return textlang['it'][text];
    }
  },

  getHostname: function () {
    return os.hostname();
  },
  testing: function () {
    return (process.env.TESTING_ON === '1');
  },

  mylog: function (...args) {
    if (!this.testing())
      console.log(args);
  },

  mylogoff: function (...args) {
    // doing nothing
  },

  mylogshow: function (...args) {
    console.log(args);
  },

  mylogserr: function (...args) {
    console.error(args);
  },

  allfieldSendMsg: function () {
    return [
      'userId',
      'source',
      'dest',
      'message',
      'datemsg',
      'read',
      'deleted',
      'origin',
      'idapp',
      'status',
      'options'];
  },

  allfieldSendNotif: function () {
    return [
      'type',
      'userId',
      'sender',
      'typedir',
      'typeid',
      'dest',
      'descr',
      'title',
      'openUrl',
      'datenotif',
      'date_updated',
      'read',
      'deleted',
      'idapp',
      'tablerec',
      'extrafield',
      'tag',
      'options',
      'idrec',
      'status'];
  },

  allfieldTodo: function () {
    return [
      'userId',
      'pos',
      'category',
      'descr',
      'note',
      'priority',
      'statustodo',
      'assignedToUsers',
      'created_at',
      'groupId',
      'modify_at',
      'completed_at',
      'expiring_at',
      'enableExpiring',
      'progress',
      'modified',
      'phase',
      'assigned_to_userId',
      'assignedToUsers',
      'hoursplanned',
      'hoursworked',
      'start_date',
      'themecolor',
      'themebgcolor'];
  },

  allfieldMyEvent: function () {
    return ['userId'];
  },

  allfieldTodoWithId: function () {
    return ['_id', ...this.allfieldTodo()];
  },

  // #TODO Projects++ Add fields ...
  allfieldProject: function () {
    return [
      'idapp',
      'userId',
      'respUsername',
      'viceRespUsername',
      'pos',
      'typeproj',
      'id_main_project',
      'id_parent',
      'descr',
      'longdescr',
      'groupId',
      'hoursplanned',
      'hoursleft',
      'themecolor',
      'themebgcolor',
      'hoursworked',
      'priority',
      'statusproj',
      'created_at',
      'modify_at',
      'completed_at',
      'expiring_at',
      'enableExpiring',
      'progressCalc',
      'modified',
      'live_url',
      'test_url',
      'begin_development',
      'begin_test',
      'totalphases',
      'actualphase',
      'hoursweeky_plannedtowork',
      'endwork_estimate'
      ,
      'privacyread',
      'privacywrite',
      'tipovisu',
      'view'];
  },

  allfieldBooking: function () {
    return [
      'idapp',
      'userId',
      'username',
      'id_bookedevent',
      'tableType',
      'numpeople',
      'numpeopleLunch',
      'numpeopleDinner',
      'numpeopleDinnerShared',
      'msgbooking',
      'modified',
      'infoevent',
      'datebooked',
      'booked'];
  },

  allfieldBookingChange: function () {
    return [
      'numpeople',
      'numpeopleLunch',
      'numpeopleDinner',
      'numpeopleDinnerShared',
      'msgbooking',
      'modified',
      'infoevent',
      'datebooked',
      'booked'];
  },

  allfieldProjectWithId: function () {
    return ['_id', ...this.allfieldProject()];
  },

  jsonCopy(src) {
    return JSON.parse(JSON.stringify(src));
  },

  CloneRecordToNew(src) {
    const myrec = Object.assign({}, src);
    delete myrec._doc['_id'];
    myrec._id = new ObjectID();

    return myrec._doc;
  },

  sendBackNotif: function (subscription, payload) {

    // console.log('sendBackNotif:', subscription, payload);

    const pushOptions = {
      vapidDetails: {
        subject: process.env.VAPI_KEY_SUBJECT,
        privateKey: process.env.PRIVATE_VAPI_KEY,
        publicKey: process.env.PUBLIC_VAPI_KEY,
      },
      TTL: payload.ttl,
      headers: {},
    };

    // Pass object into sendNotification
    return webpush.sendNotification(subscription._doc, JSON.stringify(payload),
      pushOptions).
      catch((err) => {
        if (err.statusCode === 410) {
          // Gone: is not valid anymore (Expired probably!), so I have to delete from my db
          return Subscription.findOneAndRemove({ _id: subscription._id });
        } else {
          console.log('Subscription is no longer valid: ', err);
        }
      }).then((ris) => {

      }).
      // console.log('sendNotification', ris);
      catch(err => {
        console.error(err);
      });
  },

  sendNotificationToUser: async function (userId, title, content, openUrl, openUrl2, tag, actions, id) {

    content = this.convertHTMLtoText(content);

    content = this.ConvertHTMLToPlainText(content);

    let payload = {
      actions,
      title: title,
      content,
      url: openUrl,
      url2: openUrl2,
      tag,
      id,
      // ttl: req.body.ttl,
      // icon: req.body.icon,
      // image: req.body.image,
      // badge: req.body.badge,
      // tag: req.body.tag
    };

    try {

      return await Subscription.find({ userId }, (err, subscriptions) => {
        if (err) {
          console.error(`Error occurred while getting subscriptions`);
          // res.status(500).json({
          //   error: 'Technical error occurred'
          // });

          //++ Rimuovi questo record !?

          return false;
        } else {
          let conta = 0;
          let parallelSubscriptionCalls = subscriptions.map((subscription) => {
            const trovati = subscriptions.length;
            return new Promise((resolve, reject) => {
              const pushSubscription = {
                endpoint: subscription.endpoint,
                keys: {
                  p256dh: subscription.keys.p256dh,
                  auth: subscription.keys.auth,
                },
              };

              conta++;

              const parse = require('url-parse');
              const parsedUrl = parse(subscription.endpoint);
              const audience = parsedUrl.protocol + '//' + parsedUrl.hostname;

              const vapidHeaders = webpush.getVapidHeaders(
                audience,
                process.env.VAPI_KEY_SUBJECT,
                process.env.PUBLIC_VAPI_KEY,
                process.env.PRIVATE_VAPI_KEY,
                'aes128gcm',
              );

              const pushOptions = {
                vapidDetails: {
                  subject: process.env.VAPI_KEY_SUBJECT,
                  privateKey: process.env.PRIVATE_VAPI_KEY,
                  publicKey: process.env.PUBLIC_VAPI_KEY,
                },
                TTL: payload.ttl,
                headers: vapidHeaders,
              };

              console.log('************  INVIO WEBPUSH.SENDNOTIFICATION N° ',
                conta, '/', trovati, 'A', subscription.browser);

              const pushPayload = JSON.stringify(payload);

              webpush.sendNotification(
                pushSubscription,
                pushPayload,
                pushOptions,
              ).then((value) => {
                // console.log('Invio Push', value);
                resolve({
                  status: true,
                  endpoint: subscription.endpoint,
                  data: value,
                });
              }).catch(async (err) => {
                console.error('err Push', err.body);

                if (err.body) {
                  // Cancella dal DB la notifica Push, visto che da errore! (sarà scaduto)
                  const ris = await Subscription.deleteOne({ _id: subscription._id });
                }

                reject({
                  status: false,
                  endpoint: subscription.endpoint,
                  data: err,
                });
              });
            }).catch(error => {
              console.log('ERROR: sendNotificationToUser', error.data.body ? error.data.body : error);
            });
          });
          // q.allSettled(parallelSubscriptionCalls).then((pushResults) => {
          //   console.info(pushResults);
          // });
          // res.json({
          //   data: 'Push triggered'
          // });
          return true;
        }
      });
    } catch (e) {
      console.error('sendNotificationToUser', e.message);
    }

  },

  getReqByPar(idapp, usernameOrig, groupOrig) {
    return {
      body: {
        idapp: idapp,
      },
      user: {
        username: usernameOrig,
        groupOrig,
      },
    };
  },

  sendNotificationByUsername: async function (idapp, username, cmd, telegram, usernameOrig, recObjCreator) {

    var { User } = require('../models/user');
    const { SendNotif } = require('../models/sendnotif');

    const telegrambot = require('../telegram/telegrambot');

    const user = await User.findOne({ idapp, username }, { _id: 1, username: 1, lang: 1 }).lean();
    if (!user)
      return;

    let userId = user._id;
    let lang = user.lang;

    let sendmynotif = true;

    let title = this.getNomeAppByIdApp(idapp);
    let descr = '';
    let openUrl = '/';
    let tag = '';
    let actions = [];
    let domanda = '';
    if (cmd) {
      if (cmd === shared_consts.FRIENDSCMD.REQFRIEND) {
        descr = printf(this.get__('RICHIESTA_AMICIZIA', lang), usernameOrig);
        openUrl = '/my/' + usernameOrig;
        tag = 'reqfriends';

        const userrecDest = await User.getUserShortDataByUsername(idapp, usernameOrig);

        if (userrecDest) {
          sendmynotif = false;  // non lo rimandare 2 volte !
          // SEND TELEGRAM NOTIFICATION
          telegrambot.askConfirmationUserFriend(idapp, shared_consts.CallFunz.RICHIESTA_AMICIZIA, userrecDest, username, usernameOrig);
        }

      } else if (cmd === shared_consts.FRIENDSCMD.SETHANDSHAKE) {
        const userrecDest = await User.getUserShortDataByUsername(idapp, usernameOrig);

        const foundIfAlready = await User.isMyHandShake(idapp, username, usernameOrig);
        if (!foundIfAlready) {
          telegrambot.askConfirmationUserFriend(idapp, shared_consts.CallFunz.RICHIESTA_HANDSHAKE, userrecDest, username, usernameOrig);
        }
      }
    }

    if (userId) {
      // SEND PUSH NOTIFICATION
      await this.sendNotificationToUser(userId, title, descr, openUrl, '', tag, actions);
    }

    if (telegram && sendmynotif) {

      const idtelegram = await User.TelegIdByUsername(idapp, username);

      await telegrambot.sendMsgTelegramByIdTelegram(idapp, idtelegram, descr + domanda, undefined, undefined, true, keyb);
    }

  },

  sendMsgTelegramToAdmin: async function (idapp, msg) {

    const telegrambot = require('../telegram/telegrambot');

    const idtelegram = telegrambot.ADMIN_IDTELEGRAM_SERVER;

    await telegrambot.sendMsgTelegramByIdTelegram(idapp, idtelegram, msg);
  },

  sendNotifAndMsgTelegram: async function (idapp, userId, objmsg, telegram, msgextrateleg) {

    if (userId) {
      await this.sendNotificationToUser(userId, objmsg.title, objmsg.descr, objmsg.openUrl, '', objmsg.tag, objmsg.actions);
    }

    if (telegram) {
      var { User } = require('../models/user');
      const telegrambot = require('../telegram/telegrambot');

      const idtelegram = await User.TelegIdById(idapp, userId);

      const msgteleg = objmsg.descr + (msgextrateleg ? '\n' + msgextrateleg : '');
      await telegrambot.sendMsgTelegramByIdTelegram(idapp, idtelegram, msgteleg);
    }
  },

  sendNotifGroupByUsername: async function (
    cmd, idapp, usernameOrig, usernameDest, username_action, groupname, myrecgroup, isAdmin, username_worked) {

    const { SendNotif } = require('../models/sendnotif');
    const { User } = require('../models/user');
    const telegrambot = require('../telegram/telegrambot');

    const req = this.getReqByPar(idapp, usernameOrig);

    const user = await User.findOne({ idapp, username: usernameDest },
      { _id: 1, lang: 1 });
    if (user) {

      let lang = user.lang;
      let paramsObj = {
        usernameDest,
        groupnameDest: groupname,
        username_action: username_action,
        singleadmin_username: usernameDest,
        options: 0,
        lang,
        isAdmin,
        username_worked,
      };

      let sendnotif = true;
      let typedir = shared_consts.TypeNotifs.TYPEDIR_GROUPS;
      let typeid = 0;
      let onlysave = false;
      let numuseringroup = await User.countUsersInGroup(idapp, groupname);

      if (cmd) {
        if (cmd === shared_consts.GROUPSCMD.SETGROUP) {
          if (myrecgroup && myrecgroup.createdBy === usernameDest && numuseringroup <= 1) {
            typeid = shared_consts.TypeNotifs.ID_GROUP_NEW_REC;
          } else {
            typeid = shared_consts.TypeNotifs.ID_GROUP_ACCEPTED;
          }
        } else if (cmd === shared_consts.GROUPSCMD.REFUSE_REQ_GROUP) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_REFUSED;
        } else if (cmd === shared_consts.GROUPSCMD.REMOVE_FROM_MYGROUP) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_REMOVED;
        } else if (cmd === shared_consts.GROUPSCMD.REQGROUP) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_REQUEST_TO_ENTER;
          // paramsObj.options = MessageOptions.Notify_OnlyToNotifinApp + MessageOptions.Notify_ByBotTelegram;
          const myuserdata = await User.getUserShortDataByUsername(idapp, username_action);
          telegrambot.askConfirmationUser(idapp, shared_consts.CallFunz.RICHIESTA_GRUPPO, myuserdata, usernameDest, groupname,
            myrecgroup._id);
          onlysave = false;
        } else if (cmd === shared_consts.GROUPSCMD.BLOCK_USER) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_BLOCK_USER;
        } else if (cmd === shared_consts.GROUPSCMD.DELETE_GROUP) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_DELETE_USER;
        } else if (cmd === shared_consts.GROUPSCMD.ADDADMIN_OFMYGROUP) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_ADDED_ADMIN_OFMYGROUP;
        } else if (cmd === shared_consts.GROUPSCMD.REMOVEADMIN_OFMYGROUP) {
          typeid = shared_consts.TypeNotifs.ID_GROUP_REMOVED_ADMIN_OFMYGROUP;
        }

      }

      if (sendnotif && typeid > 0) {
        // CREATE NOTIFICATION IN TABLE SENDNOTIF
        await SendNotif.createNewNotifToSingleUser(req, null, paramsObj, onlysave, typedir, typeid);
      }
    }

  },

  sendNotifCircuitByUsername: async function (
    cmd, idapp, usernameOrig, groupOrig, usernameDest, groupDest, username_action,
    circuitname, path, myreccircuit, isAdmin, username_worked, extrarec) {

    const { SendNotif } = require('../models/sendnotif');
    const { User } = require('../models/user');
    const telegrambot = require('../telegram/telegrambot');
    const { MyGroup } = require('../models/mygroup');
    const { Circuit } = require('../models/circuit');

    const req = this.getReqByPar(idapp, usernameOrig, groupOrig);

    let arrris = [];

    const user = await User.findOne({ idapp, username: usernameDest },
      { _id: 1, lang: 1 });

    let lang = 'it';
    if (user) {
      lang = user.lang;
    }
    let ris = null;

    try {
      let paramsObj = {
        usernameDest,
        circuitnameDest: circuitname,
        path,
        username_action: username_action,
        singleadmin_username: usernameDest,
        extrarec,
        options: 0,
        lang,
        isAdmin,
        username_worked,
      };

      let sendnotif = true;
      let typedir = shared_consts.TypeNotifs.TYPEDIR_CIRCUITS;
      let typeid = 0;
      let onlysave = false;
      let numuserincircuit = await User.countUsersInCircuit(idapp, circuitname);
      const mycircuit = await Circuit.findOne({ name: circuitname }).lean();
      if (mycircuit) {
        extrarec.fido_scoperto_default = mycircuit.fido_scoperto_default;
        extrarec.fido_scoperto_default_grp = mycircuit.fido_scoperto_default_grp;
      }

      if (cmd) {
        if (cmd === shared_consts.CIRCUITCMD.SET) {
          if (myreccircuit && myreccircuit.createdBy === usernameDest && numuserincircuit <= 1) {
            typeid = shared_consts.TypeNotifs.ID_CIRCUIT_NEW_REC;
          } else {
            typeid = shared_consts.TypeNotifs.ID_CIRCUIT_ACCEPTED;
          }
        } else if (cmd === shared_consts.CIRCUITCMD.REFUSE_REQ) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_REFUSED;
        } else if (cmd === shared_consts.CIRCUITCMD.REMOVE_FROM_MYLIST) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_REMOVED;
        } else if (cmd === shared_consts.CIRCUITCMD.REQ) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_REQUEST_TO_ENTER;
          // paramsObj.options = MessageOptions.Notify_OnlyToNotifinApp + MessageOptions.Notify_ByBotTelegram;
          const myuserdata = await User.getUserShortDataByUsername(idapp, username_action);
          telegrambot.askConfirmationUser(idapp, shared_consts.CallFunz.RICHIESTA_FIDO, myuserdata, usernameDest, circuitname,
            myreccircuit._id, '', extrarec.groupname);
          onlysave = false;
        } else if (cmd === shared_consts.CIRCUITCMD.ADDADMIN) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_ADDED_ADMIN;
        } else if (cmd === shared_consts.CIRCUITCMD.SETFIDO) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_SETFIDO;
        } else if (cmd === shared_consts.CIRCUITCMD.REMOVEADMIN) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_REMOVED_ADMIN;
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REQ) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_SENDCOINSREQ;
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REQ_SENT) {
          // Crea l'ID di Transazione
          paramsObj.idTransaction = new ObjectId();
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_SENDCOINSREQ_SENT;
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_COINS_ACCEPTED;
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT_SENT) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_COINS_ACCEPTED_SENT;
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_COINS_REFUSED;
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE_SENT) {
          typeid = shared_consts.TypeNotifs.ID_CIRCUIT_COINS_REFUSED_SENT;
        }

      }

      if (sendnotif && typeid > 0) {
        const groupDestoContoCom = groupDest ? groupDest : (extrarec && extrarec.contoComDest ? extrarec.contoComDest : '');
        // Check if is group:
        if (groupDestoContoCom && (
          (cmd !== shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT_SENT) &&
          (cmd !== shared_consts.CIRCUITCMD.SENDCOINS_REFUSE_SENT)
        )) {

          let arrusers = await MyGroup.getListAdminsByGroupName(idapp, groupDestoContoCom);
          if (arrusers.length <= 0)
            arrusers = await Circuit.getListAdminsByCircuitPath(idapp, groupDestoContoCom);

          let notiftoUpdate = '';
          let findrecnotif = await SendNotif.findOne({ _id: paramsObj.extrarec.notifId }).lean();
          notiftoUpdate = findrecnotif ? findrecnotif.extrarec.notifIdToUpdate : '';
          if (findrecnotif) {
            let arrrec = [];

            if (notiftoUpdate)
              arrrec = await SendNotif.find({ 'extrarec.notifIdToUpdate': notiftoUpdate });

            for (let rec of arrrec) {
              paramsObj.usernameDest = rec.dest;
              paramsObj.sender = rec.sender;
              if ((cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT) || (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE)) {
                try {
                  paramsObj.extrarec.notifId = rec._id;
                } catch (e) {
                }
              }

              ris = await SendNotif.createNewNotifToSingleUser(req, null, paramsObj, onlysave, typedir, typeid);
              if (!ris) {
                console.error('Errore createNewNotifToSingleUser! ', paramsObj.usernameDest, "dest ->", groupDestoContoCom)
              } else {
                arrris.push(ris._id);
              }

            }

          } else {

            for (let i = 0; i < arrusers.length; i++) {
              paramsObj.usernameDest = arrusers[i].username;

              ris = await SendNotif.createNewNotifToSingleUser(req, null, paramsObj, onlysave, typedir, typeid);
              if (!ris) {
                console.error('Errore createNewNotifToSingleUser! ', paramsObj.usernameDest, "dest ->", groupDestoContoCom)
              } else {
                arrris.push(ris._id);
              }
            }

          }

        } else {
          // CREATE NOTIFICATION IN TABLE SENDNOTIF
          ris = await SendNotif.createNewNotifToSingleUser(req, null, paramsObj, onlysave, typedir, typeid);

          if ((cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT) ||
            (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE)) {

            if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT) {
              typeid = shared_consts.TypeNotifs.ID_CIRCUIT_COINS_ACCEPTED_SENT;
            } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE) {
              typeid = shared_consts.TypeNotifs.ID_CIRCUIT_COINS_REFUSED_SENT;
            }

            let notiftoUpdate = '';
            let findrecnotif = await SendNotif.findOne({ _id: paramsObj.extrarec.notifId }).lean();
            notiftoUpdate = findrecnotif ? findrecnotif.extrarec.notifIdToUpdate : '';
            arrnotiftoUpdate = findrecnotif ? findrecnotif.extrarec.arrnotifIdToUpdate : '';
            if (findrecnotif && notiftoUpdate && arrnotiftoUpdate) {

              for (let myid of arrnotiftoUpdate) {
                let rec = await SendNotif.findOne({ _id: myid }).lean();
                if (rec && rec.status === 0) {
                  paramsObj.usernameDest = rec.dest;
                  paramsObj.extrarec.idStatusToSent = rec._id;


                  ris = await SendNotif.createNewNotifToSingleUser(req, null, paramsObj, onlysave, typedir, typeid);
                  if (!ris) {
                    console.error('Errore createNewNotifToSingleUser! ', paramsObj.usernameDest, "dest ->", groupDestoContoCom)
                  }
                }

              }
            }

          }
        }
      }

      return { recnotif: ris, arrris };
    } catch (e) {
      console.log(e.message);
    }

  },

  sendNotificationByGroupname: async function (idapp, usernameOrig, groupname, cmd, value, telegram, username_action) {

    const { MyGroup } = require('../models/mygroup');
    const { User } = require('../models/user');

    const group = await MyGroup.findOne({ idapp, groupname }, { _id: 1, admins: 1, createdBy: 1 }).lean();
    if (!group)
      return;

    let giainviato = false;

    try {
      // SEND TO THE ADMINS THE NOTIFICATIONS
      for (const singleadmin of group.admins) {
        try {
          if (singleadmin.username) {
            if (usernameOrig === singleadmin.username)
              giainviato = true;

            await this.sendNotifGroupByUsername(cmd, idapp, usernameOrig, singleadmin.username, username_action, groupname, group, true);
          }
        } catch (e) {
          console.error('sendNotificationByGroupname', e);
        }
      }

      if (!giainviato && (cmd !== shared_consts.GROUPSCMD.REQGROUP)) {
        // SEND TO THE USER THE NOTIFICATION
        await this.sendNotifGroupByUsername(cmd, idapp, usernameOrig, usernameOrig, username_action, groupname, null, false);
      }

    } catch (e) {
      console.error('e', e);
    }

  },

  sendNotificationByCircuit: async function (idapp, usernameOrig, circuitname, cmd, value, telegram, username_action, extrarec) {

    const { Circuit } = require('../models/circuit');
    const { SendNotif } = require('../models/sendnotif');

    const circuit = await Circuit.findOne({ idapp, name: circuitname }, { _id: 1, admins: 1, createdBy: 1, path: 1 }).lean();
    if (!circuit)
      return;

    let giainviato = false;

    try {
      // SEND TO THE ADMINS THE NOTIFICATIONS

      let ris = false;
      let inviato = false;
      let arrris = [];

      if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REQ || cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT || cmd ===
        shared_consts.CIRCUITCMD.SENDCOINS_REFUSE) {

        groupOrig = extrarec.grouporig

        let recnotif = null;

        if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REQ) {
          // msg al Mittente (o mittenti del 'groupOrig') "Stai inviando X RIS"
          let res = await this.sendNotifCircuitByUsername(shared_consts.CIRCUITCMD.SENDCOINS_REQ_SENT, idapp, extrarec.dest, extrarec.groupdest, usernameOrig, groupOrig,
            username_action, circuitname, circuit.path, null,
            false, '', extrarec);

          if (res) {
            recnotif = res.recnotif;
            arrris = res.arrris;
          }
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT) {
          // msg ai MITTENTI: "X RIS sono stati accettati da Destinatario"
          await this.sendNotifCircuitByUsername(shared_consts.CIRCUITCMD.SENDCOINS_ACCEPT_SENT, idapp, extrarec.dest, extrarec.groupdest, usernameOrig, groupOrig,
            username_action, circuitname, circuit.path, null,
            false, '', extrarec);
        } else if (cmd === shared_consts.CIRCUITCMD.SENDCOINS_REFUSE) {
          await this.sendNotifCircuitByUsername(shared_consts.CIRCUITCMD.SENDCOINS_REFUSE_SENT, idapp, extrarec.dest, extrarec.groupdest, usernameOrig, groupOrig,
            username_action, circuitname, circuit.path, null,
            false, '', extrarec);
        }

        if (recnotif)
          extrarec.notifIdToUpdate = recnotif._id;
        if (arrris)
          extrarec.arrnotifIdToUpdate = arrris;

        let usernametoOrigUse = usernameOrig
        if (extrarec.groupname) {
          usernametoOrigUse = ''
        }
        ris = await this.sendNotifCircuitByUsername(cmd, idapp, usernametoOrigUse, groupOrig, extrarec.dest, extrarec.groupdest, username_action, circuitname, circuit.path,
          null, false, '', extrarec);

        if (arrris && ris && ris.recnotif) {
          for (const rec of arrris) {
            let obj = {
              'extrarec.notifIdToUpdate': ris.recnotif._id
            };

            await SendNotif.findOneAndUpdate({ _id: rec._id }, { $set: obj }, { new: false });
          }
        }

        extrarec.notifIdToUpdate = '';

        if (ris.recnotif)
          inviato = true;
        // ++Todo: Inviare anche agli Admin ?!
      } else {

        const groupOrig = '' //++Todo: extrarec.groupOrig

        for (const singleadmin of circuit.admins) {
          try {
            if (singleadmin.username) {
              if (usernameOrig === singleadmin.username)
                giainviato = true;

              await this.sendNotifCircuitByUsername(cmd, idapp, usernameOrig, groupOrig, singleadmin.username, '', username_action, circuitname,
                circuit.path, circuit, true, '', extrarec);
              inviato = true;
            }
          } catch (e) {
            console.error('sendNotificationByCircuit', e);
          }
        }

        if (!giainviato && cmd !== shared_consts.CIRCUITCMD.REQ) {
          // SEND TO THE USER DEST THE NOTIFICATION
          ris = await this.sendNotifCircuitByUsername(cmd, idapp, usernameOrig, groupOrig, usernameOrig, '', username_action, circuitname, circuit.path,
            null,
            false, '', extrarec);
          if (ris.recnotif)
            inviato = true;
        }
      }


      if (inviato) {
        if (arrris && arrris.length > 0) {
          for (const notif of arrris) {
            await SendNotif.updatePendingTransactions(notif);
          }
        } else {
          if (ris) {
            await SendNotif.updatePendingTransactions(ris.recnotif);
          }
        }
      }

      return { ris: ris.recnotif, inviato };

    } catch (e) {
      console.error('sendNotificationByCircuit: ', e);
      return { ris: null, inviato: false };
    }

  },

  // **********************
  // SORT WITH PREV_ID
  // **********************
  /* mapSort: function (linkedList) {
    let sortedList = [];
    let remainingList = [];
    var map = new Map();
    var currentId = null;

    // console.log('linkedList', linkedList);

    // index the linked list by previous_item_id
    for (var i = 0; i < linkedList.length; i++) {
      var item = linkedList[i];
      if (item.id_prev === server_constants.LIST_START) {
        // first item
        currentId = item._id.toString();
        // console.log('currentId', currentId);
        sortedList.push(item);
      } else {
        map.set(item.id_prev.toString(), i);
      }
    }

    let conta = 0;
    while (conta < linkedList.length) {
      // get the item with a previous item ID referencing the current item
      var nextItem = linkedList[map.get(currentId)];
      if (nextItem === undefined) {

      } else {
        sortedList.push(nextItem);
        currentId = nextItem._id.toString();
      }
      conta++;
    }

    if (linkedList.length > sortedList.length) {
      // If are not in the list, I'll put at the bottom of the list
      // console.log('ATTENZIONE !!! ', sortedList.length, linkedList.length);
      for (const itemlinked of linkedList) {
        const elemtrov = sortedList.find((item) => item._id === itemlinked._id);
        if (elemtrov === undefined) {
          sortedList.push(itemlinked);
        }
      }
    }

    // console.log('DOPO sortedList', sortedList);

    return sortedList;
  },
  */

  sendNotifToAdmin(idapp, alsotelegram, title, msg, tag = '') {
    try {
      const { User } = require('../models/user');

      const queryadmins = { username: 'paoloar77', idapp };

      if (idapp === 0)
        idapp = '13';

      User.find(queryadmins).then(async (arrusers) => {
        if (arrusers !== null) {
          for (const user of arrusers) {
            if (alsotelegram) {
              this.sendMsgTelegramToAdmin(idapp, title + ' ' + msg);
            }

            await this.sendNotificationToUser(user._id, title, msg, '/', '', tag, []).
              then(ris => {
                if (ris) {

                } else {
                  // already sent the error on calling sendNotificationToUser
                }
              });
          }
        }
      });

    } catch (e) {
      console.error(e);
    }

  },

  checkUserOk(userpassed, userauth, res) {
    // this.mylog('checkUserOk', userpassed, userauth);
    if (String(userpassed) !== String(userauth)) {
      // I'm trying to write something not mine!
      this.mylog('I\'m trying to write something not mine!: userId = ', userpassed, 'req.user._id', userauth);
      if (!res) {
        return {
          exit: true,
          ret: false,
        };
      } else {
        return {
          exit: true,
          ret: false,
          // ret: res.status(404).
          //   send({ code: server_constants.RIS_CODE_TODO_CREATING_NOTMYUSER }),
        };
      }
    } else {
      return { exit: false, ret: false };
    }

  },

  ConvertHTMLToPlainText(strHTML) {
    if (strHTML)
      return strHTML.replace(/<[^>]+>/g, '');
    return '';
  },

  convertSpaces_ToUScore(mystr) {
    if (mystr)
      return mystr.replace(/\s+/g, '_');
    return '';
  },

  convertHTMLtoText(myhtml) {
    let msg = myhtml;
    if (msg) {
      msg = msg.replace('&quot;', '"');
      msg = msg.replace(/<strong>/g, '');
      msg = msg.replace(/<\/strong>/g, '');
      msg = msg.replace(/<em>/g, '');
      msg = msg.replace(/<\/em>/g, '');
      msg = msg.replace('&gt;', '>');
      msg = msg.replace('&lt;', '<');
      msg = msg.replace('&amp;', '&');
      msg = msg.replace('<br>', '\n');
    }

    return msg;
  },

  htmlToTelegramText(html) {

    try {
      const withMarkdown = html
        .replace(/<strong>(.*?)<\/strong>/g, '*$1*')
        .replace(/<b>(.*?)<\/b>/g, '*$1*')
        .replace(/<em>(.*?)<\/em>/g, '_$1_')
        .replace(/<u>(.*?)<\/u>/g, '__$1__');

      // Remove other HTML tags
      const plainText = withMarkdown.replace(/<[^>]*>/g, '');

      // Replace HTML entities with their equivalent Markdown or plain text representations
      const replacements = [
        { pattern: /&amp;/g, replacement: '&' },
        { pattern: /&lt;/g, replacement: '<' },
        { pattern: /&gt;/g, replacement: '>' },
        { pattern: /&quot;/g, replacement: '"' },
        { pattern: /&#39;/g, replacement: "'" },
      ];

      let telegramText = plainText;
      replacements.forEach((replacement) => {
        telegramText = telegramText.replace(replacement.pattern, replacement.replacement);
      });

      return telegramText;

    } catch (e) {
      console.error(e);
    };

  },

  htmlToTelegramText(html) {

    try {
      const withMarkdown = html
        .replace(/<strong>(.*?)<\/strong>/g, '*$1*')
        .replace(/<b>(.*?)<\/b>/g, '*$1*')
        .replace(/<em>(.*?)<\/em>/g, '_$1_')
        .replace(/<u>(.*?)<\/u>/g, '__$1__');

      // Remove other HTML tags
      const plainText = withMarkdown.replace(/<[^>]*>/g, '');

      // Replace HTML entities with their equivalent Markdown or plain text representations
      const replacements = [
        { pattern: /&amp;/g, replacement: '&' },
        { pattern: /&lt;/g, replacement: '<' },
        { pattern: /&gt;/g, replacement: '>' },
        { pattern: /&quot;/g, replacement: '"' },
        { pattern: /&#39;/g, replacement: "'" },
      ];

      let telegramText = plainText;
      replacements.forEach((replacement) => {
        telegramText = telegramText.replace(replacement.pattern, replacement.replacement);
      });

      return telegramText;

    } catch (e) {
      console.error(e);
    };

  },

  convertTexttoHtml(myhtml) {
    // let msg = myhtml;
    // msg = msg.replace('\n', '<br>');

    // return msg

    return myhtml;
  },

  removeSpecialCharForEmail(myhtml) {
    let msg = myhtml;
    msg = msg.replace(/"/g, '\'');

    return msg;
  },

  getNomeAppByIdApp: function (idapp) {

    const myapp = this.MYAPPS.find(item => item.idapp === idapp);
    if (myapp)
      return ((process.env.NODE_ENV === 'test') ? 'Test: ' : '') + myapp.name;
    else
      return '';
  },

  getTimeExpReg: async function (idapp, username) {
    var { User } = require('../models/user');

    return await User.createNewReqRegistrationGetLink(idapp, username);
  },

  getLinkRegByIdAppAndMsgStrutt: function (idapp, msg, rec) {

    let myapp = this.getHostByIdApp(idapp);
    if (myapp) {
      myapp += '/registrati/' + rec.aportador_solidario + '/' + msg.from.username + '/' + msg.from.id + '/' + rec.regexpire;
    }

    return myapp;
  },

  getHostByIdApp: function (idapp) {

    const myapp = this.MYAPPS.find(item => item.idapp === idapp);
    if (myapp) {
      let siteport = (myapp.portapp && myapp.portapp !== '0') ? (':' +
        myapp.portapp) : '';

      if (process.env.NODE_ENV === 'test')
        return myapp.host_test + siteport;
      else
        return myapp.host + siteport;

    } else
      return '';
  },

  getLinkUserProfile: function (idapp, username) {
    return this.getHostByIdApp(idapp) + '/my/' + username;
  },

  getLinkUserTelegram: async function (idapp, username, myuser) {
    var { User } = require('../models/user');

    let u_tg = '';
    if (myuser)
      u_tg = myuser.profile && myuser.profile.hasOwnProperty('username_telegram') ? myuser.profile.username_telegram : usernametelegram;

    if (!u_tg) {
      u_tg = await User.getUsernameTelegram(idapp, username);
    }

    if (u_tg)
      return `https://t.me/${u_tg}`;
    else
      return '';
  },

  getLinkEditUserProfile: function (idapp) {
    return this.getHostByIdApp(idapp) + '/editprofile';
  },

  getConfSiteOptionEnabledByIdApp: function (idapp, option) {

    const myapp = this.MYAPPS.find(item => item.idapp === idapp);
    if (myapp) {

      if (myapp.hasOwnProperty('confsite')) {
        if (myapp.confsite.hasOwnProperty('options')) {
          return this.isBitActive(myapp.confsite.options, option);
        }
      }
    }

    return false;
  },

  getConfParamSiteByIdApp: function (idapp, field) {

    const myapp = this.MYAPPS.find(item => item.idapp === idapp);
    if (myapp) {

      if (myapp.hasOwnProperty('confsite')) {
        if (myapp.confsite.hasOwnProperty(field)) {
          return myapp.confsite[field];
        }
      }
    }

    return false;
  },

  isAbilitaNave: function (idapp) {
    const myapp = this.MYAPPS.find(item => item.idapp === idapp);
    return myapp.abilitanave;
  },

  removeLastSlashFromPath: function (myString) {
    let regex = /\/$/;
    let result = myString.replace(regex, "");

    return result;
  },

  getdirByIdApp: function (idapp, dirmain = false) {

    let mypath = '';
    const myapp =
      this.MYAPPS.find(item => item.idapp === idapp);
    if (myapp) {
      if (process.env.NODE_ENV === 'test')
        mypath = (myapp) ? myapp.dir_test : '';
      else
        mypath = (myapp) ? myapp.dir : '';

      if (dirmain) {
        if (!this.sulServer()) {
          mypath += 'public';
        }
      }
    }

    return this.removeLastSlashFromPath(mypath);
  },

  getAdminEmailByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp)
      return myapp.adminemail;
    else
      return '';
  },

  getreplyToEmailByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp)
      return myapp.replyTo;
    else
      return '';
  },

  getpathregByIdApp: function (idapp, lang) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp) {
      const addstr = myapp.pathreg_add ? myapp.pathreg_add : '';

      return 'registration' + addstr + '/' + lang;
    } else {
      return 'registration' + '/' + lang;
    }
  },

  getAskToVerifyReg: function (idapp) {
    return this.getConfSiteOptionEnabledByIdApp(idapp, shared_consts.ConfSite.Need_Aportador_On_DataReg_To_Verify_Reg);
  },

  isManagAndAdminDifferent(idapp) {
    const manag = this.getManagerEmailByIdApp(idapp);
    return (manag !== this.getAdminEmailByIdApp(idapp)) && (manag !== '');
  },

  getManagerEmailByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (myapp)
      return !!myapp.manageremail ? myapp.manageremail : '';
    else
      return '';
  },

  getEmailByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    return (myapp) ? myapp.email_from : '';
  },

  getPwdByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    return (myapp) ? this.decryptdata(myapp.email_pwd) : '';
  },

  getTelegramBotNameByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (process.env.NODE_ENV === 'test')
      return (myapp) ? myapp.telegram_bot_name_test : '';
    else
      return (myapp) ? myapp.telegram_bot_name : '';
  },

  getTelegramSupportChat: function (idapp) {
    try {
      const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
      return (myapp && myapp.telegram_support_chat)
        ? myapp.telegram_support_chat
        : '';
    } catch (e) {
      return '';
    }
  },

  getTelegramKeyByIdApp: function (idapp) {
    const myapp = this.MYAPPS.find((item) => item.idapp === idapp);
    if (process.env.NODE_ENV === 'test')
      return (myapp) ? myapp.telegram_key_test : '';
    else
      return (myapp) ? myapp.telegram_key : '';
  },

  getLookupPipeLine(params, proj) {

    let myquery = [
      {
        $lookup: {
          from: params.lk_tab,
          let: { username: '$' + params.lk_LF, idapp: params.idapp },
          pipeline: [
            {
              $match:
              {
                $expr:
                {
                  $and:
                    [
                      { $eq: ['$' + params.lk_FF, '$$' + params.lk_FF] },
                      { $eq: ['$idapp', '$$idapp'] },
                    ],
                },
              },
            },
          ],
          as: params.lk_as,
        },
      },
      { $project: proj },
    ];

    return myquery;
  },

  getNumTabByTable(table) {
    const myrec = shared_consts.MYTABS.find((rec) => rec.table === table)
    return myrec ? myrec.id : -1
  },

  getTableByNumTab(numtab) {
    const myrec = shared_consts.MYTABS.find((rec) => rec.id === numtab)
    return myrec ? myrec.table : ''
  },

  getGroupnameLookupPipeLine(params, proj) {

    let myquery = [
      {
        $lookup: {
          from: params.lk_tab,
          let: { groupname: '$' + params.lk_LF, idapp: params.idapp },
          pipeline: [
            {
              $match:
              {
                $expr:
                {
                  $and:
                    [
                      { $eq: ['$' + params.lk_FF, '$$' + params.lk_FF] },
                      { $eq: ['$idapp', '$$idapp'] },
                    ],
                },
              },
            },
          ],
          as: params.lk_as,
        },
      },
      { $project: proj },
    ];

    return myquery;
  },

  getLookupStandardPipeline: function (params, num) {
    const query = [];

    if (!params)
      return;

    query.push(
      {
        $lookup: {
          from: params.lk_tab,
          let: { searchId: { $toObjectId: "$" + params.lk_LF } },
          pipeline: [
            {
              $match: {
                $expr: {
                  $and: [
                    { $eq: ["$" + params.lk_FF, "$$searchId"] },
                  ],
                },
              },
            },
            {
              $project: params.lk_proj
            },
          ],
          as: params.lk_as + num,
        },
      },
      {
        $replaceRoot: {
          newRoot: {
            $mergeObjects: [
              {
                $arrayElemAt: [
                  '$' + params.lk_as + num, 0],
              }, '$$ROOT'],
          },
        },
      }
    )
    return query;
  },

  getLookup: function (params, num, pass_proj, proj_add) {
    const query = [];

    if (!params)
      return;

    let mylkLF = params.lk_LF;
    if (params.af_objId_tab) {
      const myobj = {};
      myobj['myId' + num] = { '$toObjectId': '$' + params.lk_LF };

      if (params.lk_tab === 'users') {
        myobj.namecomplete = {
          $concat: [
            { $toLower: "$name" },
            { $toLower: "$surname" },
            { $toLower: "$username" },
          ],
        };
      }

      query.push(
        { '$addFields': myobj },
      );
      mylkLF = 'myId' + num;
    } else {

    }

    if (params.lk_tab) {
      let proj = params.lk_proj;
      if (!!pass_proj) {
        proj = pass_proj;
      }
      proj = Object.assign({}, proj, proj_add);

      query.push(
        {
          $lookup: {
            from: params.lk_tab,
            localField: mylkLF,    // field in my collection
            foreignField: params.lk_FF,  // field in the 'from' collection
            as: params.lk_as,
          },
        });
      if (params.unwind) {

        query.push({
          $unwind: {
            path: '$' + params.lk_as,
            preserveNullAndEmptyArrays: true,
          }
        })
      }

      if (!params.noarray) {
        query.push(
          {
            $replaceRoot: {
              newRoot: {
                $mergeObjects: [
                  {
                    $arrayElemAt: [
                      '$' + params.lk_as, 0],
                  }, '$$ROOT'],
              },
            },
          });
      }

      query.push(
        { $project: proj },
      );
    }

    return query;
  },

  getFilterParam: function (filter, fieldsearch) {
    let myregexp = {};
    myregexp = new RegExp(filter.trim().replace(' ', '|'), 'ig');

    let query = [];

    const myfilters = [];
    for (const rec of fieldsearch) {
      let data = {};
      if (rec.type === this.FieldType.exact) {
        data[rec.field] = filter.trim();
      } else if (rec.type === this.FieldType.string) {
        data[rec.field] = myregexp;
      } else if ((rec.type === this.FieldType.number) ||
        (rec.type === this.FieldType.hours)) {
        data[rec.field] = parseFloat(filter.trim());
      }
      if (data)
        myfilters.push(data);
    }

    if (myfilters.length > 0) {
      query = [
        { $match: { $or: myfilters } },
      ];
    }

    return query;
  },

  addQueryIdMatch: function (params) {

    myquery = {};

    if (shared_consts.TABLES_ID_NUMBER.includes(params.table) || shared_consts.TABLES_ID_STRING.includes(params.table)) {
      myquery = {
        $match: {
          $expr: {
            $eq: ['$_id', params.myid],
          },
        },
      };
    } else if (shared_consts.TABLES_NUM_AS_ID_NUMBER.includes(params.table)) {
      myquery = {
        $match: {
          $expr: {
            $eq: ['$Num', params.myid],
          },
        },
      };
    } else {
      myquery = {
        $match: {
          $expr: {
            $eq: ['$_id', mongoose.Types.ObjectId(params.myid)],
          },
        },
      };

    }

    return myquery;
  },

  addNumFavoriteAndBookmarkToQuery(idapp, numtab) {

    let query = [];

    try {
      /*
      query =
        [
          {
            $lookup: {
              from: "reactions",
              let: {
                tab: numtab,
                id: '$_id',
              },
              pipeline: [
                {
                  $match: {
                    $expr: {
                      $and: [
                        { $eq: ['$idrec', '$$id'] },
                        { $eq: ['$tab', numtab] },
                        { $eq: ['$idapp', idapp] },
                      ],
                    },
                  },
                },
                {
                  $group: {
                    _id: "$idrec",
                    numseen: {
                      $sum: {
                        $cond: {
                          if: { $ifNull: ["$seen", false] }, // Check if the field exists and is not null
                          then: 1, // Increment count by 1 if the field exists
                          else: 0, // Otherwise, keep the count unchanged
                        }
                      }
                    },
                    numfav: {
                      $sum: {
                        $cond: {
                          if: { $ifNull: ["$fav", false] }, // Check if the field exists and is not null
                          then: 1, // Increment count by 1 if the field exists
                          else: 0, // Otherwise, keep the count unchanged
                        }
                      }
                    },
                    numbook: {
                      $sum: {
                        $cond: {
                          if: { $ifNull: ["$book", false] }, // Check if the field exists and is not null
                          then: 1, // Increment count by 1 if the field exists
                          else: 0, // Otherwise, keep the count unchanged
                        }
                      }
                    }
                  },
                },
              ],
              as: 'myreact',
            },
          },

          {
            $unwind: {
              path: "$myreact",
              preserveNullAndEmptyArrays: true,
            },
          },
       
        ];

      if (true) {
        // RIMUOVI
        // query = {};
      }

      if (true) {
        // RIMUOVI
        // query = {};
      }

      if (Object.keys(query).length > 0) {
        const numtabbacheca = this.getNumTabByTable(shared_consts.TABLES_MYBACHECAS);
        if (numtab === numtabbacheca) {
          const queryadd = this.getQueryMyBacheca(idapp);
          query = [...query, ...queryadd];
        }
      }
      */

      proj = {
        numseen: 1,
        numfav: 1,
        numbook: 1,
        numattend: 1,
      };
    } catch (e) {
      console.error(e);
    }

    return { query, proj };

  },

  getQueryMyBacheca: function (idapp) {

    const arrquery = [{
      $lookup: {
        from: "bookings",
        let: {
          id: '$_id',
        },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [
                  { $eq: ['$id_bookedevent', '$$id'] },
                  { $eq: ['$idapp', idapp] },
                ],
              },
            },
          },
          {
            $project: {
              id_bookedevent: 1,
              username: 1,
              numpeople: 1,
              numpeopleLunch: 1,
              numpeopleDinner: 1,
              infoevent: 1,
              msgbooking: 1,
              booked: 1,
              datebooked: 1,
              userId: 1,
            }
          },
        ],
        as: 'mybookings',
      },
    },
      /**
      {
        $lookup: {
          from: "users",
          localField: "myId1",
          foreignField: "_id",
          as: "user",
        },
      },
      {
        $lookup: {
          from: "users",
          let: {
            myid: { $toObjectId: "$mybookings.userId" },
          },
          pipeline: [
            {
              $match: {
                $expr: {
                  $and: [
                    {
                      $eq: [
                        "$_id",
                        "$$myid",
                      ],
                    },
                    {
                      $eq: [
                        "$idapp",
                        "13",
                      ],
                    },
                  ],
                },
              },
            },
            {
              $project: { _id: 1, username: 1 },
            }
          ],
  
          as: "myuser",
        }, 
        }**/

    ];

    return arrquery;

  },

  getQueryTable: async function (idapp, params, user) {


    const { Search } = require('../models/search');

    if (typeof params.startRow !== 'number') {
      throw new Error('startRow must be number');
    } else if (typeof params.endRow !== 'number') {
      throw new Error('endRow must be number');
    }

    let newvers = !!params.newvers;

    let query = [];

    if (params.filter && params.fieldsearch) {
      const querytemp = this.getFilterParam(params.filter, params.fieldsearch);
      if (querytemp) {
        query = [...query, ...querytemp];
      }
    }

    let filtriadded = [];

    // if (params.table === 'extralist') {
    //   if (params.filterand.includes(shared_consts.FILTER_EXTRALIST_DELETED))
    //     filtriadded.push({ deleted: true });
    //   else
    //     filtriadded.push({ deleted: { $exists: false } });
    // }

    if (params.filterand) {

      if (params.filterand.includes(
        shared_consts.FILTER_EXTRALIST_NOT_REGISTERED))
        filtriadded.push({ registered: false });

      if (params.filterand.includes(
        shared_consts.FILTER_EXTRALIST_NOT_CONTACTED)) {
        filtriadded.push({ contacted: { $exists: false } });
      }
      if (params.filterand.includes(shared_consts.FILTER_EXTRALIST_WITH_NOTE))
        filtriadded.push({
          'note': { $exists: true },
          '$expr': { '$gt': [{ '$strLenCP': '$note' }, 1] },
        });
      if (params.filterand.includes(shared_consts.FILTER_QUALIFIED))
        filtriadded.push({ 'profile.qualified': true });
      if (params.filterand.includes(shared_consts.FILTER_USER_NO_ZOOM))
        filtriadded.push({ 'profile.saw_zoom_presentation': false });
      if (params.filterand.includes(shared_consts.FILTER_ASK_ZOOM_VISTO))
        filtriadded.push({ 'profile.ask_zoom_partecipato': false });
      if (params.filterand.includes(shared_consts.FILTER_USER_NO_INVITANTE))
        filtriadded.push({
          aportador_solidario: { $exists: false },
        });
      if (params.filterand.includes(shared_consts.FILTER_USER_NO_TELEGRAM_ID))
        filtriadded.push({ 'profile.teleg_id': { $lt: 1 } });
      if (params.filterand.includes(shared_consts.FILTER_USER_SI_TELEGRAM_ID))
        filtriadded.push({ 'profile.teleg_id': { $gt: 1 } });
      if (params.filterand.includes(
        shared_consts.FILTER_USER_CODICE_AUTH_TELEGRAM))
        filtriadded.push({ 'profile.teleg_checkcode': { $gt: 1 } });
      if (params.filterand.includes(
        shared_consts.FILTER_USER_NO_EMAIL_VERIFICATA))
        filtriadded.push({ verified_email: false });
      if (params.filterand.includes(shared_consts.FILTER_USER_NO_VERIFIED_APORTADOR))
        filtriadded.push({
          verified_by_aportador: { $exists: false },
        });
      if (params.filterand.includes(shared_consts.FILTER_USER_WITHOUT_USERNAME_TELEGRAM))
        filtriadded.push({
          $or: [
            { 'profile.username_telegram': { $exists: false } },
            { 'profile.username_telegram': { $exists: true, $eq: '' } }],

        });
      if (params.filterand.includes(shared_consts.FILTER_USER_NO_DREAM))
        filtriadded.push({
          'profile.my_dream': {
            $exists: false,
          },
        });
      if (params.filterand.includes(shared_consts.FILTER_USER_TELEGRAM_BLOCKED))
        filtriadded.push({ 'profile.teleg_id_old': { $gt: 1 } });
      if (params.filterand.includes(shared_consts.FILTER_USER_PROVINCE))
        filtriadded.push({ 'profile.resid_province': { $exists: true } });
      if (params.filterand.includes(shared_consts.FILTER_ATTIVI))
        filtriadded.push({
          $or: [
            { deleted: { $exists: false } },
            { deleted: { $exists: true, $eq: false } }],
        });

      // Hours
      if (params.filterand.includes(shared_consts.FILTER_HOURS_ALL))
        filtriadded.push(
          {
            idapp,
            todoId: params.codeId,
            $or: [
              { deleted: { $exists: false } },
              { deleted: { $exists: true, $eq: false } }],
          },
        );
      else if (params.filterand.includes(shared_consts.FILTER_HOURS_MYLIST))
        filtriadded.push(
          {
            idapp,
            userId: params.userId,
            todoId: params.codeId,
            $or: [
              { deleted: { $exists: false } },
              { deleted: { $exists: true, $eq: false } }],
          },
        );

      if (params.filterand.includes(shared_consts.FILTER_NASCOSTI))
        filtriadded.push({
          deleted: { $exists: true, $eq: true },
        });
      if (params.filterand.includes(shared_consts.FILTER_NAVI_NON_PRESENTI))
        filtriadded.push({
          navinonpresenti: { $exists: true, $eq: true },
          $or: [
            { subaccount: { $exists: false } },
            { subaccount: { $exists: true, $eq: false } }],
        });

      // Iscritti Conacreis
      if (params.filterand.includes(shared_consts.FILTER_REPORTED))
        filtriadded.push(
          { reported: { $exists: true, $eq: true } },
        );

      if (params.filterand.includes(
        shared_consts.FILTER_TO_MAKE_MEMBERSHIP_CARD))
        filtriadded.push({
          $or: [
            { codiceConacreis: { $exists: false } },
            { codiceConacreis: { $exists: true, $eq: '' } }],
        });

      if (params.filterand.includes(shared_consts.FILTER_MEMBERSHIP_CARD_OK))
        filtriadded.push({
          codiceConacreis: { $exists: true },
          '$expr': { '$gt': [{ '$strLenCP': '$codiceConacreis' }, 0] },
        });

    }

    if (params.filtercustom) {
      let condition = {};
      for (const myfilter of params.filtercustom) {
        if (myfilter['userId']) {
          myfilter['userId'] = ObjectID(myfilter['userId']);
        }
        if (myfilter['_idOBJ']) {
          filtriadded.push({ _id: ObjectID(myfilter['_idOBJ']) });
        } else if (myfilter['dateTimeStart']) {
          const gte = myfilter['dateTimeStart'].$gte;
          const lte = myfilter['dateTimeStart'].$lte;
          condition = {
            dateTimeStart: {
              $gte: new Date(gte),
              $lte: new Date(lte),
            },
          };
          filtriadded.push(condition);
        } else if (myfilter.hasOwnProperty('pub_to_share')) {
          // non aggiungere niente
        } else {
          filtriadded.push(myfilter);
        }
      }
    }

    if (params.filter_gte) {
      for (let ind = 0; ind < params.filter_gte.length; ind++) {
        for (const [key, value] of Object.entries(params.filter_gte[ind])) {
          if (value > 0) {
            let condition = {};
            condition[key] = { $gte: value };
            filtriadded.push(condition);
          }
        }
      }
    }

    if (params.filtersearch) {
      filtriadded.push(...params.filtersearch);
    }

    if (params.options) {
      if (this.isBitActive(params.options,
        shared_consts.OPTIONS_SEARCH_ONLY_FULL_WORDS)) {
      }
    }


    if (params.filterextra) {
      if (params.filterextra.length > 0)
        query = [...query, ...params.filterextra]
    }

    if (filtriadded) {
      if (filtriadded.length > 0)
        query.push({ $match: { $and: filtriadded } });
    }

    if (idapp > 0) {
      query.push({ $match: { idapp } });
    }

    // console.log('QUERYMATCH', query[0].$match.or);
    // console.log('filter', params.filter);

    let numrowend = params.endRow - params.startRow;
    if (numrowend < 0)
      numrowend = 1;

    if (params.querytype === shared_consts.QUERYTYPE_MYGROUP || params.querytype === shared_consts.QUERYTYPE_CIRCUIT) {

      const myq = this.addQueryIdMatch(params);
      if (myq)
        query.push(myq);

      query.push({ $project: { req_users: 1 } });
      const qa1 = this.getLookup(
        {
          lk_tab: 'users',
          lk_LF: 'req_users.username',
          lk_FF: 'username',
          lk_as: 'user',
        }, 0, {
        'user.idapp': 1,
        'user.username': 1,
        'user.name': 1,
        'user.surname': 1,
        'user.profile.img': 1,
        'user.profile.qualifica': 1,
      });
      if (qa1) query = [...query, ...qa1];
      query.push({ $unwind: '$user' });

      query.push({
        $match: {
          $and: [
            { 'user.idapp': params.idapp },
          ],
        },
      });


      query.push({
        $replaceRoot: {
          newRoot: '$user',
        },
      },
      );

    } else if (params.querytype === shared_consts.QUERYTYPE_REFUSED_USER_GRP || params.querytype ===
      shared_consts.QUERYTYPE_REFUSED_USER_CIRCUIT) {

      /* 
      const myq = this.addQueryIdMatch(params);
      if (myq)
        query.push(myq);
      */

      const myq = this.addQueryIdMatch(params);
      if (myq)
        query.push(myq);

      query.push({ $project: { refused_users: 1 } });
      query.push({ $unwind: '$refused_users' });
      const qa1 = this.getLookupPipeLine(
        {
          lk_tab: 'users',
          lk_LF: 'refused_users.username',
          lk_FF: 'username',
          lk_as: 'user',
          idapp,
        }, {
        'user.idapp': 1,
        'user.username': 1,
        'user.name': 1,
        'user.surname': 1,
        'user.profile.img': 1,
        'user.profile.qualifica': 1,
      });
      if (qa1) query = [...query, ...qa1];
      query.push({ $unwind: '$user' });
      /*
      query.push({
        $match: {
          $and: [
            {'user.idapp': params.idapp},
          ],
        },
      });
      */

      query.push({
        $replaceRoot: {
          newRoot: '$user',
        },
      },
      );
    } else if (params.querytype === shared_consts.QUERYTYPE_GROUP_CIRCUIT) {

      const myq = this.addQueryIdMatch(params);
      if (myq)
        query.push(myq);

      query.push({ $project: { req_groups: 1 } });
      const qa1 = this.getLookup(
        {
          lk_tab: 'mygroups',
          lk_LF: 'req_groups.groupname',
          lk_FF: 'groupname',
          lk_as: 'group',
        }, 0, {
        'group.idapp': 1,
        'group.groupname': 1,
        'group.photos': 1,
      });
      if (qa1) query = [...query, ...qa1];
      query.push({ $unwind: '$group' });

      query.push({
        $match: {
          $and: [
            { 'group.idapp': params.idapp },
          ],
        },
      });


      query.push({
        $replaceRoot: {
          newRoot: '$group',
        },
      },
      );

    } else if (params.querytype === shared_consts.QUERYTYPE_REFUSED_GROUP_CIRCUIT) {


      const myq = this.addQueryIdMatch(params);
      if (myq)
        query.push(myq);

      query.push({ $project: { refused_groups: 1 } });
      query.push({ $unwind: '$refused_groups' });
      const qa1 = this.getGroupnameLookupPipeLine(
        {
          lk_tab: 'mygroups',
          lk_LF: 'refused_groups.groupname',
          lk_FF: 'groupname',
          lk_as: 'group',
          idapp,
        }, {
        'group.idapp': 1,
        'group.groupname': 1,
        'group.descr': 1,
        'group.photos': 1,
      });
      if (qa1) query = [...query, ...qa1];
      query.push({ $unwind: '$group' });
      /*
      query.push({
        $match: {
          $and: [
            {'user.idapp': params.idapp},
          ],
        },
      });
      */

      query.push({
        $replaceRoot: {
          newRoot: '$group',
        },
      },
      );

    } else if (params.querytype === shared_consts.QUERYTYPE_LIST_MOVEMENTS) {

      const { Movement } = require('../models/movement');

      const myquery = await Movement.getQueryMovsByCircuitId(params.idapp, params.username, '', '', params.myid);

      query.push(...myquery);
    } else if (params.querytype === shared_consts.QUERYTYPE_LIST_MOVEMENTS_GROUPNAME) {

      const { Movement } = require('../models/movement');

      const myquery = await Movement.getQueryMovsByCircuitId(params.idapp, '', params.groupname, '', params.myid);

      query.push(...myquery);

    } else if (params.querytype === shared_consts.QUERYTYPE_LIST_MOVEMENTS_CONTOCOM) {

      const { Movement } = require('../models/movement');

      const myquery = await Movement.getQueryMovsByCircuitId(params.idapp, '', '', params.contocom, params.myid);

      query.push(...myquery);

    } else if (params.querytype === shared_consts.QUERYTYPE_LIST_ALLMOVEMENTS) {

      const { Movement } = require('../models/movement');

      const myquery = await Movement.getQueryAllUsersMovsByCircuitId(params.idapp, params.myid);

      query.push(...myquery);

    }

    if (params.filterextra2) {
      if (params.filterextra2.length > 0)
        query = [...query, ...params.filterextra2];
    }

    if (newvers) {
      // NUOVA VERSIONE
      let proj = params.lookup2 ? params.lookup2.lk_proj : null;

      if (params.proj0) {
        query.push({ $project: params.proj0 });
      }

      if (params.unwind0) {
        query.push({ $unwind: params.unwind0 });
      }

      let objadd = { query: [], proj: {} };

      let numtab = this.getNumTabByTable(params.table);


      const q1 = this.getLookup(params.lookup1, 1, proj, objadd.proj);
      if (q1) query = [...query, ...q1];

      if (params.unwind1) {
        query.push({ $unwind: params.unwind1 });
      }

      if (params.lookupPipeline1) {
        const q1p = this.getLookupStandardPipeline(params.lookupPipeline1, 1);
        if (q1p) query = [...query, ...q1p];
      }

      const q2 = this.getLookup(params.lookup2, 2, proj, objadd.proj);
      if (q2) query = [...query, ...q2];

      if (params.lookupPipeline2) {
        const q2p = this.getLookupStandardPipeline(params.lookupPipeline2, 2);
        if (q2p) query = [...query, ...q2p];
      }

      const q3 = this.getLookup(params.lookup3, 3, proj, objadd.proj);
      if (q3) query = [...query, ...q3];

      const q4 = this.getLookup(params.lookup4, 4, proj, objadd.proj);
      if (q4) query = [...query, ...q4];

      const q5 = this.getLookup(params.lookup5, 5, proj, objadd.proj);
      if (q5) query = [...query, ...q5];

      if (params.filtersearch2) {
        if (params.filtersearch2.length > 0) {
          query.push({ $match: { $and: params.filtersearch2 } });
        }
      }


      let filteradmin = false;
      if (params.filtercustom) {
        let condition = {};
        for (const myfilter of params.filtercustom) {

          if (!!myfilter.admins) {
            filteradmin = true;
          }

          if (myfilter['pub_to_share'] === shared_consts.PUBTOSHARE.ONLY_TABLE_FOLLOW && params.table === 'mygroups') {

            let arraygroups = [];

            if (user && user.profile.mygroups) {
              arraygroups = user.profile.mygroups.map(rec => rec.groupname);
            }
            // prendere i gruppi dell'utente

            // Cerca tra i gruppi di ogni record, se combaciano almeno 1
            condition = {
              'profile.mygroups':
              {
                $elemMatch: {
                  groupname: { $in: arraygroups },
                },

              },
            };

            query.push({ $match: { $and: [condition] } });
          } else if (myfilter['pub_to_share'] === shared_consts.PUBTOSHARE.ONLY_TABLE_FOLLOW && params.table === 'circuits') {

            let arraycircuits = [];

            if (user && user.profile.mycircuits) {
              arraycircuits = user.profile.mycircuits.map(rec => rec.circuitname);
            }
            // prendere i gruppi dell'utente

            // Cerca tra i gruppi di ogni record, se combaciano almeno 1
            condition = {
              'profile.mycircuits':
              {
                $elemMatch: {
                  circuitname: { $in: arraycircuits },
                },

              },
            };

            query.push({ $match: { $and: [condition] } });
          } else if (shared_consts.TABLES_ENABLE_GETREC_BYID.includes(params.table)) {
            // Rimuovi dalla query quelli non visibili

            let arraygroups = [];

            if (user && user.profile.mygroups) {
              arraygroups = user.profile.mygroups.map(rec => rec.groupname);
            }
            // prendere i gruppi dell'utente

            // Cerca tra i gruppi di ogni record, se combaciano almeno 1
            condition = {
              'profile.mygroups':
              {
                $elemMatch: {
                  groupname: { $in: arraygroups },
                },

              },
            };

            query.push(
              {
                $match:
                {
                  $or: [
                    {
                      $and: [condition, { pub_to_share: 1 }],
                    },
                    { $or: [{ pub_to_share: { $exists: false } }, { pub_to_share: { $exists: true, $eq: shared_consts.PUBTOSHARE.ALL } }] },
                  ],
                },
              },
            );

          }

        }
      }

      if (params.filtersearch3or) {
        if (params.filtersearch3or.length > 0) {
          query.push({ $match: { $or: params.filtersearch3or } });
        }
      }

      if (params.filtersearch3and) {
        if (params.filtersearch3and.length > 0) {
          query.push({ $match: { $and: params.filtersearch3and } });
        }
      }


      if (params.filter && params.fieldsearch_last) {
        const querytemp = this.getFilterParam(params.filter,
          params.fieldsearch_last);
        if (querytemp) {
          query = [...query, ...querytemp];
        }

        // Save the search:
        if (user._id) {
          const mysearch = new Search({ idapp, userId: user._id, text: params.filter });
          await mysearch.save();
        }
      }

      if (params.table === 'mygroups' || params.table === 'circuits') {
        // BINARY CHECK (?): const filter = [{ visibility: { $bitsAnyClear: [1] } }];
        // if (!User.isAdmin(req.user.perm)) {
        // not Visibility_Group.HIDDEN
        if (!filteradmin) {
          const filter = [
            {
              $or: [
                {
                  visibility: {
                    $nin: [shared_consts.Visibility_Group.HIDDEN],
                  },
                },
                {
                  createdBy: {
                    $eq: user.username,
                  },
                },
              ],
            },
          ];
          query.push({ $match: { $and: filter } });
        }
        // }
      }

      if (params.options && this.isBitActive(params.options,
        shared_consts.OPTIONS_ADD_COUNT_FAVORITE)) {
        // objadd = this.addNumFavoriteAndBookmarkToQuery(idapp, numtab);
        // if (Object.keys(objadd.query).length > 0)
        //   query = [...query, ...objadd.query];
      }

    } else {
      // VECCHIA VERSIONE
      const q1 = this.getLookup(params, 1);
      if (q1) query = [...query, ...q1];
    }

    if (params.sortBy) {
      // maybe we want to sort by blog title or something
      const mysort = { $sort: params.sortBy };
      // console.log('sortBy', params.sortBy);
      // console.table(mysort);
      query.push(mysort);
    }

    query.push(
      {
        $group: {
          _id: null,
          // get a count of every result that matches until now
          count: { $sum: 1 },
          // keep our results for the next operation
          results: { $push: '$$ROOT' },
        },
      },
      // and finally trim the results to within the range given by start/endRow
      {
        $project: {
          count: 1,
          rows: { $slice: ['$results', params.startRow, numrowend] },
        },
      },
    );

    if (this.testing()) {
      console.log('query', query);
    }
    // console.log('query', query);

    return query;

  },

  startTimeLog(name) {
    if (this.testing()) {
      console.log('inizio', name);
      console.time(name);
    }
  },

  endTimeLog(name) {
    if (this.testing()) {
      console.log('   ... fine', name);
      console.timeEnd(name);
    }
  },

  async executeQueryTable(mythistable, idapp, params, user) {
    let query = await this.getQueryTable(idapp, params, user);

    try {
      // console.log('query', query);
      this.startTimeLog('Query 1');

      const [ris] = await mythistable.aggregate(query);

      if (ris) {
        if (params.table === 'users') {
          for (const myrec of ris.rows) {
            myrec.password = '**********';
          }
        } else if (params.table === 'mygroups') {
          for (const myrec of ris.rows) {
            if (myrec.pwd_cryp)
              myrec.pwd_cryp = '**********';
          }
        }

        this.endTimeLog('Query 1');

        // console.table(ris.rows);
        // console.log('ROW ', ris.count);
        return ({ count: ris.count, rows: ris.rows });
      } else {
        return ({ count: 0, rows: [] });
      }
    } catch (e) {
      console.log('error', e);
    }

  },
  async DuplicateAllRecords(mythistable, idapporig, idappdest) {
    console.log(mythistable.name);

    const numrec = await mythistable.findAllIdApp(idappdest).then((arrrec) => {
      if (arrrec && arrrec.length > 1) {
        return arrrec.length; // Has Already set
      } else {
        return 0;
      }
    });

    // Before check if exist more than 1 record in the destination,
    // if Yes don't copy
    if (numrec <= 0) {
      return await mythistable.findAllIdApp(idapporig).then(async (arrrec) => {
        let num = 0;

        for (let ind = 0; ind < arrrec.length; ind++) {
          let newrec = new mythistable(arrrec[ind]);
          newrec._id = new ObjectID();
          newrec.idapp = idappdest;

          try {
            await newrec.save((err, rec) => {
              if (rec) {
                num++;
              }

            });
          } catch (e) {
            console.error('e', e);
          }

        }
        return num;
      });
    }
  },

  isBitActive(bit, whattofind) {
    return ((bit & whattofind) === whattofind);
  },

  isBitAttivoESelez(bit, whattofind, loSono) {
    const abil = this.isBitActive(bit, whattofind);

    return ((abil && loSono) || !abil);
  },

  SetBit(myval, bit) {
    let myvalout = myval
    myvalout |= bit
    return myvalout
  },

  async snooze(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  },

  IncDateNow(secs) {
    let mydate = new Date(new Date().getTime() + secs);
    // console.log('mydate', mydate);
    return mydate;
  },

  isdiffSecDateLess(mydatediffstr, secs) {
    let mydate = new Date();
    // console.log('mydate', mydate);
    let mydata2 = new Date(mydatediffstr);
    let ris = ((mydate.getTime() - mydata2.getTime()) / 1000);
    return (ris < secs);
  },

  AddDate(mydate, days) {
    let date = new Date(mydate);
    date.setTime(date.getTime() + days * 86400000);
    return date;
  },

  isMonToSat() {
    const dayOfWeek = new Date(new Date()).getDay();
    return dayOfWeek >= 1 && dayOfWeek <= 6;
  },

  isMonToFri() {
    const dayOfWeek = new Date(new Date()).getDay();
    return dayOfWeek >= 1 && dayOfWeek <= 5;
  },

  // var startTime = '15:10:10';
  // var endTime = '22:30:00';
  isBetweenTwoTime(startTime, endTime) {

    currentDate = new Date();

    startDate = new Date(currentDate.getTime());
    startDate.setHours(startTime.split(':')[0]);
    startDate.setMinutes(startTime.split(':')[1]);
    startDate.setSeconds(startTime.split(':')[2]);

    endDate = new Date(currentDate.getTime());
    endDate.setHours(endTime.split(':')[0]);
    endDate.setMinutes(endTime.split(':')[1]);
    endDate.setSeconds(endTime.split(':')[2]);

    valid = startDate < currentDate && endDate > currentDate;

    return valid;
  },

  getlimiti(mypos) {

    if (mypos.col < 0) {
      mypos.col = 0;
    }

    if (mypos.riga < 0) {
      mypos.riga = 0;
      mypos.col = 0;
    }

    return mypos;
  },

  getRigaColByPosUp(mypos) {
    if (mypos.numup > 0) {
      mypos.riga = mypos.riga - mypos.numup;
      mypos.col = Math.ceil(mypos.col / (Math.pow(2, mypos.numup)));
    }
    if (mypos.numup < 0) {
      mypos.riga = mypos.riga - mypos.numup;
      mypos.col = Math.ceil(mypos.col * (Math.pow(2, -mypos.numup)));
    }
    if (mypos.riga < 1) {
      mypos.riga = 1;
    }
    if (mypos.col < 1) {
      mypos.col = 1;
    }
  },

  getRigaColGenerica(idapp, riga, col, numup) {
    mypos = {
      idapp,
      riga,
      col,
      numup,
    };

    if (idapp === this.AYNI) {
      this.getRigaColByPosUp(mypos);
      ris = this.getlimiti(mypos);
    }

    return ris;
  },

  getRigaColSognatoreByDonatore(idapp, riga, col) {
    return this.getRigaColGenerica(idapp, riga, col, 6);
  },

  getRigaColMediatoreByFuoco(idapp, riga, col) {
    return this.getRigaColGenerica(idapp, riga, col, 3);
  },

  getRigaColSognatoreByMediatore(idapp, riga, col) {
    return this.getRigaColGenerica(idapp, riga, col, 3);
  },

  appendLeadingZeroes(n) {
    if (n <= 9) {
      return '0' + n;
    }
    return n;
  },

  getWeekDayByLangByNumCar(date, lang, num) {
    if (!lang)
      lang = 'it';

    const dayOfWeek = new Date(date).getDay();

    const myday = {
      it: [
        'Domenica',
        'Lunedì',
        'Martedì',
        'Mercoledì',
        'Giovedì',
        'Venerdì',
        'Sabato'],
      enUs: [
        'Sunday',
        'Monday',
        'Tuesday',
        'Wednesday',
        'Thursday',
        'Friday',
        'Saturday'],
      fr: [
        'Dimanche',
        'Lundi',
        'Mardi',
        'Mercredi',
        'Jeudi',
        'Vendredi',
        'Samedi'],
      es: [
        'Domingo',
        'Lunes',
        'Martes',
        'Miércoles',
        'Jueves',
        'iernes',
        'Sábado'],
      pt: [
        'Domingo',
        'Segunda',
        'Terça',
        'Quarta',
        'Quinta',
        'Sexta',
        'Sábado'],
      de: [
        'Sonntag',
        'Montag',
        'Dienstag',
        'Mittwoch',
        'Donnerstag',
        'Freitag',
        'Samstag'],
      si: [
        'Nedelja',
        'Ponedeljek',
        'Torek',
        'Sreda',
        'četrtek',
        'Petek',
        'Sobota'],
    };

    if (num > 0) {
      return isNaN(dayOfWeek) ? '' : myday[lang][dayOfWeek].substring(0, num);
    } else {
      return isNaN(dayOfWeek) ? '' : myday[lang][dayOfWeek];
    }
  },

  isSunday(mydate) {
    const dayOfWeek = new Date(mydate).getDay();
    return dayOfWeek === 0;
  },

  isMonday(mydate) {
    const dayOfWeek = new Date(mydate).getDay();
    return dayOfWeek === 1;
  },

  isSundayDate(mydate) {
    const dayOfWeek = mydate.getDay();
    return dayOfWeek === 0;
  },

  isMondayDate(mydate) {
    const dayOfWeek = mydate.getDay();
    return dayOfWeek === 1;
  },

  getWeekDayByLang(date, lang) {
    return this.getWeekDayByLangByNumCar(date, lang, 3);
  },

  getWeekDayByLangTot(date, lang) {
    return this.getWeekDayByLangByNumCar(date, lang, 0);
  },

  getWeekDay(date) {
    //Create an array containing each day, starting with Sunday.
    const weekdays = [
      'Domenica',
      'Lunedì',
      'Martedì',
      'Mercoledì',
      'Giovedí',
      'Venerdì',
      'Sabato',
    ];
    //Use the getDay() method to get the day.
    const day = date.getDay();
    //Return the element that corresponds to that index.
    return weekdays[day];
  },

  getstrDateTimeShort(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return this.getWeekDayByLang(mydate, lang) + ' ' +
        this.appendLeadingZeroes(mydate.getDate()) + '/' +
        this.appendLeadingZeroes(mydate.getMonth() + 1) + ' ORE ' +
        this.appendLeadingZeroes(mydate.getHours()) + ':' +
        this.appendLeadingZeroes(mydate.getMinutes());
    } else {
      return '';
    }
  },

  getstrTime(mytimestamp) {
    // Create a Date object with your desired date
    const date = new Date(mytimestamp); // You can replace this with your specific date

    // Get the hour and minute components from the date
    const hours = date.getHours();
    const minutes = date.getMinutes();

    // Format the hour and minute components as HH:MM
    const formattedTime = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
    return formattedTime
  },

  getstrDateShort(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return this.getWeekDayByLang(mydate, lang).substring(0, 3) + ' ' +
        this.appendLeadingZeroes(mydate.getDate()) + '/' +
        this.appendLeadingZeroes(mydate.getMonth() + 1);
    } else {
      return '';
    }
  },

  getstrDate_DD_MM_YYYY(mydate) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return this.appendLeadingZeroes(mydate.getDate()) + '/' +
        this.appendLeadingZeroes(mydate.getMonth() + 1) + '/' +
        this.appendLeadingZeroes(mydate.getFullYear());
    } else {
      return '';
    }
  },

  getstrDateYYYY_MM_DD(mydate) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return mydate.getFullYear() + '-' +
        this.appendLeadingZeroes(mydate.getMonth() + 1) + '-' +
        this.appendLeadingZeroes(mydate.getDate());
    } else {
      return '';
    }
  },

  getstrUTCDateYYYY_MM_DD(mydate) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return mydate.getUTCFullYear() + '-' +
        this.appendLeadingZeroes(mydate.getUTCMonth() + 1) + '-' +
        this.appendLeadingZeroes(mydate.getUTCDate());
    } else {
      return '';
    }
  },

  getstrDateLong(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return this.getWeekDayByLang(mydate, lang) + ' ' +
        this.appendLeadingZeroes(mydate.getDate()) + '/' +
        this.appendLeadingZeroes(mydate.getMonth() + 1) + '/' +
        mydate.getFullYear();
    } else {
      return '';
    }
  },

  getstrDateLongTot(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return this.getWeekDayByLangTot(mydate, lang) + ' ' +
        this.appendLeadingZeroes(mydate.getDate()) + '/' +
        this.appendLeadingZeroes(mydate.getMonth() + 1) + '/' +
        mydate.getFullYear();
    } else {
      return '';
    }
  },

  getstrDateLongFile(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return mydate.getFullYear() + '-' +
        this.appendLeadingZeroes(mydate.getMonth() + 1) + '-' +
        this.appendLeadingZeroes(mydate.getDate()) + ' (' +
        this.getWeekDayByLang(mydate, lang) + ')';
    } else {
      return '';
    }
  },

  getlinkzoom(rec) {
    if (rec === null) {
      rec = {
        typeconf: this.TYPECONF_ZOOM,
        id_conf_zoom: '',
      };
    }
    let typeconf = rec.typeconf;
    if (typeconf === '')
      typeconf = this.TYPECONF_ZOOM;

    let mylink = 'https://zoom.us/j/';
    if (typeconf === this.TYPECONF_JITSI)
      mylink = 'https://meet.jit.si/';

    if (rec.id_conf_zoom === '') {
      rec.id_conf_zoom = '6668882000';
    }

    return mylink + rec.id_conf_zoom;

  },

  getmd5(mystr) {
    return CryptoJS.MD5(mystr.toLowerCase()).toString();
  },

  getHash(mystr) {
    return CryptoJS.SHA512(mystr, { outputLength: 256 }).toString();
  },

  encrypt(msg, pass) {
    var salt = CryptoJS.lib.WordArray.random(128 / 8);

    var key = CryptoJS.PBKDF2(pass, salt, {
      keySize: keySize / 32,
      iterations: iterations,
    });

    var iv = CryptoJS.lib.WordArray.random(128 / 8);

    var encrypted = CryptoJS.AES.encrypt(msg, key, {
      iv: iv,
      padding: CryptoJS.pad.Pkcs7,
      mode: CryptoJS.mode.CBC,

    });

    // salt, iv will be hex 32 in length
    // append them to the ciphertext for use  in decryption
    var transitmessage = salt.toString() + iv.toString() + encrypted.toString();
    return transitmessage;
  },

  decrypt(transitmessage, pass) {
    var salt = CryptoJS.enc.Hex.parse(transitmessage.substr(0, 32));
    var iv = CryptoJS.enc.Hex.parse(transitmessage.substr(32, 32));
    var encrypted = transitmessage.substring(64);

    var key = CryptoJS.PBKDF2(pass, salt, {
      keySize: keySize / 32,
      iterations: iterations,
    });

    var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
      iv: iv,
      padding: CryptoJS.pad.Pkcs7,
      mode: CryptoJS.mode.CBC,

    });
    return decrypted;
  },

  cryptdata(mydata) {
    if (mydata === '')
      return '';
    // Encrypt
    //return CryptoJS.AES.encrypt(JSON.stringify(mydata), process.env.SECRK);
    return this.encrypt(mydata, process.env.SECRK);
  },

  decryptdata(mydatacrypted) {
    if (mydatacrypted === '')
      return '';
    // Decrypt
    // const bytes = CryptoJS.AES.decrypt(mydatacrypted.toString(), process.env.SECRK);
    // return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
    return this.decrypt(mydatacrypted, process.env.SECRK).
      toString(CryptoJS.enc.Utf8);
  },

  BoolToInt(mybool) {
    return (mybool) ? -1 : 0;
  },

  StrToBool(mystr) {
    return (mystr === '-1') ? true : false;
  },

  writelogfile(mystr, filename) {
    //   fs.appendFile(FILELOG, mystr, function (err) {
    //     if (err) throw err;
    //     console.log('Saved!');
    //   });
    try {
      mystr = this.getstrDateTimeShort(new Date(), 'it') + ': ' + mystr;

      const stream = fs.createWriteStream(filename, { flags: 'a' });
      stream.write('\n' + mystr);
      stream.end();
    } catch (e) {

    }

  },

  readlogfile(idapp, filename) {

    try {
      return fs.readFileSync(idapp + '/' + filename, 'utf8');
    } catch (e) {
      return '';
    }
  },

  writelog(mystr) {
    this.writelogfile(mystr, FILELOG);
  },

  writeEventsLog(mystr) {
    this.writelogfile(mystr, FILEEVENTS);
  },

  writeManagersLog(mystr) {
    this.writelogfile(mystr, FILEMANAGERS);
  },
  writeTransactionLog(mystr) {
    this.writelogfile(mystr, FILETRANSACTION);
  },
  writeErrorLog(mystr) {
    this.writelogfile(mystr, FILEERRLOGIN);
  },

  writeSostituzioniLog(mystr) {
    this.writelogfile(mystr, FILESOSTITUZIONI);
  },

  writeIPToBan(mystr) {
    this.writelogfile(mystr, FILEIP_TO_BAN);
  },

  writeFlottaLog(idapp, mystr, riga, col) {
    this.mkdirpath(idapp + '/');
    this.writelogfile(mystr, idapp + '/' + riga + '_' + col + '.txt');
  },

  readFlottaLog(idapp, riga, col) {
    const nomefile = riga + '_' + col + '.txt';
    return this.readlogfile(idapp, nomefile);
  },

  writeNaveLog(mystr) {
    this.writelogfile(mystr, FILENAVE);
  },

  move(oldPath, newPath, callback) {

    fs.rename(oldPath, newPath, function (err) {
      if (err) {
        if (err.code === 'EXDEV') {
          copy();
        } else {
          callback(err);
        }
        return;
      } else {
        console.log(' ... File Rinominato', oldPath, 'in:', newPath);
      }

      callback();
    });

    function copy() {
      const readStream = fs.createReadStream(oldPath);
      const writeStream = fs.createWriteStream(newPath);

      readStream.on('error', callback);
      writeStream.on('error', callback);

      readStream.on('close', function () {
        console.log('cancella file già copiato', oldPath);
        fs.unlink(oldPath, callback);
      });

      readStream.pipe(writeStream);
    }
  },

  copy(oldPath, newPath, callback) {

    fs.copyFile(oldPath, newPath, (err) => {
      if (err) throw err;
      // console.log('source.txt was copied to destination.txt');
    });
  },

  extractFileName: function (filename) {
    return path.basename(filename);
  },

  extractFilePath: function (filename) {
    return path.dirname(filename);
  },

  delete(mypath, alsothumb, callback) {

    fs.unlink(mypath, function (err) {
      if (alsothumb) {
        try {
          let img_small = path.dirname(mypath) + '/' +
            server_constants.PREFIX_IMG_SMALL + path.basename(mypath);
          fs.unlink(img_small, function (err) {
            if (err)
              console.log('Errore durante la Cancellazione del file', mypath);
            else
              console.log('deleted file', mypath);
          });

        } catch (e) {
          console.error(err);
        }
      }
      if (err) {
        console.error(err);
        callback(err);
        return;
      }
      callback();
    });
  },

  mkdirpath(dirPath) {
    if (!fs.existsSync(dirPath)) {
      try {
        fs.mkdirSync(dirPath, { recursive: true });
      } catch (e) {
        if (dirPath !== path.dirname(dirPath)) {
          const myname = path.dirname(dirPath);
          this.mkdirpath(myname);
          // this.mkdirpath(dirPath);
        }
      }
    }
  },

  extractNameAndSurnameByComplete(name_complete) {
    const suffissoCognome = ['Del', 'La', 'De', 'Lo'];

    let campi = name_complete.split(' ');
    let namesurname = {
      name: '',
      surname: '',
    };

    if (campi.length === 2) {
      namesurname.name = campi[0];
      namesurname.surname = campi[1];
    } else if (campi.length === 3) {
      if (suffissoCognome.includes(campi[1])) {
        namesurname.name = campi[0];
        namesurname.surname = campi[1] + ' ' + campi[2];
      } else {
        namesurname.name = campi[0] + ' ' + campi[1];
        namesurname.surname = campi[2];
      }
    } else if (campi.length === 4) {
      namesurname.name = campi[0] + ' ' + campi[1];
      namesurname.surname = campi[2] + ' ' + campi[3];
    } else if (campi.length > 4) {
      namesurname.name = campi[0] + ' ' + campi[1];
      namesurname.surname = ' ' + campi[2];
      for (let ind = 3; ind < campi.length; ind++) {
        namesurname.surname += ' ' + campi[ind];
      }
    }

    return namesurname;
  },

  isFileExists(filename) {
    return fs.existsSync(filename);
  },

  getiPAddressUser(req) {
    try {
      const striniziale = '::ffff:';
      if (req.ip.indexOf(striniziale) >= 0) {
        return req.ip.substring(striniziale.length);
      } else {
        return req.ip;  // Express
      }

    } catch (e) {
      return '';
    }
  },
  removespaces(mystr) {
    return mystr.replace(/\s+/g, '');
  },

  ModificheConsentite(table, fieldsvalue, idrec, user) {
    if (table === 'sharewithus') {
      return true;
    }
    if (table === 'hours') {
      return true;
    }
    if (table === 'users') {
      if ('aportador_solidario' in fieldsvalue) {
        return true;
      }
      if ('ALL' in fieldsvalue) {
        //++Todo: Cancellalo solo se sono io il creatore dell'utente ... o se posso!
        return true;
      }
    }

    if (shared_consts.TABLES_PERM_CHANGE_FOR_USERS.includes(table)) {
      return true;
    }
    if (shared_consts.TABLES_PERM_NEWREC.includes(table)) {
      return true;
    }

    return false;
  },

  NotifyIfDelRecord(table) {
    if ((table === 'users') || (table === 'extralist') ||
      (table === 'groups') || (table === 'graduatorias')) {
      return true;
    }

    return false;
  },

  getFirstWord(mystr) {
    const myarr = mystr.split(' ');
    if (myarr.length > 0)
      return myarr[0];
    else
      return mystr;
  },

  getFirst2Car(mystr) {
    if (!!mystr)
      return mystr.substring(0, 2) + '.';
    else
      return '';
  },

  getmaxcol(riga) {
    return Math.pow(2, riga - 1);
  },

  getPrimoFuocoByIndCol(col) {
    // let ris = Math.ceil(col - (col % 8)) + 1;
    let ris = ((Math.ceil(col / 8) - 1) * 8) + 1;
    if (ris <= 0)
      ris = 1;
    return ris;
  },

  getPrimaColFlotta(col) {
    // let ris = Math.ceil(col - (col % 8)) + 1;
    let ris = ((Math.ceil(col / 64) - 1) * 64) + 1;
    if (ris <= 0)
      ris = 1;
    return ris;
  },

  getStrMsgByTipoMsg(tipomsg) {

    let mystr = '';

    if (tipomsg === this.TipoMsg.SEND_MSG_EFFETTUA_IL_DONO)
      mystr = 'Inviato Messaggio per Effettuare il Dono a Tutta la Flotta';
    else if (tipomsg === this.TipoMsg.SEND_MSG_SOLLECITO_DONATORI_NO_DONO)
      mystr = 'Inviato Messaggio per RICORDARE di Effettuare il Dono a chi ancora non l\'ha fatto';
    else if (tipomsg === this.TipoMsg.SEND_MSG_A_MEDIATORI)
      mystr = 'Inviato Messaggio ai Mediatori';
    else if (tipomsg === this.TipoMsg.SEND_MSG_A_SOGNATORE)
      mystr = 'Inviato Messaggio al Sognatore';
    else if (tipomsg === this.TipoMsg.SEND_MSG_A_UTENTE_SOSTITUITO)
      mystr = 'Inviato Messaggio all\'utente Sostituito';
    else if (tipomsg === this.TipoMsg.SEND_MSG_DONO_RICEVUTO_CORRETTAMENTE)
      mystr = 'Inviato Messaggio Dono Ricevuto Correttamente';

    return mystr;
  },

  getflagtelegrambyLang(lang) {
    if (lang === 'it')
      return '🇮🇹';
    else if (lang === 'si')
      return '🇸🇮';
    else if (lang === 'es')
      return '🇪🇸';
    else if (lang === 'enUs')
      return '🇬🇧';
    else if (lang === 'uk')
      return '🇬🇧';
    else if (lang === 'fr')
      return '🇫🇷';
  },

  blockwords(mystr) {
    const arrwords = [
      'http',
      'Http',
      'Asasfasfas',
      '://',
      'mrbit',
      'admin',
      'superuser'];
    try {
      for (const myword of arrwords) {
        if (mystr) {
          if (mystr.includes(myword)) {
            return true;
          }
        }
      }
    } catch (e) {

    }

    return false;
  },

  isAlphaNumeric(str) {
    let code, i, len;

    for (i = 0, len = str.length; i < len; i++) {
      code = str.charCodeAt(i);
      if (!(code > 47 && code < 58) && // numeric (0-9)
        !(code > 64 && code < 91) && // upper alpha (A-Z)
        !(code > 96 && code < 123)) { // lower alpha (a-z)
        return false;
      }
    }
    return true;
  },

  isAlphaNumericAndSpecialCharacter(str) {
    let code, i, len;

    for (i = 0, len = str.length; i < len; i++) {
      code = str.charCodeAt(i);
      if (!(code > 47 && code < 58) && // numeric (0-9)
        !(code > 63 && code < 91) && // upper alpha (A-Z)  // e @
        (code !== 46) && // . (punto)
        (code !== 95) && // _
        (code !== 45) && // -
        !(code > 96 && code < 123)) { // lower alpha (a-z)
        return false;
      }
    }
    return true;
  },

  categorizeQueryString(queryObj) {
    let query = {};
    let order = {};
    //extract query, order, filter value
    for (const i in queryObj) {
      if (queryObj[i]) {
        // extract order
        if (i === 'order') {
          order['sort'] = queryObj[i];
          continue;
        }
        // extract range
        if (i === 'range') {
          let range_arr = [];
          let query_arr = [];
          // multi ranges
          if (queryObj[i].constructor === Array) {
            for (const r of queryObj[i]) {
              range_arr = r.split('-');
              query_arr.push({
                price: { $gt: range_arr[0], $lt: range_arr[1] },
              });
            }
          }
          // one range
          if (queryObj[i].constructor === String) {
            range_arr = queryObj[i].split('-');
            query_arr.push({
              price: { $gt: range_arr[0], $lt: range_arr[1] },
            });
          }
          Object.assign(query, { $or: query_arr });
          delete query[i];
          continue;
        }
        query[i] = queryObj[i];
      }
    }
    return { query, order };
  },

  dateToEpoch(thedate) {
    const time = new Date(thedate).getTime();
    return time - (time % 86400000);
  },

  dateToEpochStr(thedate) {
    const time = new Date(thedate).getTime();
    return new Date(time - (time % 86400000));
  },

  async loadApps() {
    try {
      this.MYAPPS = await Site.findAll(0);
      // console.log('this.MYAPPS', this.MYAPPS);
    } catch (e) {
      console.error('loadApps', e);
    }
  },
  async getApps() {
    if (this.MYAPPS.length <= 0)
      await this.loadApps();

    return this.MYAPPS;
  },

  getPaymentTypesById(idmetodo) {
    return shared_consts.PaymentTypes[idmetodo];
  },

  getVersionint(versionstr) {
    let versionarr = versionstr.split('.');
    let version = 0;

    versionarr = versionarr.reverse();

    for (let i = 0; i < versionarr.length; i++) {
      version += versionarr[i] * Math.pow(10, i * 2);
    }

    return parseInt(version);
  },

  invertescapeslash(mystr) {
    return mystr.replace(/Ç/g, '/');
  },

  isNumber(n) {
    return typeof (n) != 'boolean' && !isNaN(n) && n;
  },
  getNumObj(obj) {
    let count = 0;
    for (let properties in obj) {
      count = count + 1;
    }
    return count;
  },

  getNomeCognomeEUserNameByUser(user) {
    let nome = `${user.name} ${user.surname} (${user.username})`;
    if (!user.name) {
      nome = user.username;
    }

    return nome;
  },

  sulServer() {
    return process.env.LOCALE !== '1';
  },

  refreshAllTablesInMem(idapp, table, updatebot, username) {
    const telegrambot = require('../telegram/telegrambot');

    if (table === shared_consts.TAB_BOTS || updatebot) {
      telegrambot.reloadMenuBot(idapp);
    }

    if (table === shared_consts.TAB_USERS) {
      telegrambot.deleteRecInMemByUsername(idapp, username);
    } else if (table === shared_consts.TAB_SITES) {
      telegrambot.reloadSites();
    }

  },

  downloadImage(url, filepath) {
    try {
      return download.image({
        url,
        dest: filepath,
      });
    } catch (e) {
      console.error('Err download image', e);
      return false;
    }

  },

  async checkStr(msg, mystr, user, cmd) {
    if (msg.includes(mystr)) {
      if (cmd === 1) {
        msg = msg.replace(mystr, await this.getTimeExpReg(user.idapp, user.username));
      }
    }
    return msg;
  },

  async convertSpecialTags(user, msg) {
    try {
      if (!msg)
        return msg;

      if (!!user) {
        if (msg.includes('{appname}'))
          msg = msg.replace('{appname}', this.getNomeAppByIdApp(user.idapp));
        msg = msg.replace('{username}', user.username);
        msg = await this.checkStr(msg, '{time_exp_reg}', user, 1);
        msg = msg.replace('{name}', user.name ? user.name : user.username);
        msg = msg.replace('{surname}', user.surname ? user.surname : '');
        msg = msg.replace('{aportador_solidario}', user.aportador_solidario ? user.aportador_solidario : '');
        if (!!user.profile.link_payment)
          msg = msg.replace('{link_paypalme}', user.profile.link_payment);
        if (!!user.profile.revolut)
          msg = msg.replace('{revolut}', user.profile.revolut);
        if (!!user.profile.payeer_id)
          msg = msg.replace('{payeer_id}', user.profile.payeer_id);
        if (!!user.profile.advcash_id)
          msg = msg.replace('{advcash_id}', user.profile.advcash_id);
        if (!!user.profile.email_paypal)
          msg = msg.replace('{email_paypal}', user.profile.email_paypal);
        if (!!user.profile.note_payment)
          msg = msg.replace('{note_payment}', user.profile.note_payment);
      }

      // const cl = getclTelegByidapp(user.idapp);
      msg = msg.replace('{link_chathelp}', this.HELP_CHAT);

    } catch (e) {
      console.log(e);
    }

    return msg;
  },

  getUserInfoMsg: function (idapp, myuser, usernametelegram = '', name_telegram = '',
    surname_telegram = '') {

    let msg = '';
    try {
      const name = myuser.username +
        (myuser.name ? ` (${myuser.name} ${myuser.surname})` : '');
      const linkuserprof = this.getHostByIdApp(idapp) + '/my/' +
        myuser.username;

      msg = `<br>👉🏻 <a href="${linkuserprof}">${name}</a><br>email: ${myuser.email}`;

      let u_tg = myuser.profile && myuser.profile.hasOwnProperty('username_telegram') ? myuser.profile.username_telegram : usernametelegram;
      let name_tg = myuser.profile && myuser.profile.hasOwnProperty('firstname_telegram')
        ? myuser.profile.firstname_telegram
        : name_telegram;
      let surname_tg = myuser.profile && myuser.profile.hasOwnProperty('lastname_telegram')
        ? myuser.profile.lastname_telegram
        : surname_telegram;

      let namesurnametg = '';
      if (name_tg || surname_tg)
        namesurnametg = ` [${name_tg} ${surname_tg}]`;;

      if (u_tg) {
        msg += `<br><br>Apri chat 👉🏻 https://t.me/${u_tg}`;
      }
    } catch (e) {
      console.error('getUserInfoMsg', e);
    }

    return msg;
  },

  getUserShortInfoMsg: function (idapp, myuser, usernametelegram = '', name_telegram = '',
    surname_telegram = '') {

    let msg = '';
    try {
      const name = myuser.username +
        (myuser.name ? ` (${myuser.name} ${myuser.surname})` : '');
      const linkuserprof = this.getLinkUserProfile(idapp, myuser.username);

      let u_tg = myuser.profile && myuser.profile.hasOwnProperty('username_telegram') ? myuser.profile.username_telegram : usernametelegram;

      msg = `<br>Vedi Profilo sulla APP di <a href="${linkuserprof}">${name}</a>`;
      msg += `<br>Telegram: @${u_tg}`;

    } catch (e) {
      console.error('getUserInfoMsg', e);
    }

    return msg;
  },

  getlinkRelativeRequestNewPassword: function (idapp, email, tokenforgot) {
    const strlinkreg = process.env.LINK_UPDATE_PASSWORD +
      `?idapp=${idapp}&email=${email}&tokenforgot=${tokenforgot}`;
    return strlinkreg;
  },

  getlinkRequestNewPassword: function (idapp, email, tokenforgot) {
    const strlinkreg = this.getHostByIdApp(idapp) + this.getlinkRelativeRequestNewPassword(idapp, email, tokenforgot);
    return strlinkreg;
  },

  execScript: function (idapp, msg, script, testo) {
    const { exec } = require('child_process');
    const telegrambot = require('../telegram/telegrambot');

    const idchat = msg.chat.id;

    console.log(testo + ' (' + script + ')');
    telegrambot.local_sendMsgTelegramByIdTelegram(idapp, idchat, testo);

    exec(script, (error, stdout, stderr) => {
      if (error) {
        telegrambot.local_sendMsgTelegramByIdTelegram(idapp, idchat, 'ERROR: ' + error.message);
        console.log(`error: ${error.message}`);
        return;
      }
      if (stderr) {
        telegrambot.local_sendMsgTelegramByIdTelegram(idapp, idchat, 'ERROR: ' + stderr);
        console.log(`stderr: ${stderr}`);
        return;
      }

      // console.log(`  ... stdout: ${stdout}`);
      telegrambot.local_sendMsgTelegramByIdTelegram(idapp, idchat, stdout);
    });
  },

  capitalize(value) {
    if (!value) {
      return '';
    }
    const myval = value.toString();
    return myval.charAt(0).toUpperCase() + myval.slice(1);
  },

  firstchars(value, numchars = 200) {
    if (!value) {
      return '';
    }
    try {
      let mycar = value.substring(0, numchars);
      if (value.length > numchars) mycar += '...';
      return mycar;
    } catch (e) {
      return value;
    }
  },

  removeAtChar(mystr) {
    if (mystr && mystr[0] === '@') {
      return mystr = mystr.substring(1);
    }

    return mystr;
  },

  updateQueryStringParameter(uri, key, value) {
    if (uri === '' || !uri)
      return '';
    var re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i');
    var separator = uri.indexOf('?') !== -1 ? '&' : '?';
    if (uri.match(re)) {
      return uri.replace(re, '$1' + key + '=' + value + '$2');
    } else {
      return uri + separator + key + '=' + value;
    }
  },

  isArray(val) {
    return Array.isArray(val);
  },

  getContentByTypeMsg(typemsg) {

  },

  getContentNotifByParams(params, content, typesend) {
    if (typesend === shared_consts.TypeSend.TELEGRAM) {

      let myhost = this.getHostByIdApp(params.idapp);

      if (params.textaddTelegram) {
        content = content + '\n' + params.textaddTelegram;
      }

      if (params.linkaddTelegram) {
        if (params.openUrl)
          content = content + '\n' + '<a href="' + myhost + params.openUrl + '">' + params.linkaddTelegram + '</a>';
      }


      /*if (params.openUrl)
        content = content + '\n' + '<a href="' + myhost + params.openUrl + '">' + i18n.__('OPEN PAGE') + '</a>';

       */
    }

    return content;
  },

  getAhref(username, link) {
    return `<a href='${link}'>${username}</a>`;
  },


  firstchars(value, numchars) {
    if (!value) {
      return ''
    }
    try {
      let mycar = value.substring(0, numchars)
      if (value.length > numchars)
        mycar += '...'
      return mycar
    } catch (e) {
      return value
    }
  },

  replaceStringAtEnd(inputString, searchString, replacement) {
    // Create a regular expression that matches the searchString at the end of the inputString
    const regex = new RegExp(searchString + '$');

    // Use the replace method with the regular expression to replace the string
    const result = inputString.replace(regex, replacement);

    return result;
  },

  getstrDateTimeEvent(myevent) {
    let mystr = '';

    // is same day?
    if (this.getstrDateShort(myevent.dateTimeStart) === this.getstrDateShort(myevent.dateTimeEnd)) {
      mystr = i18n.__('DATEDAYONLY', this.getstrDateLong(myevent.dateTimeStart), this.getstrTime(myevent.dateTimeStart),
        this.getstrTime(myevent.dateTimeEnd))
    } else {
      mystr = i18n.__('DATE_2DAYS', this.getstrDateLong(myevent.dateTimeStart), this.getstrTime(myevent.dateTimeStart),
        this.getstrDateLong(myevent.dateTimeEnd), this.getstrTime(myevent.dateTimeEnd))
    }

    return '🗓 ' + mystr;
  },

  getDoveStrByEvent(myrec) {
    let dove = '🏠 ';
    if (myrec.mycities && myrec.mycities.length > 0) {
      dove += myrec.mycities[0].comune + ' (' + myrec.mycities[0].prov + ')'
    }
    return dove;
  },

  async getDescrEstesaStrByEvent(myrec) {
    let mystr = '';

    mystr = await this.firstchars(this.replaceStringAtEnd(myrec.note, '</div>', ''), 400);
    if (mystr)
      mystr = 'ℹ️ ' + mystr
    return mystr;
  },


  async getEventForTelegram(myrec, mydescr, userorig) {
    try {
      let datastr = this.getstrDateTimeEvent(myrec);
      let dovestr = this.getDoveStrByEvent(myrec);
      let descrestesa = await this.getDescrEstesaStrByEvent(myrec);
      let organizedby = myrec.organisedBy;
      let contributo = myrec.contribstr;

      let newdescr = i18n.__('NEW_EVENT', userorig, datastr, mydescr, dovestr);
      let newdescrtelegram = i18n.__('NEW_EVENT_TELEGRAM', datastr, mydescr, dovestr, descrestesa, userorig);
      if (organizedby) {
        newdescrtelegram += i18n.__('ORGANIZED_BY', organizedby);
      }
      if (contributo) {
        newdescrtelegram += i18n.__('CONTRIB', contributo);
      }
      newdescrtelegram += i18n.__('ADDED_FROM', userorig);

      return { newdescr, newdescrtelegram }
    } catch (e) {
      console.error('Error', e);
    }

  },

};
