<?php

namespace CoffeeScript;

Init::init();

/**
 * Lexical scope manager.
 */
class Scope
{
  static $root = NULL;

  public $shared = FALSE;
  private $has_assignments = FALSE;

  function __construct($parent, $expressions, $method)
  {
    $this->parent = $parent;
    $this->expressions = $expressions;
    $this->method = $method;

    $this->variables = array(
      array('name' => 'arguments', 'type' => 'arguments')
    );

    $this->positions = array();

    if ( ! $this->parent)
    {
      self::$root = $this;
    }
  }

  function add($name, $type, $immediate = FALSE)
  {
    $name = ''.$name;

    if ($this->shared && ! $immediate)
    {
      return $this->parent->add($name, $type, $immediate);
    }

    if (isset($this->positions[$name]))
    {
      $this->variables[$this->positions[$name]]['type'] = $type;
    }
    else
    {
      $this->variables[] = array('name' => $name, 'type' => $type);
      $this->positions[$name] = count($this->variables) - 1;
    }
  }

  function assign($name, $value)
  {
    $this->add($name, array('value' => $value, 'assigned' => TRUE), TRUE);
    $this->has_assignments = TRUE;
  }

  function assigned_variables()
  {
    $tmp = array();

    foreach ($this->variables as $v)
    {
      $type = $v['type'];

      if (is_array($type) && isset($type['assigned']) && $type['assigned'])
      {
        $tmp[] = "{$v['name']} = {$type['value']}";
      }
    }

    return $tmp;
  }

  function check($name, $immediate = FALSE)
  {
    $name = ''.$name;

    $found = !! $this->type($name);

    if ($found || $immediate)
    {
      return $found;
    }

    return $this->parent ? $this->parent->check($name) : FALSE;
  }

  function declared_variables()
  {
    $real_vars = array();
    $temp_vars = array();

    foreach ($this->variables as $v)
    {
      if ($v['type'] === 'var')
      {
        if ($v['name']{0} === '_')
        {
          $temp_vars[] = $v['name'];
        }
        else
        {
          $real_vars[] = $v['name'];
        }
      }
    }

    asort($real_vars);
    asort($temp_vars);

    return array_merge($real_vars, $temp_vars);
  }

  function find($name, $options = array())
  {
    if ($this->check($name, $options))
    {
      return TRUE;
    }

    $this->add($name, 'var');

    return FALSE;
  }

  function free_variable($name, $reserve = TRUE)
  {
    $index = 0;

    while ($this->check(($temp = $this->temporary($name, $index))))
    {
      $index++;
    }

    if ($reserve)
    {
      $this->add($temp, 'var', TRUE);
    }

    return $temp;
  }

  function has_assignments()
  {
    return $this->has_assignments;
  }

  function has_declarations()
  {
    return !! count($this->declared_variables());
  }

  function parameter($name)
  {
    if ($this->shared && $this->parent->check($name, TRUE))
    {
      return;
    }

    $this->add($name, 'param');
  }

  function temporary($name, $index)
  {
    if (strlen($name) > 1)
    {
      return '_'.$name.($index > 1 ? $index - 1 : '');
    }
    else
    {
      $val = strval(base_convert($index + intval($name, 36), 10, 36));
      $val = preg_replace('/\d/', 'a', $val);

      return '_'.$val;
    }
  }

  function type($name)
  {
    foreach ($this->variables as $v)
    {
      if ($v['name'] === $name)
      {
        return $v['type'];
      }
    }

    return NULL;
  }
}

?>
