<?php


mb_internal_encoding("utf-8");
error_reporting(-1);


class PrettyPrinter
{
	private $head = [
					"col0" => "№",
					"col1" => "департамент",
					"col2" => "Σрабов",
					"col3" => "Σтугриков",
					"col4" => "Σкофе",
					"col5" => "Σстраниц",
					"col6" => "тугриков/страниц"
	             ];
	private $foot = [
					"col0" => " ",
					"col1" => "Итого",
					"col2" => NULL,
					"col3" => NULL,
					"col4" => NULL,
					"col5" => NULL,
					"col6" => NULL
	             ];			 
	private $trail = [];
	private $padder;
	
	private $prettyData = [];
	private $printableResult;
	
	public function __construct(array $departments) 
	{
	    $this->setPadder();
	    $this->setHeadTrials();
	    $inputArr= [];
		foreach($departments as $department) {
			$input["col1"] = $department->getDepName(); 
			$input["col2"] = $department->getEmployeesNum();
			$input["col3"] = $department->getTotalSalary();	
			$input["col4"] = $department->getTotalCoffee();
			$input["col5"] = $department->getTotalPages();
			$input["col6"] = number_format($department->getSalaryPagesRatio(), 3);
			
			array_push($inputArr, $input);
	   	}
        $enum = range(1, count($inputArr));	
		for($i = 0; $i < count($inputArr); $i++) {
		    $inputArr[$i] = array_merge(["№" => $enum[$i]], $inputArr[$i]);
            $this->fillPrettyData($inputArr[$i]);
		}
		
		$this->setPrintableHeader();
		$this->initFooter($inputArr);
		$this->setPrintableFoot();
		foreach($this->prettyData as $row) {
		    $this->setPrintableResult($row);
		}
	}
	
	private function setPrintableHeader()
	{
	    foreach(array_values($this->head) as $header) {
	        $this->printableResult .= (string)($header);
			$this->printableResult .= " ";
		}
		$this->printableResult .= ("\n" . str_repeat("-", $this->padder) . "\n");
	}
	
	private function setHeadTrials()
	{
	    foreach($this->head as $col=>$str) {
			$this->trail[$col] = mb_strlen($str);
		}							 
	}
	
	private function setPadder()
	{
	    $func = function($value) {
		    return mb_strlen((string)$value);
	    };
	    $this->padder = array_sum(array_map($func, array_values($this->head))) +
				                  count(array_values($this->head)) - 1;
	}
	
	private function fillPrettyData(array $arr)
	{
	    $func = function($value) {
		    return mb_strlen((string)$value);
	    };
		$dataLens = [];
		$temp = array_combine(array_values($this->trail),
							 array_map($func, array_values($arr)));
		
		foreach($temp as $len1=>$len2) {
			$len = abs($len1 - $len2);
			$data = str_repeat(" ", $len);
			array_push($dataLens, $data);
		}   
		$prettyChunk = array_combine(array_values($arr),
									$dataLens);	
		
		array_push($this->prettyData, $prettyChunk);
	}
	
	private function setPrintableResult(array $arr)
	{
	    foreach($arr as $data=>$padding) {
		    $res = (string)$data . (string)$padding;
			$this->printableResult .= $res;
			$this->printableResult .= " ";
		}
		$this->printableResult .= ("\n" . str_repeat("-", $this->padder) . "\n");
	}
	
	public function __toString()
	{
	    return $this->printableResult;
	}
	
	private function initFooter(array $arr)
	{
	    $allWorkers = 0;
		$allCoffee = 0;
		$allSalary = 0;
		$allPages = 0;
		$allAvg = [];
		
		foreach($arr as $subarr) {
		    $cur_vals = array_values($subarr);
			$allWorkers += $cur_vals[2];
			$allSalary += $cur_vals[3];
			$allCoffee += $cur_vals[4];
			$allPages += $cur_vals[5];
			array_push($allAvg, $cur_vals[6]);
		}
		
		$this->foot["col2"] = $allWorkers;
		$this->foot["col3"] = $allSalary;
		$this->foot["col4"] = $allCoffee;
		$this->foot["col5"] = $allPages;
		$this->foot["col6"] = array_sum($allAvg) / count($allAvg);
	}
	
	private function setPrintableFoot()
	{
	    $this->fillPrettyData($this->foot);
	}
}


abstract class Employee
{
    private $salary;
	private $rank;
	private $coffeeAmt;
	private $pages;
	private $isBoss;
	
	public function __construct($salary, $rank, $coffeeAmt, $pages, $boss=false)
	{
	    $this->salary = $salary;
		$this->rank = $rank;
		$this->coffeeAmt = $coffeeAmt;
		$this->pages = $pages;
		$this->isBoss = $boss;
	}	
	
	public function getSalary()
	{
	    if ($this->rank === 1) {
		    $salary = $this->salary;
		} elseif($this->rank === 2) {
		    $salary = $this->salary * 1.25;
		} elseif($this->rank === 3) {
		    $salary = $this->salary * 1.5;
		} 
		
		if($this->isBoss) {
		    $salary += $salary * 0.5;
		}
		
		return $salary;
	}
	
	public function getCoffee()
	{
	    if($this->isBoss) {
		    return $this->coffeeAmt * 2;
		}
	    return $this->coffeeAmt;
	}
	
	public function getPages()
	{
	    if($this->isBoss) {
		    return 0;
		}
		return $this->pages;
	}
	
	public function isBoss()
	{
	    return $this->isBoss;
	}
	
	public function getRank()
	{
	    return $this->rank;
	}
	
	public function setRank($rank)
	{
	    $this->rank = $rank;
	}
	
	public function setSalary($salary)
	{
	    $this->salary = $salary;
	}
	
	public function setCoffee($amt)
	{
	    $this->coffeeAmt = $amt;
	}
	
	public function setPages($amt)
	{
	    $this->pages = $amt;
	}
	
	public function setBossStatus($status)
	{
	    $this->isBoss = $status;
	}
}


class Manager extends Employee
{
}


class Analyst extends Employee
{
}


class Engineer extends Employee
{
}


class Marketer extends Employee
{
}


class Department
{
    private $totalSalary;
	private $totalCoffeeAmount;
	private $totalWorkDone;
    private $employees = [];
	private $depName;
	
	public function __construct($depName)
	{
	    $this->depName = $depName;
	}
	
    public function addEmployee(Employee $emp)	
	{
	    array_push($this->employees, $emp);
	}
	
	public function fireEmployee(Employee $emp)
	{
	    if(in_array($emp, $this->employees)) {
            $key = array_search($emp, $this->employees);
			unset($this->employees[$key]);
        }
    }
	
	public function getTotalSalary() 
	{
	    $this->totalSalary = 0;
	    foreach($this->employees as $employee) {
	      	$this->totalSalary += $employee->getSalary();
		}
		return $this->totalSalary;
	}
	
	public function getTotalPages()
	{
	    $this->totalWorkDone = 0;
	    foreach($this->employees as $employee) {
		    $this->totalWorkDone += $employee->getPages();
		}
	    return $this->totalWorkDone;
	}
		
    public function getTotalCoffee() 	
	{
	    $this->totalCoffeeAmount = 0;
	    foreach($this->employees as $employee) {
		    $this->totalCoffeeAmount += $employee->getCoffee();
		}
	    return $this->totalCoffeeAmount;
	}
	
	public function getEmployeesNum()
	{
	    return count($this->employees);
	}
	
	public function getSalaryPagesRatio()
	{
	    if($this->totalWorkDone !== 0) {
	        return $this->totalSalary / $this->totalWorkDone;
		}
		exit("Division by zero");
	}
	
	public function getDepName()
	{
	    return $this->depName;
	}
	
	public function getEmployees()
	{
	    return $this->employees;
	}
	
	public function __clone()
	{
	    $clonedEmps = [];
	    foreach($this->employees as $emp) {
		    $clonedEmps[] = clone $emp;
		}
		$this->employees = $clonedEmps;
	    $this->totalSalary = 0;
	    $this->totalCoffeeAmount = 0;
	    $this->totalWorkDone = 0;
    }
}


function createEmployee(Department $dep, $cls, $rank, $amt, $boss=false)
{
    $empSalarys = ["Manager" => 500, "Marketer" => 400, "Analyst" => 800, "Engineer" => 200];
    $empCoffee = ["Manager" => 20, "Marketer" => 15, "Analyst" => 50, "Engineer" => 5];
    $empPages = ["Manager" => 200, "Marketer" => 150, "Analyst" => 5, "Engineer" => 50];

    for($i = 0; $i < $amt; $i++)     {
       $emp = new $cls($empSalarys[$cls], $rank, $empCoffee[$cls], $empPages[$cls], $boss);
       $dep->addEmployee($emp);
    }
}


function cmpRanks(Employee $emp1, Employee $emp2) 
{
    if($emp1->getRank() === $emp2->getRank()) {
	    return 0;
	}
	return ($emp1->getRank() < $emp2->getRank()) ? -1 : 1;
}

function tryModel1(Department $dep)
{
    $engineers = [];
	foreach($dep->getEmployees() as $emp) {
	    if(get_class($emp) === "Engineer" && $emp->isBoss() !== true) {
		    $engineers[] = $emp;
		}	
	}	 
	usort($engineers, "cmpRanks");
	$fireAmt = round(count($engineers) * 0.4);
    $firedEngineers = array_slice($engineers, 0, $fireAmt);	
	foreach($firedEngineers as $engineer) {
	    $dep->fireEmployee($engineer);
	}
}

function tryModel2(Department $dep)
{
    $analysts = [];
	foreach($dep->getEmployees() as $emp) {
	    if(get_class($emp) === "Analyst") {
	        $emp->setSalary(1100);
	        $emp->setCoffee(75);
			$analysts[] = $emp;
		}
	}
	if($analysts) {
	    usort($analysts, "cmpRanks");
	    foreach($dep->getEmployees() as $emp) {
	        if($emp->isBoss() && get_class($emp) !== "Analyst") {
	            $emp->setBossStatus(false);
	            $newBoss = max($analysts);
				$newBoss->setBossStatus(true);
	        }
	    }
	}
}	

function tryModel3(Department $dep) 
{
	$firstRankMan = [];
	$secondRankMan = [];
	foreach($dep->getEmployees() as $emp) {
	    if(get_class($emp) === "Manager" && $emp->getRank() === 1 && $emp->isBoss() === false) {
	        $firstRankMan[] = $emp;
		} elseif(get_class($emp) === "Manager" && $emp->getRank() === 2 && $emp->isBoss() === false) {
		    $secondRankMan[] = $emp;
		} 
	}
	$firstRankLim = array_slice($firstRankMan, 0, round(count($firstRankMan) * 0.5));
	$secondRankLim = array_slice($secondRankMan, 0, round(count($secondRankMan) * 0.5));
    foreach($firstRankLim as $manager) {
	    $manager->setRank(2);
	}
	foreach($secondRankLim as $manager) {
	    $manager->setRank(3);
	}
}


$purchaseDep = new Department("закупок");
createEmployee($purchaseDep, "Manager", 1, 9);
createEmployee($purchaseDep, "Manager", 2, 3);
createEmployee($purchaseDep, "Manager", 3, 2);
createEmployee($purchaseDep, "Marketer", 1, 2);
createEmployee($purchaseDep, "Manager", 2, 1, $boss=true);

$salesDep = new Department("продаж");
createEmployee($salesDep, "Manager", 1, 12);
createEmployee($salesDep, "Marketer", 1, 6);
createEmployee($salesDep, "Analyst", 1, 3);
createEmployee($salesDep, "Analyst", 2, 2);
createEmployee($salesDep, "Marketer", 2, 1, $boss=true);

$adsDep = new Department("рекламы");
createEmployee($adsDep, "Marketer", 1, 15);
createEmployee($adsDep, "Marketer", 2, 10);
createEmployee($adsDep, "Manager", 1, 8);
createEmployee($adsDep, "Engineer", 1, 2);
createEmployee($adsDep, "Marketer", 3, 1, $boss=true);

$logisticsDep = new Department("логистики");
createEmployee($logisticsDep, "Manager", 1, 13);
createEmployee($logisticsDep, "Manager", 2, 5);
createEmployee($logisticsDep, "Engineer", 1, 5);
createEmployee($logisticsDep, "Manager", 1, 1, $boss=true);

$allDeps = [$purchaseDep, $salesDep, $adsDep, $logisticsDep];
$clonedDeps1 = [];
$clonedDeps2 = [];
$clonedDeps3 = [];

foreach($allDeps as $department) {
    $clonedDeps1[] = clone $department;
	$clonedDeps2[] = clone $department;
	$clonedDeps3[] = clone $department;
}

function tryCrisisModel(array $clonedDeps, callable $modelFunc, $description)
{ 
    array_walk($clonedDeps, $modelFunc);
	$result = new PrettyPrinter($clonedDeps);
	echo "\n\n";
    echo "Применение атнкризисной модели: {$description}\n\n";
	echo $result;
}

$baseData = new PrettyPrinter($allDeps);
echo "Начальные данные.\n\n";
echo $baseData;

tryCrisisModel($clonedDeps1, "tryModel1", "увольнение 40% инженеров");
tryCrisisModel($clonedDeps2, "tryModel2", "повышение условий труда аналитиков");
tryCrisisModel($clonedDeps3, "tryModel3", "повышение 1 и 2 рангов у 50% менеджеров");

?>