<?PHP

  /**
   * phOSCAR
   * -------
   * An OSCAR-Implementation in PHP 5
   * 
   * This work is distributed within the terms of
   * creative commons attribution-share alike 3.0 germany
   * 
   * See http://creativecommons.org/licenses/by-sa/3.0/ for more information
   * 
   * @author Bernd Holzmueller <bernd@tiggerswelt.net>
   * @revision 04
   * @license http://creativecommons.org/licenses/by-sa/3.0/de/ Creative Commons Attribution-Share Alike 3.0 Germany
   * @homepage http://oss.tiggerswelt.net/oscar/
   * @copyright Copyright &copy; 2009 tiggersWelt.net 
   */
  
  // Load our required classes
  require_once ("oscar/roster/group.php");
  require_once ("oscar/roster/contact.php");
  require_once ("oscar/roster/transaction.php");
  
  /**
   * Oscar Roster
   * 
   * @class Oscar_Roster
   * @package Oscar
   * @author Bernd Holzmueller <bernd@tiggerswelt.net>
   **/
  class Oscar_Roster {
    private $Parent = null;
    
    private $Groups = array ();
    private $Users = array ();
    private $privacyList = array (
      "Allow" => array (),
      "Deny" => array (),
    );
    
    private $transactionLocal = null;
    private $transactionRemote = null;
    
    private $lastMod = 0;
    private $Version = 0;
    private $Diff = 0;
    private $maxID = 1;
    
    // Name of class to create contacts from
    private $contactClass = "Oscar_Roster_Contact";
    
    // {{{ __construct
    /**
     * Create a new roster
     * 
     * @param object $Parent
     * 
     * @access friendly
     * @return void
     */
    function __construct ($Parent) {
      $this->Parent = $Parent;
    }
    // }}}
    
    public function getLocalTransaction () {
    
    }
    
    // {{{ requestRefresh
    /**
     * Refresh the roster
     * 
     * @access public
     * @return void
     */
    public function requestRefresh () {
      $SNAC = new Oscar_SNAC_SSI_Checkout ($this->Parent);
      
      $SNAC->Counter =
        count ($this->Users) +
        count ($this->Groups) +
        count ($this->privacyList ["Allow"]) +
        count ($this->privacyList ["Deny"]) +
        $this->Diff;
      
      $SNAC->ModTime = $this->lastChange;
      
      return $SNAC->writeFLAP ();
    }
    // }}}
    
    // {{{ updateFromSNAC
    /**
     * Update the roster by a received SNAC of type SNAC_Feedbag_Reply
     * 
     * @param object $SNAC
     * 
     * @access public
     * @return void
     */
    public function updateFromSNAC ($SNAC) {
      // Validate the incoming SNAC
      if (!($SNAC instanceof Oscar_SNAC_Feedbag_Reply))
        return false;
      
      $this->lastChange = $SNAC->LastMod;
      $this->Version = $SNAC->Version;
      
      if (!is_array ($Items = $SNAC->Items))
        return $this->Parent->rosterUpdate ($this->Version, $this->lastChange);
      
      // Append groups to roster
      foreach ($Items as $ID=>$Item) {
        $Handled = false;
        
        if ($Item->ItemID > $this->maxID)
          $this->maxID = $Item->ItemID;
        
        // Ignore master-Group for the moment
        switch ($Item->Type) {
          // Buddy-Item
          case Oscar_SNAC_Feedbag_Item::TYPE_BUDDY:
            $Handled = true;
            $this->updateUser ($Item);
            break;
            
          // Buddy-Group
          case Oscar_SNAC_Feedbag_Item::TYPE_GROUP:
            $Handled = true;
            $this->updateGroup ($Item);
            break;
          
          // Permit
          case Oscar_SNAC_Feedbag_Item::TYPE_PERMIT:
            $Handled = true;
            $this->privacyList ["Allow"][$Item->getID ()] = $Item->Name;
            break;
          
          // Reject
          case Oscar_SNAC_Feedbag_Item::TYPE_DENY:
            $Handled = true;
            $this->privacyList ["Allow"][$Item->getID ()] = $Item->Name;
            break;
          
          // Default Permit/Deny-Setting
          case Oscar_SNAC_Feedbag_Item::TYPE_PDINFO:
            if (is_array ($Item->TLVs))
              foreach ($Item->TLVs as $TLV)
                if ($Handled = ($TLV instanceof Oscar_TLV_PrivacySetting)) {
                  $this->privacyList ["Default"] = $TLV->Setting;
                  break;
                }
          
          default:
            $this->Diff++;
            break;
        }
      
        if ($Handled)
          unset ($Items [$ID]);
      }
      
      // Debug unhandled items
      foreach ($Items as $Item) {
        print "  0x" . dechex ($Item->GroupID) . "/0x" . dechex ($Item->ItemID) . "/0x" .
              dechex ($Item->Type) . " " . $Item->Name . " (" . count ($Item->TLVs) . ")\n";
          
        foreach ($Item->TLVs as $TLV)
          print "    " . $TLV->toString () . "\n";
      }
      
      $this->Parent->rosterUpdate ($this->Version, $this->lastChange);
    }
    // }}}
    
    // {{{ setContactClass
    /**
     * Set Class to create roster contacts from
     * 
     * @param string $Class
     * 
     * @access public
     * @return bool
     **/
    public function setContactClass ($Class) {
      if (!is_subclass_of ($Class, "Oscar_Roster_Contact"))
        return false;
      
      $this->contactClass = $Class;
      
      return true;
    }
    // }}}
    
    // {{{ createContact
    /**
     * Create a new contact-handle
     * 
     * @access private
     * @return object
     **/
    private function createContact ($ID, $Name) {
      return new $this->contactClass ($ID, $Name, $this);
    }
    // }}}
    
    // {{{ updateGroup
    /**
     * Update a group from an SSI-Update
     * 
     * @param object $Group
     * 
     * @access private
     * @return void   
     */
    public function updateGroup ($Group) {
      // Get a valid group-handle
      if (!isset ($this->Groups [$Group->GroupID])) {
        $Item = new Oscar_Roster_Group;
        $Item->ID = $Group->GroupID;
      } else
        $Item = $this->Groups [$Group->GroupID];
      
      // Update the handle
      $Item->Name = $Group->Name;
      
      // Store back (just to be sure)
      $this->Groups [$Group->GroupID] = $Item;
    }
    // }}}
    
    // {{{ updateUser
    /**
     * Update a user from an SSI-Update
     * 
     * @param object $User
     * 
     * @access private
     * @return void   
     */
    private function updateUser ($User) {
      if (!($User instanceof Oscar_SNAC_Feedbag_Item))
        return false;
      
      $idx = $User->getID ();
      
      if (!isset ($this->Users [$idx]))
        $this->Users [$idx] = self::createContact ($idx, $User->Name);
      
      $Item = $this->Users [$idx];
      
      // Create link to assigned group
      if (isset ($this->Groups [$User->GroupID])) {
        if (is_object ($Item->Group) && ($Item->Group->ID != $User->GroupID)) {
          $Item->Group->removeUser ($Item);
          $this->Groups [$User->GroupID]->addUser ($Item);
        }
      } else
        $Item->Group = null;
      
      if (is_array ($User->TLVs))
        $Item->update ($User);
    }
    // }}}
    
    // {{{ setOffline
    /**
     * Set all users on our roster offlin
     *
     * @param bool $Callback (optional) Run default callback on contacts
     * 
     * @access public
     * @return void
     **/
    public function setOffline ($Callback = true) {
      foreach ($this->Users as $idx=>$User) {
        if (!$User->isOnline ())
          continue;
        
        $User->setStatus (array (), Oscar_TLV_Status::STATUS_OFFLINE);
        
        if ($Callback)
          $this->Parent->contactOffline ($User->getName ());
      }
    }
    // }}}
    
    // {{{ getContact
    /**
     * Get a contact-handle from roster
     * 
     * @param string $User
     * 
     * @access public
     * @return object
     */
    public function getContact ($Name, $Create = false) {
      if (isset ($this->Users [$Name]))
        return $this->Users [$Name];
      
      foreach ($this->Users as $User)
        if ($User->getName () == $Name)
          return $User;
      
      if (!$Create)
        return false;
      
      # TODO: Reimplement this
      # $this->Users [$User] = self::createContact ($User);
    }
    // }}}
    
    // {{{ getContacts
    /**
     * Get all users on our roster
     * 
     * @access public
     * @return array
     **/
    public function getContacts () {
      return $this->Users;
    }
    // }}}
    
    // {{{ getOscar
    /**
     * Retrive handle of our parent
     * 
     * @access public
     * @return object
     **/
    public function getOscar () {
      return $this->Parent;
    }
    // }}}
    
    public function addContact ($UID, $Question = "", $Complete = true) {
      // Retrive handle of our parent client
      if (!is_object ($Parent = $this->getOscar ()))
        return false;
      
      // Retrive handle of our transaction
      if (!is_object ($Transaction = $this->getLocalTransaction ()))
        return false;
      
      // Create a new item
      $Handle = new Oscar_SNAC_Feedbag_Item ($Parent);
      
      foreach ($this->Groups as $Group) {
        $Handle->GroupID = $Group->ID;
        break;
      }
      
      $Handle->ItemID = ++$this->maxID;
      $Handle->Type = Oscar_SNAC_Feedbag_Item::TYPE_BUDDY;
      $Handle->Name = $UID;
      
      # TODO: Where to put the question to?
      
      // Put item to transaction
      $Transaction->add (array ($Handle));
      
      // Complete transaction
      if ($Complete)
        $Transaction->finish ();
    }
  }

?>