<?php

/**
 * @brief       Text Class
 * @author      -storm_author-
 * @copyright   -storm_copyright-
 * @package     IPS Social Suite
 * @subpackage  Babble
 * @since       1.0.0 Beta 1
 * @version     -storm_version-
 */

namespace IPS\babble;

if( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
    header( ( isset( $_SERVER[ 'SERVER_PROTOCOL' ] ) ? $_SERVER[ 'SERVER_PROTOCOL' ] : 'HTTP/1.0' ) . ' 403 Forbidden' );
    exit;
}

class _Text extends \IPS\Text\Parser
{

    /**
     * @brief a list of any images we find from [img] bbc
     * @var array
     */
    protected static $images = [];

    /**
     * @brief a proof of concept bbc, as the parser class isn't too clear
     * @var array
     */
    protected $myBBC = [
        'facepalm' => [
            'tag' => 'img',
            'attributes' => [
                'src' => '{option}',
                'class' => 'ipsImage cBabbleFacePalm'
            ],
            'single' => true
        ]
    ];

    protected static $embeddable = [
        'youtube' => [
            'match' => '/youtube\.com\/watch\?v=([^\&\?\/]+)/iu',
            'template' => 'youtube'
        ],
        'youtubeAlt' => [
            'match' => '/youtu\.be\/([^\&\?\/]+)/iu',
            'template' => 'youtube'
        ],
        'ytbe' => [
            'match' => '/youtube\.com\/embed\/([^\&\?\/]+)/isu',
            'template' => 'youtube'
        ]
    ];

    /**
     * @brief used to store smilies, so emojione lib doesn't molest them.
     * @var array
     */
    protected static $store = [];

    /**
     * @brief since you can send to babble who messages, this is set the parsers also uses that users limitations,
     * instead of who is currently logged in
     * @var null
     */
    protected static $babbleMember = null;

    /**
     * @brief an override of the bbcs, so we can add out own if needed.
     * @param \IPS\Member $member
     * @param bool|string $area
     * @return array|mixed
     */
    public function bbcodeTags( \IPS\Member $member, $area )
    {
        /**
         * call the parent
         */
        $bbc = \call_user_func_array( 'parent::bbcodeTags', \func_get_args() );

        /**
         * merger ours into thiers.
         */
        $bbc = \array_merge( $bbc, $this->myBBC );

        return $bbc;
    }

    /**
     * @brief for any one who wants to add embeds, there isn't a whole lot of auto handling here, so use wisely.
     * @return array
     */
    protected static function getEmbedCode()
    {
        return static::$embeddable;
    }

    /**
     * @brief converts message to html and cleanses it.
     * @param $msg
     * @param bool $skipHtml
     * @return array
     */
    public static function build( $msg, \IPS\babble\Member $member, $skipHtml = false )
    {

        static::$babbleMember = $member;

        /**
         * lets rip out any IPS emoticons and put placeholders so we can do our cleansing in peace!
         */
        $msg = static::prep( $msg );

        /**
         * lets do our bbc stuff first before we handle it over to IPS to get mangled!
         */
        $data = static::convertBBC( $msg, $skipHtml );

        $msg = $data[ 'msg' ];

        $images = $data[ 'images' ];

        /**
         * cause there are assholes out there in the world! this removes any thing greater than 2 ln's, so double spacing is the max.
         */
        $msg = \preg_replace( '/(?:(?:\r\n|\r|\n)\s*){2}/s', "\n\n", $msg );

        /**
         * lets remove any extra ln...
         */
        $msg = \rtrim( \trim( $msg ) );

        /**
         * we convert any emoji over so they are consistent across all devices
         * was using the JS module to do this, but it was too slow, so we do it at
         * save now.
         * @todo might make this optional in a later version.
         */
//        $msg = static::convertEmojiOne( $msg );

        /**
         * restore the IPS emoti's
         */
        $msg = static::restore( $msg );

        /**
         * lets add some custom config to htmlpurifier
         * @param $config
         */
        $config = function( $config )
        {
            $config->set( 'Attr.AllowedFrameTargets', [ '_blank' ] );
        };

        /**
         * we initiate our self, cause if we call the static method here, we wont be able to inject our own bbc!
         */

        $parser = new static( true, null, $member->member(), false, true, $skipHtml, $config );

        /**
         * convert ln to br a needed evil cause we aren't using a RTE here that converts them for us...makes working
         * with templates interesting :)
         */
        $msg = \nl2br( $msg, true );

        $msg = $parser->parse( $msg );

        $msg = static::restoreEmbeds( $msg );

        /**
         * sometimes the <p> tags get removed and break the display so we add them back in if they are missing!
         */
        if( \mb_substr( $msg, 0, 3 ) !== "<p>" )
        {
            $msg = "<p>" . $msg . "</p>";
        }

        return [
            'msg' => $msg,
            'images' => $images
        ];
    }

    static protected $embedStore = [];

    /**
     * @brief we gotta do some of our own bbc cause IPS's sorta sucks
     * @param $msg
     * @return mixed
     */
    protected static function convertBBC( $msg, $skip = false )
    {

        $msg = \preg_replace( '/\[img\]([^\s]+?)\[\/img\]/iu', '[img=$1]', $msg );

        $images = [];

        $msg = \preg_replace_callback( "/\[img=(.*?)\]/imsu", function( $matches ) use ( &$images )
        {
            $images[] = $matches[ 1 ];

            return '';
        }, $msg );

        $urls = [];

        if( !$skip )
        {
            $msg = \htmlentities( $msg, ENT_NOQUOTES );
            $msg = \str_replace( '&amp;', '&', $msg );
        }
//        $msg = rawurldecode(rawurlencode ( $msg ));
        $pattern = '/(?!(?:[^[\[]+[>\]]|[^]\]]+]))(?i)\b(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?/iu';
        preg_match( $pattern, $msg, $match);
//        _print( $msg, $match );

        $embeds = static::$embedStore;

        $callback = function( $m ) use ( &$embeds )
        {
            if( \IPS\Settings::i()->editor_embeds )
            {
                $i = 0;

                foreach( static::getEmbedCode() as $key => $regex )
                {
                    $template = $regex[ 'template' ];

                    \preg_match( $regex[ 'match' ], $m[ 0 ], $matching );

                    if( \is_array( $matching ) and \count( $matching ) )
                    {
                        if( isset( $matching[ 1 ] ) )
                        {
                            $embeds[ $i ] = \preg_replace( '/(?:(?:\r\n|\r|\n)\s*)/s', '', \IPS\Theme::i()->getTemplate( 'embeds', 'babble', 'front' )->$template( $matching[ 1 ] ) );

                            return '--embeds--' . $i . '--embeds--';
                        }
                    }
                }
            }

            $target = '';

            $parseUrl = \IPS\Http\Url::createFromString( $m[ 0 ] );

            if( !( $parseUrl instanceof \IPS\Http\Url\Internal ) or \IPS\Settings::i()->babble_admin_config_general_links_external )
            {
                $target = 'target="_blank"';
            }

            if( $parseUrl instanceof \IPS\Http\Url\Internal and \IPS\Settings::i()->babble_admin_config_general_internal_embeds )
            {
                $embed = \IPS\Text\Parser::embeddableMedia( $parseUrl );
                if( $embed )
                {
                    return $embed;
                }
            }

            if( \IPS\Settings::i()->babble_admin_config_general_shorten_urls )
            {
                $info = \parse_url( $m[ 0 ] );
                $url = $info[ 'host' ];
            }
            else
            {
                $url = $m[ 0 ];
            }

            return "<a href='" . (string)$parseUrl . "' " . $target . ">" . $url . "</a>";
        };

        $msg = \preg_replace_callback( $pattern, $callback, $msg );
        static::$embedStore = $embeds;

        /* Parse [URL]url[/URL] bbcode */
        \preg_match_all( '#\[url]([^\[]+?)\[\/url\]#is', $msg, $matches );

        foreach( $matches[ 0 ] as $idx => $m )
        {
            $target = '';

            $parseUrl = \IPS\Http\Url::createFromString( $matches[ 1 ][ $idx ] );

            if( !( $parseUrl instanceof \IPS\Http\Url\Internal ) or \IPS\Settings::i()->babble_admin_config_general_links_external )
            {
                $target = 'target="_blank"';
            }

            $msg = \str_replace( $m, " <a href='" . (string)$parseUrl . "' " . $target . " >" . (string)$parseUrl . "</a> ", $msg );
        }

        /* Parse [URL=url]text[/URL] bbcode */
        \preg_match_all( '#\[url=(?:\'|"|&\#039;)?(.+?)(?:\'|"|&\#039;)?\](.+?)\[\/url\]#is', $msg, $matches );

        foreach( $matches[ 0 ] as $idx => $m )
        {
            $target = '';

            $parseUrl = \IPS\Http\Url::createFromString( $matches[ 1 ][ $idx ] );

            if( !( $parseUrl instanceof \IPS\Http\Url\Internal ) or \IPS\Settings::i()->babble_admin_config_general_links_external )
            {
                $target = 'target="_blank"';
            }

            $msg = \str_replace( $m, " <a href='" . (string)$parseUrl . "' " . $target . ">" . $matches[ 2 ][ $idx ] . "</a> ", $msg );
        }

        if( \IPS\Settings::i()->babble_front_restrict_to )
        {

            $colors = json_decode( \IPS\Settings::i()->babble_front_colors, true );

            if( json_last_error_msg() == JSON_ERROR_NONE )
            {
                $msg = \preg_replace_callback( "/\[color=(.*?)\](.*?)\[\/color\]/iu", function( $matches ) use ( $colors )
                {
                    if( in_array( $matches[ 1 ], $colors ) )
                    {
                        return $matches[ 0 ];
                    }
                    else
                    {
                        return $matches[ 2 ];
                    }
                }, $msg );

            }
        }

        return [
            'msg' => $msg,
            'images' => $images
        ];
    }

    /**
     * @brief we have to protected our ascii emoji, since we don't allow html input directly in babble.
     * @param $msg
     * @return mixed
     */
    protected static function prep( $msg )
    {
        $smiles = static::emojis();
        $i = 0;
        $store = [];
        static::$store = [];

        foreach( $smiles as $key => $val )
        {
            $msg = \preg_replace_callback( '#(?<=\s|^|\b)' . \preg_quote( $key ) . '(?=\s|$)#', function( $m ) use ( $val, $key, &$store, &$i )
            {
                $store[ ++$i ] = " <img data-cke-saved-src='{file=\"{$val['image']}\" extension=\"core_Emoticons\"}' src='{$val['image']}' title='{$key}' alt='{$key}' srcset='{file=\"{$val[ 'image_2x' ]}\" extension=\"core_Emoticons\"}' width='{$val['width']}' height='{$val['height']}' data-emoticon='true'> ";

                return 'he-' . $i . '-eh';
            }, $msg );
        }

        if( $emoji = ( new \IPS\core\modules\front\system\editor )->babbleEmojiOne() ){
            foreach( $emoji as $key => $val ){
                $msg = \preg_replace_callback( '#:'.\preg_quote( $key ).':#', function( $m ) use ( $val, $key, &$store, &$i ) {
                    $store[ ++$i ] = " <img alt=\"{$key}\" data-emoticon=\"true\" height=\"20\" data-cke-saved-src=\"//cdn.jsdelivr.net/emojione/assets/png/{$val}.png\" src=\"//cdn.jsdelivr.net/emojione/assets/png/{$val}.png\" title=\"{$key}\" width=\"20\">";

                    return 'he-' . $i . '-eh';
                }, $msg );
            }
        }

        static::$store = $store;

        return $msg;
    }

    protected static function restore( $msg )
    {
        if( \is_array( static::$store ) and \count( static::$store ) )
        {
            foreach( static::$store as $key => $val )
            {
                $msg = \preg_replace_callback( '#he-' . $key . '-eh#', function( $m ) use ( $key, $val )
                {
                    return $val;
                }, $msg );
            }
        }

        return $msg;
    }

    protected static function restoreEmbeds( $msg )
    {
        if( \is_array( static::$embedStore ) and \count( static::$embedStore ) )
        {
            foreach( static::$embedStore as $key => $val )
            {
                $msg = \preg_replace_callback( '#--embeds--' . $key . '--embeds--#', function( $m ) use ( $key, $val )
                {
                    $val = \str_replace('ipsEmbeddedVideo_limited', '', $val );
                    \IPS\Member::loggedIn()->language()->parseOutputForDisplay($val);
                    return $val;
                }, $msg );
            }

        }

        return $msg;
    }

    /**
     * @brief converts actual emoji
     * @param $msg
     * @return string
     */
//    protected static function convertEmojiOne( $msg )
//    {
//        require_once( \IPS\ROOT_PATH . '/applications/babble/sources/3rdparty/emojione/autoload.php' );
//
//        $client = new \Emojione\Babble( new \Emojione\BabbleRuleSet() );
//
//        $client->ascii = false;
//
//        $client->imagePathPNG = \IPS\Settings::i()->base_url . 'applications/babble/interface/emojione/';
//
//        return $client->toImage( $msg );
//
//    }

    /**
     * @brief stores the emojis so they can be displayed on the front end.
     * @return string
     */
    protected static function emojis()
    {
        if( !isset( \IPS\Data\Store::i()->emoticons ) )
        {
            $emoticons = \iterator_to_array( \IPS\Db::i()->select( '*', 'core_emoticons', [
                'typed<>?',
                ''
            ] )->setKeyField( 'typed' ) );
            foreach( $emoticons as $key => $emoticon )
            {
                $emoticons[ $key ] = [
                    'image' => (string)\IPS\File::get( "core_Emoticons", $emoticon[ 'image' ] )->url,
                    'image_2x' => $emoticon[ 'image_2x' ] ? (string)\IPS\File::get( "core_Emoticons", $emoticon[ 'image_2x' ] )->url . ' 2x' : false,
                    'height' => $emoticon[ 'height' ] ?: false,
                    'width' => $emoticon[ 'width' ] ?: false,
                ];
            }

            \IPS\Data\Store::i()->emoticons = $emoticons;
        }

        return \IPS\Data\Store::i()->emoticons;
    }

    protected function getAllowedCssClasses()
    {
        $ipsClasses = parent::getAllowedCssClasses();
        $myClasses = [
            'cBabbleFacePalm',
            'cBabbleImage',
            'notOpen',
            'cBabbleImageDiv',
            'ipsHide',
            'emojione',
            'ipsEmbeddedVideo',
            'ipsEmbeddedVideo_limited',
            'ipsSpacer_bottom'
        ];

        return \array_merge( $ipsClasses, $myClasses );
    }

    static public function mbUcfirst( $string )
    {
        return \mb_strtoupper( \mb_substr( $string, 0, 1 ) ) . \mb_substr( $string, 1 );
    }
}
