<?php

/**
 * This software is intended for use with Oxwall Free Community Software http://www.oxwall.org/ and is
 * licensed under The BSD license.

 * ---
 * Copyright (c) 2011, Oxwall Foundation
 * All rights reserved.

 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice, this list of conditions and
 *  the following disclaimer.
 *
 *  - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 *  the following disclaimer in the documentation and/or other materials provided with the distribution.
 *
 *  - Neither the name of the Oxwall Foundation nor the names of its contributors may be used to endorse or promote products
 *  derived from this software without specific prior written permission.

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * Clip Service Class.  
 * 
 * @author Egor Bulgakov <egor.bulgakov@gmail.com>
 * @package ow.plugin.video.bol
 * @since 1.0
 */
final class VIDEO_BOL_ClipService
{
    const EVENT_AFTER_DELETE = 'video.after_delete';
    const EVENT_BEFORE_DELETE = 'video.before_delete';
    const EVENT_AFTER_EDIT = 'video.after_edit';
    const EVENT_AFTER_ADD = 'video.after_add';
    
    const ENTITY_TYPE = 'video_comments';
    
    const TAGS_ENTITY_TYPE = "video";
    const RATES_ENTITY_TYPE = "video_rates";
    const FEED_ENTITY_TYPE = self::ENTITY_TYPE;

    /**
     * @var VIDEO_BOL_ClipDao
     */
    private $clipDao;
    /**
     * @var VIDEO_BOL_ClipFeaturedDao
     */
    private $clipFeaturedDao;
    /**
     * Class instance
     *
     * @var VIDEO_BOL_ClipService
     */
    private static $classInstance;

    /**
     * Class constructor
     *
     */
    private function __construct()
    {
        $this->clipDao = VIDEO_BOL_ClipDao::getInstance();
        $this->clipFeaturedDao = VIDEO_BOL_ClipFeaturedDao::getInstance();
    }

    /**
     * Returns class instance
     *
     * @return VIDEO_BOL_ClipService
     */
    public static function getInstance()
    {
        if ( null === self::$classInstance )
        {
            self::$classInstance = new self();
        }

        return self::$classInstance;
    }

    /**
     * Adds video clip
     *
     * @param VIDEO_BOL_Clip $clip
     * @return int
     */
    public function addClip( VIDEO_BOL_Clip $clip )
    {
        $this->clipDao->save($clip);
        
        $this->cleanListCache();

        return $clip->id;
    }
    
    public function saveClip( VIDEO_BOL_Clip $clip ) 
    {
        $this->clipDao->save($clip);
        $this->cleanListCache();
    }

    /**
     * Updates video clip
     *
     * @param VIDEO_BOL_Clip $clip
     * @return int
     */
    public function updateClip( VIDEO_BOL_Clip $clip )
    {
        $this->clipDao->save($clip);
        
        $this->cleanListCache();

        $event = new OW_Event('feed.action', array(
            'pluginKey' => 'video',
            'entityType' => self::FEED_ENTITY_TYPE,
            'entityId' => $clip->id,
            'userId' => $clip->userId
        ));
        OW::getEventManager()->trigger($event);

        $event = new OW_Event(self::EVENT_AFTER_EDIT, array('clipId' => $clip->id));
        OW::getEventManager()->trigger($event);
        
        return $clip->id;
    }

    /**
     * Finds clip by id
     *
     * @param int $id
     * @return VIDEO_BOL_Clip
     */
    public function findClipById( $id )
    {
        return $this->clipDao->findById($id);
    }
    
    /**
     * Finds clips by id list
     *
     * @param int $ids
     * @return array
     */
    public function findClipByIds( $ids )
    {
        return $this->clipDao->findByIdList($ids);
    }

    /**
     * Finds clip owner
     *
     * @param int $id
     * @return int
     */
    public function findClipOwner( $id )
    {
        $clip = $this->clipDao->findById($id);

        /* @var $clip VIDEO_BOL_Clip */

        return $clip ? $clip->getUserId() : null;
    }

    /**
     * Find latest clips authors ids
     *
     * @param integer $first
     * @param integer $count
     * @return array
     */
    public function findLatestPublicClipsAuthorsIds($first, $count)
    {
        return $this->clipDao->findLatestPublicClipsAuthorsIds($first, $count);
    }

    /**
     * Finds video clips list of specified type 
     *
     * @param string $type
     * @param int $page
     * @param int $limit
     * @return array of VIDEO_BOL_Clip
     */
    public function findClipsList( $type, $page, $limit )
    {
        if ( $type == 'toprated' )
        {
            $first = ( $page - 1 ) * $limit;
            $topRatedList = BOL_RateService::getInstance()->findMostRatedEntityList(self::RATES_ENTITY_TYPE, $first, $limit);

            $clipArr = $this->clipDao->findByIdList(array_keys($topRatedList));

            $clips = array();

            foreach ( $clipArr as $key => $clip )
            {
                $clipArrItem = (array) $clip;
                $clips[$key] = $clipArrItem;
                $clips[$key]['score'] = $topRatedList[$clipArrItem['id']]['avgScore'];
                $clips[$key]['rates'] = $topRatedList[$clipArrItem['id']]['ratesCount'];
            }

            usort($clips, array('VIDEO_BOL_ClipService', 'sortArrayItemByDesc'));
        }
        else
        {
            $clips = $this->clipDao->getClipsList($type, $page, $limit);
        }

        $list = array();
        if ( is_array($clips) )
        {
            foreach ( $clips as $key => $clip )
            {
                $clip = (array) $clip;
                $list[$key] = $clip;
                $list[$key]['thumb'] = $this->getClipThumbUrl($clip['id'], $clip['code'], $clip['thumbUrl']);
            }
        }

        return $list;
    }

    /**
     * Deletes user all clips
     * 
     * @param int $userId
     * @return boolean
     */
    public function deleteUserClips( $userId )
    {
        if ( !$userId )
        {
            return false;
        }

        $clipsCount = $this->findUserClipsCount($userId);

        if ( !$clipsCount )
        {
            return true;
        }

        $clips = $this->findUserClipsList($userId, 1, $clipsCount);

        foreach ( $clips as $clip )
        {
            $this->deleteClip($clip['id']);
        }

        return true;
    }

    public static function sortArrayItemByDesc( $el1, $el2 )
    {
        if ( $el1['score'] === $el2['score'] )
        {
            if ( $el1['rates'] === $el2['rates'] )
            {
                return 0;
            }
            
            return $el1['rates'] < $el2['rates'] ? 1 : -1;
        }

        return $el1['score'] < $el2['score'] ? 1 : -1;
    }

    /**
     * Finds user other video list
     *
     * @param $userId
     * @param $page
     * @param int $itemsNum
     * @param int $exclude
     * @return array of VIDEO_BOL_Clip
     */
    public function findUserClipsList( $userId, $page, $itemsNum, $exclude = null )
    {
        $clips = $this->clipDao->getUserClipsList($userId, $page, $itemsNum, $exclude);

        if ( is_array($clips) )
        {
            $list = array();
            foreach ( $clips as $key => $clip )
            {
                $clip = (array) $clip;
                $list[$key] = $clip;
                $list[$key]['thumb'] = $this->getClipThumbUrl($clip['id'], $clip['code'], $clip['thumbUrl']);
            }

            return $list;
        }

        return null;
    }

    /**
     * Finds list of tagged clips
     *
     * @param string $tag
     * @param int $page
     * @param int $limit
     * @return array of VIDEO_BOL_Clip
     */
    public function findTaggedClipsList( $tag, $page, $limit )
    {
        $first = ($page - 1 ) * $limit;

        $clipIdList = BOL_TagService::getInstance()->findEntityListByTag(self::TAGS_ENTITY_TYPE, $tag, $first, $limit);

        $clips = $this->clipDao->findByIdList($clipIdList);

        $list = array();
        if ( is_array($clips) )
        {
            foreach ( $clips as $key => $clip )
            {
                $clip = (array) $clip;
                $list[$key] = $clip;
                $list[$key]['thumb'] = $this->getClipThumbUrl($clip['id'], $clip['code'], $clip['thumbUrl']);
            }
        }

        return $list;
    }

    public function getClipThumbUrl( $clipId, $code = null, $thumbUrl = null )
    {
        if ( mb_strlen($thumbUrl) )
        {
            return $thumbUrl;
        }

        if ( $code == null )
        {
            $clip = $this->findClipById($clipId);
            if ( $clip )
            {
                if ( mb_strlen($clip->thumbUrl) )
                {
                    return $clip->thumbUrl;
                }
                $code = $clip->code;
            }
        }
        
        $providers = new VideoProviders($code);
        
        return $providers->getProviderThumbUrl();
    }
    
    public function getClipDefaultThumbUrl()
    {
        return OW::getThemeManager()->getCurrentTheme()->getStaticImagesUrl() . 'video-no-video.png';
    }
    

    /**
     * Counts clips
     *
     * @param string $type
     * @return int
     */
    public function findClipsCount( $type )
    {
        if ( $type == 'toprated' )
        {
            return BOL_RateService::getInstance()->findMostRatedEntityCount(self::RATES_ENTITY_TYPE);
        }

        return $this->clipDao->countClips($type);
    }

    /**
     * Counts user added clips
     *
     * @param int $userId
     * @return int
     */
    public function findUserClipsCount( $userId )
    {
        return $this->clipDao->countUserClips($userId);
    }

    /**
     * Counts clips with specified tag
     *
     * @param string $tag
     * @return array of VIDEO_BOL_Clip
     */
    public function findTaggedClipsCount( $tag )
    {
        return BOL_TagService::getInstance()->findEntityCountByTag(self::TAGS_ENTITY_TYPE, $tag);
    }

    /**
     * Gets number of clips to display per page
     *
     * @return int
     */
    public function getClipPerPageConfig()
    {
        return (int) OW::getConfig()->getValue('video', 'videos_per_page');
    }

    /**
     * Gets user clips quota
     *
     * @return int
     */
    public function getUserQuotaConfig()
    {
        return (int) OW::getConfig()->getValue('video', 'user_quota');
    }

    /**
     * Updates the 'status' field of the clip object 
     *
     * @param int $id
     * @param string $status
     * @return boolean
     */
    public function updateClipStatus( $id, $status )
    {
        /** @var $clip VIDEO_BOL_Clip */
        $clip = $this->clipDao->findById($id);

        $newStatus = $status == 'approve' ? VIDEO_BOL_ClipDao::STATUS_APPROVED : VIDEO_BOL_ClipDao::STATUS_BLOCKED;

        $clip->status = $newStatus;

        $this->updateClip($clip);

        return $clip->id ? true : false;
    }

    /**
     * Changes clip's 'featured' status
     *
     * @param int $id
     * @param string $status
     * @return boolean
     */
    public function updateClipFeaturedStatus( $id, $status )
    {
        $clip = $this->clipDao->findById($id);

        if ( $clip )
        {
            $clipFeaturedService = VIDEO_BOL_ClipFeaturedService::getInstance();

            if ( $status == 'mark_featured' )
            {
                return $clipFeaturedService->markFeatured($id);
            }
            else
            {
                return $clipFeaturedService->markUnfeatured($id);
            }
        }

        return false;
    }

    /**
     * Deletes video clip
     *
     * @param int $id
     * @return int
     */
    public function deleteClip( $id )
    {
        $event = new OW_Event(self::EVENT_BEFORE_DELETE, array('clipId' => $id));
        OW::getEventManager()->trigger($event);
        
        $this->clipDao->deleteById($id);

        BOL_CommentService::getInstance()->deleteEntityComments(self::ENTITY_TYPE, $id);
        BOL_RateService::getInstance()->deleteEntityRates($id, self::RATES_ENTITY_TYPE);
        BOL_TagService::getInstance()->deleteEntityTags($id, self::TAGS_ENTITY_TYPE);

        $this->clipFeaturedDao->markUnfeatured($id);

        BOL_FlagService::getInstance()->deleteByTypeAndEntityId(VIDEO_CLASS_ContentProvider::ENTITY_TYPE, $id);
        
        OW::getEventManager()->trigger(new OW_Event('feed.delete_item', array(
            'entityType' => self::FEED_ENTITY_TYPE,
            'entityId' => $id
        )));
        
        $this->cleanListCache();

        $event = new OW_Event(self::EVENT_AFTER_DELETE, array('clipId' => $id));
        OW::getEventManager()->trigger($event);

        return true;
    }
    
    public function cleanupPluginContent( )
    {
        BOL_CommentService::getInstance()->deleteEntityTypeComments(self::ENTITY_TYPE);
        BOL_RateService::getInstance()->deleteEntityTypeRates(self::RATES_ENTITY_TYPE);
        BOL_TagService::getInstance()->deleteEntityTypeTags(self::TAGS_ENTITY_TYPE);
        
        BOL_FlagService::getInstance()->deleteFlagList(self::ENTITY_TYPE);
    }

    /**
     * Adjust clip width and height
     *
     * @param string $code
     * @param int $width
     * @param int $height
     * @return string
     */
    public function formatClipDimensions( $code, $width, $height )
    {
        if ( !strlen($code) )
            return '';

        // remove %
        $code = preg_replace("/width=(\"|')?[\d]+(%)?(\"|')?/i", 'width=${1}' . $width . '${3}', $code);
        $code = preg_replace("/height=(\"|')?[\d]+(%)?(\"|')?/i", 'height=${1}' . $height . '${3}', $code);

        // adjust width and height
        $code = preg_replace("/width=(\"|')?[\d]+(px)?(\"|')?/i", 'width=${1}' . $width . '${3}', $code);
        $code = preg_replace("/height=(\"|')?[\d]+(px)?(\"|')?/i", 'height=${1}' . $height . '${3}', $code);

        $code = preg_replace("/width:( )?[\d]+(px)?/i", 'width:' . $width . 'px', $code);
        $code = preg_replace("/height:( )?[\d]+(px)?/i", 'height:' . $height . 'px', $code);

        return $code;
    }

    /**
     * Validate clip code integrity
     *
     * @param string $code
     * @param null $provider
     * @return string
     */
    public function validateClipCode( $code, $provider = null )
    {
        $textService = BOL_TextFormatService::getInstance();

        $code = UTIL_HtmlTag::stripJs($code);
        $code = UTIL_HtmlTag::stripTags($code, $textService->getVideoParamList('tags'), $textService->getVideoParamList('attrs'));

        $objStart = '<object';
        $objEnd = '</object>';
        $objEndS = '/>';

        $posObjStart = stripos($code, $objStart);
        $posObjEnd = stripos($code, $objEnd);

        $posObjEnd = $posObjEnd ? $posObjEnd : stripos($code, $objEndS);

        if ( $posObjStart !== false && $posObjEnd !== false )
        {
            $posObjEnd += strlen($objEnd);
            return substr($code, $posObjStart, $posObjEnd - $posObjStart);
        }
        else
        {
            $embStart = '<embed';
            $embEnd = '</embed>';
            $embEndS = '/>';

            $posEmbStart = stripos($code, $embStart);
            $posEmbEnd = stripos($code, $embEnd) ? stripos($code, $embEnd) : stripos($code, $embEndS);

            if ( $posEmbStart !== false && $posEmbEnd !== false )
            {
                $posEmbEnd += strlen($embEnd);
                return substr($code, $posEmbStart, $posEmbEnd - $posEmbStart);
            }
            else
            {
                $frmStart = '<iframe ';
                $frmEnd = '</iframe>';
                $posFrmStart = stripos($code, $frmStart);
                $posFrmEnd = stripos($code, $frmEnd);
                if ( $posFrmStart !== false && $posFrmEnd !== false )
                {
                    $posFrmEnd += strlen($frmEnd);
                    $code = substr($code, $posFrmStart, $posFrmEnd - $posFrmStart);

                    preg_match('/src=(["\'])(.*?)\1/', $code, $match);
                    if ( !empty($match[2]) )
                    {
                        $src = $match[2];
                        if ( mb_substr($src, 0, 2) == '//' )
                        {
                            $src = 'http:' . $src;
                        }

                        $urlArr = parse_url($src);
                        $parts = explode('.', $urlArr['host']);

                        if ( count($parts) < 2 )
                        {
                            return '';
                        }

                        $d1 = array_pop($parts);
                        $d2 = array_pop($parts);
                        $host = $d2 . '.' . $d1;

                        $resourceList = BOL_TextFormatService::getInstance()->getMediaResourceList();

                        if ( !in_array($host, $resourceList) && !in_array($urlArr['host'], $resourceList) )
                        {
                            return '';
                        }
                    }

                    return $code;
                }
                else
                {
                    return '';
                }
            }
        }
    }

    /**
     * Adds parameter to embed code 
     *
     * @param string $code
     * @param string $name
     * @param string $value
     * @return string
     */
    public function addCodeParam( $code, $name = 'wmode', $value = 'transparent' )
    {
        $repl = $code;

        if ( preg_match("/<object/i", $code) )
        {
            $searchPattern = '<param';
            $pos = stripos($code, $searchPattern);
            if ( $pos )
            {
                $addParam = '<param name="' . $name . '" value="' . $value . '"></param><param';
                $repl = substr_replace($code, $addParam, $pos, strlen($searchPattern));
            }
        }

        if ( preg_match("/<embed/i", isset($repl) ? $repl : $code) )
        {
            $repl = preg_replace("/<embed/i", '<embed ' . $name . '="' . $value . '"', isset($repl) ? $repl : $code);
        }

        return $repl;
    }
    
    public function updateUserClipsPrivacy( $userId, $privacy )
    {
        if ( !$userId || !mb_strlen($privacy) )
        {
            return false;
        }
        
        $clips = $this->clipDao->findByUserId($userId);
        
        if ( !$clips )
        {
            return true;
        }
        
        $this->clipDao->updatePrivacyByUserId($userId, $privacy);
        
        $this->cleanListCache();

        $status = $privacy == 'everybody';
        $event = new OW_Event(
            'base.update_entity_items_status', 
            array('entityType' => 'video_rates', 'entityIds' => $clips, 'status' => $status)
        );
        OW::getEventManager()->trigger($event);
        
        return true;
    }
    
    public function cleanListCache()
    {
        OW::getCacheManager()->clean(array(VIDEO_BOL_ClipDao::CACHE_TAG_VIDEO_LIST));
    }

    public function cacheThumbnails( $limit )
    {
        $clips = $this->clipDao->getUncachedThumbsClipsList($limit);

        if ( !$clips )
        {
            return true;
        }

        foreach ( $clips as $clip )
        {
            $prov = new VideoProviders($clip->code);
            if ( !$clip->provider )
            {
                $clip->provider = $prov->detectProvider();
            }
            $thumbUrl = $prov->getProviderThumbUrl($clip->provider);
            if ( $thumbUrl != VideoProviders::PROVIDER_UNDEFINED )
            {
                $clip->thumbUrl = $thumbUrl;
            }
            $clip->thumbCheckStamp = time();
            $this->clipDao->save($clip);
        }

        return true;
    }
}