<?php

/**
 * @file
 * Contains \LakeDrops\Docker4Drupal\Handler.
 */

namespace LakeDrops\Docker4Drupal;

use Composer\Package\PackageInterface;
use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Script\Event as ScriptEvent;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Yaml\Yaml;

class Handler {

  /**
   * @var \Composer\Composer
   */
  protected $composer;

  /**
   * @var \Composer\IO\IOInterface
   */
  protected $io;

  /**
   * @var PackageInterface
   */
  protected $drupalCorePackage;

  /**
   * Handler constructor.
   *
   * @param Composer $composer
   * @param IOInterface $io
   */
  public function __construct(Composer $composer, IOInterface $io) {
    $this->composer = $composer;
    $this->io = $io;
  }

  /**
   * Look up the Drupal core package object, or return it from where we cached
   * it in the $drupalCorePackage field.
   *
   * @return PackageInterface
   */
  protected function getDrupalCorePackage() {
    if (!isset($this->drupalCorePackage)) {
      $this->drupalCorePackage = $this->getPackage('drupal/core');
    }
    return $this->drupalCorePackage;
  }

  /**
   * Retrieve a package from the current composer process.
   *
   * @param string $name
   *   Name of the package to get from the current composer installation.
   *
   * @return PackageInterface
   */
  protected function getPackage($name) {
    return $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($name, '*');
  }

  /**
   * Configure Drupal Project for Docker.
   *
   * @param ScriptEvent $event
   */
  public function configureProject($event) {

    // We only do the fancy stuff for developers
    if (!$event->isDevMode()) {
      return;
    }

    $options = $this->getOptions();
    $fs = new Filesystem();
    $installationManager = $this->composer->getInstallationManager();

    $drupalCorePackage = $this->getDrupalCorePackage();
    if (!$drupalCorePackage) {
      // We are called too early, Drupal core is not available yet.
      return;
    }
    $corePath = $installationManager->getInstallPath($drupalCorePackage);

    // Directory where the root project is being created
    $projectRoot = getcwd();
    // Directory where Drupal's index.php is located
    $webRoot = dirname($corePath);
    // Directory where this plugin is being installed
    $pluginRoot = $installationManager->getInstallPath($this->getPackage('lakedrops/docker4drupal'));

    // If the d8-project-scaffold plugin is present we only execute this one
    // if $force is TRUE. This way we can make sure that we get executed after
    // d8-project-scaffold.
    $settingsPath = $webRoot . '/sites/default';
    if ($this->getPackage('lakedrops/d8-project-scaffold')) {
      if (!$fs->exists($projectRoot . '/settings/default')) {
        return;
      }
      $settingsPath = 'settings/default';
    }

    // Provide all the requiired files
    $twig_loader = new \Twig_Loader_Array([]);
    $twig = new \Twig_Environment($twig_loader);
    $options['webRoot'] = $webRoot . '/';
    foreach ($this->getFiles($projectRoot, $webRoot, $settingsPath) as $template => $def) {
      $file = $def['dest'] . '/' . $template;
      if (!$fs->exists($file)) {
        $twig_loader->setTemplate($template, file_get_contents($pluginRoot . '/templates/' . $template . '.twig'));
        $rendered = $twig->render($template, $options);
        file_put_contents($file, $rendered);
      }
      if (isset($def['link']) && ($def['link'] != $settingsPath)) {
        $link = $def['link'] . '/' . $template;
        if (!$fs->exists($link)) {
          $rel = substr($fs->makePathRelative($file, $projectRoot . '/' . $link), 3, -1);
          $fs->symlink($rel, $link);
        }
      }
      $fs->chmod($file, 0664);
    }

    // Make sure that settings.docker.php gets called from settings.php
    $settingsPhpFile = $settingsPath . '/settings.php';
    if ($fs->exists(($settingsPhpFile))) {
      $settingsPhp = file_get_contents($settingsPhpFile);
      if (strpos($settingsPhp, 'settings.docker.php') === FALSE) {
        $settingsPhp .= "\n\nif (file_exists(__DIR__ . '/settings.docker.php')) {\n  include __DIR__ . '/settings.docker.php';\n}\n";
        file_put_contents($settingsPhpFile, $settingsPhp);
      }
    }

    // Initialize local git working copy
    try {
      foreach ($this->getFiles($projectRoot, $webRoot, $settingsPath) as $template => $def) {
        $this->git('ignore ' . $template);
      }
    }
    catch (\Exception $ex) {
      // We're ignoring this for now
    }

    // Update host wider traefik container
    $traefikPath = $_SERVER['HOME'] . '/.traefik';
    $traefikFile = $traefikPath . '/docker-compose.yml';
    if ($fs->exists($traefikFile)) {
      $traefik = Yaml::parse(file_get_contents($traefikFile));
    }
    else {
      $fs->mkdir($traefikPath);
      $traefik = [
        'version' => '2',
        'services' => [
          'traefik' => [
            'image' => 'traefik',
            'restart' => 'unless-stopped',
            'command' => '-c /dev/null --web --docker --logLevel=DEBUG',
            'networks' => [],
            'ports' => [
              '8000:80',
              '8080:8080',
            ],
            'volumes' => [
              '/var/run/docker.sock:/var/run/docker.sock',
            ],
          ],
        ],
        'networks' => [],
      ];
    }
    if (!in_array($options['projectname'], $traefik['services']['traefik']['networks'])) {
      $traefik['services']['traefik']['networks'][] = $options['projectname'];
      $traefik['networks'][$options['projectname']] = [
        'external' => [
          'name' => $options['projectname'] . '_default',
        ],
      ];
      file_put_contents($traefikFile, Yaml::dump($traefik, 9, 2));
      exec('docker network create ' . $options['projectname'] . '_default');
      exec('docker-compose -f ' . $traefikFile . ' --project-name traefik stop');
      exec('docker-compose -f ' . $traefikFile . ' --project-name traefik up -d');
    }

  }

  protected function getFiles($projectRoot, $webRoot, $settingsPath) {
    return [
      'settings.docker.php' => [
        'dest' => $projectRoot . '/' . $settingsPath,
        'link' => $webRoot . '/sites/default',
      ],
      'docker-compose.yml' => [
        'dest' => $projectRoot,
      ],
      'aliases.drushrc.php' => [
        'dest' => $projectRoot . '/drush',
      ],
      'drushrc.php' => [
        'dest' => $projectRoot . '/drush',
      ],
      '.env' => [
        'dest' => $projectRoot,
      ],
    ];
  }

  /**
   * Retrieve excludes from optional "extra" configuration.
   *
   * @return array
   */
  protected function getOptions() {
    $extra = $this->composer->getPackage()->getExtra() + ['docker4drupal' => []];
    $options = $extra['docker4drupal'] + [
        'projectname' => str_replace([' ', '-', '_', '.'], '', basename(getcwd())),
        'docker0' => [
          'ip' => $this->getLocalIpv4('docker0'),
        ],
        'drupal' => [
          'version' => '8',
        ],
        'php' => [
          'version' => '7.0',# 5.3|5.6|7.1
          'xdebug' => 1,
        ],
        'nginx' => [
          'version' => '1.10',
        ],
        'varnish' => [
          'enable' => 0,
        ],
        'solr' => [
          'enable' => 0,
        ],
        'node' => [
          'enable' => 0,
        ],
      ];
    return $options;
  }

  /**
   * Wrapper for git command in the root directory.
   *
   * @param $command
   *   Git command name, arguments and/or options.
   * @throws \Exception
   */
  protected function git($command) {
    passthru(escapeshellcmd('git -c "user.email=d8-project@lakedrops.com" ' . $command), $exit_code);
    if ($exit_code !== 0) {
      throw new \Exception('Git returned a non-zero exit code');
    }
  }

  private function getLocalIpv4($interface = NULL) {
    $out = explode(PHP_EOL, shell_exec('LC_ALL=C /sbin/ifconfig'));
    $local_addrs = array();
    $ifname = 'unknown';
    foreach ($out as $str) {
      $matches = array();
      if (preg_match('/^([a-z0-9]+)(:\d{1,2})?(\s)+Link/', $str, $matches)) {
        $ifname = $matches[1];
        if(strlen($matches[2])>0) {
          $ifname .= $matches[2];
        }
      } elseif (preg_match('/inet addr:((?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:[.](?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3})\s/', $str, $matches)) {
        $local_addrs[$ifname] = $matches[1];
      }
    }

    if (!isset($interface)) {
      return $local_addrs;
    }
    return isset($local_addrs[$interface]) ? $local_addrs[$interface] : '127.0.0.1';
  }

}