<?php
namespace Robo\Common;

/**
 * Wrapper around \Symfony\Component\Console\Helper\ProgressBar
 */
class ProgressIndicator
{
    use Timer;

    /**
     * @var \Symfony\Component\Console\Helper\ProgressBar
     */
    protected $progressBar;

    /**
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    protected $output;

    /**
     * @var bool
     */
    protected $progressIndicatorRunning = false;

    /**
     * @var int
     */
    protected $autoDisplayInterval = 0;

    /**
     * @var int
     */
    protected $cachedSteps = 0;

    /**
     * @var int
     */
    protected $totalSteps = 0;

    /**
     * @var bool
     */
    protected $progressBarDisplayed = false;

    /**
     * @var \Robo\Contract\TaskInterface
     */
    protected $owner;

    /**
     * @param \Symfony\Component\Console\Helper\ProgressBar $progressBar
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     */
    public function __construct($progressBar, \Symfony\Component\Console\Output\OutputInterface $output)
    {
        $this->progressBar = $progressBar;
        $this->output = $output;
    }

    /**
     * @param int $interval
     */
    public function setProgressBarAutoDisplayInterval($interval)
    {
        if ($this->progressIndicatorRunning) {
            return;
        }
        $this->autoDisplayInterval = $interval;
    }

    /**
     * @return bool
     */
    public function hideProgressIndicator()
    {
        $result = $this->progressBarDisplayed;
        if ($this->progressIndicatorRunning && $this->progressBarDisplayed) {
            $this->progressBar->clear();
            // Hack: progress indicator does not reset cursor to beginning of line on 'clear'
            $this->output->write("\x0D");
            $this->progressBarDisplayed = false;
        }
        return $result;
    }

    public function showProgressIndicator()
    {
        if ($this->progressIndicatorRunning && !$this->progressBarDisplayed && isset($this->progressBar)) {
            $this->progressBar->display();
            $this->progressBarDisplayed = true;
            $this->advanceProgressIndicatorCachedSteps();
        }
    }

    /**
     * @param bool $visible
     */
    public function restoreProgressIndicator($visible)
    {
        if ($visible) {
            $this->showProgressIndicator();
        }
    }

    /**
     * @param int $totalSteps
     * @param \Robo\Contract\TaskInterface $owner
     */
    public function startProgressIndicator($totalSteps, $owner)
    {
        if (!isset($this->progressBar)) {
            return;
        }

        $this->progressIndicatorRunning = true;
        if (!isset($this->owner)) {
            $this->owner = $owner;
            $this->startTimer();
            $this->totalSteps = $totalSteps;
            $this->autoShowProgressIndicator();
        }
    }

    public function autoShowProgressIndicator()
    {
        if (($this->autoDisplayInterval < 0) || !isset($this->progressBar) || !$this->output->isDecorated()) {
            return;
        }
        if ($this->autoDisplayInterval <= $this->getExecutionTime()) {
            $this->autoDisplayInterval = -1;
            $this->progressBar->start($this->totalSteps);
            $this->showProgressIndicator();
        }
    }

    /**
     * @return bool
     */
    public function inProgress()
    {
        return $this->progressIndicatorRunning;
    }

    /**
     * @param \Robo\Contract\TaskInterface $owner
     */
    public function stopProgressIndicator($owner)
    {
        if ($this->progressIndicatorRunning && ($this->owner === $owner)) {
            $this->cleanup();
        }
    }

    protected function cleanup()
    {
        $this->progressIndicatorRunning = false;
        $this->owner = null;
        if ($this->progressBarDisplayed) {
            $this->progressBar->finish();
            // Hack: progress indicator does not always finish cleanly
            $this->output->writeln('');
            $this->progressBarDisplayed = false;
        }
        $this->stopTimer();
    }

    /**
     * Erase progress indicator and ensure it never returns.  Used
     * only during error handlers or to permanently remove the progress bar.
     */
    public function disableProgressIndicator()
    {
        $this->cleanup();
        // ProgressIndicator is shared, so this permanently removes
        // the program's ability to display progress bars.
        $this->progressBar = null;
    }

    /**
     * @param int $steps
     */
    public function advanceProgressIndicator($steps = 1)
    {
        $this->cachedSteps += $steps;
        if ($this->progressIndicatorRunning) {
            $this->autoShowProgressIndicator();
            // We only want to call `advance` if the progress bar is visible,
            // because it always displays itself when it is advanced.
            if ($this->progressBarDisplayed) {
                return $this->advanceProgressIndicatorCachedSteps();
            }
        }
    }

    protected function advanceProgressIndicatorCachedSteps()
    {
        $this->progressBar->advance($this->cachedSteps);
        $this->cachedSteps = 0;
    }
}
