<?php

/**
 * IsisImporter: provides ISIS import methods for importing data into
 * a Symfony project.
 */
class sfIsisImporter extends sfIsisImporterRelations
{
  /**
   * Log dispatcher.
   *
   * @param string $message Log message
   * @param string $level   Log level
   */
  public function log($message, $level = 'info')
  {
    $this->logger->log($message, $level);
  }

  /**
   * Constructor.
   */ 
  public function __construct($config = null) {
    $this->stats = sfIsisImporterStats::getInstance();
    $this->isis  = new IsisConnector($config);

    // Get a logger instance.
    $this->logger = sfIsisImporterLog::getInstance();
  }

  /**
   * Get the model foreign table id.
   *
   * @param  object $model Model
   * @return string        Model table id
   */
  static function getModelId($model) {
    return sfInflector::underscore(get_class($model)) .'_id';
  }

  /**
   * Get the relation foreign table id.
   *
   * @param  string $model Relation name
   * @return string        Relation table id
   */
  static function getRelationId($relation)
  {
    return sfInflector::underscore($relation) .'_id';
  }

  /**
   * Get the model and relation tablename.
   *
   * @param  object $model Model
   * @param  string $model Relation name
   * @return string        Relation table name
   */
  static function getModelRelation($model, $relation)
  {
    return sfInflector::camelize(self::getModelName($model)) . sfInflector::camelize($relation);
  }

  /**
   * Get the model name.
   *
   * @param  object $model Model
   * @return string        Model name
   */
  static function getModelName($model)
  {
    return get_class($model);
  }

  /**
   * Default implementation for parseName.
   *
   * @param  mixed $value Name
   * @return mixed        Name
   */
  public function parseName($value)
  {
    return $value;
  }  

  /**
   * Add a single entry from the database.
   *
   * @param string $base_model Model to use
   * @param int    $entry      Entry number
   */
  public function addEntry($base_model, $entry)
  {
    // Get data and setup the model.
    $this->isis->read($entry);
    $model = $this->newModel($base_model, $entry);

    if ($model)
    {
      $this->log("Importing $base_model $entry...");

      // Dispatch to custom import procedures.
      foreach (new IsisMethodIterator($this->isis, $this) as $method => $field)
      {
        if (!$this->hasDeniedCombinations($base_model, $field))
        {
          $this->{$method}($model, $field);
        }
      }      

      $model->save();
      $model->free(true);
    }
    else {
      $this->log("Skipping existing entry $entry for $base_model.");
    }
  }

  /**
   * Create a new model just if doesn't exist for a given entry. For that
   * to work the entry must provide and id.
   *
   * @param  string $base_model Model to use
   * @param  int    $entry      Entry number
   * @return mixed              New model or false
   */
  public function newModel($base_model, $entry)
  {
    $model = new $base_model();
    $id    = $this->getBaseModelId($model);

    if ($id)
    {
      $existing = call_user_func(array($base_model, 'getById'), $id);

      if (!$existing)
      {
        $this->setBaseModelId($model);
        return $model;
      }
      elseif ($this->skipExisting())
      {
        return false;
      }
      else
      {
        return $existing;
      }
    }

    return $model;
  }

  /**
   * Check if ISIS database configuration is set to not skip existing
   * entries during an import.
   *
   * @return boolean
   */
  public function skipExisting()
  {
    if (isset($this->isis->format['import']['skip_existing']))
    {
      return $this->isis->format['import']['skip_existing'];
    }

    return true;
  }

  /**
   * Set the primary key for the model by getting it or just saving it.
   *
   * @param object $model Base model
   */
  public function setBaseModelId(&$model)
  {
    $model->id = $this->getBaseModelId($model);
    $model->save();
  }

  /**
   * Get the primary key for the base model.
   *
   * @param  object $model Base model
   * @return int           Base model id
   */
  public function getBaseModelId(&$model)
  {
    $method = 'get' . $this->getModelName($model) .'PrimaryKey';
    if (method_exists($this, $method))
    {
      return $this->{$method}($model);
    }
  }

  /**
   * Check denied combinations inside a field.
   *
   * @param  string  $model Model name
   * @param  array   $field Field data from ISIS database schema
   * @return boolean        True if has a denied combination, false otherwise 
   * @todo                  Wildcard support
   * @todo                  Test
   */
  public function hasDeniedCombinations($model, $field)
  {
    foreach ($this->isis->getDeniedCombinations($field) as $combination)
    {
      $has = true;

      foreach ($combination as $item)
      {
        foreach (new IsisRowIterator($this->isis, $field) as $row)
        {
          if (!$this->hasDeniedItem($field, $item, $row))
          {
            $has = false;
            break 2;
          }
        }
      }

      if ($has)
      {
        $denied[$row] = $combination;
      }
    }

    if (isset($denied))
    {
      foreach ($denied as $row => $combination)
      {
        $combination = implode(',', $combination);
        $this->log("Found denied combination for row $row: $combination");
      }

      return true;
    }

    return false;
  }

  /**
   * Check if a field has a denied item.
   *
   * @param  array   $field Field data from ISIS database schema
   * @param  string  $item  Item code
   * @param  int     $row   Row number
   * @return boolean        True if has a denied combination, false otherwise 
   * @todo                  Wildcard
   * @todo                  Test
   */
  public function hasDeniedItem($field, $item, $row)
  {
    // Default condition: presence requirement for an item.
    $condition = true;

    // Check if item has the negation (absence) requirement for an item.
    if (substr($item, 0, 1) == '!')
    {
      $item      = substr($item, 1);
      $condition = false;
    }

    // Check if the item exists.
    $has = $this->isis->hasItem($field, $item, $row);

    if ($condition == true)
    {
      // If the condition is true, a denied combination won't be fulfilled if
      // the item is not present.
      if (!$has)
      {
        return false;
      }
    }
    else
    {
      // If the condition is false, a denied combination won't be fulfilled if
      // the item is present.
      if ($has)
      {
        return false;
      }
    }

    return true;
  }
}