<?php
/**
 * Piwik - free/libre analytics platform
 *
 * @link http://piwik.org
 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 *
 */

namespace Piwik\Plugins\CoreConsole\Commands;

use Piwik\Columns\Dimension;
use Piwik\Piwik;
use Piwik\Plugin\Manager;
use Piwik\Plugin\Report;
use Piwik\Plugin\ReportsProvider;
use Piwik\Translate;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class GenerateReport extends GeneratePluginBase
{
    protected function configure()
    {
        $this->setName('generate:report')
            ->setDescription('Adds a new report to an existing plugin')
            ->addOption('pluginname', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin which does not have a menu defined yet')
            ->addOption('reportname', null, InputOption::VALUE_REQUIRED, 'The name of the report you want to create')
            ->addOption('category', null, InputOption::VALUE_REQUIRED, 'The name of the category the report belongs to')
            ->addOption('dimension', null, InputOption::VALUE_OPTIONAL, 'The name of the dimension in case your report has a dimension')
            ->addOption('documentation', null, InputOption::VALUE_REQUIRED, 'A documentation that explains what your report is about');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $pluginName    = $this->getPluginName($input, $output);
        $this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);

        $reportName    = $this->getReportName($input, $output);
        $category      = $this->getCategory($input, $output, $pluginName);
        $documentation = $this->getDocumentation($input, $output);
        list($dimension, $dimensionClass) = $this->getDimension($input, $output, $pluginName);

        $order   = $this->getOrder($category);
        $apiName = $this->getApiName($reportName);

        $exampleFolder  = PIWIK_INCLUDE_PATH . '/plugins/ExampleReport';
        $replace        = array('GetExampleReport'  => ucfirst($apiName),
                                'getExampleReport'  => lcfirst($apiName),
                                'getApiReport'      => lcfirst($apiName),
                                'ExampleCategory'   => $category,
                                'ExampleReportName' => $this->makeTranslationIfPossible($pluginName, $reportName),
                                'ExampleReportDocumentation' => $documentation,
                                '999'               => $order,
                                'new ExitPageUrl()' => $dimension,
                                'use Piwik\Plugins\Actions\Columns\ExitPageUrl;' => $dimensionClass,
                                'ExampleReport'     => $pluginName,
        );

        $whitelistFiles = array('/Reports', '/Reports/Base.php', '/Reports/GetExampleReport.php');

        if (file_exists($this->getPluginPath($pluginName) . '/API.php')) {
            $this->copyTemplateMethodToExisitingClass('Piwik\Plugins\ExampleReport\API', 'getExampleReport', $replace);
        } else {
            $whitelistFiles[] = '/API.php';
        }

        $this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);

        $this->writeSuccessMessage($output, array(
            sprintf('plugins/%s/Reports/%s.php for %s generated.', $pluginName, ucfirst($apiName)),
            'You should now implement the method called <comment>"' . lcfirst($apiName) . '()"</comment> in API.php',
           // 'Read more about this here: link to developer guide',
            'Enjoy!'
        ));
    }

    private function getOrder($category)
    {
        $order = 1;

        $reports = new ReportsProvider();

        foreach ($reports->getAllReports() as $report) {
            if ($report->getCategoryId() === $category) {
                if ($report->getOrder() > $order) {
                    $order = $report->getOrder() + 1;
                }
            }
        }

        return $order;
    }

    private function getApiName($reportName)
    {
        $reportName = trim($reportName);
        $reportName = str_replace(' ', '', $reportName);
        $reportName = preg_replace("/[^A-Za-z0-9]/", '', $reportName);

        $apiName = 'get' . ucfirst($reportName);

        return $apiName;
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     * @return array
     * @throws \RuntimeException
     */
    protected function getReportName(InputInterface $input, OutputInterface $output)
    {
        $validate = function ($reportName) {
            if (empty($reportName)) {
                throw new \InvalidArgumentException('Please enter the name of your report');
            }

            if (preg_match("/[^A-Za-z0-9 ]/", $reportName)) {
                throw new \InvalidArgumentException('Only alpha numerical characters and whitespaces are allowed');
            }

            return $reportName;
        };

        $reportName = $input->getOption('reportname');

        if (empty($reportName)) {
            $dialog = $this->getHelperSet()->get('dialog');
            $reportName = $dialog->askAndValidate($output, 'Enter the name of your report, for instance "Browser Families": ', $validate);
        } else {
            $validate($reportName);
        }

        $reportName = ucfirst($reportName);

        return $reportName;
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     * @return array
     * @throws \RuntimeException
     */
    protected function getDocumentation(InputInterface $input, OutputInterface $output)
    {
        $validate = function ($documentation) {
            if (empty($documentation)) {
                return '';
            }

            return $documentation;
        };

        $documentation = $input->getOption('documentation');

        if (empty($documentation)) {
            $dialog = $this->getHelperSet()->get('dialog');
            $documentation = $dialog->askAndValidate($output, 'Enter a documentation that describes the data of your report (you can leave it empty and define it later): ', $validate);
        } else {
            $validate($documentation);
        }

        $documentation = ucfirst($documentation);

        return $documentation;
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     * @param string $pluginName
     * @return array
     * @throws \RuntimeException
     */
    protected function getCategory(InputInterface $input, OutputInterface $output, $pluginName)
    {
        $path = $this->getPluginPath($pluginName) . '/Reports/Base.php';
        if (file_exists($path)) {
            // category is already defined in base.php
            return '';
        }

        $validate = function ($category) {
            if (empty($category)) {
                throw new \InvalidArgumentException('Please enter the name of the category your report belongs to');
            }

            return $category;
        };

        $category = $input->getOption('category');

        $reports = new ReportsProvider();

        $categories = array();
        foreach ($reports->getAllReports() as $report) {
            if ($report->getCategoryId()) {
                $categories[] = Piwik::translate($report->getCategoryId());
            }
        }
        $categories = array_values(array_unique($categories));

        if (empty($category)) {
            $dialog = $this->getHelperSet()->get('dialog');
            $category = $dialog->askAndValidate($output, 'Enter the report category, for instance "Visitor" (you can reuse any existing category or define a new one): ', $validate, false, null, $categories);
        } else {
            $validate($category);
        }

        $translationKey = Translate::findTranslationKeyForTranslation($category);
        if (!empty($translationKey)) {
            return $translationKey;
        }

        $category = ucfirst($category);

        return $category;
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     * @param string $pluginName
     * @return array
     * @throws \RuntimeException
     */
    protected function getDimension(InputInterface $input, OutputInterface $output, $pluginName)
    {
        $dimensions = array();
        $dimensionNames = array();

        $reports = new ReportsProvider();

        foreach ($reports->getAllReports() as $report) {
            $dimension = $report->getDimension();
            if (is_object($dimension)) {
                $name = $dimension->getName();
                if (!empty($name)) {
                    $dimensions[$name] = get_class($dimension);
                    $dimensionNames[]  = $name;
                }
            }
        }

        $plugin     = Manager::getInstance()->loadPlugin($pluginName);
        $dimensions = Dimension::getAllDimensions();
        $dimensions = array_merge($dimensions, Dimension::getDimensions($plugin));

        foreach ($dimensions as $dimension) {
            $name = $dimension->getName();
            if (!empty($name)) {
                $dimensions[$name] = get_class($dimension);
                $dimensionNames[]  = $name;
            }
        }

        $dimensionNames = array_values(array_unique($dimensionNames));

        $validate = function ($dimension) use ($dimensions) {
            if (empty($dimension)) {
                return '';
            }

            if (!empty($dimension) && !array_key_exists($dimension, $dimensions)) {
                throw new \InvalidArgumentException('Leave dimension either empty or use an existing one. You can also create a new dimension by calling .console generate:dimension before generating this report.');
            }

            return $dimension;
        };

        $actualDimension = $input->getOption('dimension');

        if (null === $actualDimension) {
            $dialog = $this->getHelperSet()->get('dialog');
            $actualDimension = $dialog->askAndValidate($output, 'Enter the report dimension, for instance "Browser" (you can leave it either empty or use an existing one): ', $validate, false, null, $dimensionNames);
        } else {
            $validate($actualDimension);
        }

        if (empty($actualDimension)) {
            return array('null', '');
        }

        $className = $dimensions[$actualDimension];
        $parts = explode('\\', $className);
        $name = end($parts);

        return array('new ' . $name . '()', 'use ' . $className . ';');
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     * @return array
     * @throws \RuntimeException
     */
    protected function getPluginName(InputInterface $input, OutputInterface $output)
    {
        $pluginNames = $this->getPluginNames();
        $invalidName = 'You have to enter a name of an existing plugin.';

        return $this->askPluginNameAndValidate($input, $output, $pluginNames, $invalidName);
    }

}
