<?php

namespace DeliciousBrains\WPMDB\Common;

use DeliciousBrains\WPMDB\Common\DryRun\DiffEntity;
use DeliciousBrains\WPMDB\Common\DryRun\DiffInterpreter;
use DeliciousBrains\WPMDB\Common\Error\ErrorLog;
use DeliciousBrains\WPMDB\Common\FormData\FormData;
use DeliciousBrains\WPMDB\Common\Http\Helper;
use DeliciousBrains\WPMDB\Common\Http\Http;
use DeliciousBrains\WPMDB\Common\Http\WPMDBRestAPIServer;
use DeliciousBrains\WPMDB\Common\MigrationState\MigrationStateManager;
use DeliciousBrains\WPMDB\Common\Properties\Properties;
use DeliciousBrains\WPMDB\Common\Replace\ReplacePairInterface;
use DeliciousBrains\WPMDB\Common\Replace\PairFactory;
use DeliciousBrains\WPMDB\Common\Sql\TableHelper;
use DeliciousBrains\WPMDB\Common\Util\Util;

class Replace
{

    /**
     * @var
     */
    protected $search;
    /**
     * @var
     */
    protected $replace;
    /**
     * @var
     */
    protected $subdomain_replaces_on;
    /**
     * @var
     */
    protected $intent;
    /**
     * @var
     */
    protected $base_domain;
    /**
     * @var
     */
    protected $site_domain;
    /**
     * @var
     */
    protected $site_details;
    /**
     * @var
     */
    protected $source_protocol;
    /**
     * @var
     */
    protected $destination_protocol;
    /**
     * @var
     */
    protected $destination_url;
    /**
     * @var bool
     */
    protected $is_protocol_mismatch = false;

    /**
     * @var
     */
    public $state_data;
    /**
     * @var TableHelper
     */
    public $table_helper;
    /**
     * @var MigrationStateManager
     */
    public $migration_state_manager;

    /**
     * @var
     */
    protected $table;
    /**
     * @var
     */
    protected $column;
    /**
     * @var
     */
    protected $row;
    /**
     * @var ErrorLog
     */
    protected $error_log;
    /**
     * @var Util\Util
     */
    protected $util;
    /**
     * @var array
     */
    protected $json_search;
    /**
     * @var array
     */
    protected $json_replace;
    /**
     * @var array
     */
    protected $json_replace_tables;
    /**
     * @var array
     */
    protected $json_replace_columns;
    /**
     * @var FormData
     */
    private static $form_data;
    /**
     * @var bool
     */
    protected $json_merged;

    /**
     * @var array
     */
    private $regex;

    /**
     * @var array
     */
    private $case_sensitive;

    /**
     * @var ReplacePairInterface[]
     */
    private $pairs = [];

    /**
     * @var Properties
     */
    private $properties;

    /**
     * @var PairFactory
     */
    private $pair_factory;

    /**
     * @var WPMDBRestAPIServer
     */
    private $rest_api_server;

    /**
     * @var Helper
     */
    private $http_helper;

    /**
     * @var Http
     */
    private $http;

    /**
     * @var DiffInterpreter
     */
    private $diff_interpreter;

    public function __construct(
        MigrationStateManager $migration_state_manager,
        TableHelper $table_helper,
        ErrorLog $error_log,
        Util $util,
        FormData $form_data,
        Properties $properties,
        PairFactory $pairs_factory,
        WPMDBRestAPIServer $rest_api_server,
        Helper $http_helper,
        Http $http,
        DiffInterpreter $diff_interpreter
    ) {
        $this->migration_state_manager = $migration_state_manager;
        $this->table_helper            = $table_helper;
        $this->error_log               = $error_log;
        $this->util                    = $util;
        $this->properties              = $properties;
        $this->pair_factory            = $pairs_factory;
        $this->rest_api_server         = $rest_api_server;
        $this->http_helper             = $http_helper;
        $this->http                    = $http;
        $this->diff_interpreter        = $diff_interpreter;
        self::$form_data               = $form_data;

        //Setup REST API routes
        add_action('rest_api_init', [$this, 'register_rest_routes']);
    }

    public function get($prop)
    {
        return $this->$prop;
    }

    public function set($prop, $value)
    {
        return $this->$prop = $value;
    }

    public function register($args)
    {
        $keys = array(
            'table',
            'search',
            'replace',
            'regex',
            'case_sensitive',
            'intent',
            'base_domain',
            'site_domain',
            'wpmdb',
            'site_details',
        );

        if (!is_array($args)) {
            throw new \InvalidArgumentException('WPMDB_Replace constructor expects the argument to be an array');
        }

        foreach ($keys as $key) {
            if (!isset($args[$key])) {
                throw new \InvalidArgumentException("WPMDB_Replace constructor expects '$key' key to be present in the array argument");
            }
        }

        $this->table                = $args['table'];
        $this->search               = $args['search'];
        $this->replace              = $args['replace'];
        $this->regex                = $args['regex'];
        $this->case_sensitive       = $args['case_sensitive'];
        $this->intent               = $args['intent'];
        $this->base_domain          = $args['base_domain'];
        $this->site_domain          = $args['site_domain'];
        $this->site_details         = $args['site_details'];
        $this->json_search          = '';
        $this->json_replace         = '';
        $this->json_replace_tables  = '';
        $this->json_replace_columns = '';
        $this->json_merged          = false;

        // Set diff interpreter table name
        $this->diff_interpreter->getGroup()->setTable($this->table);

        global $wpdb;

        $prefix = $wpdb->base_prefix;

        $this->json_replaces($prefix);
        $this->create_pairs();

        // Detect a protocol mismatch between the remote and local sites involved in the migration
        $this->detect_protocol_mismatch();
        return $this;
    }

    private function create_pairs($search = null, $replace = null, $json_pairs = false) {
        if (null === $search) {
            $search = $this->search;
        }
        if (null === $replace) {
            $replace = $this->replace;
        }

        foreach ($search as $key => $pattern) {
            if (!$json_pairs && array_key_exists($key, $this->regex) && true === $this->regex[$key]) {
                $this->pairs[] = $this->pair_factory->create($pattern, $replace[$key], PairFactory::REGEX);
                continue;
            }
            if ( array_key_exists($key, $this->case_sensitive) && true === $this->case_sensitive[$key]) {
                $this->pairs[] = $this->pair_factory->create($pattern, $replace[$key], PairFactory::CASE_SENSITIVE);
                continue;
            }
            $this->pairs[] = $this->pair_factory->create($pattern, $replace[$key], PairFactory::CASE_INSENSITIVE);
        }
    }

    public static function parse_find_replace_pairs($intent = '', $site_url = '')
    {
        $find_replace_pairs     = [
            'regex'          => [],
            'case_sensitive' => []
        ];
        $tmp_find_replace_pairs = [];
        $migration_options     = self::$form_data->getFormData();


        if(!empty($migration_options['regex'])) {
            $find_replace_pairs['regex'] = $migration_options['regex'];
        }

        if(!empty($migration_options['case_sensitive'])) {
            $find_replace_pairs['case_sensitive'] = $migration_options['case_sensitive'];
        }


        // Standard Pairs
        if (
            isset($migration_options['search_replace']['standard_search_replace'], $migration_options['search_replace']['standard_search_visible'])
            && !empty($migration_options['search_replace']['standard_search_replace'])
            && $migration_options['search_replace']['standard_search_visible']
        ) {
            $standard_pairs = $migration_options['search_replace']['standard_search_replace'];
            foreach ($standard_pairs as $key => $pair) {
                if (in_array($key, $migration_options['search_replace']['standard_options_enabled'])) {
                    $tmp_find_replace_pairs[$pair['search']] = $pair['replace'];
                }
            }
        }

        // Custom pairs
        if (
            isset($migration_options['search_replace']['custom_search_replace']) &&
            !empty($migration_options['search_replace']['custom_search_replace'])
        ) {
            $standard_pairs_count = count($tmp_find_replace_pairs);
            $custom_pairs         = $migration_options['search_replace']['custom_search_replace'];

            $i = 1;
            foreach ($custom_pairs as $pair) {
                $index = $i + $standard_pairs_count;
                $tmp_find_replace_pairs[$pair['replace_old']] = $pair['replace_new'];

                if(empty($migration_options['regex']) && isset($pair['regex'])) {
                    $find_replace_pairs['regex'][$index] = $pair['regex'];
                }

                if(empty($migration_options['case_sensitive']) && isset($pair['case_sensitive'])) {
                    $find_replace_pairs['case_sensitive'][$index] = $pair['case_sensitive'];
                }

                $i++;
            }
        }

        $tmp_find_replace_pairs = apply_filters('wpmdb_find_and_replace', $tmp_find_replace_pairs, $intent, $site_url);

        if (!empty($tmp_find_replace_pairs)) {
            $i = 1;
            foreach ($tmp_find_replace_pairs as $replace_old => $replace_new) {
                $find_replace_pairs['replace_old'][$i] = $replace_old;
                $find_replace_pairs['replace_new'][$i] = $replace_new;
                $i++;
            }
        }

        if (empty($find_replace_pairs)) {
            $find_replace_pairs['replace_old'] = [];
            $find_replace_pairs['replace_new'] = [];
        }

        return $find_replace_pairs;
    }

    /**
     * Determine whether to apply a subdomain replace over each value in the database.
     *
     * @return bool
     */
    function is_subdomain_replaces_on()
    {
        if (!isset($this->subdomain_replaces_on)) {
            $this->subdomain_replaces_on = (is_multisite() && is_subdomain_install() && !$this->has_same_base_domain() && apply_filters('wpmdb_subdomain_replace', true));
        }

        return $this->subdomain_replaces_on;
    }


    /**
     * Determine if the replacement has the same base domain as the search. Produces doubled replacement strings
     * otherwise.
     *
     * @return bool
     */
    function has_same_base_domain()
    {
        if ('push' !== $this->intent || 'pull' !== $this->intent) {
            $destination_url = $this->base_domain;
        } else {
            $destination_url = isset($this->destination_url) ? $this->destination_url : $this->site_details['local']['site_url'];
        }

        if (stripos($destination_url, $this->site_domain)) {
            return true;
        }

        return false;
    }


    /**
     * Automatically replace URLs for subdomain based multisite installations
     * e.g. //site1.example.com -> //site1.example.local for site with domain example.com
     * NB: only handles the current network site, does not work for additional networks / mapped domains
     *
     * @param $new
     *
     * @return mixed
     */
    function subdomain_replaces($new)
    {
        if (empty($this->base_domain)) {
            return $new;
        }

        $pattern     = '|//(.*?)\\.' . preg_quote($this->site_domain, '|') . '|';
        $replacement = '//$1.' . trim($this->base_domain);
        $new         = preg_replace($pattern, $replacement, $new);

        return $new;
    }

    /**
     * Detect a protocol mismatch between the remote and local sites involved in the migration
     *
     * @return bool
     */
    function detect_protocol_mismatch()
    {
        if (!isset($this->site_details['remote']) && 'import' !== $this->intent) {
            return false;
        }

        $wpmdb_home_urls = array(
            // TODO: rewrite unit tests that only pass site_url so that we can rely on home_url's existence
            'local' => isset($this->site_details['local']['home_url']) ? $this->site_details['local']['home_url'] : $this->site_details['local']['site_url'],
        );

        if ('import' !== $this->intent) {
            $wpmdb_home_urls['remote'] = isset($this->site_details['remote']['home_url']) ? $this->site_details['remote']['home_url'] : $this->site_details['remote']['site_url'];
        } else {
            $this->state_data = $this->migration_state_manager->set_post_data();

            if (!isset($this->state_data['import_info']) || !isset($this->state_data['import_info']['protocol'])) {
                return false;
            }
            $wpmdb_home_urls['remote'] = $this->state_data['import_info']['protocol'] . ':' . $this->state_data['import_info']['URL'];
        }

        /**
         * Filters the site_urls used to check if there is a protocol mismatch.
         *
         * @param array
         */
        $wpmdb_home_urls = apply_filters('wpmdb_replace_site_urls', $wpmdb_home_urls);

        $local_url_is_https  = false === stripos($wpmdb_home_urls['local'], 'https') ? false : true;
        $remote_url_is_https = false === stripos($wpmdb_home_urls['remote'], 'https') ? false : true;
        $local_protocol      = $local_url_is_https ? 'https' : 'http';
        $remote_protocol     = $remote_url_is_https ? 'https' : 'http';

        if (($local_url_is_https && !$remote_url_is_https) || (!$local_url_is_https && $remote_url_is_https)) {
            $this->is_protocol_mismatch = true;
        }

        if ('push' === $this->intent) {
            $this->destination_protocol = $remote_protocol;
            $this->source_protocol      = $local_protocol;
            $this->destination_url      = $wpmdb_home_urls['remote'];
        } else {
            $this->destination_protocol = $local_protocol;
            $this->source_protocol      = $remote_protocol;
            $this->destination_url      = $wpmdb_home_urls['local'];
        }

        return $this->is_protocol_mismatch;
    }

    /**
     *
     * Handles replacing the protocol if the local and destination don't have matching protocols (http > https and
     * vice-versa).
     *
     * Can be filtered to disable entirely.
     *
     * @param string $new
     * @param string $destination_url
     *
     * @return mixed
     */
    function do_protocol_replace($new, $destination_url)
    {
        /**
         * Filters $do_protocol_replace, return false to prevent protocol replacement.
         *
         * @param bool true                   If the replace should be skipped.
         * @param string $destination_url The URL of the target site.
         */
        $do_protocol_replace = apply_filters('wpmdb_replace_destination_protocol', true, $destination_url);

        if (true !== $do_protocol_replace) {
            return $new;
        }

        $parsed_destination = Util::parse_url($destination_url);
        unset($parsed_destination['scheme']);

        if (isset($parsed_destination['port'])) {
            $parsed_destination['port'] = ':' . $parsed_destination['port'];
        }

        $protocol_search  = $this->source_protocol . '://' . implode('', $parsed_destination);
        $protocol_replace = $destination_url;
        // JSON search & replace
        if (
            in_array($this->table, $this->json_replace_tables)
            && in_array($this->column, $this->json_replace_columns)
        ) {
            $protocol_search  = [$protocol_search, Util::json_encode_trim($protocol_search)];
            $protocol_replace = [$protocol_replace, Util::json_encode_trim($protocol_replace)];
        }
        $new              = str_ireplace($protocol_search, $protocol_replace, $new, $count);

        return $new;
    }


    public function maybe_merge_json_replaces()
    {
        if ($this->json_merged) {
            return false;
        }

        if (
            !in_array($this->table, $this->json_replace_tables) ||
            !in_array($this->column, $this->json_replace_columns)
        ) {
            return false;
        }

        if (empty($this->search) && empty($this->replace)) {
            return false;
        }

        if (!is_array($this->json_search) || !is_array($this->json_replace)) {
            return false;
        }

        //Create the json replace pairs.
        $this->create_pairs($this->json_search, $this->json_replace, true);

        //Only add json replacements once
        $this->json_merged = true;

        return true;
    }

    /**
     * Applies find/replace pairs to a given string.
     *
     * @param string $subject
     *
     * @return string
     */
    public function apply_replaces($subject)
    {
        $original = $subject;

        if (empty($this->search) && empty($this->replace)) {
            return $subject;
        }

        if (count($this->search) !== count($this->replace)) {
            return $subject;
        }

        $this->maybe_merge_json_replaces(); // Maybe merge in json_encoded find/replace values

        foreach ($this->pairs as $pair) {
            $subject = $pair->apply($subject);
        }

        if ($this->is_subdomain_replaces_on()) {
            $subject = $this->subdomain_replaces($subject);
        }

        if (true === $this->is_protocol_mismatch) {
            $subject = $this->do_protocol_replace($subject, $this->destination_url);
        }

        if ('find_replace' === $this->intent) {
            $this->diff_interpreter->compute(DiffEntity::create($original, $subject, $this->column, is_object($this->row) ? reset($this->row) : null));
        }

        return $subject;
    }

    /**
     * Take a serialized array and unserialize it replacing elements as needed and
     * unserialising any subordinate arrays and performing the replace on those too.
     *
     * Mostly from https://github.com/interconnectit/Search-Replace-DB
     *
     * @param mixed $data              Used to pass any subordinate arrays back to in.
     * @param bool  $serialized        Does the array passed via $data need serialising.
     * @param bool  $parent_serialized Passes whether the original data passed in was serialized
     * @param bool  $filtered          Should we apply before and after filters successively
     *
     * @return mixed    The original array with all elements replaced as needed.
     */
    function recursive_unserialize_replace($data, $serialized = false, $parent_serialized = false, $filtered = true)
    {
        $pre = apply_filters('wpmdb_pre_recursive_unserialize_replace', false, $data, $this);
        if (false !== $pre) {
            return $pre;
        }

        //Check if find and replace needs be skipped for the current table
        $skipped_tables = apply_filters('wpmdb_skip_search_replace_tables', []);
        if (in_array($this->table, $skipped_tables, true)) {
            return $data;
        }

        //If the intent is find_replace we need to prefix the tables with the temp prefix and wp base table prefix.
        $table_prefix = '';
        if ( 'find_replace' === $this->get_intent() ) {
            global $wpdb;
            $table_prefix = $this->properties->temp_prefix . $wpdb->base_prefix;
        }

        // Some options contain serialized self-references which leads to memory exhaustion. Skip these.
        if ( $this->table_is( 'options', $table_prefix ) && 'option_value' === $this->get_column() && is_serialized( $data ) ) {
            if ( preg_match( '/r\:\d+;/i', $data ) ) {
                return $data;
            }
        }

        $is_json           = false;
        $before_fired      = false;
        $successive_filter = $filtered;

        if (true === $filtered) {
            list($data, $before_fired, $successive_filter) = apply_filters('wpmdb_before_replace_custom_data', array(
                $data,
                $before_fired,
                $successive_filter,
            ), $this);
        }

        // some unserialized data cannot be re-serialized eg. SimpleXMLElements
        try {
            if (is_string($data) && ($unserialized = Util::unserialize($data, __METHOD__)) !== false) {
                // PHP currently has a bug that doesn't allow you to clone the DateInterval / DatePeriod classes.
                // We skip them here as they probably won't need data to be replaced anyway
                if ('object' == gettype($unserialized)) {
                    if ($unserialized instanceof \DateInterval || $unserialized instanceof \DatePeriod) {
                        return $data;
                    }
                    if ($unserialized instanceof \__PHP_Incomplete_Class && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                        $objectName = array();
                        preg_match('/O:\d+:\"([^\"]+)\"/', $data, $objectName);
                        $objectName = $objectName[1] ? $objectName[1] : $data;
                        $error      = sprintf(__("WP Migrate DB - Failed to instantiate object for replacement. If the serialized object's class is defined by a plugin, you should enable that plugin for migration requests. \nClass Name: %s", 'wp-migrate-db'), $objectName);
                        error_log($error);

                        return $data;
                    }
                }
                $data = $this->recursive_unserialize_replace($unserialized, true, true, $successive_filter);
            } elseif (is_array($data)) {
                $_tmp = array();
                foreach ($data as $key => $value) {
                    $_tmp[$key] = $this->recursive_unserialize_replace($value, false, $parent_serialized, $successive_filter);
                }

                $data = $_tmp;
                unset($_tmp);
            } elseif (is_object($data)) { // Submitted by Tina Matter
                if ($this->is_object_cloneable($data)) {
                    $_tmp = clone $data;
                    foreach ($data as $key => $value) {
                        // Integer properties are crazy and the best thing we can do is to just ignore them.
                        // see http://stackoverflow.com/a/10333200 and https://github.com/deliciousbrains/wp-migrate-db-pro/issues/853
                        if (is_int($key)) {
                            continue;
                        }
                        $_tmp->$key = $this->recursive_unserialize_replace($value, false, $parent_serialized, $successive_filter);
                    }

                    $data = $_tmp;
                    unset($_tmp);
                }
            } elseif (Util::is_json($data, true)) {
                $_tmp = array();
                $data = json_decode($data, true);

                foreach ($data as $key => $value) {
                    $_tmp[$key] = $this->recursive_unserialize_replace($value, false, $parent_serialized, $successive_filter);
                }

                $data = $_tmp;
                unset($_tmp);
                $is_json = true;
            } elseif (is_string($data)) {
                list($data, $do_replace) = apply_filters('wpmdb_replace_custom_data', array($data, true), $this);

                if ($do_replace) {
                    $data = $this->apply_replaces($data);
                }
            }

            if ($is_json) {
                $flags = apply_filters('wpmdb_replace_json_encode_flags', 0, $data, $this);
                $data  = json_encode($data, $flags);
            }

            if ($serialized) {
                $data = serialize($data);
            }
        } catch (\Exception $error) {
            $error_msg     = __('Failed attempting to do the recursive unserialize replace. Please contact support.', 'wp-migrate-db');
            $error_details = $error->getMessage() . "\n\n";
            $error_details .= var_export($data, true);
            $this->error_log->log_error($error_msg, $error_details);
        }

        if (true === $filtered) {
            $data = apply_filters('wpmdb_after_replace_custom_data', $data, $before_fired, $this);
        }

        return $data;
    }

    /**
     * Getter for the $table class property.
     *
     * @return string Name of the table currently being processed in the migration.
     */
    public function get_table()
    {
        return $this->table;
    }

    /**
     * Getter for the $column class property.
     *
     * @return string Name of the column currently being processed in the migration.
     */
    public function get_column()
    {
        return $this->column;
    }

    /**
     * Getter for the $row class property.
     *
     * @return string Name of the row currently being processed in the migration.
     */
    public function get_row()
    {
        return $this->row;
    }

    /**
     * Setter for the $column class property.
     *
     * @param string $column Name of the column currently being processed in the migration.
     */
    public function set_column($column)
    {
        $this->column = $column;
    }

    /**
     * Setter for the $row class property.
     *
     * @param string $row Name of the row currently being processed in the migration.
     */
    public function set_row($row)
    {
        $this->row = $row;
    }

	/**
	 * Multsite safe way of comparing the table currently being processed in the migration against a desired table.
	 *
	 * The table prefix should be omitted, example:
	 *
	 * $is_posts = $this->table_is( 'posts' );
	 *
	 * @TODO Cover table prefixing with Unit Tests
	 *
	 * @param  string $desired_table Name of the desired table, table prefix omitted.
	 * @param  string $prefix        The table prefix
	 *
	 * @return boolean                Whether or not the desired table is the table currently being processed.
	 */
	public function table_is( $desired_table, $prefix = '' ) {
		return $this->table_helper->table_is( $desired_table, $this->table, 'table', $prefix );
	}

    /**
     * Intent of the current replace migration.
     *
     * Helpful for hookers who need to know what intent they are working on.
     *
     * @return string Intent of the current migration
     */
    public function get_intent()
    {
        return $this->intent;
    }

    /**
     * @param string $prefix
     */
    protected function json_replaces($prefix)
    {
        $prefix         = in_array($this->intent, ['find_replace', 'import']) ? '_mig_' . $prefix : $prefix;
        $default_tables = [
            "${prefix}posts",
        ];

        // Account for multisite subsites.
        if (is_multisite()) {
            $pattern = '/^' . $prefix . '\d+_posts$/';
            if (preg_match($pattern, $this->table)) {
                $default_tables = [$this->table];
            }
        }

        $this->json_replace_tables = apply_filters('wpmdb_json_replace_tables', $default_tables);

        $this->json_replace_columns = apply_filters('wpmdb_json_replace_columns', [
            'post_content',
            'post_content_filtered',
        ]);

        if (empty($this->search) && empty($this->replace)) {
            return;
        }

        if (is_array($this->search)) {
            $this->json_search = array_map(function ($item) {
                return Util::json_encode_trim($item);
            }, $this->search);
        }

        if (is_array($this->replace)) {
            $this->json_replace = array_map(function ($item) {
                return Util::json_encode_trim($item);
            }, $this->replace);
        }
    }

    /**
     * @throws \WP_CLI\ExitException
     * @throws \DI\DependencyException
     * @throws \DI\NotFoundException
     */
    public function validate_regex_pattern() {
        $_POST = $this->http_helper->convert_json_body_to_post();

        $key_rules = array(
            'pattern' => 'string',
        );

        $state_data = $this->migration_state_manager->set_post_data( $key_rules );
        return $this->http->end_ajax( Util::is_regex_pattern_valid( $state_data['pattern'] ) );
    }

    public function register_rest_routes() {
        $this->rest_api_server->registerRestRoute('/regex-validate', [
            'methods'  => 'POST',
            'callback' => [ $this, 'validate_regex_pattern' ],
        ]);
    }


    /**
     * Check if a given object can be cloned.
     *
     * @param object $object
     *
     * @return bool
     */
    private function is_object_cloneable($object) {
        return (new \ReflectionClass(get_class($object)))->isCloneable();
    }

    /**
     * Empties pairs array
     */
    public function reset_pairs() {
        $this->pairs = [];
    }


    /**
     * @return DiffInterpreter
     */
    public function get_diff_interpreter() {
        return $this->diff_interpreter;
    }


    /**
     * Returns an array of json serialized entities.
     *
     * @return array
     */
    public function get_diff_result() {
        $result = [];
        foreach($this->diff_interpreter->getGroup()->getEntities() as $entity) {
            $result[] = $entity->jsonSerialize();
        }
        return $result;
    }
}
