<?php

namespace Cadoles\CoreBundle\Service;

use Cadoles\CoreBundle\Entity\User;
use Cadoles\CoreBundle\Entity\Niveau01;
use Cadoles\CoreBundle\Entity\Niveau02;
use Cadoles\CoreBundle\Entity\Group;
use Cadoles\CoreBundle\Entity\UserGroup;

class ldapService
{

    protected $host;
    protected $port;
    protected $tls;
    protected $type;
    protected $addomainehome;
    protected $addomaineprofil;

    protected $baseDN;
    protected $baseUser;
    protected $baseNiveau01;
    protected $baseNiveau02;
    protected $baseGroup;


    protected $user = null;
    protected $password = null;
    private $connection = null;
    private $ldapSync = false;

    public function __construct($host, $port, $tls, $type, $addomainehome, $addomaineprofil)
    {
        $this->host             = $host;
        $this->port             = $port;
        $this->tls              = $tls;
        $this->type             = $type;
        $this->addomainehome    = $addomainehome;
        $this->addomaineprofil  = $addomaineprofil;
    }

    public function isEnabled() {
        return $this->ldapSync;
    }

    public function connect() {
        // Si on est déjà co = on rebind pour gérer le cas d'un timeout de connection
        if($this->connection){
            if(!ldap_bind($this->connection, $this->user, $this->password)){
                $this->disconnect();
            }
        }

        if($this->connection){
            return $this->connection;
        } else {
            $ldapConn = ldap_connect($this->host, $this->port);

            if($ldapConn){
                ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3);
                ldap_set_option($ldapConn, LDAP_OPT_REFERRALS, 0);
                if($this->tls) ldap_start_tls($ldapConn);

                if(ldap_bind( $ldapConn, $this->user, $this->password)){
                    $this->connection = $ldapConn;
                    return $this->connection;
                }
            }
        }
    }

    public function search($filter, $attributes = array(), $subBranch = '') {
        $connection = $this->connect();
        $branch = ($subBranch ? $subBranch : $this->baseDN);
        $result = ldap_search($connection, $branch, $filter, $attributes,0,0,0);
        if(!$result) {
            $this->ldapError();
        }
        return $this->resultToArray($result);
    }

    public function deleteByDN($dn){
        $connection = $this->connect();
        $removed = ldap_delete($connection, $dn);
        if(!$removed){
            $this->ldapError();
        }
    }

    public function rename($oldDN, $newDN, $parentDN = '', $deleteOldDN = true){
        $connection = $this->connect();
        $result = ldap_rename($connection, $oldDN, $newDN, $parentDN, $deleteOldDN);
        if(!$result) $this->ldapError();
        return $result;
    }

    
    private function resultToArray($result){

        $connection = $this->connect();
        $resultArray = array();

        if($result){
            $entry = ldap_first_entry($connection, $result);
            while ($entry){
                $row = array();
                $attr = ldap_first_attribute($connection, $entry);
                while ($attr){
                    $val = ldap_get_values_len($connection, $entry, $attr);
                    if(array_key_exists('count', $val) AND $val['count'] == 1){
                        $row[strtolower($attr)] = $val[0];
                    } else {
                        $row[strtolower($attr)] = $val;
                    }
                    $attr = ldap_next_attribute($connection, $entry);
                }
                $resultArray[] = $row;
                $entry = ldap_next_entry($connection, $entry);
            }
        }

        return $resultArray;
    }

    public function in_array_r($item , $array){
        return preg_match('/"'.$item.'"/i' , json_encode($array));
    }

    public function disconnect(){
        if($this->connection) {
            ldap_unbind($this->connection);
            $this->connection=null;
        }
    }

    public function ldapError(){
        $connection = $this->connect();
        throw new \Exception(
            'Error: ('. ldap_errno($connection) .') '. ldap_error($connection)
        );
    }

    public function ldapModify($dn,$attrs) {
        $connection = $this->connect();
        $result = ldap_modify($connection, $dn, $attrs);
        if(!$result) $this->ldapError();        
    }

//==================================================================================================================================================================
//== Function User==================================================================================================================================================
//==================================================================================================================================================================

    public function addUser(User $user) {

        $connection = $this->connect();
        $dn = $this->getUserDN($user);

        $attrs = array();
        $attrs['objectclass'] = $this->getObjectClassesUser();
        $this->fillAttributesUser($user, $attrs);

        foreach($attrs as $key => $value){
            if(empty($value)){
                unset($attrs[$key]);
            }
        }


        $result = ldap_add($connection, $dn, $attrs);
        if(!$result) $this->ldapError();

        return $result;
    }

    public function modifyUser(User $user){
        $dn = $this->baseDN;
        $connection = $this->connect();

        $attrs = array();
        $this->fillAttributesUser($user, $attrs);
        
        // En modification impossible de modifier le cn d'un user
        unset($attrs["cn"]);
        
        // Rechercher le DN du user
        $dn = $this->getUserDN($user);

        foreach($attrs as $key => $value){
            if(empty($value)){
                // Bien mettre un @ car si l'attribut est déjà vide cela crache une erreur car l'attribut n'existe déjà plus
                @ldap_mod_del($connection, $dn, array($key => array()));
                unset($attrs[$key]);
            }
        }


        $result = ldap_modify($connection, $dn, $attrs);
        if(!$result) $this->ldapError();    
    }

    public function modifyUserpwd(User $user){
        $dn = $this->baseDN;
        $connection = $this->connect();

        $attrs = array();

        // Attributs associés au password
        if($this->type=="AD")
            $attrs["unicodepwd"] = $user->getPasswordad();
        $attrs['userpassword'] = $user->getPassword();

        // Rechercher le DN du user
        $dn = $this->getUserDN($user);

        foreach($attrs as $key => $value){
            if(empty($value)){
                // Bien mettre un @ car si l'attribut est déjà vide cela crache une erreur car l'attribut n'existe déjà plus
                @ldap_mod_del($connection, $dn, array($key => array()));
                unset($attrs[$key]);
            }
        }


        $result = ldap_modify($connection, $dn, $attrs);
        if(!$result) $this->ldapError();    
    }

    public function addGroupUser(User $user) {
        $dn = $this->baseDN;
        $connection = $this->connect(); 

        // NIVEAU01
        // On recherche le Niveau01 actuellement asscocié à l'utilisateur
        $criteria = '(&(cn=*)(memberUid='.$user->getUsername().'))';
        $subbranch=$this->baseNiveau01;
        $results = $this->search($criteria, array('cn'), $subbranch);
        foreach($results as $result) {
            // Si Niveau01 différent de celui en cours on le détache de ce Niveau01
            if($result["cn"]!=$user->getNiveau01()->getLabel()) {
                $dn = $this->getNiveau01DN($result["cn"]);
                $entry['memberuid'] = $user->getUsername(); 
                $entry['cadolesMember'] = $this->getUserDN($user); 
                if($this->type=="AD") $entry['member'] = $this->getUserDN($user);

                $result = ldap_mod_del($connection, $dn, $entry); 
                if(!$result) $this->ldapError();
            }
        }

        // On recherche le Niveau01 en cours
        $criteria = '(cn='.$user->getNiveau01()->getLabel().')';
        $subbranch=$this->baseNiveau01;
        $result = $this->search($criteria, array('memberuid'), $subbranch);

        // S'il n'est pas membre du Niveau01 on le rattache
        if(!$this->in_array_r($user->getUsername(),$result[0])) {
            $dn = $this->getNiveau01DN($user->getNiveau01()->getLabel());
            $entry['memberuid'] = $user->getUsername(); 
            $entry['cadolesMember'] = $this->getUserDN($user); 
            if($this->type=="AD") $entry['member'] = $this->getUserDN($user);

            $result = ldap_mod_add($connection, $dn, $entry); 
            if(!$result) $this->ldapError();
        }

        // NIVEAU02
        // On recherche le Niveau02 actuellement asscocié à l'utilisateur
        $criteria = '(&(cn=*)(memberUid='.$user->getUsername().'))';
        $subbranch=$this->baseNiveau02;
        $results = $this->search($criteria, array('cn'), $subbranch);
        foreach($results as $result) {
            // Si Niveau02 différent de celui en cours on le détache de ce Niveau02
            if($user->getNiveau02()===null||$result["cn"]!=$user->getNiveau02()->getLabel()) {
                $dn = $this->getNiveau02DN($result["cn"]);
                $entry['memberuid'] = $user->getUsername(); 
                $entry['cadolesMember'] = $this->getUserDN($user);
                if($this->type=="AD") $entry['member'] = $this->getUserDN($user);

                $result = ldap_mod_del($connection, $dn, $entry); 
                if(!$result) $this->ldapError();
            }
        }

        // On recherche le Niveau02 en cours
        if($user->getNiveau02()!==null) {
            $criteria = '(cn='.$user->getNiveau02()->getLabel().')';
            $subbranch=$this->baseNiveau02;
            $result = $this->search($criteria, array('memberuid'), $subbranch);

            // S'il n'est pas membre du Niveau02 on le rattache
            if(!$this->in_array_r($user->getUsername(),$result[0])) {
                $dn = $this->getNiveau02DN($user->getNiveau02()->getLabel());
                $entry['memberuid'] = $user->getUsername(); 
                $entry['cadolesMember'] = $this->getUserDN($user);
                if($this->type=="AD") $entry['member'] = $this->getUserDN($user);

                $result = ldap_mod_add($connection, $dn, $entry); 
                if(!$result) $this->ldapError();
            }
        }
        
        return $result;        
    } 

    public function deleteUser(User $user){
        $dn = $this->getUserDN($user);
        return $this->deleteByDN($dn);
    }

    private function getObjectClassesUser() {
        $oc = array(
            'top',
            'person',
            'organizationalPerson',
            'inetOrgPerson',
            'cadolesPerson',
            'cadolesSiren',
            'cadolesSiret'
        );
        return $oc;
    }

    private function fillAttributesUser(User $user, array &$attrs) {
        if($this->type=="LDAP") {
            $attrs['uid']                       = $user->getUsername();
            $attrs['cn']                        = $user->getFirstname() . ' ' . $user->getLastname();
        }
        else {
            $attrs['cn']                        = $user->getUsername();
            $attrs['sAMAccountName']            = $user->getUsername();
            $attrs["userAccountControl"]        = 544;
            $attrs["homeDrive"]                 = "U:";
            $attrs["homeDirectory"]             = "\\\\".$this->addomainehome."\\".$user->getUsername();
            if($this->addomaineprofil) {
                $attrs["profilePath"]               = "\\\\".$this->addomaineprofil."\\profiles\\".$user->getUsername();
            }
        }

        $attrs['givenName']                 = $user->getFirstname();
        $attrs['sn']                        = $user->getLastname();
        $attrs['mail']                      = $user->getEmail();
        $attrs['displayName']               = $user->getFirstname() . ' ' . $user->getLastname();      
        $attrs['siren']                     = $user->getNiveau01()->getSiren();
        $attrs['niveau01']                  = $user->getNiveau01()->getLabel();
        $attrs['siret']                     = ($user->getNiveau02()!==null?$user->getNiveau02()->getSiret():"");
        $attrs['niveau02']                  = ($user->getNiveau02()!==null?$user->getNiveau02()->getLabel():"");
        $attrs['authlevel']                 = $user->getAuthlevel();
        $attrs['usualname']                 = $user->getUsualname();
        $attrs['telephoneNumber']           = $user->getTelephonenumber();
        $attrs['postalAddress']             = $user->getPostaladress();
        $attrs['givensname']                = $user->getGivensname();
        $attrs['birthdate']                 = ($user->getBirthdate()!==null?$user->getBirthdate()->format("Y-m-d"):"");
        $attrs['gender']                    = $user->getGender();
        $attrs['job']                       = $user->getJob();
        $attrs['position']                  = $user->getPosition();
        $attrs['belongingpopulation']       = $user->getBelongingpopulation();
        $attrs['birthcountry']              = ($user->getBirthcountry()!==null?$user->getBirthcountry()->getCode():"");
        $attrs['birthplace']                = ($user->getBirthplace()!==null?$user->getBirthplace()->getCode():"");
    }

    public function getUserDN(User $user) {
        if($this->type=="LDAP")
            return 'uid='.$user->getUsername().','.$this->baseUser;
        else
            return 'cn='.$user->getUsername().','.$this->baseUser;
    }

//==================================================================================================================================================================
//== Function Niveau01==============================================================================================================================================
//==================================================================================================================================================================

    public function addNiveau01(Niveau01 $niveau01) {

        $connection = $this->connect();
        $dn = $this->getNiveau01DN($niveau01->getLabel());

        $attrs = array();
        $attrs['objectclass'] = $this->getObjectClassesNiveau01();
        $this->fillAttributesNiveau01($niveau01, $attrs);

        foreach($attrs as $key => $value){
            if(empty($value)){
                unset($attrs[$key]);
            }
        }

        $result = ldap_add($connection, $dn, $attrs);
        if(!$result) $this->ldapError();

        return $result;
    }

    public function modifyNiveau01(Niveau01 $niveau01,$oldid){

        $dn = $this->baseDN;
        $connection = $this->connect();

        $attrs = array();
        $this->fillAttributesNiveau01($niveau01, $attrs);
        unset($attrs["cn"]);

        $dn = $this->getNiveau01DN($niveau01->getLabel());

        foreach($attrs as $key => $value){
            if(empty($value)){
                // Bien mettre un @ car si l'attribut est déjà vide cela crache une erreur car l'attribut n'existe déjà plus
                @ldap_mod_del($connection, $dn, array($key => array()));
                unset($attrs[$key]);
            }
        }

        if(isset($oldid)&&$oldid!=$niveau01->getLabel()) {
            $olddn = $this->getNiveau01DN($oldid);
            $this->rename($olddn,"cn=".$niveau01->getLabel(),$this->baseNiveau01);
        }

        $result = ldap_modify($connection, $dn, $attrs);
        if(!$result) $this->ldapError();
    }



    public function deleteNiveau01(Niveau01 $niveau01){
        $dn = $this->getNiveau01DN($niveau01->getLabel());
        return $this->deleteByDN($dn);
    }

    private function getObjectClassesNiveau01() {
        if($this->type=="LDAP")
            $oc = array(
                'top',
                'posixGroup',
                'cadolesGroup',
                'cadolesSiren'
            );
        else 
            $oc = array(
                'top',
                'Group',
                'cadolesGroup',
                'cadolesSiren'
            );

        return $oc;
    }

    private function fillAttributesNiveau01(Niveau01 $niveau01, array &$attrs) {
        $attrs['cn']        = $niveau01->getLabel();
        $attrs['gidNumber'] = $niveau01->getId();
        $attrs['siren']     = $niveau01->getSiren();

        if($this->type=="AD") {
            $attrs['sAMAccountName'] = $niveau01->getLabel();
        }
    }

    public function getNiveau01DN($id) {
        return 'cn='.$id.','.$this->baseNiveau01;
    }

//==================================================================================================================================================================    
//== Function Niveau02==============================================================================================================================================
//==================================================================================================================================================================

    public function addNiveau02(Niveau02 $niveau02) {

        $connection = $this->connect();
        $dn = $this->getNiveau02DN($niveau02->getLabel());

        $attrs = array();
        $attrs['objectclass'] = $this->getObjectClassesNiveau02();
        $this->fillAttributesNiveau02($niveau02, $attrs);
        
        foreach($attrs as $key => $value){
            if(empty($value)){
                unset($attrs[$key]);
            }
        }

        $result = ldap_add($connection, $dn, $attrs);
        if(!$result) $this->ldapError();

        return $result;
    }

    public function modifyNiveau02(Niveau02 $niveau02,$oldid){
        $dn = $this->baseDN;
        $connection = $this->connect();

        $attrs = array();
        $this->fillAttributesNiveau02($niveau02, $attrs);
        unset($attrs["cn"]);

        $dn = $this->getNiveau02DN($niveau02->getLabel());

        foreach($attrs as $key => $value){
            if(empty($value)){
                // Bien mettre un @ car si l'attribut est déjà vide cela crache une erreur car l'attribut n'existe déjà plus
                @ldap_mod_del($connection, $dn, array($key => array()));
                unset($attrs[$key]);
            }
        }

        if(isset($oldid)&&$oldid!=$niveau02->getLabel()) {
            $olddn = $this->getNiveau02DN($oldid);
            $this->rename($olddn,"cn=".$niveau02->getLabel(),$this->baseNiveau02);
        }

        $result = ldap_modify($connection, $dn, $attrs);
        if(!$result) $this->ldapError();
    }



    public function deleteNiveau02(Niveau02 $niveau02){
        $dn = $this->getNiveau02DN($niveau02->getLabel());
        return $this->deleteByDN($dn);
    }

    private function getObjectClassesNiveau02() {
        if($this->type=="LDAP")
            $oc = array(
                'top',
                'posixGroup',
                'cadolesGroup',
                'cadolesSiret'
            );
        else 
            $oc = array(
                'top',
                'Group',
                'cadolesGroup',
                'cadolesSiret'
            );

        return $oc;
    }

    private function fillAttributesNiveau02(Niveau02 $niveau02, array &$attrs) {
        $attrs['cn']            = $niveau02->getLabel();
        $attrs['gidNumber']     = $niveau02->getId();
        $attrs['siret']         = $niveau02->getSiret();
        $attrs['postalAddress'] = $niveau02->getPostaladress();

        if($this->type=="AD") {
            $attrs['sAMAccountName'] = $niveau02->getLabel();
        }        
    }

    public function getNiveau02DN($id) {
        return 'cn='.$id.','.$this->baseNiveau02;
    }

//==================================================================================================================================================================    
//== Function Group=================================================================================================================================================
//==================================================================================================================================================================

    public function addGroup(Group $group) {

        $connection = $this->connect();
        $dn = $this->getGroupDN($group->getLabel());

        $attrs = array();
        $attrs['objectclass'] = $this->getObjectClassesGroup();
        $this->fillAttributesGroup($group, $attrs);

        foreach($attrs as $key => $value){
            if(empty($value)){
                unset($attrs[$key]);
            }
        }

        $result = ldap_add($connection, $dn, $attrs);
        if(!$result) $this->ldapError();

        return $result;
    }

    public function modifyGroup(Group $group,$oldid){
        $dn = $this->baseDN;
        $connection = $this->connect();

        $attrs = array();
        $this->fillAttributesGroup($group, $attrs);
        unset($attrs["cn"]);

        $dn = $this->getGroupDN($group->getLabel());
        
        foreach($attrs as $key => $value){
            if(empty($value)){
                // Bien mettre un @ car si l'attribut est déjà vide cela crache une erreur car l'attribut n'existe déjà plus
                @ldap_mod_del($connection, $dn, array($key => array()));
                unset($attrs[$key]);
            }
        }

        if(isset($oldid)&&$oldid!=$group->getLabel()) {
            $olddn = $this->getGroupDN($oldid);
            $this->rename($olddn,"cn=".$group->getLabel(),$this->baseGroup);
        }

        $result = ldap_modify($connection, $dn, $attrs);
        if(!$result) $this->ldapError();
    }



    public function deleteGroup(Group $group){
        $dn = $this->getGroupDN($group->getLabel());
        return $this->deleteByDN($dn);
    }

    private function getObjectClassesGroup() {
        if($this->type=="LDAP")
            $oc = array(
                'top',
                'posixGroup',
                'cadolesGroup'
            );
        else 
            $oc = array(
                'top',
                'Group',
                'cadolesGroup'
            );

        return $oc;
    }

    private function fillAttributesGroup(Group $group, array &$attrs) {
        $attrs['cn']        = $group->getLabel();
        $attrs['gidNumber'] = $group->getId();
        $attrs['mail']      = $group->getEmail();

        if($this->type=="AD") {
            $attrs['sAMAccountName'] = $group->getLabel();
        }         
    }

    public function getGroupDN($id) {
        return 'cn='.$id.','.$this->baseGroup;
    }

//==================================================================================================================================================================    
//== Function UserGroup=============================================================================================================================================
//==================================================================================================================================================================

    function addUserGroup(UserGroup $usergroup) {
        $dn = $this->baseDN;
        $connection = $this->connect();
        
        // On recherche le group en cours
        $criteria = '(cn='.$usergroup->getGroup()->getLabel().')';
        $subbranch=$this->baseGroup;
        $result = $this->search($criteria, array('memberuid'), $subbranch);

        // S'il n'est pas membre du Niveau01 on le rattache
        if(!$this->in_array_r($usergroup->getUser()->getUsername(),$result[0])) {
            $dn = $this->getGroupDN($usergroup->getGroup()->getLabel());
            $entry['memberuid'] = $usergroup->getUser()->getUsername(); 
            $entry['cadolesMember'] = $this->getUserDN($usergroup->getUser());
            if($this->type=="AD") $entry['member'] = $this->getUserDN($usergroup->getUser());

            $result = ldap_mod_add($connection, $dn, $entry); 
            if(!$result) $this->ldapError();
        }

        return $result;
    }

    function delUserGroup(UserGroup $usergroup) {
        $dn = $this->baseDN;
        $connection = $this->connect();
        
        // On recherche le group en cours
        $criteria = '(cn='.$usergroup->getGroup()->getLabel().')';
        $subbranch=$this->baseGroup;
        $result = $this->search($criteria, array('memberuid'), $subbranch);

        // S'il n'est pas membre du Niveau01 on le rattache
        if($this->in_array_r($usergroup->getUser()->getUsername(),$result[0])) {
            $dn = $this->getGroupDN($usergroup->getGroup()->getLabel());
            $entry['memberuid'] = $usergroup->getUser()->getUsername(); 
            $entry['cadolesMember'] = $this->getUserDN($usergroup->getUser());
            if($this->type=="AD") $entry['member'] = $this->getUserDN($usergroup->getUser());

            $result = ldap_mod_del($connection, $dn, $entry); 
            if(!$result) $this->ldapError();
        }

        return $result;
    }

//==================================================================================================================================================================
//== Init du Service Synfony========================================================================================================================================
//==================================================================================================================================================================

    public function getUser() {
        return $this->user;
    }

    public function setUser($user) {
        $this->user = $user;
        return $this;
    }

    public function getPassword() {
        return $this->password;
    }

    public function setPassword($password) {
        $this->password = $password;
        return $this;
    }

    public function getBaseDN() {
        return $this->baseDN;
    }

    public function setBaseDN($baseDN) {
        $this->baseDN = $baseDN;
        return $this;
    }

    public function getBaseUser() {
        return $this->baseUser;
    }

    public function setBaseUser($baseUser) {
        $this->baseUser = $baseUser;
        return $this;
    }

    public function getBaseNiveau01() {
        return $this->baseNiveau01;
    }

    public function setBaseNiveau01($baseNiveau01) {
        $this->baseNiveau01 = $baseNiveau01;
        return $this;
    }

    public function getBaseNiveau02() {
        return $this->baseNiveau02;
    }

    public function setBaseNiveau02($baseNiveau02) {
        $this->baseNiveau02 = $baseNiveau02;
        return $this;
    }    

    public function getBaseGroup() {
        return $this->baseGroup;
    }

    public function setBaseGroup($baseGroup) {
        $this->baseGroup = $baseGroup;
        return $this;
    }
        
    public function setLdapSync($ldapSync,$masteridentity) {
        $this->ldapSync = ($ldapSync&&($masteridentity=="SQL"));
        return $this;
    }    
}
