<?php
    //Убрал в функция createEmployee, padLeft, padRight - тайп-хинт
    //Для int и string, т.к. ideone считает их за объекты
    
    error_reporting(-1);
    mb_internal_encoding('utf-8');
  
    define("SMALL_COL", 7);
    define("MEDIUM_COL", 12);
    define("LARGE_COL", 20);
  
    class Company
    {
        private $name;
        private $listOfDepatments;
        
        public function __construct($name)
        {
            $this->name = $name;
            $this->listOfDepatments = array();
        }
        
        public function __clone()
        {
            $countDepartment = $this->getCountDeparments();
            for ($i = 0; $i < $countDepartment; $i++) {
                $this->listOfDepatments[$i] = clone $this->listOfDepatments[$i];
            }
        }

        /**
         *  Метод возвращающий имя компании
         *
         *  @return string  Имя компании
         */
        public function getNameCompany()
        {
            return $this->name;
        }
        
        /**
         *  Метод возвращающий количество департаментов
         *
         *  @return integer  Имя компании
         */
        public function getCountDeparments()
        {
            return count($this->listOfDepatments);
        }
        
        /**
         *  Метод добавляющий новый департамент в компанию
         *
         *  @param Department $newDepartment Добавляемый департамент
         *  @return void
         */
        public function addDepartmentToCompany(Department $newDepartment)
        {
            $this->listOfDepatments[] = $newDepartment;
        }
        
        /**
         *  Метод возвращающий список департаментов
         *
         *  @return array  Массив департаментов
         */
        public function getListOfDepartments()
        {
            return $this->listOfDepatments;
        }
    }
    
    class Department
    {
        private $name;
        private $listOfEmployee;
        
        public function __construct($name)
        {
            $this->name = $name;
            $this->listOfEmployee = array();
        }
        
        public function __clone()
        {
            $countEmployee = $this->getCountEmployee();
            for ($i = 0; $i < $countEmployee; $i++) {
                $this->listOfEmployee[$i] = clone $this->listOfEmployee[$i];
            }
        }
        
        /**
         *  Метод возвращающий имя департамента
         *
         *  @return string  Имя департамент
         */
        public function getDepartmentName()
        {
            return $this->name;
        }
        
        /**
         *  Метод возвращающий список сотрудников
         *
         *  @return array Список сотрудников
         */
        public function getListOfEmployee()
        {
            return $this->listOfEmployee;
        }
        
        /**
         *  Метод изменяет список список
         * 
         *  @param array $listOfEmployee Новый список сотрудников
         *  @return void
         */
        public function setListOfEmployee(array $listOfEmployee)
        {
            $this->listOfEmployee = $listOfEmployee;
        }
        
        /**
         *  Метод добавляющий нового сотрудника
         *
         *  $param Employee $newEmployee Новый сотрудник
         *  @return void
         */
        public function addEmployeeToDepartment(Employee $newEmployee)
        {
            $this->listOfEmployee[] = $newEmployee;
        }
        
        /**
         *  Метод возвращающий количество сотрудников
         *
         *  @return integer  Количество сотрудников
         */
        public function getCountEmployee()
        {
            return count($this->listOfEmployee);
        }

         /**
         *  Метод возвращающий зарплату всего департамента
         *
         *  @return integer  Зарплата департамента
         */
        public function getTotalSalary()
        {
            $totalSalary = 0;
            foreach ($this->listOfEmployee as $employee) {
                $totalSalary += $employee->getSalary();
            }
            return $totalSalary;
        }
        
        /**
         *  Метод возвращающий общее число наработанных страниц
         *
         *  @return integer  Количество страниц
         */
        public function getTotalPages()
        {
            $totalPages = 0;
            foreach ($this->listOfEmployee as $employee) {
                $totalPages += $employee->getPages();
            }
            return $totalPages;
        }

         /**
         *  Метод возвращающий общее число выпитых кружен
         *
         *  @return integer  Число выпитых кружек
         */
        public function getTotalCups()
        {
            $totalCups = 0;
            foreach ($this->listOfEmployee as $employee) {
                $totalCups += $employee->getCupOfCoffee();
            }
            return $totalCups;
        }
        
        /**
         *  Метод возвращает количество инженеров в департаменте
         *
         *  @return integer  Количество инженеров
         */
        public function totalEngineer()
        {
            $result = 0;
            foreach ($this->listOfEmployee as $employee) {
                if ($employee->checkProffession('Engineer')) {
                    $result++;
                }
            }
            return $result;
        }
        
        /**
         *  Метод возвращает количество менеджеров в департаменте
         *
         *  @return integer  Количество менеджеров
         */
        public function totalManager()
        {
            $result = 0;
            foreach ($this->listOfEmployee as $employee) {
                if ($employee->checkProffession('Manager')) {
                    $result++;
                }
            }
            return $result;
        }
        
        /**
         *  Метод возвращает руководителя департамента, если он существует, либо NULL
         *
         *  @return Employee Сотрудник руководитель департамента
         */
        public function findDirector()
        {
            $director = NULL;
            foreach ($this->listOfEmployee as $employee) {
                if ($employee->isDirector) {
                    $director = $employee;
                    break;
                }
            }
            return $director;
        }
    }
    
    abstract class Employee
    {
        public  $isDirector;
        protected  $rank;
        protected  $proffession;
        protected  $salary;
        protected  $pages;
        protected  $cupOfCoffee;
        
        public function __construct($rank, $isDirector = false)
        {
            $this->rank = $rank;
            $this->isDirector = $isDirector;
            $this->setSalary();
            $this->setPages();
            $this->setCupOfCoffee();
            $this->setProffession();
        }
        
        abstract public function setSalary();
        abstract public function setPages();
        abstract public function setCupOfCoffee();
        abstract public function setProffession();
        
        public function setRank($rank)
        {
            $this->rank = $rank;
        }
        
        /**
         *  Метод возвращающий профессию сотрудника
         *
         *  @return string Профессия сотрудника
         */
        public function getEmployeeProffession()
        {
            return $this->proffession;
        }
        
        /**
         *  Метод возвращающий ранг сотрудника
         *
         *  @return integer Ранг сотрудника
         */
        public function getEmployeeRank()
        {
            return $this->rank;
        }
        
        /**
         *  Метод возвращающий зарплату сотрудника
         *
         *  @return integer  Зарплата сотрудника
         */
        public function getSalary()
        {
            $totalSalary = $this->salary;
            if ($this->rank == 2) {
                $totalSalary *= 1.25;
            } elseif ($this->rank == 3) {
                $totalSalary *= 1.5;
            }
            
            if ($this->isDirector) {
                $totalSalary *= 1.5;
            }
            return $totalSalary;
        }
        
        /**
         *  Метод возвращающий количество наработанных страниц сотрудником
         *
         *  @return integer Общее число страниц сотрудника
         */
        public function getPages()
        {
            return ($this->isDirector ? 0 : $this->pages);
        }
        
        /**
         *  Метод возвращающий количество выпитых кружек сотрудником
         *
         *  @return integer Количество выпитых кружек
         */
        public function getCupOfCoffee()
        {
            return ($this->isDirector ? ($this->cupOfCoffee * 2) : $this->cupOfCoffee);
        }
        
        /**
         *  Метод проверяющий соответствие сотрудника заданной профессии
         *
         *  @param string $proffession Заданная профессия
         *  @return boolean Соответствует ли сотрудник указанной профессии
         */
        public function checkProffession($proffession) {
            return (static::class == $proffession) ? true : false;
        }
    }
    
    class Manager extends Employee
    {
        public function setSalary($salary = 500)
        {
            $this->salary = $salary;
        }
        
        public function setPages($pages = 200)
        {
            $this->pages = $pages;
        }
        
        public function setCupOfCoffee($cups = 20)
        {
            $this->cupOfCoffee = $cups;
        }

        public  function setProffession()
        {
            $this->proffession = 'Менеджер';
        }
    }
    
    class Marketer extends Employee
    {
        public function setSalary($salary = 400)
        {
            $this->salary = $salary;
        }
        
        public function setPages($pages = 150)
        {
            $this->pages = $pages;
        }
        
        public function setCupOfCoffee($cups = 15)
        {
            $this->cupOfCoffee = $cups;
        }

        public  function setProffession()
        {
            $this->proffession = 'Маркетолог';
        }
    }
    
    class Engineer extends Employee
    {
        public function setSalary($salary = 200)
        {
            $this->salary = $salary;
        }
        
        public function setPages()
        {
            $this->pages = 50;
        }
        
        public function setCupOfCoffee()
        {
            $this->cupOfCoffee = 5;
        }

        public  function setProffession()
        {
            $this->proffession = 'Инженер';
        }
    }
    
    class Analyst extends Employee
    {
        public function setSalary($salary = 800)
        {
            $this->salary = $salary;
        }
        
        public function setPages($pages = 5)
        {
            $this->pages = $pages;
        }
        
        public function setCupOfCoffee($cups = 50)
        {
            $this->cupOfCoffee = $cups;
        }

        public  function setProffession()
        {
            $this->proffession = 'Аналитик';
        }
    }
    
    class CrisisCommittee
    {
        /**
         *  Метод "отдающий приказ" на увольнение инженеров в каждом департаменте
         *
         *  @param Company $company Компания, в которой идет сокращение
         *  @return void
         */
        //Дать приказ на увольнение инженеров в каждом департаменте
        public function reduceEngineer(Company $company)
        {
            $listOfDepatments = $company->getListOfDepartments();
            foreach ($listOfDepatments as $departament) {
                $this->reduceEngineerInDep($departament, 40);
            }
        }

        /**
         *  Метод возвращающий список номеров сотрудников соответствующие указанной профессии
         *
         *  @param array $listOfEmployee Список всех сотрудников
         *  @param string $proffession Профессия для фильтра
         *  @return array Список номеров нужных сотрудников
         */
        public function getEmployeeByCondition(array $listOfEmployee, $proffession)
        {
            $result = array();

            uasort($listOfEmployee, 'sortByRank');
            $countEmployee = count($listOfEmployee);
            
            for ($i = 0; $i < $countEmployee; $i++) {
                if ($listOfEmployee[$i]->checkProffession($proffession) && (!$listOfEmployee[$i]->isDirector)) {
                    $result[] = $i;
                }
            }            
            return $result;
        }
        
        /**
         *  Метод увольнения инженеров с наименьшим рангов и не являющихся руководителями
         *
         *  @param Department $departament Департамент, в котором происходит увольнение
         *  @return void
         */
        public function reduceEngineerInDep(Department $departament)
        {
            $listOfEmployee = $departament->getListOfEmployee();
            $listOfEngineers = $this->getEmployeeByCondition($listOfEmployee, 'Analyst');
            
            $countEngineer = count($listOfEngineers);
            $engineerDelete = ceil(0.4 * $countEngineer);
            $listOfReduceEngineer = array_slice($listOfEngineers, 0, $engineerDelete);
            
            foreach ($listOfReduceEngineer as $numberEngineer) {
                unset($listOfEmployee[$numberEngineer]);
            }

            $departament->setListOfEmployee($listOfEmployee);
        }
        
        /**
         *  Метод повышения зарплаты
         *
         *  @param Department $departament Департамент, в котором происходит повышение
         *  @param string $proffession Профессия, которой повышают зарплату
         *  @return void
         */
        public function raiseSalary(Department $departament, $proffession)
        {
            $listOfEmployee = $departament->getListOfEmployee();
            foreach ($listOfEmployee as $employee) {
                if ($employee->checkProffession($proffession)) {
                    $employee->setSalary(1100);
                    $employee->setCupOfCoffee(75);
                }
            }
        }
        
        /**
         *  Метод "отдающий приказ" на повышение зарплаты аналитикам и повышении их в должности
         *  (вторая антикризисная мера)
         *
         *  @param Company $company Компания
         *  @return integer Количество выпитых кружек
         */
        public function raiseAnalytics(Company $company)
        {
            $listOfDepatments = $company->getListOfDepartments();
            foreach($listOfDepatments as $departament) {
                $this->raiseSalary($departament, "Analyst");
                $this->changeDirector($departament, "Analyst");
            }
        }
        
        /**
         *  Метод поиска сотрудника с высшим рангом указанной профессии
         *
         *  @param Department $departament Департамент, в котором идет поиск
         *  @param string $proffession Профессия для фильтра
         *  @return Employee | boolean
         */
        public function findHighestRankInProffession(Department $departament, $proffession)
        {
            $rank = 0;
            
            $listOfEmployee = $departament->getListOfEmployee();
            foreach ($listOfEmployee as $employee) {
                if (($employee->getEmployeeRank() > $rank) && ($employee->checkProffession($proffession))) {
                    $analytics = $employee;
                    $rank = $employee->getEmployeeRank();
                }
            }
            
            return ($rank) ? $analytics : false;
        }
        
        /**
         *  Метод смены руководителя департамента
         *
         *  @param Department $departament Департамент, в котором происходит смена
         *  @param string $proffession Указывает, какая профессия должна быть у нового руководителя
         *  @return void
         */
        public function changeDirector(Department $departament, $proffession)
        {
            $director = $departament->findDirector();
            
            if (!$director) {
                echo "Не задан директор";
                exit();
            }

            if (!$director->checkProffession('Analyst')) {
                
                $highestAnalytics = $this->findHighestRankInProffession($departament, $proffession);
                
                if ($highestAnalytics) {
                    $highestAnalytics->isDirector = true;
                    $director->isDirector = false;
                }
            } 
        }
        
        /**
         *  Метод "отдающий приказ" на повышение менеджеров в каждом департаменте
         *  (третий антикризисный план)
         *
         *  @param Company $company Компания, в которой идет повышение
         *  @return void
         */
        public function raiseManager(Company $company)
        {
            $listOfDepatments = $company->getListOfDepartments();
            foreach($listOfDepatments as $departament) {
                $this->raiseManagerInDep($departament, 50);
            }
        }
        
        /**
         *  Метод повышающий ранг $percent процентам менеджерам
         *
         *  @param Department $departament Департамент, в котором идет повышение
         *  @param  int $percent Процент менеджеров, которым повышается ранг
         *  @return void
         */
        public function raiseManagerInDep(Department $departament, $percent)
        {
            $countEmployee = $departament->getCountEmployee();
            $countManager = $departament->totalManager();
            $managerRaise = ceil(0.5 * $countManager);
            
            $listOfEmployee = $departament->getListOfEmployee();
            for ($i = 0; $i < $countEmployee; $i++) {
                if ($managerRaise == 0) {
                    break;
                }
                if ($listOfEmployee[$i]->checkProffession('Manager') && ($listOfEmployee[$i]->getEmployeeRank() != 3)) {
                    $oldRank = $listOfEmployee[$i]->getEmployeeRank();
                    $listOfEmployee[$i]->setRank($oldRank + 1);
                    $managerRaise--;
                }
            }
        }
    }
    
    /**
     *  Класс для создания отчета по компании
     */
    class CompanyReport
    {
        public function printInfoAboutCompany(Company $company, $mssg = '')
        {
            $countDepartment = $company->getCountDeparments();
            $total = array('сотр' => 0, 'тугр' => 0, 'кофе' => 0, 'стр' => 0);

            echo "\"{$company->getNameCompany()}\"\n";
            echo "{$mssg}\n";
            echo padRigth("Деп. ", LARGE_COL) . padLeft("сотр.", SMALL_COL) . padLeft("тугр.", MEDIUM_COL) .
                 padLeft("кофе", SMALL_COL) . padLeft("стр.", MEDIUM_COL) . padLeft("тугр./стр.", LARGE_COL) . "\n";
            $listOfDepatments = $company->getListOfDepartments();
            foreach ($listOfDepatments as $departament) {
                echo padRigth($departament->getDepartmentName(), LARGE_COL) . padLeft($departament->getCountEmployee(), SMALL_COL) .
                     padLeft($departament->getTotalSalary(), MEDIUM_COL) . padLeft($departament->getTotalCups(), SMALL_COL) .
                     padLeft($departament->getTotalPages(), MEDIUM_COL) . padLeft($departament->getTotalSalary() / $departament->getTotalPages(), LARGE_COL) . "\n";
                $total['сотр'] += $departament->getCountEmployee();
                $total['тугр'] += $departament->getTotalSalary();
                $total['кофе'] += $departament->getTotalCups();
                $total['стр'] += $departament->getTotalPages();
            }
            
            echo padRigth('Среднее', LARGE_COL) . padLeft($total['сотр'] / $countDepartment, SMALL_COL) .
                     padLeft($total['тугр'] / $countDepartment, MEDIUM_COL) . padLeft($total['кофе'] / $countDepartment, SMALL_COL) .
                     padLeft($total['стр'] / $countDepartment, MEDIUM_COL) . padLeft(($total['тугр'] / $total['стр']) / $countDepartment, LARGE_COL) . "\n";
            echo padRigth('Всего', LARGE_COL) . padLeft($total['сотр'], SMALL_COL) .
                     padLeft($total['тугр'], MEDIUM_COL) . padLeft($total['кофе'], SMALL_COL) .
                     padLeft($total['стр'], MEDIUM_COL) . padLeft($total['тугр'] / $total['стр'], LARGE_COL) . "\n"; 
        }
    }
    
    function sortByRank($a, $b)
    {
        return ($a->getEmployeeRank() > $b->getEmployeeRank()) ? 1 : -1;
    }
    
    function padLeft($text, $length)
    {
        if (mb_strlen($text) < $length) {
            $text = str_repeat(" ", $length - mb_strlen($text)) . $text;
        }
        
        return $text;
    }
    
    function padRigth($text, $length) {
        if (mb_strlen($text) < $length) {
            $text .= str_repeat(" ", $length - mb_strlen($text));
        }        
        
        return $text;
    }
    
    /**
     *  Функция создания массива сотрудников
     *
     *  @param string $message Строка вида:9xме1;3xмe2;2xмe3;2xмa1;1xмe2 (последний идет руководитель) 
     *  @return array Список сотрудников
     */
    function createEmployee($message)
    {
        $employee = array();
        
        $people = explode(';', $message);
        $totalPeople = count($people);
        
        
        for ($i = 0; $i < $totalPeople; $i++) {
            
            $matches = array();
            preg_match('/([0-9]+)x([а-яё]+)([0-9])/iu', $people[$i], $matches);
             
            $countEmployee = (int) $matches[1];
            $proffession = $matches[2];
            $rank = (int) $matches[3];
        
            $isDirector = ($i == ($totalPeople - 1)) ? true : false;
        
            for ($j = 0; $j < $countEmployee; $j++ ) {
                switch ($proffession) {
                    case "ме": $employee[] = new Manager($rank, $isDirector);
                               break;
                    case "ма": $employee[] = new Marketer($rank, $isDirector);
                               break;
                    case "ан": $employee[] = new Analyst($rank, $isDirector);
                               break;
                    case "ин": $employee[] = new Engineer($rank, $isDirector);
                               break;  
                    default: echo "Проверьте входные данные! {$proffession}";
                             exit();
                }
            }
        }

        return $employee;        
    }
    
    /**
     *  Функция добавляет созданный список сотрудников в департамент
     *
     *  @return void
     */
    function addAllEmployeeToDepartment(array $employee, Department $departament)
    {
        $totalEmployee = count($employee);
        
        for ($i = 0; $i < $totalEmployee; $i++) {
            $departament->addEmployeeToDepartment($employee[$i]);
        }
    }
    
    function createCompany($procurementPeople, $salesPeople, $advertisingPeople, $logisticsPeople)
    {
        $vector = new Company('Вектор');

        $procurementDepartment = new Department('закупок');
        $vector->addDepartmentToCompany($procurementDepartment);
        
        $salesDepartament = new Department('продаж');
        $vector->addDepartmentToCompany($salesDepartament);
        
        $advertisingDepartment = new Department('рекламы');
        $vector->addDepartmentToCompany($advertisingDepartment);
        
        $logisticsDepartament = new Department('логистики');
        $vector->addDepartmentToCompany($logisticsDepartament);
        

        $procurementEmployee = array();
        $procurementEmployee = createEmployee($procurementPeople);
        addAllEmployeeToDepartment($procurementEmployee, $procurementDepartment);
        
        $salesEmployee = array();
        $salesEmployee = createEmployee($salesPeople);
        addAllEmployeeToDepartment($salesEmployee, $salesDepartament);
        
        $advertisingEmployee = array();
        $advertisingEmployee = createEmployee($advertisingPeople);
        addAllEmployeeToDepartment($advertisingEmployee, $advertisingDepartment);
        
        $logisticsEmployee = array();
        $logisticsEmployee = createEmployee($logisticsPeople);
        addAllEmployeeToDepartment($logisticsEmployee, $logisticsDepartament);
        
        return $vector;
    }
   
    $vector = createCompany(
        '9xме1;3xме2;2xме3;2xма1;1xме2', 
        '12xме1;6xма1;3xан1;2xан2;1xма2',
        '15xма1;10xма2;8xме1;2xин1;1xма3',
        '13xме1;5xме2;5xин1;1xме1');
    
    $report = new CompanyReport();
    $report->printInfoAboutCompany($vector, '[Без антикризисных мер]');
    echo str_repeat('-', 80) . "\n";
    
    $crisisCommittee = new CrisisCommittee();
    
    $vectorWithoutEngineer = clone $vector;
    $crisisCommittee->reduceEngineer($vectorWithoutEngineer);
    $report->printInfoAboutCompany($vectorWithoutEngineer, '[Первая антикризисная мера]');
    echo str_repeat('-', 80) . "\n";
    
    $vectorRaiseAnalytics = clone $vector;
    $crisisCommittee->raiseAnalytics($vectorRaiseAnalytics);
    $report->printInfoAboutCompany($vectorRaiseAnalytics, '[Вторая антикризисная мера]');
    echo str_repeat('-', 80) . "\n";

    $vectorRaiseManager = clone $vector;
    $crisisCommittee->raiseManager($vectorRaiseManager);
    $report->printInfoAboutCompany($vectorRaiseManager, '[Третья антикризисная мера]');
?>
    