eidng8/sttbot

View on GitHub
src/Wiki/Templates/Mission.php

Summary

Maintainability
D
1 day
Test Coverage
<?php
/**
 * Created by PhpStorm.
 * User: JC
 * Date: 2016-11-20
 * Time: 11:13
 */

namespace eidng8\Wiki\Templates;

use eidng8\Exceptions\EmptyTemplateException;
use eidng8\Exceptions\InvalidTemplateException;
use eidng8\Log\Log;
use eidng8\Wiki\Models\Mission as MissionModel;
use eidng8\Wiki\Models\MissionStep;
use eidng8\Wiki\Models\ReqAndBonus;
use eidng8\Wiki\Template;

/**
 * Parse a single mission page
 *
 * @method MissionModel get()
 */
class Mission extends Template
{
    private $advanced = false;

    /**
     * Mission constructor.
     * The `advanced` key in the `$options` array denotes if it's adv cadet
     *
     * @param string $wikiText
     * @param string $name
     * @param array  $options
     */
    public function __construct(
        $wikiText,
        $name = null,
        $options = null
    ) {
        $this->advanced = !empty($options) && !empty($options['advanced']);
        parent::__construct($wikiText, $name, $options);
    }//end __construct()

    /**
     * {@inheritdoc}
     */
    public function parse(): MissionModel
    {
        $this->found = [];
        $sections = $this->sections(2, $this->wikiText);
        $info = $this->parseInfoBox($sections[0]);
        if ('away team' == $info->type()) {
            $this->found = array_merge(
                $this->found,
                $this->parseWalkThru($sections[1])
            );
        }
        $this->found['info'] = $info;

        return $this->found = $this->createModel();
    }//end parse()

    /**
     * Process info box
     *
     * @param string $mission
     *
     * @return InfoBox
     */
    public function parseInfoBox(string $mission): InfoBox
    {
        $info = new InfoBox($mission);
        $this->name = $info->name();

        return $info;
    }//end parseInfoBox()

    /**
     * Process mission walk through
     *
     * @param string $mission
     *
     * @return string[]
     */
    public function parseWalkThru(string $mission): array
    {
        $regex = '/^\{\{MWHead.+?^}}$/imsu';
        preg_match_all($regex, $mission, $intro);
        $intro = $this->parseMWHead($intro[0][0]);
        $steps = $this->parseMWalk($mission);

        return compact('intro', 'steps');
    }//end parseWalkThru()

    /**
     * Process mission walk through header
     *
     * @param string $head
     *
     * @return string[]
     */
    public function parseMWHead(string $head): array
    {
        $lines = explode("\n", $head);

        $intro = [];
        foreach ($lines as $line) {
            if (preg_match_all(
                '/^\s*\|\s*ChronCost\s*=\s*(\{\{triple[^\}]+}})$/iu',
                $line,
                $found
            )) {
                // We've got cost fro info box
                // $intro['cost'] = new MissionCost($found[1][0]);
            } elseif (preg_match_all(
                '/^\s*<!--Traits-->\s*((?:\|[^|]+)+)$/iu',
                $line,
                $found
            )) {
                $traits = Template::explode($found[1][0]);
                if (!empty($traits)) {
                    $intro['traits'] = array_values(
                        array_map(
                            'strtolower',
                            array_filter($traits)
                        )
                    );
                }
            }
        }//end foreach

        return $intro;
    }//end parseMWHead()

    /**
     * Process mission steps
     *
     * @param string $text
     *
     * @return string[]
     */
    public function parseMWalk(string $text): array
    {
        $regex = '/^\{\{MWalk(\d)\s*$.+?^}}$/imsu';
        preg_match_all($regex, $text, $found);
        $steps = [];
        foreach ($found[0] as $step) {
            $steps[] = $this->parseStep($step);
        }//end foreach

        return $steps;
    }//end parseMWalk()

    /**
     * Process a mission step
     *
     * @param string $text
     *
     * @return string[]
     */
    public function parseStep(string $text): array
    {
        $columns = [
            'steptitle'  => 'name',
            'choice'     => 'alt',
            'bonustrait' => 'traits',
            'otherreq'   => 'locks',
            'skillreq'   => 'skills',
        ];
        $fields = implode('|', array_keys($columns));
        $regex
            = "/^\\h*\\|\\h*($fields)([a-z])?(Pic)?\\h*=\\h*(.+)?\\h*$/imu";
        preg_match_all($regex, $text, $found);

        $props = [];
        // $found[1] holds the list of keys, e.g. 'SkillReq', 'Choice', etc.
        $found[1] = array_map('strtolower', $found[1]);
        // $found[2] holds the step alternative, e.g. 'A', 'B', etc.
        $found[2] = array_map('strtolower', $found[2]);
        // $found[3] is only useful for alternative thumbnail
        $found[3] = array_map('strtolower', $found[3]);
        // $found[4] holds value of properties
        // loop through all found properties
        foreach ($found[4] as $idx => $value) {
            $key = $columns[$found[1][$idx]];
            if (!empty($found[2][$idx])) {
                $alt = ord($found[2][$idx]) - ord('a');
            } else {
                $alt = null;
            }
            if ('choice' == $found[1][$idx] && 'pic' == $found[3][$idx]) {
                $props['images'][$alt] = trim($value);
            } elseif (null !== $alt) {
                $props[$key][$alt] = trim($value);
            } else {
                $props[$key] = trim($value);
            }

            if (in_array($key, ['locks', 'skills', 'traits'])) {
                try {
                    $values = [];
                    if (!empty($value)) {
                        preg_match(
                            '/(\{\{[^}]+}})(?:.*Adv:\h*(\{\{[^}]+}}))?/iu',
                            $value,
                            $values
                        );
                    }
                    $props[$key][$alt] = $this->parseStepProp(
                        $key,
                        empty($values[1]) ? '' : $values[1],
                        empty($values[2]) ? '' : $values[2]
                    );
                } catch (InvalidTemplateException | EmptyTemplateException $e) {
                    Log::notice($e->getMessage());
                } catch (\Exception $e) {
                    Log::warn($e->getMessage());
                }
            }
        }//end foreach

        return $props;
    }//end parseStep()

    /**
     * Parse mission step properties
     *
     * @param string $key
     * @param string $values
     * @param string $advValues
     *
     * @return array|Triple|mixed|null
     */
    public function parseStepProp(
        string $key,
        string $values,
        string $advValues
    ) {
        if (empty($values)) {
            return null;
        }

        $loadTriple = function ($value) {
            if (empty($value) || '{' !== $value[0]) {
                throw new EmptyTemplateException(
                    "$this->name invalid triple: $value"
                );
            }

            $triple = Triple::load($value);
            if (!$triple) {
                throw new InvalidTemplateException(
                    "$this->name invalid triple: $value"
                );
            }

            return $triple;
        };

        $return = null;
        switch ($key) {
            case 'skills':
                $triple = $loadTriple($values);
                if ($this->advanced) {
                    try {
                        $adv = $loadTriple($advValues);
                        $adv->name = $triple->name;
                    } catch (\Exception $ex) {
                        $adv = Triple::empty($triple->name);
                    }
                    $triple = $adv;
                }
                $return = $triple;
                break;

            case 'traits':
                $triple = $loadTriple(trim($values));
                if ($this->advanced) {
                    try {
                        $adv = $loadTriple(trim($advValues));
                        $adv->name = $triple->name;
                    } catch (\Exception $e) {
                        $adv = Triple::empty($triple->name);
                    }
                    $triple = $adv;
                }

                // fill empty values with guess values
                $guessFactor = 2;
                if (!$triple->elite()) {
                    $triple->elite($triple->normal() * $guessFactor);
                }
                if (!$triple->epic()) {
                    $triple->epic($triple->elite() * $guessFactor);
                }
                $return = $triple;
                break;

            case 'locks':
                $locks = explode('|', trim($values, " {}-\t\0\xb"));
                array_shift($locks);
                $return = array_filter(
                    array_map(
                        'strtolower',
                        array_map('trim', $locks)
                    )
                );
                break;
        }

        return $return;
    }//end parseStepProp()

    /**
     * Create a mission model
     *
     * @return MissionModel
     */
    public function createModel(): MissionModel
    {
        $model = new MissionModel();
        /* @var InfoBox $info */
        $info = $this->found['info'];
        $model->name = $info->name();
        $model->page = $info->name();
        $model->episode = $info->episode();
        $model->index = $info->sequence();
        $model->cost = $info->cost()->get();
        $model->image = $info->image();
        if ('away team' != $info->type()) {
            $model->type = MissionModel::SPACE_BATTLE;
        } else {
            $model->type = MissionModel::AWAY_TEAM;
            $model->traits = $this->found['intro']['traits'];

            $model->steps = [];
            foreach ($this->found['steps'] as $step) {
                $values = new MissionStep();
                $values->name = $step['name'];
                $values->alt = $step['alt'];
                $values->images = $step['images'];

                $values->skills = array_map(
                    [$this, 'loadReqAndBonusModel'],
                    $step['skills']
                );

                if (!empty($step['traits'])) {
                    $values->traits = array_map(
                        [$this, 'loadReqAndBonusModel'],
                        $step['traits']
                    );
                }

                if (!empty($step['locks'])) {
                    $values->locks = $step['locks'];
                }

                $model->steps[] = $values;
            }//end foreach
        }

        $this->validateModel($model);

        return $model;
    }//end createModel()

    /**
     * @param MissionModel $model
     *
     * @return bool
     */
    public function validateModel(MissionModel $model): bool
    {
        return $model->validate();
    }//end loadReqAndBonusModel()

    /**
     * @param Triple|null $values
     *
     * @return ReqAndBonus|null
     */
    public function loadReqAndBonusModel(Triple $values = null)
    {
        if (empty($values)) {
            return null;
        }

        $model = new ReqAndBonus();
        $model->name($values->name());
        $model->set($values->get());

        return $model;
    }
}//end class