<?php namespace im\Primitive\String;

use JWT;
use Countable;
use Traversable;
use ArrayAccess;
use ArrayIterator;
use IteratorAggregate;
use BadMethodCallException;
use InvalidArgumentException;

use Stringy\Stringy;
use Stringy\StaticStringy;
use im\Primitive\Support\Str;
use im\Primitive\Int\Int;
use im\Primitive\Bool\Bool;
use im\Primitive\Float\Float;
use im\Primitive\Container\Container;
use im\Primitive\Support\Abstracts\Type;
use im\Primitive\Support\Contracts\StringContract;
use im\Primitive\Support\Traits\SliceableTrait;
use im\Primitive\Support\Traits\StringCheckerTrait;
use im\Primitive\String\Exceptions\StringException;

/**
 * Class String
 * String manipulation class with support of multi-byte
 *
 * @package im\Primitive\String
 * @author Igor Krimerman <i.m.krimerman@gmail.com>
 */
class String extends Type implements StringContract, Countable, ArrayAccess, IteratorAggregate {

    use SliceableTrait;
    use StringCheckerTrait;

    /**
     * Storing value.
     * @var string
     */
    protected $string;

    /**
     * Construct String Type.
     *
     * @param mixed $string
     */
    public function __construct($string = '')
    {
        $this->initialize($string);
    }

    /**
     * Magic get method to support method calls (without parameters) as variables.
     *
     * @param string|StringContract $value
     * @return mixed
     */
    public function __get($value)
    {
        $value = $this->retrieveValue($value);

        if (method_exists($this, $value))
        {
            return $this->{$value}();
        }

        throw new BadMethodCallException('No method: ' . $value . ' found');
    }

    /**
     * Setter.
     * It can set from string, numeric, bool, array, object that implements __toString,
     * ArrayableContract, ContainerContract, BooleanContract, IntegerContract, FloatContract.
     *
     * @param mixed $string
     * @return $this
     */
    public function set($string)
    {
        $this->string = $this->retrieveValue($string);

        return $this;
    }

    /**
     * Getter. Alias for value method.
     *
     * @return string
     */
    public function get()
    {
        return $this->value();
    }

    /**
     * Return storing value.
     *
     * @return string
     */
    public function value()
    {
        return $this->string;
    }

    /**
     * Return length of string.
     *
     * @return int
     */
    public function length()
    {
        return $this->measure();
    }

    /**
     * Return Container of all chars.
     *
     * @return \im\Primitive\Container\Container
     */
    public function chars()
    {
        return new Container(StaticStringy::chars($this->string));
    }

    /**
     * Return String with appended delimiter and content.
     *
     * @param mixed $string
     * @param mixed $delimiter
     * @return $this|static
     */
    public function append($string, $delimiter = '')
    {
        if ($this->isValidStringAndDelimiter($string, $delimiter))
        {
            return new static($this->string.$this->retrieveValue($delimiter).$this->retrieveValue($string));
        }

        return $this;
    }

    /**
     * Return String with prepended content and delimiter.
     *
     * @param mixed $string
     * @param mixed $delimiter
     * @return $this|static
     */
    public function prepend($string, $delimiter = '')
    {
        if ($this->isValidStringAndDelimiter($string, $delimiter))
        {
            return new static($this->retrieveValue($string).$this->retrieveValue($delimiter).$this->string);
        }

        return $this;
    }

    /**
     * Return String all lower case.
     *
     * @return static
     */
    public function lower()
    {
        return new static(mb_strtolower($this->string));
    }

    /**
     * Return String with lower case first letter.
     *
     * @return static
     */
    public function lowerFirst()
    {
        return new static(StaticStringy::lowerCaseFirst($this->string));
    }

    /**
     * Return String with all upper case.
     *
     * @return static
     */
    public function upper()
    {
        return new static(mb_strtoupper($this->string));
    }

    /**
     * Return String with upper case first letter.
     *
     * @return static
     */
    public function upperFirst()
    {
        return new static(StaticStringy::upperCaseFirst($this->string));
    }

    /**
     * Return camel case String with upper case first letter.
     *
     * @return static
     */
    public function upperCamel()
    {
        return $this->camel()->upperFirst();
    }

    /**
     * Return Title Case String. (<- like this)
     *
     * @return static
     */
    public function title()
    {
        return new static(StaticStringy::toTitleCase($this->string));
    }

    /**
     * Return camelCase String.
     *
     * @return static
     */
    public function camel()
    {
        return new static(Str::camel($this->string));
    }

    /**
     * Return dashed-case String.
     *
     * @return static
     */
    public function dashed()
    {
        return new static(Str::dashed($this->string));
    }

    /**
     * Return snake_case String.
     *
     * @return static
     */
    public function snake()
    {
        return new static(StaticStringy::underscored($this->string));
    }

    /**
     * Return StudlyCase String.
     *
     * @return static
     */
    public function studly()
    {
        return new static(Str::studly($this->string));
    }

    /**
     * Return String with swapped case.
     * UpperCase -> uPPERcASE
     *
     * @return static
     */
    public function swapCase()
    {
        return new static(StaticStringy::swapCase($this->string));
    }

    /**
     * Return capitalized first word of the string, replaces underscores with
     * spaces, and strips '_id'.
     *
     * @return static
     */
    public function humanize()
    {
        return new static(StaticStringy::humanize($this->string));
    }

    /**
     * Return a trimmed string with the first letter of each word capitalized.
     * Ignores the case of other letters, preserving any acronyms. Also accepts
     * an arrayable, $ignore, allowing you to list words not to be capitalized.
     *
     * @param mixed $ignore
     * @return static
     */
    public function titleize($ignore = null)
    {
        return new static(StaticStringy::titleize($this->string, $this->getArrayable($ignore)));
    }

    /**
     * Return true if the string contains $needle, false otherwise. By default,
     * the comparison is case-sensitive, but can be made insensitive by setting
     * $caseSensitive to false.
     *
     * @param mixed $needle
     * @param mixed $caseSensitive
     * @return bool
     */
    public function has($needle, $caseSensitive = true)
    {
        return StaticStringy::contains(
            $this->string, $this->retrieveValue($needle), $this->getBoolable($caseSensitive)
        );
    }

    /**
     * Return true if the string contains any $needles, false otherwise. By
     * default, the comparison is case-sensitive, but can be made insensitive
     * by setting $caseSensitive to false.
     *
     * @param mixed $strings
     * @param mixed $caseSensitive
     * @return bool
     */
    public function hasAny($strings, $caseSensitive = true)
    {
        return StaticStringy::containsAny(
            $this->string, $this->getArrayable($strings), $this->getBoolable($caseSensitive)
        );
    }

    /**
     * Return true if the string contains all $needles, false otherwise. By
     * default, the comparison is case-sensitive, but can be made insensitive
     * by setting $caseSensitive to false.
     *
     * @param mixed $strings
     * @param mixed $caseSensitive
     * @return bool
     */
    public function hasAll($strings, $caseSensitive = true)
    {
        return StaticStringy::containsAll(
            $this->string, $this->getArrayable($strings), $this->getBoolable($caseSensitive)
        );
    }

    /**
     * Trim the string and replaces consecutive whitespace characters with a
     * single space. This includes tabs and newline characters, as well as
     * multi-byte whitespace such as the thin space and ideographic space.
     *
     * @return static
     */
    public function collapseWhitespace()
    {
        return $this->replaceRegex('[[:space:]]+', ' ')->trim();
    }

    /**
     * Return an ASCII version of the String. A set of non-ASCII characters are
     * replaced with their closest ASCII counterparts, and the rest are removed.
     *
     * @return static
     */
    public function toAscii()
    {
        return new static(StaticStringy::toAscii($this->string));
    }

    /**
     * Convert each tab in the string to some number of spaces, as defined by
     * $tabLength. By default, each tab is converted to 4 consecutive spaces.
     *
     * @param int $tabLength
     * @return static
     */
    public function toSpaces($tabLength = 4)
    {
        return new static(StaticStringy::toSpaces($this->string, $this->getIntegerable($tabLength)));
    }

    /**
     * Convert each occurrence of some consecutive number of spaces, as
     * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
     * are converted to a tab.
     *
     * @param int $tabLength
     * @return static
     */
    public function toTabs($tabLength = 4)
    {
        return new static(StaticStringy::toTabs($this->string, $this->getIntegerable($tabLength)));
    }

    /**
     * Surround String with given string.
     *
     * @param mixed $surround
     * @return static
     */
    public function surround($surround)
    {
        return new static(StaticStringy::surround($this->string, $this->retrieveValue($surround)));
    }

    /**
     * Insert $insert into the String at the $index provided.
     *
     * @param mixed $insert
     * @param mixed $index
     * @return static
     */
    public function insert($insert, $index)
    {
        return new static(StaticStringy::insert(
            $this->string, $this->retrieveValue($insert), $this->getIntegerable($index))
        );
    }

    /**
     * Return a reversed string. A multi-byte version of strrev().
     *
     * @return static
     */
    public function reverse()
    {
        return new static(StaticStringy::reverse($this->string));
    }

    /**
     * Return char at provided $index
     *
     * @param mixed $index
     * @return static
     */
    public function at($index)
    {
        return new static(StaticStringy::at($this->string, $this->getIntegerable($index)));
    }

    /**
     * Return first $length chars
     *
     * @param mixed $length
     * @return static
     */
    public function first($length)
    {
        return new static(StaticStringy::first($this->string, $this->getIntegerable($length)));
    }

    /**
     * Return last $length chars
     *
     * @param mixed $length
     * @return static
     */
    public function last($length)
    {
        return new static(StaticStringy::last($this->string, $this->getIntegerable($length)));
    }

    /**
     * Ensure that the String begins with $string. If it doesn't, it's
     * prepended.
     *
     * @param mixed $string
     * @return static
     */
    public function ensureLeft($string)
    {
        return new static(StaticStringy::ensureLeft($this->string, $this->retrieveValue($string)));
    }

    /**
     * Ensure that the String ends with $substring. If it doesn't, it's
     * appended.
     *
     * @param mixed $string
     * @return static
     */
    public function ensureRight($string)
    {
        return new static(StaticStringy::ensureRight($this->string, $this->retrieveValue($string)));
    }

    /**
     * Remove the prefix $string from left, if present.
     *
     * @param mixed $string
     * @return static
     */
    public function removeLeft($string)
    {
        return new static(StaticStringy::removeLeft($this->string, $this->retrieveValue($string)));
    }

    /**
     * Remove the prefix $string from right, if present.
     *
     * @param mixed $string
     * @return static
     */
    public function removeRight($string)
    {
        return new static(StaticStringy::removeRight($this->string, $this->retrieveValue($string)));
    }

    /**
     * Replaces all occurrences of $search by $replacement.
     *
     * @param mixed $search
     * @param string|StringContract $replace
     * @return static
     */
    public function replace($search, $replace)
    {
        $string = Stringy::create($this->string);

        $replace = $this->retrieveValue($replace);

        foreach ((array) $this->getSearchable($search) as $find)
        {
            $string = $string->replace($find, $replace);
        }

        return new static((string) $string);
    }

    /**
     * Replaces all occurrences of $pattern by $replacement. An alias
     * for mb_ereg_replace().
     *
     * @param string|StringInterface $pattern
     * @param string|StringInterface $replace
     * @return static
     */
    public function replaceRegex($pattern, $replace)
    {
        return new static(StaticStringy::regexReplace(
            $this->string, $this->retrieveValue($pattern), $this->retrieveValue($replace))
        );
    }

    /**
     * Determine if String starts with a given needles.
     * The comparison is case-sensitive, but can be made insensitive
     * by setting $caseSensitive to false.
     *
     * @param mixed $needles
     * @param bool $caseSensitive
     * @return bool
     */
    public function startsWith($needles, $caseSensitive = true)
    {
        return Str::startsWith(
            $this->string, $this->getSearchable($needles), $this->getBoolable($caseSensitive, true)
        );
    }

    /**
     * Determine if String ends with a given needles.
     *
     * @param mixed $needles
     * @return bool
     */
    public function endsWith($needles)
    {
        return Str::endsWith($this->string, $this->getSearchable($needles, []));
    }

    /**
     * Return true if String matches the supplied pattern, false otherwise.
     *
     * @param string|StringContract $pattern
     * @return bool
     */
    public function is($pattern)
    {
        return (bool) Str::matches($this->retrieveValue($pattern), $this->string);
    }

    /**
     * Cap a String with a single instance of a given $cap.
     *
     * @param string|StringContract $cap
     * @return static
     */
    public function finish($cap)
    {
        return new static(Str::finish($this->string, $this->retrieveValue($cap)));
    }

    /**
     * Limit the number of words in a String.
     *
     * @param int|IntegerContract $limit
     * @param string|StringContract $end
     * @return static
     */
    public function limitWords($limit, $end = '...')
    {
        return new static(Str::words($this->string, $this->getIntegerable($limit), $this->retrieveValue($end)));
    }

    /**
     * Parse a Class@method style callback into class and method.
     * Return Container with parsed.
     *
     * @param string|StringContract $default
     * @return \im\Primitive\Container\Container
     */
    public function parseCallback($default = '')
    {
        return new Container(Str::parseCallback($this->string, $this->retrieveValue($default)));
    }

    /**
     * Generates random Unique User Identifier
     *
     * @return static
     */
    public function uuid()
    {
        return new static(sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
            mt_rand(0, 0x0fff) | 0x4000,
            mt_rand(0, 0x3fff) | 0x8000,
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
        ));
    }

    /**
     * Generate a more truly "random" alpha-numeric String.
     *
     * @param int|IntegerContract $length
     * @return static
     */
    public function random($length = 16)
    {
        return new static(Str::random($this->getIntegerable($length)));
    }

    /**
     * Generate a "random" alpha-numeric String.
     *
     * @param int|IntegerContract $length
     * @return static
     */
    public function quickRandom($length = 16)
    {
        return new static(Str::quickRandom($this->getIntegerable($length)));
    }

    /**
     * Generate a URL friendly "slug".
     *
     * @param string|StringContract $delimiter
     * @return static
     */
    public function slug($delimiter = '-')
    {
        return new static(Str::slug($this->string, $this->retrieveValue($delimiter)));
    }

    /**
     * Split a String by string
     *
     * @param string|StringContract $delimiter
     * @return \im\Primitive\Container\Container
     */
    public function split($delimiter)
    {
        return new Container(explode($this->retrieveValue($delimiter), $this->string));
    }

    /**
     * Join arrayable elements with a String
     *
     * @param string|StringContract $glue
     * @param mixed $array
     * @return $this
     * @throws InvalidArgumentException
     */
    public function join($glue, $array)
    {
        if ($this->isArrayable($array))
        {
            $this->string = implode($this->retrieveValue($glue), $this->getArrayable($array));

            return $this;
        }

        throw new InvalidArgumentException('Argument 2 should be array, Container or instance of Arrayable');
    }

    /**
     * Strip whitespace (or other characters) from a String
     * Can trim:
     *      'front' from the beginning
     *      'back' from the end
     *      'all' all whitespace chars will be replace
     *       null from the end and beginning
     *
     * @param null|string|StringContract $what
     * @return static
     */
    public function trim($what = null)
    {
        switch ($this->retrieveValue($what))
        {
            case 'front':
                return new static(ltrim($this->string));
            case 'back':
                return new static(rtrim($this->string));
            case 'all':
                return $this->replaceRegex('[[:space:]]+', '');
            default:
                return new static(trim($this->string));
        }
    }

    /**
     * Repeat a String
     *
     * @param int|IntegerContract $quantity
     * @return static
     */
    public function repeat($quantity = 2)
    {
        return new static(str_repeat($this->string, $this->getIntegerable($quantity)));
    }

    /**
     * A multi-byte str_shuffle() function. It returns a string with its
     * characters in random order.
     *
     * @return static
     */
    public function shuffle()
    {
        return new static(StaticStringy::shuffle($this->string));
    }

    /**
     * Strip HTML and PHP tags from a String.
     * You can use the optional second parameter to specify tags which should
     * not be stripped.
     *
     * @param null|mixed $allowed
     * @return static
     */
    public function stripTags($allowed = null)
    {
        return new static(strip_tags($this->string, $this->getArrayable($allowed)));
    }

    /**
     * Encodes String with MIME base64
     *
     * @return static
     */
    public function base64()
    {
        return new static(base64_encode($this->string));
    }

    /**
     * Decodes String encoded with MIME base64
     *
     * @param string|StringContract $base
     * @return $this
     */
    public function fromBase64($base)
    {
        return $this->initialize(base64_decode($this->retrieveValue($base)));
    }

    /**
     * Convert all applicable characters to HTML entities.
     *
     * @param int $flags
     * @param string|StringContract $encoding
     * @return static
     */
    public function toEntities($flags = ENT_QUOTES, $encoding = 'UTF-8')
    {
        return new static(htmlentities($this->string, $flags, $this->retrieveValue($encoding)));
    }

    /**
     * Convert all HTML entities to their applicable characters.
     * First optional argument can be specified to decode and construct from $entities.
     *
     * @param string|StringContract|null $entities
     * @param int $flags
     * @param string|StringContract $encoding
     * @return $this
     */
    public function fromEntities($entities = null, $flags = ENT_QUOTES, $encoding = 'UTF-8')
    {
        if ( ! is_null($entities))
        {
            $this->string = html_entity_decode(
                $this->retrieveValue($entities), $flags, $this->retrieveValue($encoding)
            );

            return $this;
        }

        return new static(html_entity_decode($this->string, $flags, $this->retrieveValue($encoding)));
    }

    /**
     * Echo string
     *
     * @param string|StringContract $before
     * @param string|StringContract $after
     * @return $this
     */
    public function say($before = '', $after = '')
    {
        echo $this->retrieveValue($before), $this->string, $this->retrieveValue($after);

        return $this;
    }

    /**
     * Get part of String
     *
     * @param int|IntegerContract $offset
     * @param int|IntegerContract|null $length
     * @param string|StringContract $encoding
     * @return static
     */
    public function cut($offset, $length = null, $encoding = 'UTF-8')
    {
        return new static(
            mb_substr(
                $this->string,
                $this->getIntegerable($offset),
                $this->getIntegerable($length),
                $this->retrieveValue($encoding)
            )
        );
    }

    /**
     * Limit the number of characters in a String.
     *
     * @param int|IntegerContract $limit
     * @param string|StringContract $end
     * @return static
     */
    public function limit($limit = 100, $end = '...')
    {
        return new static(
            Str::limit($this->string, $this->getIntegerable($limit), $this->retrieveValue($end))
        );
    }

    /**
     * Truncates the string to a given length, while ensuring that it does not
     * split words. If word will be in the middle of limit, minus one word will be returned.
     *
     * @param int|IntegerContract $limit
     * @param string|StringContract $end
     * @return static
     */
    public function limitSafe($limit = 100, $end = '...')
    {
        return new static(
            StaticStringy::safeTruncate($this->string, $this->getIntegerable($limit)).$this->retrieveValue($end)
        );
    }

    /**
     * Parse GET data.
     *
     * @return \im\Primitive\Container\Container
     */
    public function toVars()
    {
        $vars = [];

        return mb_parse_str($this->string, $vars) && ! is_null($vars) ? new Container($vars) : new Container;
    }

    /**
     * Clean String.
     * Strip tags, convert all entities and trim.
     *
     * @return $this
     */
    public function clean()
    {
        return $this->stripTags()->toEntities()->trim();
    }

    /**
     * Reset to an empty String.
     *
     * @return $this
     */
    public function reset()
    {
        $this->string = '';

        return $this;
    }

    /**
     * Compress a String.
     *
     * @return static
     */
    public function compress()
    {
        return new static(gzcompress($this->string));
    }

    /**
     * Decompress a String.
     * If optional argument $string specified than it will
     * decompress and construct from it.
     *
     * @param null|string|StringContract $string
     * @return $this
     */
    public function decompress($string = null)
    {
        $string = $this->isStringable($string) ? $this->retrieveValue($string) : $this->string;

        $this->string = gzuncompress($string);

        return $this;
    }

    /**
     * Encrypt and sign a String into a JWT token.
     *
     * @param string|StringContract $key to encrypt with
     * @param int|IntegerContract $expires timestamp
     * @return static
     */
    public function encrypt($key, $expires)
    {
        $payload = [
            'exp' => $this->getIntegerable($expires),
            'string' => $this->string
        ];

        return JWT::encode($payload, $this->getStringable($key));
    }

    /**
     * Construct from proper encrypted String with JWT.
     *
     * @param string|StringContract $encrypted
     * @param string|StringContract $key
     * @return $this
     */
    public function fromEncrypted($encrypted, $key)
    {
        $encrypted = $this->retrieveValue($encrypted);

        $key = $this->retrieveValue($key);

        if ($this->isEncryptedString($encrypted, $key))
        {
            $data = JWT::decode($encrypted, $key);

            return $this->initialize($data->string);
        }

        throw new BadMethodCallException('Expected encrypted String, got: ' . $encrypted);
    }

    /**
     * Return value. Alias for value method.
     *
     * @return string
     */
    public function all()
    {
        return $this->get();
    }

    /**
     * Get file contents, if String is proper file path.
     *
     * @return static
     * @throws \im\Primitive\String\Exceptions\StringException
     */
    public function contents()
    {
        if ($this->isFile())
        {
            return new static(file_get_contents($this->string));
        }

        throw new StringException('Not is file: '.$this->string);
    }

    /**
     * Check if String is empty.
     *
     * @return bool
     */
    public function isEmpty()
    {
        return ! (bool) $this->length();
    }

    /**
     * Check if String is not empty.
     *
     * @return bool
     */
    public function isNotEmpty()
    {
        return (bool) $this->length();
    }

    /**
     * Check if String has only alpha chars.
     *
     * @return bool
     */
    public function isAlpha()
    {
        return $this->is('^[[:alpha:]]*$');
    }

    /**
     * Check if String has only alphanumeric chars.
     *
     * @return bool
     */
    public function isAlphanumeric()
    {
        return $this->is('^[[:alnum:]]*$');
    }

    /**
     * Check if String has only whitespace chars.
     *
     * @return bool
     */
    public function isWhitespaces()
    {
        return $this->is('^[[:space:]]*$');
    }

    /**
     * Check if String is hexadecimal.
     *
     * @return bool
     */
    public function isHex()
    {
        return $this->is('^[[:xdigit:]]*$');
    }

    /**
     * Check if String is in lower case.
     *
     * @return bool
     */
    public function isLower()
    {
        return $this->is('^[[:lower:]]*$');
    }

    /**
     * Check if String is in upper case.
     *
     * @return bool
     */
    public function isUpper()
    {
        return $this->is('^[[:upper:]]*$');
    }

    /**
     * Check if String is Unique User Identifier generated by String.
     * If optional $uuid argument is specified, it will check it not inner value.
     *
     * @param null|string|StringContract $uuid
     * @return bool
     */
    public function isUuid($uuid = null)
    {
        if (is_null($uuid)) $uuid = $this->string;

        $uuid = $this->retrieveValue($uuid);

        return (bool) preg_match(
            '/^\{?[0-9a-f]{8}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?[0-9a-f]{12}\}?$/i', $uuid
        );
    }

    /**
     * Check if String is proper json.
     *
     * @return bool
     */
    public function isJson()
    {
        return Str::isJson($this->string);
    }

    /**
     * Check if String is proper file path.
     *
     * @return bool
     */
    public function isFile()
    {
        return Str::isFile($this->string);
    }

    /**
     * Check if String is proper serialized.
     *
     * @return bool
     */
    public function isSerialized()
    {
        return Str::isSerialized($this->string);
    }

    /**
     * {@inheritdoc}
     */
    public function __toString()
    {
        return $this->value();
    }

    /**
     * Convert String Type to Bool Type.
     *
     * @return \im\Primitive\Bool\Bool
     */
    public function toBool()
    {
        return new Bool($this->value());
    }

    /**
     * Convert String Type to Int Type.
     *
     * @return \im\Primitive\Int\Int
     */
    public function toInt()
    {
        return new Int($this->value());
    }

    /**
     * @return \im\Primitive\Float\Float
     */
    public function toFloat()
    {
        return new Float($this->value());
    }

    /**
     * Convert String Type to Container Type.
     * If String is proper json, serialized or file path with json or serialized
     * than it will grab contents decode it and construct Container from it.
     *
     * @return \im\Primitive\Container\Container
     */
    public function toContainer()
    {
        $value = $this->value();

        if ( ! $this->isFile() || ! $this->isJson() || ! $this->isSerialized())
        {
            $value = [$value];
        }

        return new Container($value);
    }

    /**
     * Destructor
     *
     * @return void
     */
    public function __destruct()
    {
        unset($this->string);
    }

    /**
     * Measure String length.
     *
     * @return int
     */
    protected function measure()
    {
        return mb_strlen($this->string);
    }

    /**
     * {@inheritdoc}
     */
    protected function initialize($value)
    {
        if ( ! $this->isStringable($value))
        {
            throw new InvalidArgumentException('Argument 1 should be string or object implementing __toString');
        }

        $this->string = $this->retrieveValue($value);

        return $this;
    }

    /**
     * Check if given $string and $delimiter is Stringable.
     *
     * @param mixed $string
     * @param mixed $delimiter
     * @return bool
     */
    protected function isValidStringAndDelimiter($string, $delimiter)
    {
        return $this->isStringable($string) && $this->isStringable($delimiter);
    }

    /**
     * {@inheritdoc}
     */
    protected function retrieveValue($value)
    {
        return $this->getStringable($value, $this->getDefault());
    }

    /**
     * {@inheritdoc}
     */
    protected function getDefault()
    {
        return '';
    }

    /*
    |--------------------------------------------------------------------------
    | ArrayAccess
    |--------------------------------------------------------------------------
    */
    /**
     * (PHP 5 >= 5.0.0)
     * Offset to set. Sets chars to given index.
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
     * @param mixed $offset
     * The offset to assign the value to.
     * @param mixed $value
     * The value to set.
     *
     * @return void
     */
    public function offsetSet($offset, $value)
    {
        if (is_null($offset))
        {
            $this->string = $this->string.$value;
        }

        if ($this->offsetExists($offset))
        {
            $this->string = (string) $this->chars()->set($offset, $this->retrieveValue($value))->join();
        }
    }

    /**
     * (PHP 5 >= 5.0.0)
     * Whether a offset exists.
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
     * @param mixed $offset
     * An offset to check for.
     *
     * @return boolean true on success or false on failure.
     * The return value will be casted to boolean if non-boolean was returned.
     */
    public function offsetExists($offset)
    {
        $length = $this->length();
        $offset = $this->getIntegerable($offset);

        if ($offset >= 0) return ($length > $offset);

        return ($length >= abs($offset));
    }

    /**
     * (PHP 5 >= 5.0.0)
     * Offset to unset.
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
     * @param mixed $offset
     * The offset to unset.
     *
     * @return void
     */
    public function offsetUnset($offset)
    {
        if ($this->offsetExists($offset))
        {
            $this->string = (string) $this->chars()->forget($offset)->join();
        }
    }

    /**
     * Get offset. Implements python-like string slices.
     * If slice (['2:4:1']) is not present it will return char at offset,
     * otherwise slices the string if the offset
     * contains at least a single colon. Slice notation follow the format
     * "start:stop:step". If no colon is present, returns the character at the
     * given index. Offsets may be negative to count from the last character in
     * the string. Throws an exception if the index does not exist, more than 3
     * slice args are given, or the step is 0.
     *
     * @param int|string|StringContract $args
     * @return \im\Primitive\String\String
     */
    public function offsetGet($args)
    {
        if ( ! $this->isStringable($args, true) || strpos($args, ':') === false)
        {
            return $this->at($args);
        }

        return $this->slice($args);
    }

    /*
    |--------------------------------------------------------------------------
    | Countable
    |--------------------------------------------------------------------------
    */
    /**
     * (PHP 5 >= 5.1.0)
     * Count chars of a String.
     * @link http://php.net/manual/en/countable.count.php
     *
     * @return int The custom count as an integer.
     * The return value is cast to an integer.
     */
    public function count()
    {
        return $this->measure();
    }

    /*
    |--------------------------------------------------------------------------
    | IteratorAggregate
    |--------------------------------------------------------------------------
    */
    /**
     * (PHP 5 >= 5.0.0)
     * Retrieve an external iterator.
     * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
     *
     * @return Traversable An instance
     */
    public function getIterator()
    {
        return new ArrayIterator($this->chars()->all());
    }
}


<?php

namespace Login;

use Conn\Read;
use Conn\TableCrud;
use Conn\Update;
use Entity\Dicionario;
use Entity\Entity;
use Entity\Metadados;
use Helpers\Check;
use Helpers\Helper;
use ReCaptcha\ReCaptcha;

class Login
{
    private $user;
    private $senha;
    private $recaptcha;
    private $attempts = 0;
    private $setor;
    private $result;

    /**
     * Login constructor.
     * @param array $data
     * @param string|null $setor
     */
    public function __construct(array $data, string $setor = null)
    {
        if ($setor)
            $this->setor = $setor;

        if ($data) {
            if (isset($data['recaptcha']) && !empty($data['recaptcha']))
                $this->setRecaptcha($data['recaptcha']);
            if (isset($data['user']) && !empty($data['user']))
                $this->setUser($data['user']);
            if (isset($data['password']) && !empty($data['password']))
                $this->setSenha($data['password']);

            $this->start();
        }
    }

    /**
     * @param mixed $result
     */
    public function setResult($result)
    {
        $this->result = $result;
    }

    /**
     * @param string $email
     */
    public function setUser($user)
    {
        if (!empty($user))
            $this->user = (string)strip_tags(trim($user));
    }

    /**
     * @param string $senha
     */
    public function setSenha($senha)
    {
        if (!empty($senha) && strlen($senha) > 3)
            $this->senha = (string)Check::password(trim($senha));
        else
            $this->setResult('Senha muito Curta');
    }

    /**
     * @param mixed $recaptcha
     */
    public function setRecaptcha($recaptcha)
    {
        $this->recaptcha = $recaptcha;
    }

    /**
     * @return mixed
     */
    public function getResult()
    {
        return $this->result;
    }

    private function start()
    {
        if ($this->user && $this->senha && !$this->attemptExceded()) {
//            if (!empty($_SESSION['userlogin']))
//                $this->setResult('Você já esta logado.');
//            elseif ($this->isHuman())
//                $this->checkUserInfo();

            if ($this->isHuman())
                $this->checkUserInfo();

        } elseif ($this->user && $this->senha) {
            $cont = 10 - $this->attempts;
            $this->setResult($cont > 0 ? "{$cont} tentativas faltantes" : " bloqueado por 15 minutos");
        }
    }

    /**
     * Vetifica usuário e senha no banco de dados!
     */
    private function checkUserInfo()
    {
        if (!$this->getResult()) {
            $dicionarios = Entity::dicionario(null, !0);

            $whereUser = [];
            foreach ($dicionarios as $entity => $dados) {
                if (empty($whereUser[$entity]) && !empty($dados['info']['user']) && $dados['info']['user'] === 1) {
                    $whereUser[$entity] = "WHERE usuarios_id = :id";
                    $where = "";
                    if (!empty($dados['info']['unique'])) {
                        foreach ($dados['info']['unique'] as $id)
                            $where .= (empty($where) ? " && (" : " || ") . $dados['dicionario'][$id]['column'] . " = '{$this->user}'";
                    }
                    $whereUser[$entity] .= $where . (!empty($where) ? ")" : "");
                }
            }

            $user = null;
            $read = new Read();
            $where = "WHERE password = :pass" . (!empty($this->setor) ? " && setor = '{$this->setor}'" : "");
            $read->exeRead(PRE . "usuarios", $where, "pass={$this->senha}");
            if ($read->getResult()) {
                foreach ($read->getResult() as $users) {
                    unset($users['password']);
                    if (strtolower($users['nome']) === strtolower($this->user)) {
                        if ($users['status'] === "1") {
                            if (!empty($users['setor']) && $users['setor'] !== "admin") {
                                $read->exeRead($users['setor'], "WHERE usuarios_id = :uid", "uid={$users['id']}");
                                if ($read->getResult()) {
                                    $users['setorData'] = $read->getResult()[0];
                                    unset($users['setorData']['usuarios_id']);
                                    foreach (Metadados::getDicionario($users['setor']) as $col => $meta) {
                                        if ($meta['format'] === "password" || $meta['key'] === "information")
                                            unset($users['setorData'][$meta['column']]);
                                    }
                                    $user = $users;
                                }
                            } else {
                                $users['setor'] = "admin";
                                $users['setorData'] = "";
                                $user = $users;
                            }
                        } else {
                            $this->setResult('Usuário Desativado!');
                        }
                        break;
                    } elseif (!empty($users['setor']) && !empty($whereUser[$users['setor']])) {
                        $read->exeRead($users['setor'], $whereUser[$users['setor']], "id={$users['id']}");
                        if ($read->getResult()) {
                            if ($users['status'] === "1") {
                                $users['setorData'] = $read->getResult()[0];
                                unset($users['setorData']['usuarios_id']);
                                foreach (Metadados::getDicionario($users['setor']) as $col => $meta) {
                                    if ($meta['format'] === "password" || $meta['key'] === "information")
                                        unset($users['setorData'][$meta['column']]);
                                }
                                $user = $users;
                            } else {
                                $this->setResult('Usuário Desativado!');
                            }
                            break;
                        }
                    }
                }

                //busca informações do grupo de usuário pertencente
                if (!empty($user)) {
                    $user['groupData'] = "";
                    if (!empty($user['setorData'])) {
                        foreach ($dicionarios[$user['setor']]['dicionario'] as $meta) {
                            if ($meta['format'] === "list" && $dicionarios[$meta['relation']]['info']['user'] === 2 && !empty($user['setorData'][$meta['column']])) {
                                $read->exeRead($meta['relation'], "WHERE id = :rid", "rid={$user['setorData'][$meta['column']]}");
                                $user['groupData'] = ($read->getResult() ? $read->getResult()[0] : "");
                            }
                        }
                    }
                }
            }

            if ($user && (empty($this->setor) || $this->setor === $user['setor'])) {
                $this->setLogin($user);
            } elseif (empty($this->getResult())) {
                $this->setResult('Login Inválido!');

                $attempt = new TableCrud("login_attempt");
                $attempt->loadArray(array("ip" => filter_var(Helper::getIP(), FILTER_VALIDATE_IP), "data" => date("Y-m-d H:i:s"), "username" => $this->user));
                $attempt->save();
            }
        }
    }

    /**
     * Seta dados de um usuário como login de acesso
     * @param array $usuario
     */
    public function setLogin(array $usuario)
    {
        $_SESSION['userlogin'] = $usuario;
        $_SESSION['userlogin']['imagem'] = "";
        $_SESSION['userlogin']['token'] = $this->getToken();

        //atualiza banco com token
        $up = new Update();
        $up->exeUpdate("usuarios", ['token' => $_SESSION['userlogin']['token'], "token_expira" => date("Y-m-d H:i:s"), "token_recovery" => null], "WHERE id = :id", "id={$_SESSION['userlogin']['id']}");

        //Cookies
        $this->setCookie("token", 0, -1);
        $this->setCookie("id", 0, -1);
        $this->setCookie("nome", 0, -1);
        $this->setCookie("imagem", 0, -1);
        $this->setCookie("setor", 0, -1);
    }

    /**
     * @param string $imagem
     * @return string
     */
    /*private function getImagem(string $imagem): string
    {
        if (!empty($imagem) && Check::isJson($imagem)) {
            $imagem = json_decode($imagem, true);

            if (!empty($imagem[0]) && !empty($imagem[0]['image']))
                return $imagem[0]['image'];
        }

        return "";
    }*/

    private function attemptExceded()
    {
        $ip = filter_var(Helper::getIP(), FILTER_VALIDATE_IP);
        $read = new Read();
        $read->exeRead(PRE . "login_attempt", "WHERE data > DATE_SUB(NOW(), INTERVAL 15 MINUTE) && ip = '{$ip}' && email = '{$this->user}'");
        $this->attempts = $read->getRowCount();

        return ($this->attempts > 10); // maximo de 10 tentativas por IP e email iguais em um intervalo de 15 minutos
    }

    private function isHuman()
    {
        if (defined("RECAPTCHA") && $this->attempts < 6) {
            if (empty($this->recaptcha))
                $this->setResult("resolva o captcha");

            $recaptcha = new ReCaptcha(RECAPTCHA);
            $resp = $recaptcha->verify($this->recaptcha, filter_var(Helper::getIP(), FILTER_VALIDATE_IP));
            if (!$resp->isSuccess())
                $this->setResult('
' . implode('

', $resp->getErrorCodes()) . '
');
        }

        return $this->getResult() ? false : true;
    }

    private function setCookie($name, $value, int $dias = 60)
    {
        $tempo = $dias < 0 ? time() - 1 : time() + (86400 * $dias);
        setcookie($name, $value, $tempo, "/"); // 2 meses de cookie
    }

    /**
     * @return string
     */
    private function getToken()
    {
        return md5("tokes" . rand(9999, 99999) . md5(base64_encode(date("Y-m-d H:i:s"))) . rand(0, 9999));
    }
}


<?php
/*
Copyright 2017 Thomas Bollmeier <entwickler@tbollmeier.de>

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 tbollmeier\parsian\codegen;

use tbollmeier\parsian\metagrammar\Parser as MetaGrammarParser;

class Api
{
    /**
     * Generate a parser from a grammar definition
     *
     * @param string $grammarFilePath path of grammar file
     * @param string $parserName name of parser class
     * @param string $namespace namespace of parser class
     * @param Output $output output to be written to
     * @param string $pathToHeaderComment path to file that contains header comment
     * @return mixed[] [true, ""] in case of success, [false, <error_message>] in error cases
     */
    static function generateParserFromGrammar(
        string $grammarFilePath,
        string $parserName="MyParser",
        string $namespace="",
        Output $output=null,
        string $pathToHeaderComment="")
    {
        $parser = new MetaGrammarParser();

        $ast = !empty($grammarFilePath) ?
            $parser->parseFile($grammarFilePath) :
            $parser->parseStdin();

        if ($ast !== false) {

            $generator = new CodeGenerator($parserName, $namespace);
            $generator->setHeaderCommentFile($pathToHeaderComment);
            $generator->generate($ast, $output);

            return [true, ""];

        } else {

            return [false, $parser->error()];

        }
    }

}


<?php
/**
 * DocumentSearch file class for cxense api
 */

namespace Bonnier\WP\Cxense\Services;

use Bonnier\WP\Cxense\Exceptions\DocumentSearchMissingSearch;
use Bonnier\WP\Cxense\Exceptions\DocumentSearchMissingCount;
use Bonnier\WP\Cxense\Exceptions\DocumentSearchWrongFacet;
use Bonnier\WP\Cxense\Exceptions\DocumentSearchWrongFilter;
use Bonnier\WP\Cxense\Exceptions\DocumentSearchWrongSorting;
use Bonnier\WP\Cxense\Http\HttpRequest;
use Bonnier\WP\Cxense\Settings\SettingsPage;
use Bonnier\WP\Cxense\Parsers\Document;
use Bonnier\WP\Cxense\WpCxense;

/**
 * DocumentSearch class
 */
class DocumentSearch
{

    /**
     * Instance object
     *
     * @var DocumentSearch $objInstance
     */
    private static $objInstance;

    /**
     * Search array
     *
     * @var array $arrSearch
     */
    private $arrSearch = [];

    /**
     * Payload array
     *
     * @var array $arrPayload
     */
    private $arrPayload = [];

    /**
     * Returning fields
     *
     * @var array $arrFields
     */
    private $arrFields = [
        'organisation' => [
            'taxo-cat',
            'taxo-cat-top',
            'taxo-cat-url',
            'taxo-editorialtype',
            'pagetype'
        ],
        'generic' => [
            'author',
            'description',
            'title',
            'click_url',
            'url',
            'bod-commercial-label',
            'bod-commercial-format',
            'dominantimage',
            'dominantthumbnail',
            'recs-publishtime',
            'recs-articleid'
        ]
    ];

    /**
     * Constructor
     */
    private function __construct()
    {
        //
    }

    /**
     * Singleton implementation
     *
     * @return DocumentSearch
     */
    public static function get_instance()
    {
        if (!isset(self::$objInstance)) {
            $obj = __CLASS__;
            self:: $objInstance = new $obj();
        }
        return self::$objInstance;
    }

    /**
     * Set settings object
     *
     * @return DocumentSearch
     */
    public function set_settings()
    {
        if ($strPrefix = WpCxense::instance()->settings->getOrganisationPrefix()) {
            $this->arrFields['organisation'] = array_map(function ($strValue) use ($strPrefix) {
                return $strPrefix . '-' . $strValue;
            }, $this->arrFields['organisation']);
        }
        return $this;
    }

    /**
     * Set search input array
     *
     * @param array $arrSearch
     * @return DocumentSearch
     */
    public function set_search(array $arrSearch)
    {
        $this->arrSearch = $arrSearch;

        return $this;
    }

    /**
     * Get documents from search result
     *
     * @return \stdClass
     */
    public function get_documents()
    {
        $this->validate_query_key();

        $objDocuments = $this
            ->set_per_page()
            ->set_page()
            ->set_facets()
            ->set_filter()
            ->set_exclude_filter()
            ->set_sorting()
            ->set_spellcheck()
            ->set_highlights()
            ->set_result_fields()
            ->get();

        $objDocuments->matches = $this->parse_documents($objDocuments->matches);
        return $objDocuments;
    }

    /**
     * Get documents
     *
     * @return \Illuminate\Support\Collection
     */
    private function get()
    {
        $this->set_site_id();
        $this->set_log_query();
        $this->arrPayload['query'] = QueryLanguage::getQuery(
            WpCxense::instance()->settings->getOrganisationPrefix(),
            $this->arrSearch['query']
        );

        $objResponse = HttpRequest::get_instance()->set_auth()->post('document/search', [
            'body' => json_encode($this->arrPayload, JSON_UNESCAPED_UNICODE)
        ]);

        return json_decode($objResponse->getBody());
    }

    /**
     * Validate the search request
     *
     * @return null
     * @throws DocumentSearchMissingSearch
     */
    private function validate_query_key()
    {
        if (!isset($this->arrSearch['query'])) {
            throw new DocumentSearchMissingSearch('Missing request "query" key!');
        }
    }

    /**
     * Set siteId to the search request
     *
     * @return DocumentSearch
     */
    private function set_site_id()
    {
        $this->arrPayload['siteId'] = WpCxense::instance()->settings->getSiteId();
        return $this;
    }

    /**
     * Set token operator
     *
     * @todo Not used at the moment, check need of token operator
     * @return DocumentSearch
     */
    private function set_token_operator()
    {
        $this->arrPayload['token-op'] = 'and';
        return $this;
    }

    /**
     * Set highlights
     *
     * @return DocumentSearch
     */
    private function set_highlights()
    {
        if (isset($this->arrSearch['highlights'])) {
            $this->arrPayload['highlights'] = $this->arrSearch['highlights'];
        }

        return $this;
    }

    /**
     * Set facets
     *
     * @return DocumentSearch
     * @throws DocumentSearchWrongFacet
     */
    private function set_facets()
    {
        if (isset($this->arrSearch['facets'])) {
            if (!is_array($this->arrSearch['facets'])) {
                throw new DocumentSearchWrongFacet('"Facets" key is not an array');
            }

            $this->arrPayload['facets'] = $this->arrSearch['facets'];
        }

        return $this;
    }

    /**
     * Set filter
     *
     * @return DocumentSearch
     * @throws DocumentSearchWrongFilter
     */
    private function set_filter()
    {
        if (isset($this->arrSearch['filter'])) {
            if (!is_array($this->arrSearch['filter'])) {
                throw new DocumentSearchWrongFilter('"Filter" key is not an array');
            }

            $arrFilterLines = [];
            foreach ($this->arrSearch['filter'] as $field => $value) {
                $arrFilterLines[] = sprintf(
                    'filter(%s:%s)',
                    $field,
                    json_encode(array_map('stripslashes', $value), JSON_UNESCAPED_UNICODE)
                );
            }

            $strFilterOperator = 'OR';
            if (isset($this->arrSearch['filter_operator'])) {
                $strFilterOperator = $this->arrSearch['filter_operator'];
            }

            $this->arrPayload['filter'] = implode(' ' . $strFilterOperator . ' ', $arrFilterLines);
        }

        return $this;
    }

    private function set_exclude_filter()
    {
        if (isset($this->arrSearch['filter_exclude'])) {
            if (!is_array($this->arrSearch['filter_exclude'])) {
                throw new DocumentSearchWrongFilter('"filter_exclude" key is not an array');
            }

            $arrFilterLines = [];
            foreach ($this->arrSearch['filter_exclude'] as $field => $value) {
                $arrFilterLines[] = sprintf(
                    'NOT filter(%s:%s)',
                    $field,
                    json_encode(array_map('stripslashes', $value), JSON_UNESCAPED_UNICODE)
                );
            }

            if (isset($this->arrPayload['filter'])) {
                $this->arrPayload['filter'] .= ' AND '. implode(' AND ', $arrFilterLines);
            } else {
                $this->arrPayload['filter'] = implode(' AND ', $arrFilterLines);
            }
        }

        return $this;
    }

    /**
     * Set sorting
     *
     * @return $this
     * @throws DocumentSearchWrongSorting
     */
    private function set_sorting()
    {
        if (isset($this->arrSearch['sorting'])) {
            if (!is_array($this->arrSearch['sorting']) ||
                !in_array($this->arrSearch['sorting']['type'], ['score', 'time']) ||
                !in_array($this->arrSearch['sorting']['order'], ['ascending', 'descending'])
            ) {
                throw new DocumentSearchWrongSorting(
                    '"sorting" key must be an array with keys "type" = "score|time" & "order" = "ascending|descending" '
                );
            }

            if ($this->arrSearch['sorting']['type'] === 'time') {
                $this->arrSearch['sorting']['field'] = 'recs-publishtime';
            }

            $this->arrPayload['sort'][] = $this->arrSearch['sorting'];
        }

        return $this;
    }
    /**
     * Set page limit to the search array
     *
     * @param integer $intCount Total items per page
     * @return $this
     */
    private function set_per_page($intCount = 10)
    {
        $this->arrPayload['count'] = $intCount;

        if (isset($this->arrSearch['count'])) {
            $this->arrPayload['count'] = $this->arrSearch['count'];
        }

        return $this;
    }

    /**
     * Set page number for calculating the correct starting offset
     *
     * @param integer $intPage Page number
     * @return $this
     * @throws DocumentSearchMissingCount
     */
    private function set_page($intPage = 1)
    {
        if (!isset($this->arrPayload['count'])) {
            throw new DocumentSearchMissingCount('\DocumentSearch::setPerPage() is required before \DocumentSearch::setPage()');
        }

        // get the page
        if (isset($this->arrSearch['page'])) {
            $intPage = $this->arrSearch['page'];
        }

        // set the starting point
        $this->arrPayload['start'] = $this->arrPayload['count'] * ($intPage - 1);

        return $this;
    }

    /**
     * Set result fields
     *
     * @return DocumentSearch
     */
    private function set_result_fields()
    {
        $this->arrPayload['resultFields'] = array_merge($this->arrFields['generic'], $this->arrFields['organisation']);
        return $this;
    }

    /**
     * Set logQuery
     *
     * @return DocumentSearch
     */
    private function set_log_query()
    {
        $this->arrPayload['logQuery'] = $this->arrSearch['query'];
        return $this;
    }

    /**
     * Set spellcheck
     *
     * @return DocumentSearch
     */
    private function set_spellcheck()
    {
        if (isset($this->arrSearch['spellcheck']) && $this->arrSearch['spellcheck'] === true) {
            $this->arrPayload['spellcheck']['query'] = $this->arrSearch['query'];
        }
        return $this;
    }

    /**
     * Parse documents to cxense object
     *
     * @param array $arrDocuments
     * @return array
     */
    private function parse_documents(array $arrDocuments)
    {
        $arrCollection = [];

        foreach ($arrDocuments as $objDocument) {
            $arrCollection[] = new Document($objDocument);
        }

        return $arrCollection;
    }
}


<?php namespace im\Primitive\Support\Traits;

use Closure;
use im\Primitive\Bool\Bool;
use im\Primitive\Support\Abstracts\Number;
use im\Primitive\Support\Abstracts\Type;
use im\Primitive\Support\Contracts\ArrayableContract;
use im\Primitive\Support\Contracts\BooleanContract;
use im\Primitive\Support\Contracts\ContainerContract;
use im\Primitive\Support\Contracts\FloatContract;
use im\Primitive\Support\Contracts\IntegerContract;
use im\Primitive\Support\Contracts\StringContract;

/**
 * Class RetrievableTrait
 *
 * @package im\Primitive\Support\Traits
 * @author Igor Krimerman <i.m.krimerman@gmail.com>
 */
trait RetrievableTrait {

    /**
     * Retrieve array from array, ContainerContract, ArrayableContract or
     * object, otherwise return $default.
     *
     * @param mixed $value
     * @param mixed $default
     * @return mixed
     */
    public function getArrayable($value, $default = null)
    {
        switch (true)
        {
            case is_array($value);
                return $value;
            case $value instanceof ContainerContract:
                return $value->value();
            case $value instanceof ArrayableContract:
                return $value->toArray();
            case is_object($value):
                return get_object_vars($value);
            default:
                return value($default);
        }
    }

    /**
     * Retrieve string from number, string, bool, array,  FloatContract, IntegerContract
     * StringContract, BooleanContract, ContainerContract, ArrayableContract or
     * object that has __toString method, otherwise return $default.
     *
     * @param mixed $value
     * @param mixed $default
     * @return string
     */
    public function getStringable($value, $default = null)
    {
        switch (true)
        {
            case is_string($value):
            case $value instanceof StringContract:
            case $value instanceof BooleanContract:
            case $value instanceof IntegerContract:
            case $value instanceof FloatContract:
                return (string) $value;

            case is_array($value):
            case $value instanceof ContainerContract:
            case $value instanceof ArrayableContract:
                return (string) container($value)->join();

            case is_bool($value):
                return (string) bool($value);

            case is_numeric($value):
            case is_object($value) && method_exists($value, '__toString'):
                return (string) $value;

            default:
                return value($default);
        }
    }

    /**
     * Retrieve bool from number, string, bool, FloatContract, IntegerContract
     * StringContract, BooleanContract, ContainerContract, otherwise return $default.
     *
     * @param mixed $value
     * @param mixed $default
     * @return bool
     */
    public function getBoolable($value, $default = null)
    {
        switch (true)
        {
            case is_bool($value):
                return $value;

            case is_numeric($value):
                return (bool) ((int) $value);

            case is_string($value):
            case $value instanceof StringContract:
                return $this->boolFromString($value);

            case $value instanceof BooleanContract:
                return $value->value();

            case $value instanceof IntegerContract:
            case $value instanceof FloatContract:
                return $value->toBool()->value();

            case $value instanceof ContainerContract:
                return $value->isNotEmpty();

            default:
                return value($default);
        }
    }

    /**
     * Retrieve int from number, string, bool, FloatContract, IntegerContract
     * StringContract, BooleanContract, otherwise return $default.
     *
     * @param mixed $value
     * @param mixed $default
     * @return int
     */
    public function getIntegerable($value, $default = null)
    {
        switch (true)
        {
            case is_numeric($value):
            case is_string($value):
            case is_bool($value):
                return (int) $value;

            case $value instanceof IntegerContract:
            case $value instanceof StringContract:
                return (int) $value->value();

            case $value instanceof FloatContract:
            case $value instanceof BooleanContract:
                return $value->toInt()->value();

            default:
                return value($default);
        }
    }

    /**
     * Retrieve float from number, string, bool, FloatContract, IntegerContract
     * StringContract, BooleanContract, otherwise return $default.
     *
     * @param mixed $value
     * @param mixed $default
     * @return float|mixed
     */
    public function getFloatable($value, $default = null)
    {
        switch (true)
        {
            case is_numeric($value):
            case is_string($value):
            case is_bool($value):
                return (float) $value;

            case $value instanceof FloatContract:
            case $value instanceof StringContract:
                return (float) $value->value();

            case $value instanceof IntegerContract:
            case $value instanceof BooleanContract:
                return $value->toFloat()->value();

            default:
                return value($default);
        }
    }

    /**
     * Retrieve number from any type or return default.
     *
     * @param $value
     * @param null|mixed $default
     * @return float|int|mixed
     */
    public function getNumberable($value, $default = null)
    {
        switch(true)
        {
            case is_int($value):
            case is_float($value):
                return $value;
            case is_numeric($value) && strpos($value, '.'):
                return (float) $value;
            case is_numeric($value):
            case is_bool($value):
                return (int) $value;
            case $value instanceof IntegerContract:
            case $value instanceof FloatContract:
                return $value->value();
            case $value instanceof BooleanContract:
                return $value->toInt()->value();
            case $value instanceof StringContract:
                return (int) $value->value();
            default:
                return value($default);
        }
    }

    /**
     * Retrieve value that can be used in search methods.
     *
     * @param mixed $value
     * @param mixed $default
     * @return array|string|\Closure|mixed
     */
    public function getSearchable($value, $default = [])
    {
        if ($this->isArrayable($value))
        {
            return $this->getArrayable($value, $default);
        }
        elseif ($this->isStringable($value))
        {
            return $this->getStringable($value, $default);
        }
        elseif ($value instanceof Closure)
        {
            return $value;
        }

        return value($default);
    }

    /**
     * Check if $value is Arrayable.
     *
     * @param mixed $value
     * @return bool
     */
    public function isArrayable($value)
    {
        return  is_array($value) ||
                $value instanceof ContainerContract ||
                $value instanceof ArrayableContract ||
                is_object($value) && ! $this->isStringable($value) && ! $this->isIntegerable($value);
    }

    /**
     * Check if $value is Stringable.
     * Second argument can be specified to check strict.
     *
     * @param mixed $value
     * @param bool  $strict
     * @return bool
     */
    public function isStringable($value, $strict = false)
    {
        if ( ! $this->getBoolable($strict))
        {
            return  is_string($value) ||
                    is_array($value) ||
                    is_numeric($value) ||
                    is_bool($value) ||
                    $value instanceof StringContract ||
                    $value instanceof ContainerContract ||
                    $value instanceof ArrayableContract ||
                    (is_object($value) && method_exists($value, '__toString'));
        }

        return is_string($value) || $strict instanceof StringContract;
    }

    /**
     * Check if $value is Integerable.
     * Second argument can be specified to check strict.
     *
     * @param mixed $value
     * @param bool  $strict
     * @return bool
     */
    public function isIntegerable($value, $strict = false)
    {
        if ( ! $this->getBoolable($strict))
        {
            return  is_numeric($value) ||
                    is_string($value) ||
                    is_bool($value) ||
                    $value instanceof IntegerContract ||
                    $value instanceof FloatContract ||
                    $value instanceof StringContract ||
                    $value instanceof BooleanContract;
        }

        return  is_int($value) ||
                is_real($value) ||
                $value instanceof IntegerContract ||
                $value instanceof FloatContract;
    }

    /**
     * Check if $value is Floatable.
     * Second argument can be specified to check strict.
     *
     * @param mixed $value
     * @param bool  $strict
     * @return bool
     */
    public function isFloatable($value, $strict = false)
    {
        return $this->isIntegerable($value, $strict);
    }

    /**
     * Check if $value is Boolable.
     * Second argument can be specified to check strict.
     *
     * @param mixed $value
     * @param bool  $strict
     * @return bool
     */
    public function isBoolable($value, $strict = false)
    {
        if ( ! $this->getBoolable($strict))
        {
            return $this->isIntegerable($value);
        }

        return is_bool($value) || $value instanceof BooleanContract;
    }

    /**
     * Construct proper Bool from string using grammar.
     *
     * @param mixed $value
     * @return bool
     */
    protected function boolFromString($value)
    {
        $grammar = $this->getGrammar();

        $value = $this->getStringable($value);

        if (isset($grammar[$value])) return $grammar[$value];

        return false;
    }

    /**
     * Return grammar to construct Bool from string.
     *
     * @return array
     */
    protected function getGrammar()
    {
        return [
            'true' => true, 'false' => false,
            'on' => true,   'off' => false,
            'yes' => true,  'no' => false,
            'y' => true,    'n' => false,
            '+' => true,    '-' => false
        ];
    }

    /**
     * Check if $value is instance of Primitive Type.
     *
     * @param mixed $value
     * @return bool
     */
    public function isType($value)
    {
        return $value instanceof Type;
    }

    /**
     * Check if $value is not instance of Primitive Type.
     *
     * @param mixed $value
     * @return bool
     */
    public function isNotType($value)
    {
        return ! $this->isType($value);
    }
}


<?php
/*
Copyright 2016-2017 Thomas Bollmeier <entwickler@tbollmeier.de>

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 tbollmeier\parsian\input;

class TokenStream
{
    private $tokenIn;
    private $tokens;
    private $bufSize;
    private $consumedTokens;

    public function __construct(TokenInput $tokenIn)
    {
        $this->tokenIn = $tokenIn;
        $this->tokens = [];
        $this->bufSize = 1;
        $this->consumedTokens = [[]];
    }

    public function openTokenInput()
    {
        $this->tokenIn->open();
    }

    public function closeTokenInput()
    {
        $this->tokenIn->close();
    }

    public function lookup()
    {
        $tokens = $this->lookupMany(1);

        return !empty($tokens) ? $tokens[0] : false;
    }

    public function checkFor(...$expectedTokenTypes)
    {
        $numExpected = count($expectedTokenTypes);

        $actualTokens = $this->lookupMany($numExpected);
        $numActual = count($actualTokens);

        if ($numActual === $numExpected) {
            for ($i=0; $i<$numActual; $i++) {
                $actualToken = $actualTokens[$i];
                $expectedTokenType = $expectedTokenTypes[$i];
                if (!$this->matches($actualToken, $expectedTokenType)) {
                    return false;
                }
            }

            return $actualTokens;

        } else {
            return false;
        }

    }

    private function matches(Token $actToken, $expType)
    {
        if (!is_array($expType)) {
            return $actToken->getType() == $expType;
        } else {
            return array_search($actToken->getType(), $expType) !== false;
        }
    }

    public function consumeExpected(...$expectedTokenTypes)
    {
        $actualTokens = call_user_func_array([$this, "checkFor"], $expectedTokenTypes);
        if ($actualTokens !== false) {
            $cnt = count($actualTokens);
            for ($i=0; $i<$cnt; $i++) {
                $this->recordConsumption(array_shift($this->tokens));
            }
            return $actualTokens;
        } else {
            throw $this->createParseException($expectedTokenTypes);
        }
    }

    private function createParseException($expectedTokenTypes)
    {
        $numExpected = count($expectedTokenTypes);

        $actualTokens = $this->lookupMany($numExpected);
        $numActual = count($actualTokens);

        for ($i=0; $i < $numExpected; $i++) {

            $expected = $expectedTokenTypes[$i];

            if ($i > $numActual-1) {
                return is_array($expected) ?
                    ParseException::noTokenForTokenTypes($expected) :
                    ParseException::noTokenForTokenType($expected);

            }

            $actualToken = $actualTokens[$i];

            if (!$this->matches($actualToken, $expected)) {
                return ParseException::unexpectedToken($actualToken);
            }

        }

        return ParseException::createError("Parsing error");

    }

    /** lookup next n tokens
     *
     */
    public function lookupMany($n = 1)
    {
        if ($n > $this->bufSize) {
            $this->bufSize = $n;
        }

        $this->fillBuffer();

        $tokens = [];
        $size = min($n, count($this->tokens));
        for ($i=0; $i<$size; $i++) {
            $tokens[] = $this->tokens[$i];
        }

        return $tokens;
    }

    public function consume()
    {
        if (empty($this->tokens)) {
            $this->fillBuffer();
        }

        if (!empty($this->tokens)) {
            $token = array_shift($this->tokens);
            $this->recordConsumption($token);
            return $token;
        } else {
            return false;
        }
    }

    public function consumeMany($n)
    {
        $consumed = $this->lookupMany($n);
        $cnt = count($consumed);

        for ($i=0; $i<$cnt; $i++) {
            $this->recordConsumption(array_shift($this->tokens));
        }

        return $consumed;
    }

    private function fillBuffer()
    {
        while ($this->tokenIn->hasMoreTokens() && count($this->tokens) < $this->bufSize) {
            $this->tokens[] = $this->tokenIn->nextToken();
        }
    }

    public function newConsumption()
    {
        array_unshift($this->consumedTokens, []);
    }

    public function commitConsumption()
    {
        $tokens = array_shift($this->consumedTokens);
        if (count($this->consumedTokens) > 0) {
            $this->consumedTokens[0] = array_merge($this->consumedTokens[0], $tokens);
        } else {
            $this->consumedTokens = [[]];
        }
    }

    public function rollbackConsumption()
    {
        $tokens = array_reverse(array_shift($this->consumedTokens));
        foreach ($tokens as $token) {
            array_unshift($this->tokens, $token);
        }
    }

    private function recordConsumption($token)
    {
        array_push($this->consumedTokens[0], $token);
    }

    public function hasMoreTokens() : bool
    {
        return !empty($this->tokens) || $this->tokenIn->hasMoreTokens();
    }

    public function getLastUnconsumedToken()
    {
        $num = count($this->tokens);

        return $num > 0 ?
            $this->tokens[$num -1] :
            null;
    }

}


<?php namespace im\Primitive\Support\Abstracts;

use Serializable;
use UnexpectedValueException;

use im\Primitive\Int\Int;
use im\Primitive\Bool\Bool;
use im\Primitive\Float\Float;
use im\Primitive\String\String;
use im\Primitive\Container\Container;
use im\Primitive\Support\Contracts\TypeContract;
use im\Primitive\Support\Traits\RetrievableTrait;

/**
 * Class Number
 *
 * @package im\Primitive\Support\Abstracts
 * @author Igor Krimerman <i.m.krimerman@gmail.com>
 */
abstract class Number extends Type implements TypeContract, Serializable {

    use RetrievableTrait;

    /**
     * Storing value.
     * @var number
     */
    protected $value;

    /**
     * Construct Number Type.
     *
     * @param mixed $value
     */
    public function __construct($value)
    {
        $this->initialize($value);
    }

    /**
     * Setter for inner value.
     *
     * @param mixed $value
     * @return $this
     */
    public function set($value)
    {
        $this->value = $this->retrieveValue($value);

        return $this;
    }

    /**
     * Getter for inner value.
     *
     * @return number
     */
    public function get()
    {
        return $this->value();
    }

    /**
     * {@inheritdoc}
     */
    public function value()
    {
        return $this->value;
    }

    /**
     * Plus $value to inner value.
     *
     * @param mixed $value
     * @return static
     */
    public function plus($value)
    {
        return new static($this->value + $this->retrieveValue($value));
    }

    /**
     * Minus $value from inner value.
     *
     * @param mixed $value
     * @return static
     */
    public function minus($value)
    {
        return new static($this->value - $this->retrieveValue($value));
    }

    /**
     * Multiply inner value by $value.
     *
     * @param mixed $value
     * @return static
     */
    public function multiply($value)
    {
        return new static($this->value * $this->retrieveValue($value));
    }

    /**
     * Divide inner value by $value.
     *
     * @param mixed $value
     * @throws UnexpectedValueException
     * @return static
     */
    public function divide($value)
    {
        $retrieved = $this->retrieveValue($value);

        if ($retrieved === 0)
        {
            throw new UnexpectedValueException('Division by zero is unacceptable');
        }

        return new static($this->value / $retrieved);
    }

    /**
     * Calculate modulo.
     *
     * @param mixed $modulo
     * @return \im\Primitive\Int\Int
     */
    public function modulo($modulo)
    {
        return new Int($this->value % $this->retrieveValue($modulo));
    }

    /**
     * Raise inner value to the $power.
     *
     * @param $power
     * @return static
     */
    public function power($power)
    {
        return new static(pow($this->value, $this->retrieveValue($power)));
    }

    /**
     * Calculate square root.
     *
     * @return static
     */
    public function sqrt()
    {
        return new Float(sqrt($this->value));
    }

    /**
     * Return absolute value.
     *
     * @return static
     */
    public function abs()
    {
        return new static(abs($this->value));
    }

    /**
     * Sine.
     *
     * @return \im\Primitive\Float\Float
     */
    public function sin()
    {
        return new Float(sin($this->value));
    }

    /**
     * Cosine.
     *
     * @return \im\Primitive\Float\Float
     */
    public function cos()
    {
        return new Float(cos($this->value));
    }

    /**
     * Tangent.
     *
     * @return \im\Primitive\Float\Float
     */
    public function tan()
    {
        return new Float(tan($this->value));
    }

    /**
     * Return pi.
     *
     * @return \im\Primitive\Float\Float
     */
    public function pi()
    {
        return new Float(pi());
    }

    /**
     * Calculate factorial of inner value.
     *
     * @return int|number
     */
    public function factorial()
    {
        if ($this->value < 100)
        {
            return factorial_recursive($this->value);
        }

        return factorial($this->value);
    }

    /**
     * Format a number with grouped thousands and return String.
     *
     * @param int|IntegerContract $decimals
     * @param string|StringContract $decimalDelimiter
     * @param string|StringContract $thousandDelimiter
     *
     * @return \im\Primitive\Float\Float
     */
    public function format($decimals = 2, $decimalDelimiter = '.', $thousandDelimiter = ' ')
    {
        return new String(
            number_format(
                (float) $this->value,
                $this->getIntegerable($decimals),
                $this->getStringable($decimalDelimiter),
                $this->getStringable($thousandDelimiter)
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function length()
    {
        return (new String($this->value))->length();
    }

    /**
     * Check if is inner value is true.
     *
     * @return bool
     */
    public function isTrue()
    {
        return $this->toBool()->isTrue();
    }

    /**
     * Check if is inner value is false.
     * @return bool
     */
    public function isFalse()
    {
        return $this->toBool()->isFalse();
    }

    /**
     * Check if is inner value is equals to $value.
     *
     * @param mixed $value
     * @return bool
     */
    public function isEqual($value)
    {
        return $this->value === $this->retrieveValue($value);
    }

    /**
     * Check if is inner value is negative.
     *
     * @return bool
     */
    public function isNegative()
    {
        return $this->value < 0;
    }

    /**
     * Check if is inner value is not negative.
     *
     * @return bool
     */
    public function isNotNegative()
    {
        return ! $this->isNegative();
    }

    /**
     * Check if is inner value is greater than $value.
     *
     * @param mixed $value
     * @return bool
     */
    public function isGreaterThan($value)
    {
        return $this->value > $this->retrieveValue($value);
    }

    /**
     * Check if is inner value is greater than or equal to $value.
     *
     * @param mixed $value
     * @return bool
     */
    public function isGreaterThanOrEqual($value)
    {
        return $this->value >= $this->retrieveValue($value);
    }

    /**
     * Check if is inner value is lower than $value.
     *
     * @param mixed $value
     * @return bool
     */
    public function isLowerThan($value)
    {
        return $this->value < $this->retrieveValue($value);
    }

    /**
     * Check if is inner value is lower than or equal to $value.
     *
     * @param mixed $value
     * @return bool
     */
    public function isLowerThanOrEqual($value)
    {
        return $this->value <= $this->retrieveValue($value);
    }

    /**
     * Convert Number Type to Bool Type.
     *
     * @return \im\Primitive\Bool\Bool
     */
    public function toBool()
    {
        return new Bool((bool) $this->value);
    }

    /**
     * Convert Number Type to String Type.
     *
     * @return \im\Primitive\String\String
     */
    public function toString()
    {
        return new String((string) $this->value);
    }

    /**
     * Convert Number Type to Container Type.
     *
     * @return \im\Primitive\Container\Container
     */
    public function toContainer()
    {
        return new Container([$this->value]);
    }

    /**
     * Destructor
     */
    public function __destruct()
    {
        unset($this->value);
    }

    /**
     * {@inheritdoc}
     */
    public function __toString()
    {
        return (string) $this->value;
    }
}


<?php

namespace Sonic\Extension;

/**
 * Twig
 *
 * @package    Sonic
 * @subpackage Extension
 * @author     Hassan Khan <contact@hassankhan.me>
 * @link       https://github.com/hassankhan/Sonic
 * @license    MIT
 * @since      0.6
 */
class Twig extends \Twig_Extension
{
    /**
     * I don't even know why this is here, but it is, so deal with it
     *
     * @return string
     */
    public function getName()
    {
        return 'Twig';
    }

    /**
     * So apparently you have to explicitly return the functions you want
     * to be available
     *
     * @return array
     */
    public function getFunctions()
    {
        return array(
            new \Twig_SimpleFunction('config',     array($this, 'config')),
            new \Twig_SimpleFunction('site_url',   array($this, 'site_url')),
            new \Twig_SimpleFunction('site_title', array($this, 'site_title')),
            new \Twig_SimpleFunction('url_for',    array($this, 'url_for')),
            new \Twig_SimpleFunction('link_for',   array($this, 'link_for'), array('is_safe' => array('html')))
        );
    }

    /**
     * Returns Sonic configuration
     *
     * @return array
     * @codeCoverageIgnore
     */
    public function config($option)
    {
        $sonic = \Sonic\Sonic::instance();
        return $sonic->app['settings'][$option];
    }

    /**
     * Returns the site root
     *
     * @return string
     * @codeCoverageIgnore
     */
    public function site_url()
    {
        $sonic = \Sonic\Sonic::instance();
        return $sonic->app['settings']['site.site_root'];
    }

    /**
     * Returns the site title
     *
     * @return string
     * @codeCoverageIgnore
     */
    public function site_title()
    {
        $sonic = \Sonic\Sonic::instance();
        return $sonic->app['settings']['site.site_title'];
    }

    /**
     * Returns a fully-qualified URL for a given filename in the 'content' directory
     *
     * @see    \Sonic\Helper::url_for()
     * @param  string $file_name
     * @return string|null
     * @codeCoverageIgnore
     */
    public function url_for($file_name)
    {
        $sonic = \Sonic\Sonic::instance();
        return $sonic->app['helper']->url_for($file_name);
    }

    /**
     * Returns a HTML <a> for a given filename in the 'content' directory
     *
     * @see    \Sonic\Helper::link_for()
     * @param  string $file_name
     * @return string|null
     * @codeCoverageIgnore
     */
    public function link_for($file_name)
    {
        $sonic = \Sonic\Sonic::instance();
        return $sonic->app['helper']->link_for($file_name);
    }

}


<?php

/*
 * This file is part of Mandango.
 *
 * (c) Pablo Díez <pablodip@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Mandango\Mondator\Definition;

/**
 * Represents a definition of a class.
 *
 * @author Pablo Díez <pablodip@gmail.com>
 *
 * @api
 */
class Definition
{
    private $class;
    private $parentClass;
    private $interfaces;
    private $final;
    private $abstract;
    private $properties;
    private $methods;
    private $docComment;

    /**
     * Constructor.
     *
     * @param string $class The class.
     *
     * @api
     */
    public function __construct($class)
    {
        $this->setClass($class);
        $this->interfaces = array();
        $this->final = false;
        $this->abstract = false;
        $this->properties = array();
        $this->methods = array();
    }

    /**
     * Set the class.
     *
     * @param string $class The class.
     *
     * @api
     */
    public function setClass($class)
    {
        $this->class = $class;
    }

    /**
     * Returns the class.
     *
     * @return string The class.
     *
     * @api
     */
    public function getClass()
    {
        return $this->class;
    }

    /**
     * Returns the namespace.
     *
     * @return string|null The namespace.
     *
     * @api
     */
    public function getNamespace()
    {
        if (false !== $pos = strrpos($this->class, '\\')) {
            return substr($this->class, 0, $pos);
        }

        return null;
    }

    /**
     * Returns the class name.
     *
     * @return string|null The class name.
     *
     * @api
     */
    public function getClassName()
    {
        if (false !== $pos = strrpos($this->class, '\\')) {
            return substr($this->class, $pos + 1);
        }

        return $this->class;
    }

    /**
     * Set the parent class.
     *
     * @param string $parentClass The parent class.
     *
     * @api
     */
    public function setParentClass($parentClass)
    {
        $this->parentClass = $parentClass;
    }

    /**
     * Returns the parent class.
     *
     * @return string The parent class.
     *
     * @api
     */
    public function getParentClass()
    {
        return $this->parentClass;
    }

    /**
     * Add an interface.
     *
     * @param string $interface The interface.
     *
     * @api
     */
    public function addInterface($interface)
    {
        $this->interfaces[] = $interface;
    }

    /**
     * Set the interfaces.
     *
     * @param array $interfaces The interfaces.
     *
     * @api
     */
    public function setInterfaces(array $interfaces)
    {
        $this->interfaces = array();
        foreach ($interfaces as $interface) {
            $this->addInterface($interface);
        }
    }

    /**
     * Returns the interfaces.
     *
     * @return array The interfaces.
     *
     * @api
     */
    public function getInterfaces()
    {
        return $this->interfaces;
    }

    /**
     * Set if the class is final.
     *
     * @param bool $final If the class is final.
     *
     * @api
     */
    public function setFinal($final)
    {
        $this->final = (bool) $final;
    }

    /**
     * Returns if the class is final.
     *
     * @return bool Returns if the class is final.
     *
     * @api
     */
    public function isFinal()
    {
        return $this->final;
    }

    /**
     * Set if the class is abstract.
     *
     * @param bool $abstract If the class is abstract.
     *
     * @api
     */
    public function setAbstract($abstract)
    {
        $this->abstract = (bool) $abstract;
    }

    /**
     * Returns if the class is abstract.
     *
     * @return bool If the class is abstract.
     *
     * @api
     */
    public function isAbstract()
    {
        return $this->abstract;
    }

    /**
     * Add a property.
     *
     * @param Mandango\Mondator\Definition\Property $property The property.
     *
     * @api
     */
    public function addProperty(Property $property)
    {
        $this->properties[] = $property;
    }

    /**
     * Set the properties.
     *
     * @param array $properties An array of properties.
     *
     * @api
     */
    public function setProperties(array $properties)
    {
        $this->properties = array();
        foreach ($properties as $name => $property) {
            $this->addProperty($property);
        }
    }

    /**
     * Returns the properties.
     *
     * @return array The properties.
     *
     * @api
     */
    public function getProperties()
    {
        return $this->properties;
    }

    /**
     * Returns if a property exists by name.
     *
     * @param string $name The property name.
     *
     * @return bool If the property exists.
     *
     * @api
     */
    public function hasPropertyByName($name)
    {
        foreach ($this->properties as $property) {
            if ($property->getName() == $name) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns a property by name.
     *
     * @param string $name The property name.
     *
     * @return Mandango\Mondator\Definition\Property The property.
     *
     * @throws \InvalidArgumentException If the property does not exists.
     *
     * @api
     */
    public function getPropertyByName($name)
    {
        foreach ($this->properties as $property) {
            if ($property->getName() == $name) {
                return $property;
            }
        }

        throw new \InvalidArgumentException(sprintf('The property "%s" does not exists.', $name));
    }

    /**
     * Remove property by name.
     *
     * @param string $name The property name.
     *
     * @throws \InvalidArgumentException If the property does not exists.
     *
     * @api
     */
    public function removePropertyByName($name)
    {
        foreach ($this->properties as $key => $property) {
            if ($property->getName() == $name) {
                unset($this->properties[$key]);
                return;
            }
        }

        throw new \InvalidArgumentException(sprintf('The property "%s" does not exists.', $name));
    }

    /**
     * Add a method.
     *
     * @param Mandango\Mondator\Definition\Method $method The method.
     *
     * @api
     */
    public function addMethod(Method $method)
    {
        $this->methods[] = $method;
    }

    /**
     * Set the methods.
     *
     * @param array $methods An array of methods.
     *
     * @api
     */
    public function setMethods(array $methods)
    {
        $this->methods = array();
        foreach ($methods as $name => $method) {
            $this->addMethod($method);
        }
    }

    /**
     * Returns the methods.
     *
     * @return array The methods.
     *
     * @api
     */
    public function getMethods()
    {
        return $this->methods;
    }

    /**
     * Returns if exists a method by name.
     *
     * @param string $name The method name.
     *
     * @return bool If the method exists.
     *
     * @api
     */
    public function hasMethodByName($name)
    {
        foreach ($this->methods as $method) {
            if ($method->getName() == $name) {
                return true;
            }
        }

        return false;
    }

    /**
     * Return a method by name.
     *
     * @param string $name The method name.
     *
     * @return Mandango\Mondator\Definition\Method The method.
     *
     * @throws \InvalidArgumentException If the method does not exists.
     *
     * @api
     */
    public function getMethodByName($name)
    {
        foreach ($this->methods as $method) {
            if ($method->getName() == $name) {
                return $method;
            }
        }

        throw new \InvalidArgumentException(sprintf('The method "%s" does not exists.', $name));
    }

    /**
     * Remove a method by name.
     *
     * @param string $name The method name.
     *
     * @throws \InvalidArgumentException If the method does not exists.
     *
     * @api
     */
    public function removeMethodByName($name)
    {
        foreach ($this->methods as $key => $method) {
            if ($method->getName() == $name) {
                unset($this->methods[$key]);
                return;
            }
        }

        throw new \InvalidArgumentException(sprintf('The method "%s" does not exists.', $name));
    }

    /**
     * Set the doc comment.
     *
     * @param string|null $docComment The doc comment.
     *
     * @api
     */
    public function setDocComment($docComment)
    {
        $this->docComment = $docComment;
    }

    /**
     * Returns the doc comment.
     *
     * @return string|null The doc comment.
     *
     * @api
     */
    public function getDocComment()
    {
        return $this->docComment;
    }
}


<?php

namespace Sonic\Flysystem\Plugin;

use Michelf\MarkdownExtra;

/**
 * Markdown
 *
 * @package    Sonic
 * @subpackage Flysystem
 * @author     Hassan Khan <contact@hassankhan.me>
 * @link       https://github.com/hassankhan/Sonic
 * @license    MIT
 * @since      0.7
 */
class Markdown implements \League\Flysystem\PluginInterface
{
    /**
     * Filesystem object
     *
     * @var \League\Flysystem\FilesystemInterface
     */
    protected $filesystem;

    /**
     * Markdown parser
     * @var \Michelf\MarkdownInterface
     */
    protected $parser;

    /**
     * Sets the filesystem for this plugin
     *
     * @param LeagueFlysystemFilesystemInterface $filesystem
     * @codeCoverageIgnore
     */
    public function setFilesystem(\League\Flysystem\FilesystemInterface $filesystem)
    {
        $this->filesystem = $filesystem;
    }

    /**
     * Sets the Markdown parser
     *
     * @param \Michelf\MarkdownInterface $parser
     * @codeCoverageIgnore
     */
    public function setParser(\Michelf\MarkdownInterface $parser)
    {
        $this->parser = $parser;
    }

    /**
     * Returns plugin's method name
     *
     * @return string
     */
    public function getMethod()
    {
        return 'parse';
    }

    /**
     * Plugin handler
     *
     * @param  string $path
     * @return array
     */
    public function handle($path = '')
    {
        $file = $this->filesystem->getAdapter()->read($path);
        return array(
            'meta'     => $this->parse_meta($file['contents']),
            'contents' => $this->parse_content($file['contents']),
            'path'     => $file['path']
        );
    }

    /**
     * Parses Markdown file headers for metadata
     *
     * @param  string $file The loaded Markdown file as a string
     * @return array        An array containing file metadata
     * @codeCoverageIgnore
     */
    protected function parse_meta($file)
    {
        // Grab meta section between '/* ... */' in the content file
        preg_match_all('#/\*(.*?)\*/#s', $file, $meta);

        // Retrieve individual meta fields
        preg_match_all('#^[\t]?(\w*):(.*)$#mi', $meta[1][0], $match);

        for ($i=0; $i < count($match[1]); $i++) {
            $result[strtolower($match[1][$i])] = trim(htmlentities($match[2][$i]));
        }

        return $result;
    }

    /**
     * Parses Markdown file for content
     *
     * @param  string $file The loaded Markdown file as a string
     * @return string       The parsed string as HTML
     * @codeCoverageIgnore
     */
    protected function parse_content($file)
    {
        $content = preg_replace('#/\*.+?\*/#s', '', $file);
        return MarkdownExtra::defaultTransform($content);
    }

}