<?php

namespace Rights;

class Condition
{
	protected $mode = "and";
	protected $list = [];
	protected $subs = [];
	
	
	public function __construct($mode = "and") {$this->mode = $mode;}
	
	public function Add($field, $value, $table = null) {
		$this->list[] = ["field" => $field, "value" => $value, "table" => $table];
		return $this;
	}
	
	public function Sub(Condition $condition) {
		$this->subs[] = $condition;
		return $this;
	}
	
	private function checkValue($is, $should) {
		if (is_array($is)) {
			if (array_key_exists("ID", $is)) {
				return $should==$is["ID"];
			} else {
				foreach ($is as $entry) {
					if ($this->checkValue($entry, $should)) {
						return true;
				} }
				return false;
			}
			return in_array($should, $is);
		} else {
			return $should==$is;
	} }
	
	public function CheckInput($json) {
		if ($this->mode=="and") {
			foreach ($this->list as $cond) {
	  		if (!$this->checkValue($json[$cond["field"]], $cond["value"])) {
	  			return false;
	  	} }
	  	foreach ($this->subs as $sub) {
	  		if (!$sub->CheckInput($json)) {
	  			return false;
	  	} }
	  	return true;
		} else if ($this->mode=="or") {
			foreach ($this->list as $cond) {
  			if ($this->checkValue($json[$cond["field"]], $cond["value"])) {
  				return true;
	  	} }
	  	foreach ($this->subs as $sub) {
	  		if ($sub->CheckInput($json)) {
	  			return true;
	  	} }
	  	return false;
		}
		return false; // Failsafe if mode was misconfigured
	}
	
	public function GetFilterCondition() {
		$conditionList = new \Filter\ConditionList($this->mode);
		foreach ($this->list as $cond) {
			$conditionList->Add(new \Filter\Condition($cond["field"], "=", $cond["value"], $cond["table"]));
		}
		foreach ($this->subs as $sub) {
			$conditionList->Add($sub->GetFilterCondition());
		}
		return $conditionList;
	}
	
	public function Export() {
		if (sizeof($this->list) + sizeof($this->subs) == 1) {
			return empty($this->list) ? $this->subs[0]->Export() : $this->list[0];
		}
		$ret = [
			"mode" => $this->mode,
			"conditions" => $this->list
		];
		foreach ($this->subs as &$condition) {
			$ret["conditions"][] = $condition->Export();
		}
		return $ret;
	}
	
	public static function Make($mode = "and") {
		return (new self($mode));
	}
}

class Restriction
{
	protected $condition = null;
	protected $limits = [];
		
	public function AddCondition(Condition $condition) {
		if (is_null($this->condition)) {
			$this->condition = Condition::Make("or");
		}
		$this->condition->Sub($condition);
		return $this;
	}
	
	public function AddLimits($fields) {
		if (is_array($fields)) {
  		foreach ($fields as $field) {
  			$this->limits[] = $field;
  		}
		} else {
			$this->limits[] = $fields;
		}
  	return $this;
	}
	
	public function CheckInput($id = null, $json = []) {
		// Check limits
		if (!empty($json) && !empty($this->limits)) {
			foreach ($json as $field => $value) {
				if (!in_array($field, $this->limits)) {
					return false;
		} } }
		
		// Check condition
		return is_null($this->condition) || $this->condition->CheckInput(array_merge(["ID" => $id], $json));
	}
	
	public function GetFilterCondition() {
		return is_null($this->condition) ? null : $this->condition->GetFilterCondition();
	}
	
	public function Export() {
		return [
			"limits" => $this->limits,
			"condition" => is_null($this->condition) ? null : $this->condition->Export()
		];
	}
}

abstract class Right
{
	protected $requiredRight = null;
  protected $restriction = null;
  protected $allow = [];
  
  public function __construct($requiredRight = null) {
  	$this->requiredRight = $requiredRight;
  }
  
  public function IsApplicable() {
  	return is_null($this->requiredRight) || \Login::HasRight($this->requiredRight);
  }
  
  public function IsAdmin() {
  	return sizeof($this->allow)==6 && is_null($this->restriction);
  }
  
  public function Require(Condition $condition) {
  	if (is_null($this->restriction)) {
  		$this->restriction = new Restriction();
  	}
  	$this->restriction->AddCondition($condition);
  	return $this;
  }
  
  public function Limit($fields) {
  	if (is_null($this->restriction)) {
  		$this->restriction = new Restriction();
  	}
  	$this->restriction->AddLimits($fields);
  	return $this;
  }
  
  public function GetAllowed() {
  	return $this->allow;
  }
  
  public function GetRestriction() {
  	return $this->restriction;
  }

  public static function Make($requiredRight = null) {
  	$class = get_called_class();
  	return (new $class($requiredRight));
  }
}

class Main extends Right
{
	public static function AdminAllow() {
		return ["get", "update", "insert", "remove", "upload", "erase"];
	}
	
	public function Allow($allow = "readonly") {
    if (is_array($allow)) {
    	$this->allow = array_intersect($allow, self::AdminAllow());
    } else {
	    switch ($allow) {
	      case "admin":
	        $this->allow = self::AdminAllow();
	        break;
	      case "nodelete":
	        $this->allow = ["get", "update", "insert", "upload"];
	        break;
	      case "justdata":
	        $this->allow = ["get", "update", "insert", "remove"];
	        break;
	      case "update":
	        $this->allow = ["get", "update"];
	        break;
	      case "readonly":
	        $this->allow = ["get"];
	        break;
	      default:
	        $this->allow = [];
	  } }
    return $this;
  }
}

class Link extends Right
{
	public static function AdminAllow() {
		return ["set", "update", "unset"];
	}
	
	public function Allow($allow = "readonly") {
		if (is_array($allow)) {
    	$this->allow = array_intersect($allow, self::AdminAllow());
    } else {
	    switch ($allow) {
	      case "admin":
	        $this->allow = self::AdminAllow();
	        break;
	      case "nodelete":
	        $this->allow = ["set", "update"];
	        break;
	      case "justadd":
	        $this->allow = ["set"];
	        break;
	      case "update":
	        $this->allow = ["update"];
	        break;
	      default:
	        $this->allow = [];
	  } }
    return $this;
  }
}

abstract class Rights {
	protected function __construct($adminAllow) {
		$this->actions = array_fill_keys($adminAllow, false);
	}
	
	public function Add($right) {
		// If user already has admin rights or does not posess this right, skip
		if ($right->IsApplicable()) {
			$restriction = $right->GetRestriction();
			// Parse the right for every action
			foreach ($right->GetAllowed() as $action) {
				if (is_null($restriction)) {
					// No restriction in the added right, so just overwrite current restrictions
					$this->actions[$action] = true;
				} else if (is_array($this->actions[$action])) {
					// Restrictions applied before (or nothing was allowed) and in the new one, so add new restrictions
					$this->actions[$action][] = $restriction;
				} else if ($this->actions[$action]===false) {
					// Not allowed before, but now restrictions apply
					$this->actions[$action] = [$restriction];
		} }	}
		return $this;
	}
	
	public function CheckInput($action, $id = null, $json = []) {
		if (!is_array($this->actions[$action])) {
		  return $this->actions[$action];
		}
		
		// At this point, some but not everything is allowed for this user and this action, so check more carefully
		foreach ($this->actions[$action] as $restriction) {
			if ($restriction->CheckInput($id, $json)) {
				return true;
		}	}
		return false;
	}
	
	public function RestrictFilter() {
		if (is_array($this->actions["get"])) {
			$restrictions = new \Filter\ConditionList("or");
			foreach ($this->actions["get"] as $restriction) {
				$filterCondition = $restriction->GetFilterCondition();
				if (!is_null($filterCondition)) {
					$restrictions->Add($filterCondition);
				}
				
			}
			\Filter\Filter::Restrict($restrictions);
			return true;
		} else {
			return $this->actions["get"];
	} }
	
	public function Export() {
		$ret = [];
		foreach ($this->actions as $action => $restrictions) {
			if (is_array($restrictions)) {
				$ret[$action] = [];
				foreach ($restrictions as $restriction) {
					$ret[$action][] = $restriction->Export();
				}
			} else {
				$ret[$action] = $restrictions;
		} }
		return $ret;
	}
}

class MainRights extends Rights
{
	
	// false == not allowed
	// array with refs to restrictions [a ... n] == restrictions apply
	// true == allowed without restrictions
	protected $actions = [];
	
	public static function Make() {
  	return (new self(Main::AdminAllow()))->Add(Main::Make("ADMIN")->Allow("admin"));
  }
}



class LinkRights extends Rights
{
	// false == not allowed
	// array with refs to restrictions [a ... n] == restrictions apply
	// true == allowed without restrictions
	protected $actions = [];
	
	public static function Make() {
  	return (new self(Link::AdminAllow()))->Add(Link::Make("ADMIN")->Allow("admin"));
  }
}