<?php

ini_set('display_errors', 'on');
error_reporting(E_ALL);
mb_internal_encoding('UTF-8');

class Employee
{
    const FIRST_RANK = 1.0;
    const SECOND_RANK = 1.25;
    const THIRD_RANK = 1.5;
    const BOSS_PREMIUM = 1.5;
    const BOSS_COFFEE_PREMIUM = 2.0;
    const BOSS_PAGES_MULTIPLIER = 0;

    public $isBoss;
    public $rank;
    public $profession;
    
    private static $dependencies = array(
            'manager'=>array('coffee'=>20, 'pages'=>200, 'wageRate'=>500),
            'marketer'=>array('coffee'=>15, 'pages'=>150, 'wageRate'=>400),
            'engineer'=>array('coffee'=>5, 'pages'=>50, 'wageRate'=>200),
            'analyst'=>array('coffee'=>50, 'pages'=>5, 'wageRate'=>800),
        );

    public function getDependencies()
    {
        return self::$dependencies;
    }
    
    public static function updateDependencies(array $dependencies)
    {
        self::$dependencies = array_merge(self::$dependencies, $dependencies);
    }

    private function getProfDependencies()
    {
        $allDependencies = $this->getDependencies();
        return $allDependencies[$this->profession];
    }


    public function getCoffeeConsuming()
    {
        $profDependencies = $this->getProfDependencies($this->profession);
        return ($this->isBoss) ? self::BOSS_COFFEE_PREMIUM * $profDependencies['coffee']
                         : $profDependencies['coffee'];
    }

    public function getSalary()
    {
        $profDependencies = $this->getProfDependencies($this->profession);
        return ($this->isBoss) ? self::BOSS_PREMIUM * $profDependencies['wageRate'] * $this->rank
                         : $profDependencies['wageRate'] * $this->rank;
    }

    public function getPages()
    {
        $profDependencies = $this->getProfDependencies($this->profession);
        return ($this->isBoss) ? self::BOSS_PAGES_MULTIPLIER * $profDependencies['pages']
                         : $profDependencies['pages'];
    }

    public function __construct($profession, $rank = 1, $isBoss = false)
    {
        $this->profession = $profession;
        $this->rank = $rank;
        $this->isBoss = $isBoss;
    }
}

class Report
{
    public $departmentName;
    public $salary;
    public $coffee;
    public $pages;
    public $employeeCount;
}

class Department
{
    public $name;
    public $employees = array();

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getReport()
    {
        $report = new Report;
        foreach ($this->employees as $employee) {
            $report->salary += $employee->getSalary();
            $report->coffee += $employee->getCoffeeConsuming();
            $report->pages += $employee->getPages();
        }
        $report->departmentName = $this->name;
        $report->employeeCount = count($this->employees);
        return $report;
    }

    public function hireEmployee(Employee $employee)
    {
        $this->employees[] = $employee;
    }

    public function fireEmployee(Employee $e)
    {
        foreach ($this->employees as $employee) {
            if ($employee != $e) continue;
            $index = array_search($employee, $this->employees);
            unset($this->employees[$index]);
            break;
        }
    }
    
    public function fireMassive($profession, $amount)
    {
        $tolalCount = 0;
        foreach ($this->employees as $employee) {
            if ($employee->profession == $profession) $tolalCount += 1;
        }
        $toFire = intval(ceil($tolalCount * $amount));
        $fired = 0;
        foreach ($this->employees as $employee) {
            if ($employee->profession != $profession or $employee->isBoss) continue;
            if ($fired == $toFire) break;
            $this->fireEmployee($employee);
            $fired++;
        }
    }
}

function hireCrowd(Department $dep, $number, $profession, $rank = 1, $isBoss = false) {
    for ($i = 0; $i < $number; $i++) {
        $employee = new Employee($profession, $rank, $isBoss);
        $dep->hireEmployee($employee);
    }
}

class Company
{
    private $departments = array();
    public function getDepartments()
    {
        return $this->departments;
    }

    public $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getReport()
    {
        $report = array();
        foreach ($this->departments as $department) {
            $report[] = $department->getReport();
        }
        return $report;
    }

    public function addDepartment(Department $department)
    {
        $this->departments[] = $department;
    }

    public function dismissDepartment(Department $dep)
    {
        foreach ($this->departments as $department) {
            if ($department != $dep) continue;
            unset($department);
            break;
        }
    }
}

$procurement = new Department('Отдел закупок'); 
hireCrowd($procurement, 9, 'manager');
hireCrowd($procurement, 3, 'manager', Employee::SECOND_RANK);
hireCrowd($procurement, 2, 'manager', Employee::THIRD_RANK);
hireCrowd($procurement, 2, 'marketer');
hireCrowd($procurement, 1, 'manager', Employee::SECOND_RANK, true);

$selling = new Department('Отдел продаж');
hireCrowd($selling, 12, 'manager');
hireCrowd($selling, 6, 'marketer');
hireCrowd($selling, 3, 'analyst');
hireCrowd($selling, 2, 'analyst', Employee::SECOND_RANK);
hireCrowd($selling, 1, 'marketer', Employee::SECOND_RANK, true);

$advertisement = new Department('Отдел рекламы');
hireCrowd($advertisement, 15, 'marketer');
hireCrowd($advertisement, 10, 'marketer', Employee::SECOND_RANK);
hireCrowd($advertisement, 8, 'manager');
hireCrowd($advertisement, 2, 'engineer');
hireCrowd($advertisement, 1, 'marketer', Employee::THIRD_RANK, true);

$logistics = new Department('Отдел логистики');
hireCrowd($logistics, 13, 'manager');
hireCrowd($logistics, 5, 'manager', Employee::SECOND_RANK);
hireCrowd($logistics, 5, 'engineer');
hireCrowd($logistics, 1, 'manager', Employee::FIRST_RANK, true);

$vector = new Company('Вектор');
$vector->addDepartment($procurement);
$vector->addDepartment($selling);
$vector->addDepartment($advertisement);
$vector->addDepartment($logistics);

// Начальные условия
ViewHelper::printReport($vector);

// 1. Антикризисные меры: увольняю 40% инженеров во всех отделах
/*foreach ($vector->getDepartments() as $department) {
    $department->fireMassive('engineer', 0.4);
}
ViewHelper::printReport($vector);*/
// 2. Антикризисные меры: меняю расходы на аналитиков, делаю аналитиков начальниками
// в тех департаментах, где они есть, иначе не меняю босса
Employee::updateDependencies(array('analyst'=>array('wageRate'=>1100,'coffee'=>75,'pages'=>5)));
foreach ($vector->getDepartments() as $department) {
    foreach ($department->employees as $employee) {
        if ($employee->isBoss) {
            $oldBoss = $employee;
            break;
        }
    }
    if ($oldBoss->profession == 'analyst') continue;
    $pretendent = null;
    foreach ($department->employees as $employee) {
        if ($employee->profession != 'analyst') continue;
        if (!$pretendent or $pretendent->rank < $employee->rank) {
            $pretendent = $employee;
        }
    }
    if ($pretendent) {
        $oldBossIndex = array_search($oldBoss, $department->employees);
        $department->employees[$oldBossIndex]->isBoss = false;
        $pretendentIndex = array_search($pretendent, $department->employees);
        $department->employees[$pretendentIndex]->isBoss = true;
    }
}
ViewHelper::printReport($vector);

class ViewHelper
{
    public static function printReport(Company $company)
    {
        echo self::padRight('Департамент');
        echo self::padLeft('сотр.');
        echo self::padLeft('тугр.');
        echo self::padLeft('кофе');
        echo self::padLeft('стр.');
        echo self::padLeft('тугр./стр.'), "\n";
        for ($i=0; $i < 95; $i++) echo '-';
        echo "\n";
        $reports = $company->getReport();
        foreach ($reports as $report) {
            echo self::padRight($report->departmentName);
            echo self::padLeft($report->employeeCount);
            echo self::padLeft($report->salary);
            echo self::padLeft($report->coffee);
            echo self::padLeft($report->pages);
            echo self::padLeft(round($report->salary / $report->pages, 1));
            echo "\n";
        }
        echo self::padRight('Среднее');
        echo self::padLeft(self::average($reports, 'employeeCount'));
        echo self::padLeft(self::average($reports, 'salary'));
        echo self::padLeft(self::average($reports, 'coffee'));
        echo self::padLeft(self::average($reports, 'pages'));
        echo "\n";
        echo self::padRight('Всего');
        echo self::padLeft(self::summary($reports, 'employeeCount'));
        echo self::padLeft(self::summary($reports, 'salary'));
        echo self::padLeft(self::summary($reports, 'coffee'));
        echo self::padLeft(self::summary($reports, 'pages'));
        echo "\n";
    }
    
    private static function summary($reports, $propertyName)
    {
        if (!is_array($reports) || count($reports) == 0) return 0;
        for ($i=0, $sum=0; $i < count($reports); $i++) {
            $sum += $reports[$i]->$propertyName;
        }
        return $sum;
    }
    
    private static function average($reports, $propertyName)
    {
        $sum = self::summary($reports, $propertyName);
        return round($sum / count($reports), 1);
    }
    
    private static function padRight($string, $columnSize=20) {
        $paddingSize = $columnSize - mb_strlen($string);
        $string .= str_repeat(' ', $paddingSize);
        return $string;
    }
    
    private static function padLeft($string, $columnSize=15) {
        $paddingSize = $columnSize - mb_strlen($string);
        $string = str_repeat(' ', $paddingSize) . $string;
        return $string;
    }
}