<?php

class check {
  // do we have true global vars or just GETSET functions?
  // Used to filter out get/set global functions to fake vars...
  const GETSET = 1;

  private static $_extension = null;

  private static $_werror = false;

  static function get_extension() {
    if (self::$_extension === null) {
      foreach(get_included_files() as $f) {
	$module_name = preg_filter('/.*\/([^\/]+)_runme\.php$/', '\1', $f);
	if ($module_name !== null) break;
      }
      if ($module_name === null) {
	print("Failed to determine module name from get_included_files()\n");
	exit(1);
      }
      self::$_extension = new ReflectionExtension($module_name);
    }
    return self::$_extension;
  }

  static function werror($v) {
    self::$_werror = $v;
  }

  static function classname($string,$object) {
    if (!is_object($object))
      return check::fail("The second argument is a " . gettype($object) . ", not an object.");
    if (strtolower($string)!=strtolower($classname=get_class($object))) return check::fail("Object: \$object is of class %s not class %s",$classname,$string);
    return TRUE;
  }

  static function classmethods($classname,$methods) {
    if (is_object($classname)) $classname=get_class($classname);
    $classmethods=array_flip(get_class_methods($classname));
    $message=NULL;
    $missing=array();
    $extra=array();
    foreach($methods as $method) {
      if (! isset($classmethods[$method])) $missing[]=$method;
      else unset($classmethods[$method]);
    }
    $extra=array_keys($classmethods);
    if ($missing) $message[]="does not have these methods:\n  ".join(",",$missing);
    if ($message) {
      return check::fail("Class %s %s\nFull class list:\n  %s\n",$classname,join("\nbut ",$message),join("\n  ",get_class_methods($classname)));
    }
    if ($extra) $message[]="Class ".$classname." has these extra methods:\n  ".join(",",$extra);
    if ($message) return check::warn(join("\n  ",$message));
    return TRUE;
  }

  static function set($var,$value) {
    $func=$var."_set";
    if (self::GETSET) $func($value);
    else $_GLOBALS[$var]=$value;
  }

  static function get($var) {
    $func=$var."_get";
    if (self::GETSET) return $func();
    else return $_GLOBALS[$var];
  }

  static function is_a($a,$b) {
    if (is_object($a)) $a=strtolower(get_class($a));
    if (is_object($b)) $a=strtolower(get_class($b));
    $parents=array();
    $c=$a;
    while($c!=$b && $c) {
      $parents[]=$c;
      $c=strtolower(get_parent_class($c));
    }
    if ($c!=$b) return check::fail("Class $a does not inherit from class $b\nHierarchy:\n  %s\n",join("\n  ",$parents));
    return TRUE;
  }

  static function classparent($a,$b) {
    if (is_object($a)) $a=get_class($a);
    if (is_object($b)) $a=get_class($b);
    $parent=get_parent_class($a);

    if ($parent!=$b) return check::fail("Class $a parent not actually $b but $parent");
    return TRUE;
  }

  static function classes($classes) {
    if (! is_array($classes)) $classes=array($classes);
    $message=array();
    $missing=array();
    $extra = array_flip(array_filter(self::get_extension()->getClassNames(),
				     function ($e) { return !preg_match('/^SWIG\\\\/', $e); }));
    foreach($classes as $class) {
      if (! class_exists($class)) $missing[]=$class;
      else unset($extra[$class]);
    }
    if ($missing) $message[]=sprintf("Classes missing: %s",join(",",$missing));
    if ($message) return check::fail(join("\n  ",$message));
    if ($extra) $message[]=sprintf("These extra classes are defined: %s",join(",",array_keys($extra)));
    if ($message) return check::warn(join("\n  ",$message));
    return TRUE;    
  }

  static function functions($functions) {
    if (! is_array($functions)) $functions=array($functions);
    $message=array();
    $missing=array();
    $extra = self::get_extension()->getFunctions();
    foreach ($functions as $func) {
      if (! function_exists($func)) $missing[]=$func;
      else unset($extra[$func]);
    }
    $extra = array_filter(array_keys($extra),
			  function ($e) { return !preg_match('/_[gs]et$|^is_python_/', $e); });
    if ($missing) $message[]=sprintf("Functions missing: %s",join(",",$missing));
    if ($message) return check::fail(join("\n  ",$message));
    if ($extra) $message[]=sprintf("These extra functions are defined: %s",join(",",$extra));
    if ($message) return check::warn(join("\n  ",$message));
    return TRUE;    
  }

  static function globals($globals) {
    if (! is_array($globals)) $globals=array($globals);
    $message=array();
    $missing=array();
    $extra = self::get_extension()->getFunctions();
    foreach ($globals as $glob) {
      if (! function_exists($glob . "_get") && ! function_exists($glob . "_set")) $missing[]=$glob;
      else {
        unset($extra[$glob . "_get"]);
        unset($extra[$glob . "_set"]);
      }
    }
    $extra = array_filter(array_keys($extra),
			  function ($e) { return preg_match('/_[gs]et$/', $e); });
    if ($missing) $message[]=sprintf("Globals missing: %s",join(",",$missing));
    if ($message) return check::fail(join("\n  ",$message));
    if ($extra) $message[]=sprintf("These extra globals are defined: %s",join(",",$extra));
    if ($message) return check::warn(join("\n  ",$message));
    return TRUE;    

  }

  static function constants($constants) {
    if (! is_array($constants)) $constants=array($constants);
    $message=array();
    $missing=array();
    $extra = self::get_extension()->getConstants();
    unset($extra['swig_runtime_data_type_pointer']);
    foreach($constants as $constant) {
      if (! defined($constant)) $missing[]=$constant;
      else unset($extra[$constant]);
    }
    if ($missing) $message[]=sprintf("Constants missing: %s",join(",",$missing));
    if ($message) return check::fail(join("\n  ",$message));
    if ($extra) $message[]=sprintf("These extra constants are defined: %s",join(",",array_keys($extra)));
    if ($message) return check::warn(join("\n  ",$message));
    return TRUE;
  }

  static function functionref($a,$type,$message) {
    if (! preg_match("/^_[a-f0-9]+$type$/i", $a))
      return check::fail($message);
    return TRUE;
  }

  static function equal($a,$b,$message=null) {
    if (! ($a===$b)) return check::fail_($message, "'$a'!=='$b'");
    return TRUE;
  }

  static function equivalent($a,$b,$message=null) {
    if (! ($a==$b)) return check::fail_($message, "'$a'!='$b'");
    return TRUE;
  }

  static function str_contains($a,$b,$message=null) {
    # Use strpos as PHP function str_contains requires PHP 8
    return check::equal(strpos($a,$b)!==false,true,$message);
  }

  static function isnull($a,$message=null) {
    return check::equal($a,NULL,$message);
  }

  private static function fail_($message, $pattern, ...$args) {
    $bt = debug_backtrace(0);
    $bt = $bt[array_key_last($bt)-1];
    print("{$bt['file']}:{$bt['line']}: Failed on: ");
    if ($message !== NULL) print("$message: ");
    print(sprintf($pattern, ...$args) . "\n");
    exit(1);
  }

  static function fail(...$args) {
    check::fail_(null, ...$args);
  }

  static function warn($pattern, ...$args) {
    if (self::$_werror) self::fail($pattern, ...$args);
    print("Warning on: " . sprintf($pattern, ...$args) . "\n");
    return FALSE;
  }

  static function done() {
#    print $_SERVER[argv][0]." ok\n";
  }
}
