<?php

/**
 * @file
 * A drush script that imports/updates migrated pages.
 */

require_once(__DIR__ . "/vendor/autoload.php");

use Drupal\node\Entity\Node;
use Drupal\paragraphs\Entity\Paragraph;
use PHPHtmlParser\Dom;

// conversion from external terms to internal terms
$nav_terms = []; // not used

// This is the csv file holdings the list of incoming legacy URLs.
// @see README.md
$source_urls_csv = dirname(__FILE__) . '/../source_urls.csv';

if (file_exists($source_urls_csv)) {
  // Log all the errors together.
  $error_log = [];

  $file_lines = file($source_urls_csv);
  // Get rid of the CSV header.
  array_shift($file_lines);

  // Array to hold already located term names.
  $navigation_terms = [];

  // Iterate through all the lines and extract the URL and title.
  foreach ($file_lines as $index => $file_line) {
    $line_tokens = array_map(
      function ($value) {
        return trim($value);
      },
      array_filter(
        explode("\t", $file_line),
        function ($value) {
          return !empty(trim($value));
        }
      )
    );

    $title = trim(array_shift($line_tokens));
    $title = empty($title) ? "No title found" : $title;
    $url = trim(array_shift($line_tokens));

    $file_path = __DIR__ . '/../content/' . str_replace('http://', '', $url);

    if (!file_exists($file_path)) {
      fwrite(STDERR, "File not found: $file_path; Source URL unreachable?\n");
      continue;
    }

    $dom = new Dom;
    $dom->loadFromFile($file_path);
    $body = dom_extract_body($dom);
    $intro = dom_extract_intro($dom);
    // remove the intro from the body
    $body = str_replace($intro, '', $body);

    $nids = \Drupal::entityQuery('node')
      ->condition('type', 'page')
      ->condition('field_migration_source_url', $url)
      ->execute();

    $breadcrumbs = join('>>', dom_extract_breadcrumbs($dom));
    $term_keys = (array) @$nav_terms[$breadcrumbs];

    $navigation_term_key = implode('>>', $term_keys);
    print "$navigation_term_key\n";
    $term_name = array_pop($term_keys);

    // Some memoization in order here.
    if (isset($navigation_terms[$navigation_term_key])) {
      $term_tid = $navigation_terms[$navigation_term_key];
    }
    else {
      $term = locate_term($term_name, $term_keys);
      if ($term) {
        $term_tid = $navigation_terms[$navigation_term_key] = $term->id();
      }
      // The term was not found, should be logged somewhere.
      else {
        $term_tid = 0;
      }
    }

    $paragraph = Paragraph::create([
      'type' => 'text',
      'field_paragraph' => ['value' => $body, 'format' => 'rich_text'],
      'langcode' => 'de',
    ]);
    $paragraph->save();

    // Create.
    if (empty($nids)) {
      $node_values = [
        'type' => 'page',
        'title' => $title,
        'body' => ['value' => $intro, 'format' => 'rich_text'],
        'field_migration_source_url' => $url,
        'status' => 0,
        'uid' => 1,
        'field_page_type'  => [
          ['target_id' => 81] // article
        ],
        'langcode' => 'de',
      ];
      if ($term_tid > 0 && $term) {
        $node_values['field_navigation'] = $term->id();
      }
      $node = Node::create($node_values);
      $node->field_paragraphs->setValue([$paragraph]);
    }
    // Update.
    else {
      $nid = array_shift($nids);
      $node = Node::load($nid);
      $node->title = $title;
      $node->body = ['value' => $intro, 'format' => 'rich_text'];
      $node->field_page_type  = 81;
      if ($term_tid > 0 && $term) {
        $node->field_navigation = $term;
      }

      $node->field_paragraphs->setValue([$paragraph]);
    }
    // Set all the content in "archived" state
    $node->field_in_archive = 1;
    $node->set('moderation_state', "published");
    $node->save();

    /*
     * Insert all the translation for the node and its paragraphs
     * The language_map icons get the values from the csv translation_map.csv
     * That is generated from the old website DOM reading the language dropdown.
     */
    $translation_map = get_language_map();

    if (is_array($translation_map) && isset($translation_map[$url])) {
      foreach ($translation_map[ $url ] as $lang => $url_tr) {

        $lang = strtolower($lang);

        echo "We are processing $lang; $url\n";

        // German is the defaul translation and is already created and saved.
        if ($lang != 'de' && trim($url_tr) != '') {

          // All the translation are retrieved from a local copy of the page.
          $file_path_tr = __DIR__ . '/../content/' . str_replace('http://', '', $url_tr);
          if (!file_exists($file_path_tr)) {
            fwrite(STDERR, "File not found: $file_path_tr; Source URL unreachable?\n");
            continue;
          }

          // Lets translate the node before with the title, intro and pagetype.
          $dom_tr = new Dom;
          $dom_tr->loadFromFile($file_path_tr);
          $body = dom_extract_body($dom_tr);
          $intro = dom_extract_intro($dom_tr);
          $title = dom_extract_title($dom_tr);
          $title = empty($title) ? "No title found for $lang" : $title;
          // Add translation for the node
          if (!$node->hasTranslation($lang)) {
            $entity_array = $node->toArray();
            $translated_fields = [];
            $translated_fields['title'] = $title;
            $translated_fields['body'] = [
              'value' => $intro,
              'format' => 'rich_text',
            ];
            $translated_entity_array = array_merge($entity_array, $translated_fields);
            $node->addTranslation($lang, $translated_entity_array)->save();
          }
          // Translate paragraphs
          else if ($node->hasTranslation($lang)) {
            $tr_node = $node->getTranslation($lang);
            $tr_node->body = ['value' => $intro, 'format' => 'rich_text'];
            $tr_node->title = $title;
            $tr_node->save();
          }

          $node->save();

          // The content of the page is stored into the paragraph bundle text.
          foreach ($node->field_paragraphs->referencedEntities() as $paragraph) {
            echo "Paragraph translation";
            $paragraph_data = Paragraph::load($paragraph->id())->toArray();
            $paragraph_data['field_paragraph'] = $body;
            if ($paragraph->hasTranslation($lang)) {
              $paragraph = $paragraph->getTranslation($lang);
              $paragraph->addTranslation($lang, $paragraph_data);
            }
            else {
              $paragraph->addTranslation($lang, $paragraph_data);
            }
            $paragraph->save();
          }
          $node->save();
        }
      }
    }


    // Logging problematic Navigation paths to STDERR.
    if ($term_tid == 0) {
      $error_log[] = t(
        '[line @line] @url have a wrong navigation path [@navigation_term_key]',
        [
          '@url' => $url,
          '@line' => $index,
          '@navigation_term_key' => $navigation_term_key,
        ]
      );
    }
  }

  // Report all the errors in one single log message.
  if ($error_log) {
    fwrite(STDERR, implode("\n", $error_log));
    \Drupal::logger('SVV legacy content import')->error(implode("<br>\n", $error_log));
  }
}

/**
 * Finds a term given its name  and parents, in the 'navigation' vocabulary.
 */
function locate_term($term_name, $parents_names) {
  if (isset($term_name)) {
    $terms = get_terms_by_name($term_name, 'navigation');

    foreach ($terms as $tid => $term) {
      $parents = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadAllParents($tid);
      array_shift($parents);

      // We need at least one parent in the hierarchy to identify the term
      // No need to check all of them, because the hierarchy and levels can change.
      if ($parents_names) {
        $term_parents_found = [];
        foreach ($parents as $parent) {
          if (in_array($parent->label(), $parents_names)) {
            $term_parents_found[] = $parent->label();
          }
        }

        // If neither one parent has been found return false.
        if (!count($term_parents_found)) {
          return NULL;
        }
      }

      if ($term) {
        return $term;
      }
    }
  }
  return NULL;
}

/**
 * Finds a term given its name  and parents, in the 'navigation' vocabulary.
 */
function get_terms_by_name($name = NULL, $vid = NULL) {
  $properties = [];
  if (!empty($name)) {
    $properties['name'] = $name;
  }
  if (!empty($vid)) {
    $properties['vid'] = $vid;
  }
  $terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties($properties);
  return $terms;
}


/**
 * Extract bread crumbs from DOM as array.
 */
function dom_extract_breadcrumbs($dom) {
  $links = [];
  $breadcrumbs = $dom->find('.breadcrumb-show a');

  foreach ($breadcrumbs as $link) {
    $links[] = trim($link->text());
  }

  array_shift($links);

  return $links;
}

/**
 * Extract filtered body content from DOM as string.
 */
function dom_extract_body($dom) {
  $contents_area = $dom->find('#content-area');
  $children = [];
  try {
    $contents = $contents_area->innerHTML;

    $children = dom_filter_children_flat($contents_area, function ($child) {
      return in_array($child->getTag()->name(), ['p', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
    });

    $children = array_map(function ($child) {
      return trim($child->innerHTML) ? $child->outerHTML : '';
    }, $children);
  }
  catch (\Exception $ex) {
    fwrite(STDERR, 'Exception: ' . $ex->getMessage() . "\n");
    \Drupal::logger('SVV legacy content import')->error(__FUNCTION__ . ': ' . $ex->getMessage());    
  }

  return join('', $children);
}

/**
 * Extract filtered body content from DOM as string.
 */
function dom_extract_intro($dom) {
  $contents_area = $dom->find('#content-area .intro-wrapper');
  $children = [];
  try {
    $contents = $contents_area->innerHTML;
    
      $children = dom_filter_children_flat($contents_area, function ($child) {
        return in_array($child->getTag()->name(), ['p', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
      });
    
      $children = array_map(function ($child) {
        return trim($child->innerHTML) ? $child->outerHTML : '';
      }, $children);    
  }
  catch (\Exception $ex) {
    fwrite(STDERR, 'Exception: ' . $ex->getMessage() . "\n");
    \Drupal::logger('SVV legacy content import')->error(__FUNCTION__ . ': ' . $ex->getMessage());    
  }
  return join('', $children);    
}

/**
 * Extract filtered body content from DOM as string.
 */
function dom_extract_title($dom) {
  $contents_area = $dom->find('h1.title');

  $contents = '';
  try {
    $contents = $contents_area->innerHTML;
  }
  catch (\Exception $ex) {
    fwrite(STDERR, 'Exception: ' . $ex->getMessage() . "\n");
    \Drupal::logger('SVV legacy content import')->error(__FUNCTION__ . ': ' . $ex->getMessage());    
  }
  
  return $contents;
}

/**
 * Filter children from given DOM node with given predicate.
 */
function dom_filter_children_flat($node, $predicate) {
  $children = [];

  foreach ($node->getChildren() as $child) {
    if ($predicate($child)) {
      $children[] = $child;
    }

    if (method_exists($child, 'getChildren')) {
      $children = array_merge($children, dom_filter_children_flat($child, $predicate));
    }
  }

  return $children;
}

/*
 * Convert the translation_map.csv into an array to retrieve quickly the
 * translations related to the german url.
 */
function get_language_map() {
  // This is the csv file holdings the list of incoming legacy URLs.
  // @see README.md
  $urls = dirname(__FILE__) . '/../translations_map.csv';
  $translations_map = [];
  if (file_exists($urls)) {
    $map = ['DE', 'FR', 'IT', 'EN'];

    $file_lines = file($urls);
    // Get rid of the CSV header.
    array_shift($file_lines);

    // Array to hold already located term names.
    $navigation_terms = [];

    // Iterate through all the lines and extract the URL and title.
    foreach ($file_lines as $index => $file_line) {
      $line = str_getcsv($file_line);
      $translations_map[ $line[0] ] = array_combine($map, $line);
    }
  }
  return $translations_map;
}
