<?php
// Copyright 2004-present Facebook. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Facebook\WebDriver\Firefox;

use Facebook\WebDriver\Exception\WebDriverException;
use FilesystemIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ZipArchive;

class FirefoxProfile {

  /**
   * @var array
   */
  private $preferences = array();

  /**
   * @var array
   */
  private $extensions = array();

  /**
   * @var array
   */
  private $extensions_datas = array();

  /**
   * @var string
   */
  private $rdf_file;

  /**
   * @param string $extension The path to the xpi extension.
   * @return FirefoxProfile
   */
  public function addExtension($extension) {
    $this->extensions[] = $extension;
    return $this;
  }

  /**
   * @param string $extension_datas The path to the folder containing the datas to add to the extension
   * @return FirefoxProfile
   */
  public function addExtensionDatas($extension_datas) {
    if (!is_dir($extension_datas)) {
      return;
    }

    $this->extensions_datas[basename($extension_datas)] = $extension_datas;
    return $this;
  }

  /**
   * @param string $rdf_file The path to the rdf file
   * @return FirefoxProfile
   */
  public function setRdfFile($rdf_file) {
    if (!is_file($rdf_file)) {
      return;
    }

    $this->rdf_file = $rdf_file;
    return $this;
  }

  /**
   * @param string $key
   * @param string|bool|int $value
   * @return FirefoxProfile
   * @throws WebDriverException
   */
  public function setPreference($key, $value) {
    if (is_string($value)) {
      $value = sprintf('"%s"', $value);
    } else if (is_int($value)) {
      $value = sprintf('%d', $value);
    } else if (is_bool($value)) {
      $value = $value ? 'true' : 'false';
    } else {
      throw new WebDriverException(
        'The value of the preference should be either a string, int or bool.');
    }
    $this->preferences[$key] = $value;
    return $this;
  }

  /**
   * @param $key
   * @return mixed
   */
  public function getPreference($key)
  {
    if (array_key_exists($key, $this->preferences)) {
      return $this->preferences[$key];
    }

    return null;
  }

  /**
   * @return string
   */
  public function encode() {
    $temp_dir = $this->createTempDirectory('WebDriverFirefoxProfile');

    if (isset($this->rdf_file)) {
      copy($this->rdf_file, $temp_dir . DIRECTORY_SEPARATOR . "mimeTypes.rdf");
    }

    foreach ($this->extensions as $extension) {
      $this->installExtension($extension, $temp_dir);
    }

    foreach ($this->extensions_datas as $dirname => $extension_datas) {
      mkdir($temp_dir . DIRECTORY_SEPARATOR . $dirname);
      $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($extension_datas, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
      foreach ($iterator as $item) {
        if ($item->isDir()) {
	  mkdir($temp_dir . DIRECTORY_SEPARATOR . $dirname . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
	} else {
	  copy($item, $temp_dir . DIRECTORY_SEPARATOR . $dirname . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
	}
      }
    }

    $content = "";
    foreach ($this->preferences as $key => $value) {
      $content .= sprintf("user_pref(\"%s\", %s);\n", $key, $value);
    }
    file_put_contents($temp_dir.'/user.js', $content);

    $zip = new ZipArchive();
    $temp_zip = tempnam(sys_get_temp_dir(), 'WebDriverFirefoxProfileZip');
    $zip->open($temp_zip, ZipArchive::CREATE);

    $dir = new RecursiveDirectoryIterator($temp_dir);
    $files = new RecursiveIteratorIterator($dir);
    foreach ($files as $name => $object) {
      if (is_dir($name)) {
        continue;
      }
      $dir_prefix = preg_replace(
        '#\\\\#',
        '\\\\\\\\',
        $temp_dir.DIRECTORY_SEPARATOR
      );
      $path = preg_replace("#^{$dir_prefix}#", "", $name);
      $zip->addFile($name, $path);
    }
    $zip->close();

    $profile = base64_encode(file_get_contents($temp_zip));

    // clean up
    $this->deleteDirectory($temp_dir);
    unlink($temp_zip);

    return $profile;
  }

  /**
   * @param string $extension The path to the extension.
   * @param string $profile_dir The path to the profile directory.
   * @return string The path to the directory of this extension.
   */
  private function installExtension($extension, $profile_dir) {
    $temp_dir = $this->createTempDirectory('WebDriverFirefoxProfileExtension');

    $this->extractTo($extension, $temp_dir);

    $install_rdf_path = $temp_dir.'/install.rdf';
    // This is a hacky way to parse the id since there is no offical
    // RDF parser library.
    $matches = array();
    $xml = file_get_contents($install_rdf_path);
    preg_match('#<em:id>([^<]+)</em:id>#', $xml, $matches);
    $ext_dir = $profile_dir.'/extensions/'.$matches[1];

    mkdir($ext_dir, 0777, true);

    $this->extractTo($extension, $ext_dir);

    // clean up
    $this->deleteDirectory($temp_dir);

    return $ext_dir;
  }

  /**
   * @param string $prefix Prefix of the temp directory.
   *
   * @return string The path to the temp directory created.
   * @throws WebDriverException
   */
  private function createTempDirectory($prefix = '') {
    $temp_dir = tempnam(sys_get_temp_dir(), $prefix);
    if (file_exists($temp_dir)) {
      unlink($temp_dir);
      mkdir($temp_dir);
      if (!is_dir($temp_dir)) {
        throw new WebDriverException('Cannot create firefox profile.');
      }
    }
    return $temp_dir;
  }

  /**
   * @param string $directory The path to the directory.
   */
  private function deleteDirectory($directory) {
    $dir = new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS);
    $paths = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::CHILD_FIRST);

    foreach ($paths as $path) {
      if ($path->isDir() && !$path->isLink()) {
        rmdir($path->getPathname());
      } else {
        unlink($path->getPathname());
      }
    }

    rmdir($directory);
  }

  /**
   * @param string $xpi        The path to the .xpi extension.
   * @param string $target_dir The path to the unzip directory.
   *
   * @return FirefoxProfile
   * @throws \Exception
   */
  private function extractTo($xpi, $target_dir) {
    $zip = new ZipArchive();
    if ($zip->open($xpi)) {
      $zip->extractTo($target_dir);
      $zip->close();
    } else {
      throw new \Exception("Failed to open the firefox extension. '$xpi'");
    }
    return $this;
  }
}
