<?php
namespace FSpires\CommitKeeperBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ExecutionContext;

/**
 * FSpires\CommitKeeperBundle\Entity\User
 *
 * @Assert\Callback(methods={"validateAuthMetods"})
 *
 * @ORM\Table(name="user")
 * @ORM\Entity
 */
class User extends UserBase implements AdvancedUserInterface, EquatableInterface
{
    /**
     * A collection of OpenIdentifier objects
     * @var Doctrine\Common\Collections\ArrayCollection $openIdentifier
     * 
     * @ORM\OneToMany(targetEntity="OpenIdentifier", mappedBy="user", cascade={"persist"})
     */
    private $openIdentifier;


    /**
     * Encrypted password
     * @var string $password
     *
     * @ORM\Column(name="password", type="string", length=32, nullable=false)
     */
    private $password;


    /**
     * Clear text password
     * Not saved in the database, only used for input of new password
     */
    private $ctPassword;

    /**
     * @var datetime $passwordExpiryDate
     *
     * @ORM\Column(name="password_expiry_date", type="datetime", nullable=false)
     */
    private $passwordExpiryDate;

    /**
     * @var datetime $createdDate
     *
     * @ORM\Column(name="created_date", type="datetime", nullable=false)
     */
    private $createdDate;

    /**
     * @var datetime $updatedDate
     *
     * @ORM\Column(name="updated_date", type="datetime", nullable=false)
     */
    private $updatedDate;

    /**
     * @var string $userLevel
     *
     * @ORM\Column(name="user_level", type="string", length=10, nullable=false)
     */
    private $userLevel;

    /**
     * @var integer $userType
     *
     * @ORM\Column(name="user_type", type="integer", nullable=false)
     */
    private $userType;

    /**
     * @var integer $userOwnerId
     *
     * @ORM\Column(name="user_owner_id", type="integer", nullable=false)
     */
    private $userOwnerId;

    /**
     * @var integer $loggedIn
     *
     * @ORM\Column(name="logged_in", type="integer", nullable=true)
     */
    private $loggedIn;

    /**
     * @var integer $active
     *
     * @ORM\Column(name="active", type="integer", nullable=false)
     */
    private $active;

    /**
     * @var string $defaultDateInterval;
     *
     * @ORM\Column(name="default_date_interval", type="string", length=12, nullable=false)
     */
    private $defaultDateInterval;

    /**
     * @var object $lastRequestor
     *
     * @ORM\ManyToOne(targetEntity="UserName")
     * @ORM\JoinColumn(name="last_requestor_id", referencedColumnName="id")
     */
    private $lastRequestor;

    /**
     * @var object $lastPerformer
     *
     * @ORM\ManyToOne(targetEntity="UserName")
     * @ORM\JoinColumn(name="last_performer_id", referencedColumnName="id")
     */
    private $lastPerformer;

    /**
     * @var integer $managerUserId
     *
     * @ORM\Column(name="manager_user_id", type="integer", nullable=true)
     */
    private $managerUserId;

    /**
     * @var integer $companyId
     *
     * @ORM\Column(name="company_id", type="integer", nullable=false)
     */
    private $companyId;

    /**
     * Do we want to keep a local password for this user, checkbox
     * Not stored in database, but used in user register and user edit forms
     * It is a string and not a bool because we want more than two states
     * 
     * @var string $keep
     */
    private $keep = null;


    /**
     * Constructor
     */
    public function __construct()
    {
        $this->openIdentifier = new ArrayCollection();
    }

    /**
     * Call this function when a new user is created
     * Adds some initial data to a new user
     */
    public function initNew() {
      if (!$this->getMiddleName()) {
        $this->setMiddleName('');
      }

      if (!isset($this->password)) {
        $this->password='';
      }
      if (!isset($this->passwordExpiryDate)) {
        $this->passwordExpiryDate = new \DateTime('0000-00-00');
      }

      $date = new \DateTime('now');
      $this->setCreatedDate($date);
      $this->setUpdatedDate($date);
      $this->setActive(1);

      // Set initial authorization level and user type
      $this->setUserLevel(1);
      $this->setUserType(2);

      // TODO: How to set the initial userOwner and company
      $this->userOwnerId=0;
      $this->companyId=0;
    }

    /**
     * Add openIdentifier
     *
     * @param FSpires\CommitKeeperBundle\Entity\OpenIdentifier $openIdentifier
     */
    public function addOpenIdentifier(OpenIdentifier $openIdentifier)
    {
        $openIdentifier->setUser($this);
        $this->openIdentifier[] = $openIdentifier;
    }

    /**
     * Set the openIdentifier collection
     *
     * @param Doctrine\Common\Collections\ArrayCollection $openIdentifiers
     */
    public function setOpenIdentifier(ArrayCollection $openIdentifiers)
    {
        foreach ($openIdentifiers as $oid) {
          $oid->setUser($this);
        }
        $this->openIdentifier = $openIdentifiers;
    }

    /**
     * Get openIdentifier
     *
     * @return Doctrine\Common\Collections\ArrayCollection 
     */
    public function getOpenIdentifier()
    {
        return $this->openIdentifier;
    }

    /**
     * Check if we have a valid password
     *
     */
    protected function validatePassword(ExecutionContext $ec)
    {
      // Set the property path so that is done
      $propertyPath = $ec->getPropertyPath() . '.ctPassword';
      $ec->setPropertyPath($propertyPath);

      if (empty($this->ctPassword)) {
        if (!empty($this->password)) {
          // The user do not want to change the password
          return;
        }
        //Make sure it is a string, so the next test is problem free
        $this->ctPassword='';
      }

      if (strlen($this->ctPassword)<6) {
        $ec->addViolation('Password must be at least 6 characters long.',
                          array(), $this->ctPassword);
      }
    }

    /**
     * Check if we have at least one valid authentication method.
     * Either at least one OpenIdentifier or a valid password
     */
    public function validateAuthMetods(ExecutionContext $ec) {
      if ($this->getKeep()) {
        $this->validatePassword($ec);
        // We have a valid password we are going to keep
        return;
      }
      foreach ($this->openIdentifier as $oid) {
        if ($oid->getKeep()) {
          // We have at least one openIdentifier we are going to keep
          // So everything is ok
          return;
        }
      }
      // We have no authentication method
      $propertyPath = $ec->getPropertyPath() . '.openIdentifier';
      $ec->setPropertyPath($propertyPath);
      $ec->addViolation('You must have at least one authentication method.<br /> Please check one.', array(), false);
    }

    /**
     * Finish the authorization setup for this user.
     *
     * Check if any authentication providers have been unchecked
     * Then the user do not want them anymore, so remove them
     *
     * Also make sure the password is encrypted.
     *
     * @param Doctrine\ORM\EntityManager $em Needed when the users a. providers
     *                                       already are in the database
     */
    public function finishAuthSetup(EntityManager $em=null) {
      foreach ($this->openIdentifier as $key => $oid) {
        if (!$oid->getKeep()) {
          $this->openIdentifier->remove($key);
          if (isset($em)) {
            $em->remove($oid);
          }
        }
      }
      if ($this->getKeep()) {
        // Only encrypt a new password if it is not empty
        if (!empty($this->ctPassword)) {
          $this->encryptPassword();
        }
      } else {
        $this->password = '';
      }
    }


    /**
     * Encrypt the password
     * and set a new salt
     */
    private function encryptPassword() {
      // Just copy it for now
      $this->password = $this->ctPassword;
    }


    /**
     * Returns the roles granted to the user.
     *
     * @return Role[] The user roles
     */
    function getRoles() {
      return array('ROLE_LEVEL' . $this->userLevel,
                   'ROLE_TYPE' . $this->userType);
    }

    /**
     * Returns the password used to authenticate the user.
     *
     * @return string The password
     */
    public function getPassword() {
        return $this->password;
    }


    /**
     * Returns the salt that was originally used to encode the password.
     *
     * This can return null if the password was not encoded using a salt.
     *
     * @return string The salt
     */
    public function getSalt() {
      return null;
    }

    /**
     * Returns the username used to authenticate the user.
     *
     * @return string The username
     */
    public function getUsername() {
      return $this->email;
    }

    /**
     * Removes sensitive data from the user.
     *
     * This is important if, at any given point, sensitive information like
     * the plain-text password is stored on this object.
     *
     * @return void
     */
    public function eraseCredentials() {
      $this->password = null;
    }

    /**
     * Returns whether or not the given user is equivalent to *this* user.
     *
     * The equality comparison should neither be done by referential equality
     * nor by comparing identities (i.e. getId() === getId()).
     *
     * However, you do not need to compare every attribute, but only those that
     * are relevant for assessing whether re-authentication is required.
     *
     * @param UserInterface $user
     *
     * @return Boolean
     */
    public function isEqualTo(UserInterface $user) {
      if (!$user instanceof User) {
        return false;
      }

      if ($this->email !== $user->getUsername()) {
        return false;
      }

      if ($this->userLevel !== $user->getUserLevel()) {
        return false;
      }

      if ($this->userType !== $user->getUserType()) {
        return false;
      }

      return true;
    }

    /**
     * Checks whether the user's account has expired.
     *
     * Internally, if this method returns false, the authentication system
     * will throw an AccountExpiredException and prevent login.
     *
     * @return Boolean true if the user's account is non expired, false otherwise
     *
     * @see AccountExpiredException
     */
    public function isAccountNonExpired() {
      return true;
    }

    /**
     * Checks whether the user is locked.
     *
     * Internally, if this method returns false, the authentication system
     * will throw a LockedException and prevent login.
     *
     * @return Boolean true if the user is not locked, false otherwise
     *
     * @see LockedException
     */
    public function isAccountNonLocked() {
      return true;
    }

    /**
     * Checks whether the user's credentials (password) has expired.
     *
     * Internally, if this method returns false, the authentication system
     * will throw a CredentialsExpiredException and prevent login.
     *
     * @return Boolean true if the user's credentials are non expired, false otherwise
     *
     * @see CredentialsExpiredException
     */
    public function isCredentialsNonExpired() {
      $valid = true;
      if ($this->passwordExpiryDate &&
          $this->passwordExpiryDate > new \DateTime('1111-01-01')) {
        $valid = $this->passwordExpiryDate > new \DateTime('now');
      }
      return $valid;
    }

    /**
     * Checks whether the user is enabled.
     *
     * Internally, if this method returns false, the authentication system
     * will throw a DisabledException and prevent login.
     *
     * @return Boolean true if the user is enabled, false otherwise
     *
     * @see DisabledException
     */
    public function isEnabled() {
      return ($this->active > 0) || ($this->active === true);
    }


    /**
     * Set clear text password
     *
     * @param string $password
     */
    public function setCtPassword($ctxtpassword)
    {
        $this->ctPassword = $ctxtpassword;
    }

    /**
     * Get clear text password
     *
     * @param string $password
     */
    public function getCtPassword()
    {
        return $this->ctPassword;
    }


    /**
     * Set passwordExpiryDate
     *
     * @param datetime $passwordExpiryDate
     */
    public function setPasswordExpiryDate($passwordExpiryDate)
    {
        $this->passwordExpiryDate = $passwordExpiryDate;
    }

    /**
     * Get passwordExpiryDate
     *
     * @return datetime 
     */
    public function getPasswordExpiryDate()
    {
        return $this->passwordExpiryDate;
    }

    /**
     * Set createdDate
     *
     * @param datetime $createdDate
     */
    public function setCreatedDate($createdDate)
    {
        $this->createdDate = $createdDate;
    }

    /**
     * Get createdDate
     *
     * @return datetime 
     */
    public function getCreatedDate()
    {
        return $this->createdDate;
    }

    /**
     * Set updatedDate
     *
     * @param datetime $updatedDate
     */
    public function setUpdatedDate($updatedDate=null)
    {
      if (empty($updatedDate)) {
        $updatedDate = new \DateTime('now');
      }
      $this->updatedDate = $updatedDate;
    }

    /**
     * Get updatedDate
     *
     * @return datetime 
     */
    public function getUpdatedDate()
    {
        return $this->updatedDate;
    }

    /**
     * Set userLevel
     *
     * @param string $userLevel
     */
    public function setUserLevel($userLevel)
    {
        $this->userLevel = $userLevel;
    }

    /**
     * Get userLevel
     *
     * @return string 
     */
    public function getUserLevel()
    {
        return $this->userLevel;
    }

    /**
     * Set userType
     *
     * @param integer $userType
     */
    public function setUserType($userType)
    {
        $this->userType = $userType;
    }

    /**
     * Get userType
     *
     * @return integer 
     */
    public function getUserType()
    {
        return $this->userType;
    }

    /**
     * Set userOwnerId
     *
     * @param integer $userOwnerId
     */
    public function setUserOwnerId($userOwnerId)
    {
        $this->userOwnerId = $userOwnerId;
    }

    /**
     * Get userOwnerId
     *
     * @return integer 
     */
    public function getUserOwnerId()
    {
        return $this->userOwnerId;
    }

    /**
     * Set loggedIn
     *
     * @param integer $loggedIn
     */
    public function setLoggedIn($loggedIn)
    {
        $this->loggedIn = $loggedIn;
    }

    /**
     * Get loggedIn
     *
     * @return integer 
     */
    public function getLoggedIn()
    {
        return $this->loggedIn;
    }

    /**
     * Set active
     *
     * @param integer $active
     */
    public function setActive($active)
    {
        $this->active = $active;
    }

    /**
     * Get active
     *
     * @return integer 
     */
    public function getActive()
    {
        return $this->active;
    }

    /**
     * Set lastPerformer
     *
     * @param FSpires\CommitKeeperBundle\Entity\UserBase $lastPerformer
     */
    public function setLastPerformer(UserBase $lastPerformer)
    {
        $this->lastPerformer = $lastPerformer;
    }

    /**
     * Get lastPerformer
     *
     * @return FSpires\CommitKeeperBundle\Entity\UserBase
     */
    public function getLastPerformer()
    {
        return $this->lastPerformer;
    }

    /**
     * Set lastRequestor
     *
     * @param FSpires\CommitKeeperBundle\Entity\UserBase $lastRequestor
     */
    public function setLastRequestor(UserBase $lastRequestor)
    {
        $this->lastRequestor = $lastRequestor;
    }

    /**
     * Get lastRequestor
     *
     * @return FSpires\CommitKeeperBundle\Entity\UserBase
     */
    public function getLastRequestor()
    {
        return $this->lastRequestor;
    }

    /**
     * Get Default Date to be used when creating new requests/commitments
     *
     * @return DateTime
     */
    public function getDefaultDate()
    {
      $defaultDate = new \DateTime('now');
      return $defaultDate->add(new \DateInterval($this->defaultDateInterval));
    }

    /**
     * Set DefaultDateInterval
     *
     * @param string $defaultDateInterval (1 week = 'P1W')
     */
    public function setDefaultDateInterval($defaultDateInterval)
    {
      $this->defaultDateInterval = $defaultDateInterval;
    }

    /**
     * Get DefaultDateInterval
     *
     * @return string
     */
    public function getDefaultDateInterval()
    {
      return $this->defaultDateInterval;
    }


    /**
     * Set managerUserId
     *
     * @param integer $managerUserId
     */
    public function setManagerUserId($managerUserId)
    {
        $this->managerUserId = $managerUserId;
    }

    /**
     * Get managerUserId
     *
     * @return integer 
     */
    public function getManagerUserId()
    {
        return $this->managerUserId;
    }

    /**
     * Set companyId
     *
     * @param integer $companyId
     */
    public function setCompanyId($companyId)
    {
        $this->companyId = $companyId;
    }

    /**
     * Get companyId
     *
     * @return integer 
     */
    public function getCompanyId()
    {
        return $this->companyId;
    }

    /**
     * Set to false if we want to drop using local password
     *
     * @param bool $keep
     */
    public function setKeep($keep)
    {
      if ($keep) {
        $this->keep = 'Yes';
      } else {
        $this->keep = 'No';
      }
    }


    /**
     * Do we keep using local password authentication?
     *
     * @return bool
     */
    public function getKeep()
    {
      if (empty($this->keep)) {
        if (empty($this->password)) {
          $this->keep = 'No';
        } else {
          $this->keep = 'Yes';
        }
      }
      return 'Yes'===$this->keep;
    }


    //Serializing related functions

    public function serialize()
    {
        return serialize(array(
                 $this->userLevel,
                 $this->userType,
                 $this->active,
                 parent::serialize()
                               ));
    }

    public function unserialize($str)
    {
        list(
                 $this->userLevel,
                 $this->userType,
                 $this->active,
                 $parentstr
             ) = unserialize($str);
        parent::unserialize($parentstr);
    }
}
