<?php

namespace Garradin\Plugin\RecusFiscaux;

use Garradin\DB;
use KD2\ZipWriter;

class Utils
{
    /**
     * @return informations sur les tarifs
     */
    public static function getTarifs()
    {
        $db = DB::getInstance();
        $sql = sprintf(
            'SELECT
                 id,
                 id_service as idActivite,
                 label,
                 description,
                 amount as montant
             FROM services_fees');
        return Utils::toAssoc($db->get($sql), 'id');
    }

    /**
     * @return informations sur les activités
     */
    public static function getActivites()
    {
        $db = DB::getInstance();
        $sql = sprintf(
            'SELECT
                 services.id,
                 services.label,
                 services.description
             FROM services');
        return Utils::toAssoc($db->get($sql), 'id');
    }

    /**
     * @return comptes sur lesquels des versements de membres ont été faits
     * @param string $annee
     * @param  $op : opérateur de combinaison des comptes
     * @param array $comptes
     */
    public static function getComptes($annee, $op, $comptes)
    {
        $db = DB::getInstance();
        $sql = sprintf(
            'SELECT
                 acc_accounts.id,
                 acc_accounts.code as codeCompte,
                 acc_accounts.label as nomCompte
             FROM acc_transactions_users
             INNER JOIN membres
                 ON acc_transactions_users.id_user = membres.id
             INNER JOIN acc_transactions
                 ON acc_transactions_users.id_transaction = acc_transactions.id
             INNER JOIN acc_transactions_lines
                 ON acc_transactions_lines.id_transaction = acc_transactions.id
             INNER JOIN acc_accounts
                 ON acc_transactions_lines.id_account = acc_accounts.id
             WHERE
                 (strftime(%s, acc_transactions.date) = "%d"
             AND
                 acc_accounts.%s
             )
             GROUP by acc_accounts.id
             ORDER by acc_accounts.id',
            '"%Y"',
            $annee,
            $db->where('code', $op, $comptes)
        );
        return Utils::toAssoc($db->get($sql), 'id');
    }

    /**
     * @return tarifs des activités et comptes ayant des versements de
     *         membres dans l'année
     * @param string $annee
     * @param  $op : opérateur de combinaison des comptes
     * @param array $comptes
     */
    public static function getTarifsComptes($annee, $op, $comptes)
    {
        $db = DB::getInstance();
        $sql = sprintf(
            '
            SELECT
                services_users.id_fee as idTarif,
                acc_accounts.id as idCompte,
                acc_accounts.code as codeCompte
            FROM acc_transactions_users
            INNER JOIN acc_transactions
               ON acc_transactions_users.id_transaction = acc_transactions.id
            INNER JOIN services_users
               ON acc_transactions_users.id_service_user = services_users.id
            INNER JOIN services_fees
               ON services_users.id_fee = services_fees.id
            INNER JOIN acc_transactions_lines
               ON acc_transactions_lines.id_transaction = acc_transactions.id
            INNER JOIN acc_accounts
               ON acc_transactions_lines.id_account = acc_accounts.id
            WHERE
               (strftime(%s, acc_transactions.date) = "%d"
            AND
               acc_accounts.%s
            )
            GROUP BY services_fees.id,acc_accounts.id
        ',
            '"%Y"',
            $annee,
            $db->where('code', $op, $comptes)
        );
        return $db->get($sql);
    }

    /**
     * faire un tableau associatif avec le résultat d'une requête
     */
    static function toAssoc($array, $nomCle)
    {
        $assoc = array();
        foreach ($array as $elem)
        {
            $ro = new \ReflectionObject($elem);
            $proprietes = $ro->getProperties();
            $obj = new \stdClass();
            foreach ($proprietes as $p)
            {
                $pname = $p->getName();
                if ($pname == $nomCle) {
                    $key = $p->getValue($elem);
                }
                else {
                    $obj->$pname = $p->getValue($elem);
                }
            }
            $assoc[$key] = $obj;
        }
        return $assoc;
    }

    /**
     * @return versements correspondants à l'année donnée
     * @param $annee
     * @param array $champsNom : liste non vide des champs de nom/prénom
     */
    public static function getVersementsPersonnes($annee, $op, $comptes, $champsNom)
    {
        $db = DB::getInstance();
        $tri = Utils::combinerTri($champsNom);
        $sql = sprintf(
            'SELECT
                 membres.id as idUser,
                 acc_accounts.id as idCompte,
                 acc_accounts.code as codeCompte,
                 acc_transactions_lines.credit as versement,
                 acc_transactions.date
             FROM acc_transactions_users
             INNER JOIN membres
                 ON acc_transactions_users.id_user = membres.id
             INNER JOIN acc_transactions
                 ON acc_transactions_users.id_transaction = acc_transactions.id
             INNER JOIN acc_transactions_lines
                 ON acc_transactions_lines.id_transaction = acc_transactions.id
             INNER JOIN acc_accounts
                 ON acc_transactions_lines.id_account = acc_accounts.id
             WHERE
                 (strftime(%s, acc_transactions.date) = "%d"
             AND
                acc_accounts.%s
             )
             ORDER by %s, acc_accounts.id, acc_transactions.date',
            '"%Y"',
            $annee,
            $db->where('code', $op, $comptes),
            $tri
        );
        return $db->get($sql);
    }

    /**
     * @return versements correspondants à :
     * @param $annee           : année fiscale
     * @param $tarifs          : tarifs sélectionnés
     * @param array $comptes   : comptes associés aux tarifs
     * @param array $champsNom : liste non vide des champs de nom/prénom
     * @remarks tri par tarif, nom, compte, date
     */
    public static function getVersementsTarifsComptes($annee,
                                                      $tarifs,
                                                      $comptes,
                                                      $champsNom)
    {
        $db = DB::getInstance();
        $tri = Utils::combinerTri($champsNom);
        $sql = sprintf(
            'SELECT
                services_fees.id as idTarif,
                acc_accounts.id as idCompte,
                acc_accounts.code as codeCompte,
                membres.id as idUser,
                acc_transactions_lines.credit as versement,
                acc_transactions.date
             FROM acc_transactions_users
             INNER JOIN membres
                ON acc_transactions_users.id_user = membres.id
             INNER JOIN acc_transactions
                ON acc_transactions_users.id_transaction = acc_transactions.id
             INNER JOIN services_users
                ON acc_transactions_users.id_service_user = services_users.id
             INNER JOIN services_fees
                ON services_users.id_fee = services_fees.id
             INNER JOIN acc_transactions_lines
                ON acc_transactions_lines.id_transaction = acc_transactions.id
             INNER JOIN acc_accounts
                ON acc_transactions_lines.id_account = acc_accounts.id
             WHERE
                (strftime(%s, acc_transactions.date) = "%d"
             AND
                services_fees.%s
             AND
                acc_accounts.%s
             )
             ORDER by services_fees.id, %s, acc_accounts.id, acc_transactions.date',
            '"%Y"',
            $annee,
            $db->where('id', 'in', $tarifs),
            $db->where('id', 'in', $comptes),
            $tri
        );
        return $db->get($sql);
    }

    /**
     * @return versements correspondants à :
     * @param $annee  année fiscale
     * @param $comptesIsoles comptes NON associés à un tarif
     * @param array $champsNom : liste non vide des champs de nom/prénom
     * @remarks tri par nom, compte, date
     */
    public static function getVersementsComptes($annee,
                                                $comptesIsoles,
                                                $champsNom)
    {
        $db = DB::getInstance();
        $tri = Utils::combinerTri($champsNom);
        $sql = sprintf(
            '
             SELECT
                 0 as idTarif,
                 acc_accounts.id as idCompte,
                 acc_accounts.code as codeCompte,
                 membres.id as idUser,
                 acc_transactions_lines.credit as versement,
                 acc_transactions.date
             FROM acc_transactions_users
             INNER JOIN membres
                 ON acc_transactions_users.id_user = membres.id
             INNER JOIN acc_transactions
                 ON acc_transactions_users.id_transaction = acc_transactions.id
             INNER JOIN acc_transactions_lines
                 ON acc_transactions_lines.id_transaction = acc_transactions.id
             INNER JOIN acc_accounts
                 ON acc_transactions_lines.id_account = acc_accounts.id
             WHERE
                 (strftime(%s, acc_transactions.date) = "%d"
             AND
                acc_accounts.%s
             )

             ORDER by %s, acc_accounts.id, acc_transactions.date
            ',
            '"%Y"',
            $annee,
            $db->where('id', 'in', $comptesIsoles),
            $tri
        );
        return $db->get($sql);
    }

    /**
     * @return personnes ayant versé des dons pour une année donnée
     * @param $annee
     * @param array $champsNom : champs qui définissent le nom et le prénom d'une personne
     */
    public static function getDonateurs($annee, $champsNom) : array
    {
        // concaténer les champs nom/prénoms pour la sélection
        $nom = Utils::combinerChamps($champsNom);
        // et pour le tri
        $tri = Utils::combinerTri($champsNom);
        $sql = sprintf(
            'SELECT
                 membres.id as idUser,
                 row_number() over(order by %s) as rang,
                 %s as nom,
                 membres.adresse as adresse,
                 membres.code_postal as codePostal,
                 membres.ville as ville
             FROM
                 acc_transactions_users,
                 membres,
                 acc_transactions
             INNER JOIN acc_transactions_lines
             ON acc_transactions_lines.id_transaction = acc_transactions.id
             WHERE (
                 strftime(%s, acc_transactions.date) = "%d"
             AND
                 acc_transactions_users.id_transaction = acc_transactions.id
             AND
                 acc_transactions_users.id_user = membres.id
             )
             GROUP by membres.id
             ORDER by %1$s COLLATE U_NOCASE
            ',
            $tri,
            $nom,
            '"%Y"',
            $annee
        );
        $donateurs = array();
        foreach (DB::getInstance()->iterate($sql) as $personne)
        {
            $donateurs[$personne->idUser] = new Personne($personne->idUser,
                                                         $personne->rang,
                                                         $personne->nom,
                                                         $personne->adresse,
                                                         $personne->codePostal,
                                                         $personne->ville);
        }
        return $donateurs;
    }

    /**
     * combiner les champs avec un opérateur
     * @param array $champs : liste (non vide) de champs
     * @return chaîne combinée
     */
    private static function combinerChamps($champs)
    {
        $op = ' || " " || ';
        $result = 'ifnull(membres.' . $champs[0] . ', "")';
        for ($i = 1; $i < count($champs); ++$i)
        {
            $result .= $op . 'ifnull(membres.' . $champs[$i] . ', "")';
        }
        return 'trim(' . $result . ')';
    }

    /**
     * combiner les clés de tri
     * @param clés de tri
     * @return chaîne combinée
     */
    private static function combinerTri(array $champs) : string
    {
        $tri = 'membres.' . $champs[0];
        for ($i = 1; $i < count($champs); ++$i)
        {
            $tri .= ', membres.' . $champs[$i];
        }
        return $tri;
    }

    /**
     * @return liste des années fiscales
     */
    public static function getAnneesFiscales() : array
    {
        $rows = DB::getInstance()->get(
            "SELECT strftime('%Y', start_date) as annee
             FROM acc_years
             ORDER by start_date DESC"
        );
        $anneesFiscales = array();
        foreach ($rows as $row) {
            $anneesFiscales[] = $row->annee;
        }
        return $anneesFiscales;
    }

    public static function getLignesReduction($lesTaux)
    {
        foreach ($lesTaux as $elem)
        {
            $lignes[$elem->taux] = $elem->remarque;
        }
        return $lignes;
    }

    /**
     * récupérer dans la config du plugin les champs des membres
     * utilisés pour le nom et le prénom ; ajouter/supprimer les
     * modifications par rapport à la config garradin
     * @return array tableau des champs : clé = nom, valeur = { titre, position }
     */
    public static function getChampsNom($config, $plugin) : array
    {
        // récupérer dans la config du plugin les champs mémorisés
        // pour le nom et le prénom (le tableau est vide si pas mémorisé)
        $champsNom = (array) $plugin->getConfig('champsNom');

        // récupérer dans la config Garradin les champs des membres
        // utilisés pour le nom et le préno
        $champsGarradin = $config->get('champs_membres')->listAssocNames();

        foreach ($champsGarradin as $name => $title)
        {
            if (stristr($title, 'nom'))
            {
                // retenir les champs dont le titre contient le terme 'nom'
                // est-il présent dans la config du plugin ?
                if (! array_key_exists($name, $champsNom))
                {
                    // absent => l'ajouter
                    $champ = new \stdClass();
                    $champ->titre = $title;
                    $champ->position = 0;
                    $champsNom[$name] = $champ;
                }
            }
        }
        // opération symétrique : un champ mémorisé dans la config du
        // plugin a-t-il disparu de la config garradin ?
        foreach ($champsNom as $nom => $champ)
        {
            if (! array_key_exists($nom, $champsGarradin))
            {
                // absent => le supprimer
                unset($champsNom[$nom]);
            }
        }
        // mettre à jour la config du plugin
        $plugin->setConfig('champsNom', $champsNom);
        return $champsNom;
    }

    /**
     * enregistrer les fichiers dans une archive zip
     * @param $fileList : liste des fichiers à archiver
     * @param $year : pour générer le nom de l'archive
     * @param $archiveDir : ne sert plus
     */
    static function makeArchive(
        $fileList,
        $year,
        $archiveDir = null)
    {
        $zipFilename = "recus_dons" . $year . ".zip";
        header('Content-type: application/zip');
        header(sprintf('Content-Disposition: attachment; filename="%s"', $zipFilename));
        $zip = new ZipWriter('php://output');
        $zip->setCompression(0);
        foreach ($fileList as $fileName)
        {
            $zip->add(basename($fileName), null, $fileName);
        }
        $zip->close();
    } // makeArchive
}