<?php

class Company
{
    private $departments = [];

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

        return $this;
    }

    public function getCountEmployee() : int
    {
        $data = 0;
        foreach ($this->departments as $department) {
            $data += $department->getCountEmployee();
        }
        return $data;
    }

    public function getExpenses() : array
    {
        $rate = 0;
        $coffee = 0;

        foreach ($this->departments as $department) {
            $rate   += $department->getExpenses()['Зарплата'];
            $coffee += $department->getExpenses()['Кофе'];
        }

        return [$rate, $coffee];
    }

    public function getAverageExpenses() : array
    {
        $rate = 0;
        $coffee = 0;

        foreach ($this->departments as $department) {
            $rate   += $department->getExpenses()['Зарплата'];
            $coffee += $department->getExpenses()['Кофе'];
        }

        return [
            $rate / count($this->getDepartments()),
            $coffee / count($this->getDepartments())
        ];
    }

    public function getReports() : float
    {
        $reports = 0;
        foreach ($this->departments as $department) {
            $reports += $department->getReports();
        }
        return $reports;
    }

    public function getAverageReports() : float
    {
        $reports = 0;
        foreach ($this->departments as $department) {
            $reports += $department->getReports();
        }
        return $reports / count($this->getDepartments());
    }

    public function getAverageConsumptionMoneyPerPage() : float
    {
        $moneyConsumption = 0;
        foreach ($this->departments as $department) {
            $moneyConsumption += $department->getAverageConsumptionMoneyPerPage();
        }
        return $moneyConsumption / count($this->getDepartments());
    }

    public function getConsumptionMoneyPerPage() : float
    {
        $moneyConsumption = 0;
        foreach ($this->departments as $department) {
            $moneyConsumption += $department->getAverageConsumptionMoneyPerPage();
        }
        return $moneyConsumption;
    }

    public function getDepartments() : array
    {
        return $this->departments;
    }

    public function getAverageCountEmployers() : float
    {
        $countEmployers  = 0;
        foreach ($this->departments as $department) {
            $countEmployers += $department->getCountEmployee();
        }
        return $countEmployers / count($this->getDepartments());
    }

    public function setAntiCrisisMeasuresFirst() : self
    {
        foreach ($this->departments as $department) {
            $department->setAntiCrisisMeasuresFirst();
        }

        return $this;
    }

    public function setAntiCrisisMeasuresSecond() : self
    {
        foreach ($this->departments as $department) {
            $department->setAntiCrisisMeasuresSecond();
        }

        return $this;
    }

    public function setAntiCrisisMeasuresThird() : self
    {
        foreach ($this->departments as $department) {
            $department->setAntiCrisisMeasuresThird();
        }

        return $this;
    }
}

class Department
{
    protected $name;
    protected $employees = [];

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

    public function addEmployee(Employee $employee, int $count = 1) : self
    {
        $employee = serialize($employee);
        for ($i = 0; $i < $count; $i++) {
            $this->employees[] = unserialize($employee);
        }

        return $this;
    }

    public function getCountEmployee() : int
    {
        return count($this->employees);
    }

    public function getExpenses() : array
    {
        $money  = 0;
        $coffee = 0;

        foreach ($this->employees as $employee) {
            $money  += $employee->getJob()->getRate();
            $coffee += $employee->getJob()->getCoffee();
        }

        return [
            'Зарплата' => $money,
            'Кофе'     => $coffee
        ];
    }

    public function getReports() : float
    {
        $reports = 0;

        foreach ($this->employees as $employee) {
            $reports += $employee->getJob()->getReport();
        }

        return $reports;
    }

    public function getName() : string
    {
        return $this->name;
    }

    public function getAverageConsumptionMoneyPerPage() : float
    {
        return round($this->getExpenses()['Зарплата'] / $this->getReports(), 2);
    }

    public function dismissEmployee(int $id) : self
    {
        unset($this->employees[$id]);

        return $this;
    }
    
    public function getEmployeesByJob(Job $job) : array
    {
        $data = [];
        foreach ($this->employees as $employee) {
            if ($employee->getJob() instanceof $job) {
                $data[] = $employee;
            }
        }
        return $data;
    }

    public function getLeaders() : array
    {
        $data = [];
        foreach ($this->employees as $employee) {
            if ($employee->isLeader()) {
                $data[] = $employee;
            }
        }
        return $data;
    }

    public function getEmployeersByJobAndRank(Job $job, int $rank) : array
    {
        $data = [];
        foreach ($this->employees as $id => $employee) {
            if ($employee->getJob() instanceof $job) {
                if ($employee->getRank() === $rank) {
                    $data[] = $employee;
                }
            }
        }
        return $data;
    }

    public function setAntiCrisisMeasuresFirst()
    {
        $reduction = ceil(count($this->getEmployeesByJob(new Engineer())) * 0.4);
        $minRank = 1;
        $iteration = 1;

        $skippableEmployees = [];

        while($reduction !== 0) {
            foreach ($this->employees as $id => $employee) {
                if ($employee->getJob() instanceof Engineer) {
                    if ($employee->getRank() === $minRank) {
                        if ($employee->isLeader()) {
                            if (! in_array($id, $skippableEmployees)) {
                                $skippableEmployees[] = $id;
                            }
                        }

                        if (! in_array($id, $skippableEmployees)) {
                            if ($reduction > 0) {
                                $this->dismissEmployee($id);
                                $reduction -= 1;
                            }
                        }
                    }
                }
            }
            if ($iteration % 2 === 0) {
                $minRank++;
                if ($minRank > 3) {
                    break;
                }
            }
            $iteration++;
        }
    }

    public function setAntiCrisisMeasuresSecond()
    {
        $maxRank = 0;

        foreach ($this->employees as $id => $employee) {
            if ($employee->isLeader()) {
                if (! is_a($employee->getJob(), Analyst::class)) {

                    foreach ($this->employees as $search) {
                        if ($search->getJob() instanceof Analyst && $search->getRank() > $maxRank) {
                            $maxRank = $search->getRank();
                        }
                    }

                    if($maxRank !== 0) {
                        foreach ($this->employees as $search) {
                            if ($search->getJob() instanceof Analyst && $search->getRank() === $maxRank) {
                                $job = get_class($search->getJob());
                                $search->setJob(new $job(), $maxRank, true);
                            }
                        }
                        $job = get_class($employee->getJob());
                        $employee->setJob(new $job(), $maxRank);
                    }

                }
            }
            if ($employee->getJob() instanceof Analyst) {
                $employee->getJob()->setRate(1100);
                $employee->getJob()->setCoffee(75);

                $employee->updateJob($employee->getRank(), $employee->isLeader());
            }
        }
    }

    public function setAntiCrisisMeasuresThird()
    {
        $employeeFirstRank  = $this->getEmployeersByJobAndRank(new Manager(), 1);
        $employeeSecondRank = $this->getEmployeersByJobAndRank(new Manager(), 2);

        $countFirstRank  = count($employeeFirstRank) * 0.5;
        $countSecondRank = count($employeeSecondRank) * 0.5;

        $upgradedFirstRank  = 0;
        $upgradedSecondRank = 0;

        foreach ($this->employees as $id => $employee) {
            if ($employee->getJob() instanceof Manager) {
                if ($employee->getRank() === 1) {
                    if($upgradedFirstRank < $countFirstRank) {
                        $employee->setJob(new Manager(), 2);
                        $upgradedFirstRank++;
                    }
                } elseif($employee->getRank() === 2) {
                    if($upgradedSecondRank < $countSecondRank) {
                        $employee->setJob(new Manager(), 3);
                        $upgradedSecondRank++;
                    }
                }
            }
        }
    }
}

abstract class Job
{
    public function setRate(int $rate) : self
    {
        $this->rate = $rate;

        return $this;
    }

    public function getRate() : int
    {
        return $this->rate;
    }

    public function setCoffee(int $coffee) : self
    {
        $this->coffee = $coffee;

        return $this;
    }

    public function getCoffee() : int
    {
        return $this->coffee;
    }

    public function setReport(int $report) : self
    {
        $this->report = $report;

        return $this;
    }

    public function getReport() : int
    {
        return $this->report;
    }
}

class Employee
{
    protected $rank;
    protected $isLeader = false;
    protected $job;

    public function __construct(Job $job, int $rank, bool $isLeader = false)
    {
        $this->rank     = $rank;
        $this->isLeader = $isLeader;
        $this->job      = $job;

        $this->updateJob($rank, $isLeader);
    }

    public function updateJob(int $rank, bool $isLeader = false)
    {
        switch ($rank) {
            case 1:
                break;
            case 2:
                $this->job->setRate($this->job->getRate() * 1.25);
                break;
            case 3:
                $this->job->setRate($this->job->getRate() * 1.5);
                break;
        }

        if ($isLeader === true) {
            $this->job->setRate($this->job->getRate() * 1.5);
            $this->job->setCoffee($this->job->getCoffee() * 2);
            $this->job->setReport(0);
        }
    }

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

    public function isLeader() : bool
    {
        return $this->isLeader;
    }

    public function getJob() : Job
    {
        return $this->job;
    }

    public function setJob(Job $job, int $rank, bool $isLeader = false) : self
    {
        $this->job      = $job;
        $this->rank     = $rank;
        $this->isLeader = $isLeader;

        $this->updateJob($rank, $isLeader);

        return $this;
    }
}

class Manager extends Job
{
    protected $rate   = 500;
    protected $coffee = 20;
    protected $report = 200;
}

class Marketer extends Job
{
    protected $rate   = 400;
    protected $coffee = 15;
    protected $report = 150;
}

class Engineer extends Job
{
    protected $rate   = 200;
    protected $coffee = 5;
    protected $report = 50;
}

class Analyst extends Job
{
    protected $rate   = 800;
    protected $coffee = 50;
    protected $report = 5;
}

function padRight(string $string, int $length): string
{
    return $string . str_repeat(' ', $length - mb_strlen($string));
}

function padLeft(string $string, int $length): string
{
    return str_repeat(' ', $length - mb_strlen($string)) . $string;
}

function write($vector) {
    foreach ($vector->getDepartments() as $department) {
        echo padRight($department->getName(), 11)
            .padLeft($department->getCountEmployee(), 11)
            .padLeft($department->getExpenses()['Зарплата'], 11)
            .padLeft($department->getExpenses()['Кофе'], 11)
            .padLeft($department->getReports(), 11)
            .padLeft($department->getAverageConsumptionMoneyPerPage(), 11) . PHP_EOL;
    }
    echo padRight('Среднее', 11)
        .padLeft($vector->getAverageCountEmployers(), 11)
        .padLeft($vector->getAverageExpenses()[0], 11)
        .padLeft($vector->getAverageExpenses()[1], 11)
        .padLeft($vector->getAverageReports(), 11)
        .padLeft($vector->getAverageConsumptionMoneyPerPage(), 11) . PHP_EOL;
    echo padRight('Всего', 11)
        .padLeft($vector->getCountEmployee(), 11)
        .padLeft($vector->getExpenses()[0], 11)
        .padLeft($vector->getExpenses()[1], 11)
        .padLeft($vector->getReports(), 11)
        .padLeft($vector->getConsumptionMoneyPerPage(), 11) . PHP_EOL;
}

$vector = new Company();

$purchasing = (new Department('закупок'))
    ->addEmployee(new Employee(new Manager(), 3), 9)
    ->addEmployee(new Employee(new Manager(), 2), 3)
    ->addEmployee(new Employee(new Manager(), 3), 2)
    ->addEmployee(new Employee(new Marketer(), 1), 2)
    ->addEmployee(new Employee(new Manager(), 2, true));

$sells = (new Department('продаж'))
    ->addEmployee(new Employee(new Manager(), 1), 12)
    ->addEmployee(new Employee(new Marketer(), 1), 6)
    ->addEmployee(new Employee(new Analyst(), 1), 3)
    ->addEmployee(new Employee(new Analyst(), 2), 2)
    ->addEmployee(new Employee(new Manager(), 2, true));

$ad = (new Department('рекламы'))
    ->addEmployee(new Employee(new Marketer(), 1), 15)
    ->addEmployee(new Employee(new Marketer(), 2), 10)
    ->addEmployee(new Employee(new Manager(), 1), 8)
    ->addEmployee(new Employee(new Engineer(), 1), 2)
    ->addEmployee(new Employee(new Marketer(), 3, true));

$logistics = (new Department('логистики'))
    ->addEmployee(new Employee(new Manager(), 1), 13)
    ->addEmployee(new Employee(new Manager(), 2), 5)
    ->addEmployee(new Employee(new Engineer(), 1), 5)
    ->addEmployee(new Employee(new Manager(), 1, true));

$vector->addDepartment($purchasing);
$vector->addDepartment($sells);
$vector->addDepartment($ad);
$vector->addDepartment($logistics);

echo padLeft("Департамент", 11)
    .padLeft("сотр.", 11)
    .padLeft("тугр.", 11)
    .padLeft("кофе", 11)
    .padLeft("стр.", 11)
    .padLeft("тугр./стр.", 15) . PHP_EOL;

echo '---------------------------------------------------------------------' . PHP_EOL;
echo "=== DEFAULT ===" . PHP_EOL;
write($vector);

$vector = serialize($vector);

$antiCrisisVectorFirst  = unserialize($vector);
$antiCrisisVectorSecond = unserialize($vector);
$antiCrisisVectorThird  = unserialize($vector);

$antiCrisisVectorFirst->setAntiCrisisMeasuresFirst();
$antiCrisisVectorSecond->setAntiCrisisMeasuresSecond();
$antiCrisisVectorThird->setAntiCrisisMeasuresThird();

echo "=== ANTI-CRISIS FIRST ===" . PHP_EOL;
write($antiCrisisVectorFirst);

echo "=== ANTI-CRISIS SECOND ===" . PHP_EOL;
write($antiCrisisVectorSecond);

echo "=== ANTI-CRISIS THIRD ===" . PHP_EOL;
write($antiCrisisVectorThird);