<?php

error_reporting(-1);
/* Сотрудник */
abstract class Employee
{
	private $rate;//начальный рейт
	private $rateWithRank;//рейт расчитанный с рангом
	private $litresOfCoffee;
	private $pgsOfDocs;
	private $rank;
	private $boss;
	private $litresOfCoffeeWR;//аналогично для кофе
	private $pgsOfDocsWR;//аналогично для доков

	public function __construct(int $rate, int $litresOfCoffee, int $pgsOfDocs, int $rank, bool $boss = false){
		$this->rate = $rate;
		$this->litresOfCoffee = $litresOfCoffee;
		$this->pgsOfDocs = $pgsOfDocs;
		$this->rank = $rank;
		$this->boss = $boss;
		$this->litresOfCoffeeWR = $this->litresOfCoffee;
		$this->pgsOfDocsWR = $this->pgsOfDocs;
		$this->setRateWithRank();
		if ($this->boss) {
			$this->setBossPrivelege();
		}
	}
	/*получить начальные свойства сотрудника*/
	public function getAllProperties(){
		return [$this->rate, $this->litresOfCoffee, $this->pgsOfDocs, $this->rank, $this->boss];
	}
	/*сетеры и гетеры для всех свойств*/
	public function getRate(){
		return $this->rateWithRank;
	}

	public function setRate(int $rate){
		$this->rate = $rate;
		$this->setRateWithRank();
		if ($this->boss) {
			$this->setBossPrivelege();
		}
	}

	public function getCoffee(){
		return $this->litresOfCoffeeWR;
	}

	public function setCoffee(int $litresOfCoffee){
		$this->litresOfCoffee = $litresOfCoffee;
		if ($this->boss) {
			$this->litresOfCoffeeWR = $this->litresOfCoffee * 2;
		} else {
			$this->litresOfCoffeeWR = $this->litresOfCoffee;
		}
	}

	public function getRank(){
		return $this->rank;
	}

	public function setRank(int $rank){
		$this->rank = $rank;
		$this->setRateWithRank();
		if ($this->boss) {
			$this->setBossPrivelege();
		}	
	}

	public function getPages(){
		return $this->pgsOfDocsWR;
	}

	public function setPages(int $pagesOfDocs){
		$this->pgsOfDocs = $pagesOfDocs;
	}

	public function isBoss(){
		return $this->boss;
	}

	public function setBossStatus(bool $bossStatus){
		$this->boss = $bossStatus;
		if ($this->boss) {
			$this->setBossPrivelege();
		} else {
			$this->setRateWithRank();
			$this->litresOfCoffeeWR = $this->litresOfCoffee;
			$this->pgsOfDocsWR = $this->pgsOfDocs;
		}
	}

	private function setRateWithRank(){
		switch ($this->rank) {
			case 1:
				$this->rateWithRank = $this->rate;
				break;
			case 2:
				$this->rateWithRank = $this->rate * 1.25;
				break;
			case 3:
				$this->rateWithRank = $this->rate * 1.50;
				break;
		}
	}

	private function setBossPrivelege(){
		$this->rateWithRank = $this->rateWithRank * 1.50;
		$this->litresOfCoffeeWR = $this->litresOfCoffee * 2;
		$this->pgsOfDocsWR = 0;
	}
}
/* Группа сотрудников хранит уникального сотр-ка в N кол-ве*/
class EmployeeGroup
{
	private $numOfEmployees;
	private $employeeType;

	public function __construct(int $numOfEmployees, Employee $employeeType){
		$this->numOfEmployees = $numOfEmployees;
		$this->employeeType = $employeeType;
	}

	public function getEmployee(){
		return [$this->numOfEmployees, $this->employeeType];
	}

	public function addEmployee($num = 1){
		$this->numOfEmployees += $num;
	}

	public function removeEmployee(){
		if ($this->numOfEmployees > 0) {
			$this->numOfEmployees--;
		}
	}

}
/* Штат департамента хранит группы сотрудников*/
class DepartmentStaff
{
	private $listOfEmployeeTypes;

	public function __construct(EmployeeGroup ...$listOfEmployeeTypes){
		$this->listOfEmployeeTypes = $listOfEmployeeTypes;
	}

	public function getEmployeeTypes(){
		return $this->listOfEmployeeTypes;
	}

	public function addEmployeeType(EmployeeGroup $addedEmployee){
		$this->listOfEmployeeTypes[] = $addedEmployee;
	}

	public function removeEmployeeType(EmployeeGroup $deletedEmployeeType){
		foreach ($this->listOfEmployeeTypes as $key => $employeeType) {
			if ($deletedEmployeeType == $employeeType) {
				unset($this->listOfEmployeeTypes[$key]);
			}
		}
	}
}

class Manager extends Employee
{}
class Marketer extends Employee
{}
class Engineer extends Employee
{}
class Analyst extends Employee
{}
/* Департамент хранит штат и считает всякие штуки */
class Department
{
	private $name;
	private $employees;

	public function __construct(string $name, DepartmentStaff $employees){
		$this->name = $name;
		$this->employees = $employees;
	}

	public function getDepartmentStaff(){
		return $this->employees;
	}

	public function getNumOfEmployees(){
		$numOfEmployees = 0;
		$employees = $this->employees->getEmployeeTypes();
		foreach ($employees as $employee) {
			list($num, $position) = $employee->getEmployee();
			$numOfEmployees += $num;
		}
		return $numOfEmployees;
	}

	public function getSalaryOfEmployees(){
		$salaryOfEmployees = 0;
		$employees = $this->employees->getEmployeeTypes();
		foreach ($employees as $employee) {
			list($num, $position) = $employee->getEmployee();
			$salaryOfEmployees += $position->getRate() * $num;	
		}
		return $salaryOfEmployees;
	}
/* Удаляет сотрудника из штата - одного из группы, или всех(группу разом)*/
	public function fireEmployee(Employee $deletedPosition, $all = false){
		$employees = $this->employees->getEmployeeTypes();
		foreach ($employees as $employee) {
			list($num, $position) = $employee->getEmployee();
				if ($position == $deletedPosition) {
					if ($all) {
						$this->employees->removeEmployeeType($employee);	
					} else {
						$employee->removeEmployee();
						list($num, $position) = $employee->getEmployee();
						if ($num == 0) {
							$this->employees->removeEmployeeType($employee);
						}
					}		
				}
		}	
	}
/* Добавляет сотрудника(группу в штат)*/
	public function addEmployee(int $num, Employee $position){
		$this->employees->addEmployeeType(new EmployeeGroup($num, $position));
	}

	public function getDrunkCoffee(){
		$drunkCoffee = 0;
		$employees = $this->employees->getEmployeeTypes();
		foreach ($employees as $employee) {
			list($num, $position) = $employee->getEmployee();
			$drunkCoffee += $position->getCoffee() * $num;
		}
		return $drunkCoffee;
	}

	public function getPagesOfDocs(){
		$pagesOfDocs = 0;
		$employees = $this->employees->getEmployeeTypes();
		foreach ($employees as $employee) {
			list($num, $position) = $employee->getEmployee();
			$pagesOfDocs += $position->getPages() * $num;
		}
		return $pagesOfDocs;
	}

	public function getTugricsPerPage(){
		return round(($this->getSalaryOfEmployees() / $this->getPagesOfDocs()), 1);
	}

	public function getNameOfDept(){
		return $this->name;
	}

}

class Company
{
	private $departments;
	private $name;

	public function __construct(string $name, Department ...$departments)
	{
		$this->name = $name;
		$this->departments = $departments;
	}

	public function getNameOfCompany(){
		return $this->name;
	}
	public function getDepartments(){
		return $this->departments;
	}
}

function padRight($string, $widthOfCol){
	$lengthOfString = mb_strlen($string);
	if ($lengthOfString < $widthOfCol) {
		$formattedString = $string . str_repeat(" ", $widthOfCol - $lengthOfString);
		return $formattedString;
	}
}

function padLeft($string, $widthOfCol){
	$lengthOfString = mb_strlen($string);
	if ($lengthOfString < $widthOfCol) {
		$formattedString = str_repeat(" ", $widthOfCol - $lengthOfString) . $string;
		return $formattedString;
	}
}
//существующие виды сотрудников в компании
$manager1 = new Manager(500, 20, 200, 1);
$manager2 = new Manager(500, 20, 200, 2);
$manager2boss = new Manager(500, 20, 200, 2, true);
$manager3 = new Manager(500, 20, 200, 3);
$marketer1 = new Marketer(400, 15, 150, 1);
$analyst1 = new Analyst(800, 50, 5, 1);
$analyst2 = new Analyst(800, 50, 5, 2);
$marketer2boss = new Marketer(400, 15, 150, 2, true);
$marketer2 = new Marketer(400, 15, 150, 2);
$engineer1 = new Engineer(200, 5, 50, 1);
$marketer3boss = new Marketer(400, 15, 150, 3, true);
$manager1boss = new Manager(500, 21, 200, 1, true);

//Группы сотрудников одного вида по департаментам
$procurementGroup1 = new EmployeeGroup(9, $manager1);
$procurementGroup2 = new EmployeeGroup(3, $manager2);
$procurementGroup3 = new EmployeeGroup(2, $manager3);
$procurementGroup4 = new EmployeeGroup(2, $marketer1);
$procurementGroup5 = new EmployeeGroup(1, $manager2boss);

$salesGroup1 = new EmployeeGroup(12, $manager1);
$salesGroup2 = new EmployeeGroup(6, $marketer1);
$salesGroup3 = new EmployeeGroup(3, $analyst1);
$salesGroup4 = new EmployeeGroup(2, $analyst2);
$salesGroup5 = new EmployeeGroup(1, $marketer2boss);

$advGroup1 = new EmployeeGroup(15, $marketer1);
$advGroup2 = new EmployeeGroup(10, $marketer2);
$advGroup3 = new EmployeeGroup(8, $manager1);
$advGroup4 = new EmployeeGroup(2, $engineer1);
$advGroup5 = new EmployeeGroup(1, $marketer3boss);

$logstcGroup1 = new EmployeeGroup(13, $manager1);
$logstcGroup2 = new EmployeeGroup(5, $manager2);
$logstcGroup3 = new EmployeeGroup(5, $engineer1);
$logstcGroup4 = new EmployeeGroup(1, $manager1boss);

//Штаты сотрудников по департаментам
$procurementStaff = new DepartmentStaff($procurementGroup1, $procurementGroup2, $procurementGroup3, $procurementGroup4, $procurementGroup5);
$salesStaff = new DepartmentStaff($salesGroup1, $salesGroup2, $salesGroup3, $salesGroup4, $salesGroup5);
$advStaff = new DepartmentStaff($advGroup1, $advGroup2, $advGroup3, $advGroup4, $advGroup5);
$logstcStaff = new DepartmentStaff($logstcGroup1, $logstcGroup2, $logstcGroup3, $logstcGroup4);

//Департаменты
$procurementDep = new Department("Закупок", $procurementStaff);
$salesDep = new Department("Продаж", $salesStaff);
$advDep = new Department("Рекламы", $advStaff);
$logstcDep = new Department("Логистики", $logstcStaff);

//Компания
$company = new Company("Вектор", $procurementDep, $salesDep, $advDep, $logstcDep);

//Вывод отчета
function displayReport(Company $company){

	$col1 = 15;
	$col2 = 10;
	$col3 = 10;
	$col4 = 10;
	$col5 = 10;
	$col6 = 12;

	echo padRight("Департамент", $col1) .
	     padLeft("Сотр.", $col2) . 
	     padLeft("Тугр.", $col3) . 
	     padLeft("Кофе", $col4) . 
	     padLeft("Стр.", $col5) . 
	     padLeft("Тугр./стр.", $col6) . "\n\n";

	$allEmployees = 0;
	$allSalary = 0;
	$allCoffee = 0;
	$allPages = 0;
	$allTugPerPgs = 0;

	foreach ($company->getDepartments() as $department) {

		echo padRight($department->getNameOfDept(), $col1) .
		padLeft($department->getNumOfEmployees(), $col2) .
		padLeft($department->getSalaryOfEmployees(), $col3) .
		padLeft($department->getDrunkCoffee(), $col4) .
		padLeft($department->getPagesOfDocs(), $col5) . 
		padLeft($department->getTugricsPerPage(), $col6) . "\n";

		$allEmployees += $department->getNumOfEmployees();
		$allSalary += $department->getSalaryOfEmployees();
		$allCoffee += $department->getDrunkCoffee();
		$allPages += $department->getPagesOfDocs();
		$allTugPerPgs += $department->getTugricsPerPage();
	}

	$numOfDepartments = count($company->getDepartments());

	echo padRight("Среднее", $col1) .
	     padLeft(round(($allEmployees / $numOfDepartments),1), $col2) . 
	     padLeft(round(($allSalary / $numOfDepartments), 1), $col3) . 
	     padLeft(round(($allCoffee / $numOfDepartments), 1), $col4) . 
	     padLeft(round(($allPages / $numOfDepartments), 1), $col5) .
	     padLeft(round(($allTugPerPgs / $numOfDepartments), 1), $col6) . "\n";

	echo padRight("Всего", $col1) .
	     padLeft($allEmployees, $col2) . 
	     padLeft($allSalary, $col3) . 
	     padLeft($allCoffee, $col4) . 
	     padLeft($allPages, $col5) .
	     padLeft($allTugPerPgs, $col6) . "\n\n";
}

//До введения мер

echo "До введения мер:\n";
displayReport($company);

//План 1

foreach ($company->getDepartments() as $department) {
	$sortedEngs = [];
	$numOfEng = 0;
	$staff = $department->getDepartmentStaff();
	$groups = $staff->getEmployeeTypes();
	foreach ($groups as $group) { /*считаем и сохраняем инженеров*/
		list($num, $position) = $group->getEmployee();
		if (is_a($position, "Engineer") and !($position->isBoss())) {
			$numOfEng += $num;
			$sortedEngs[$position->getRank()] = $group;
		}
	}
	$numOfFiredEng =  round($numOfEng * 0.4);
	ksort($sortedEngs); /*отсортируем по рангу*/
	if ($numOfFiredEng > 0) { /*увольняем сколько нужно*/
		while (true) {
			foreach ($sortedEngs as $group) {
				list($num, $position) = $group->getEmployee();
				if (!($position->isBoss())) {
					$department->fireEmployee($position);
					$numOfFiredEng--;
					if ($numOfFiredEng == 0) {
						break 2;		
					}	
				}
			}			
		}
	}
}

echo "План 1:\n";
displayReport($company);

//Вернем начальные значения
$advGroup4->addEmployee();
$logstcGroup3->addEmployee(2);

//План 2

foreach ($company->getDepartments() as $department) {
	$staff = $department->getDepartmentStaff();
	$groups = $staff->getEmployeeTypes();
	foreach ($groups as $key=>$group) { /*Меняем зп и литры кофе аналитикам*/
		list($num, $position) = $group->getEmployee();
		if (is_a($position, "Analyst")) {
			$position->setRate(1100);
			$position->setCoffee(75);
		}
	}
}

$analystBoss = null;
foreach ($company->getDepartments() as $department) {
	$analysts = [];
	$analystWasFinded = false;
	$staff = $department->getDepartmentStaff();
	$groups = $staff->getEmployeeTypes();
	foreach ($groups as $key=>$group) { /*Сохраняем аналитиков*/
		list($num, $position) = $group->getEmployee();
		if (is_a($position, "Analyst")) {
			$analysts[$position->getRank()] = [$position, $group];
			$analystWasFinded = true;
		}
	}
	if ($analystWasFinded){ /*Если анал-ки были текущего босса делаем не боссом*/
		foreach ($groups as $group) {
			list($num, $position) = $group->getEmployee();
			if ($position->isBoss()) {
				$position->setBossStatus(false);	
			}
		}
		krsort($analysts);/*сортируем анал-ов по рангу*/
		list($position, $group) = current($analysts);
		$department->fireEmployee($position);/*увольняем высшего и снова нанимаем уже как босса*/
		list($rate, $litresOfCoffee, $pgsOfDocs, $rank) = $position->getAllProperties();
		$analystBoss = new Analyst($rate, $litresOfCoffee, $pgsOfDocs, $rank, true);
		$department->addEmployee(1, $analystBoss);
	}
}

echo "План 2:\n";
displayReport($company);

//вернем начальные значения
$salesDep->fireEmployee($analystBoss);
$marketer2boss->setBossStatus(true);
$analyst1->setRate(800);
$analyst1->setCoffee(50);
$analyst2->setRate(800);
$analyst2->setCoffee(50);
$salesGroup4->addEmployee();

//План 3
$addedEmplGroups = [];
foreach ($company->getDepartments() as $department) {
	$elevatedEmployees = [];
	$numOfMan = 0;
	$objectsWasCopied = false;
	$staff = $department->getDepartmentStaff();
	$groups = $staff->getEmployeeTypes();
	foreach ($groups as $key=>$group) { /*Считаем мен-ов 1 2 ранга*/
		list($num, $position) = $group->getEmployee();
		if (is_a($position, "Manager")) {
			if ($position->getRank() == 1 or $position->getRank() == 2) {
				$numOfMan += $num;
			}
		}
	}
	$numOfFiredMan =  round($numOfMan * 0.5);
	$numOfNewMan = $numOfFiredMan;
	$key = $numOfNewMan;
	$addedEmplTypes = [];
	if ($numOfMan > 0) {
		while (true) { /*Уволим мен-ов 1 2 ранга и скопируем эти позиции*/
			foreach ($groups as $group) {
				list($num, $position) = $group->getEmployee();
				if (is_a($position, "Manager")) {
					if ($position->getRank() == 1 or $position->getRank() == 2) {
							if ($num == 0) {
								continue;
							}
							if(!$objectsWasCopied){
								$elevatedEmployees[] = new EmployeeGroup(0, $position);
							}
							$group->removeEmployee();
							$numOfFiredMan--;
							if ($numOfFiredMan == 0) {
							break 2;
							}
					}
				}
			}
			$objectsWasCopied = true;	
		}
		/*скопированным группам добавим число сотрудников(уволенные = нанятые снова)*/
		while (true) {
			foreach ($elevatedEmployees as $employee) {
				list($num, $position) = $employee->getEmployee();
				if ($position->isBoss() and $num == 1) {
					continue;
				} else {
					$employee->addEmployee();
					$numOfNewMan--;
				}
				if ($numOfNewMan == 0) {
					break 2;		
				}	
			}
		}
		/*Добавим в департамент эти группы увеличив им ранг*/
		foreach ($elevatedEmployees as $employee) {
			list($num, $position) = $employee->getEmployee();
			list($rate, $litresOfCoffee, $pgsOfDocs, $rank, $boss) = $position->getAllProperties();
			$position = new Manager($rate, $litresOfCoffee, $pgsOfDocs, ++$rank, $boss);
			$addedEmplTypes[] = new EmployeeGroup($num, $position);
			$department->addEmployee($num, $position);
		}
	}
	$addedEmplGroups[$key] = $addedEmplTypes;
}

echo "План 3:\n";
displayReport($company);

//вернем обратно
$departments = $company->getDepartments();

foreach ($addedEmplGroups as $numOfAddedEmpls=>$addedGroups) {
	if ($numOfAddedEmpls == 0) {
			continue;
	} else {
		$department = current($departments);
		$staff = $department->getDepartmentStaff();
		foreach ($addedGroups as $group) {
				$staff->removeEmployeeType($group);
			}
		$groups = $staff->getEmployeeTypes();
		while (true) {
			foreach ($groups as $group) {
				list($num, $position) = $group->getEmployee();
					if (is_a($position, "Manager")) {
						if ($position->getRank() == 1 or $position->getRank() == 2) {
							if ($position->isBoss() and $num == 1) {
								continue;
							} else {
								$group->addEmployee();
								$numOfAddedEmpls--;
							}
							if ($numOfAddedEmpls == 0) {
								break 2;
							}
						}
					}
			}
		}
		$department = next($departments);
	}
}

//displayReport($company);