<?php

class Application_Model_Webstream implements Application_Model_LibraryEditable
{
    private $id;

    public function __construct($webstream)
    {
        //TODO: hacky...
        if (is_int($webstream)) {
            $this->webstream = CcWebstreamQuery::create()->findPK($webstream);
            if (is_null($this->webstream)) {
                throw new Exception();
            }
        } else {
            $this->webstream = $webstream;
        }
    }

    public function getOrm()
    {
        return $this->webstream;
    }

    public function getName()
    {
        return $this->webstream->getDbName();
    }

    public function getId()
    {
        return $this->webstream->getDbId();
    }

    public function getCreatorId()
    {
        return $this->webstream->getDbCreatorId();
    }

    public function getLastModified($p_type)
    {
        return $this->webstream->getDbMtime();
    }

    public function getDefaultLength()
    {
        $dateString = $this->webstream->getDbLength();
        $arr = explode(":", $dateString);
        if (count($arr) == 3) {
            list($hours, $min, $sec) = $arr;
            $di = new DateInterval("PT{$hours}H{$min}M{$sec}S");

            return $di->format("%Hh %Im");
        }

        return "";
    }

    public function getLength()
    {
        return $this->getDefaultLength();
    }

    public function getDescription()
    {
        return $this->webstream->getDbDescription();
    }

    public function getUrl()
    {
        return $this->webstream->getDbUrl();
    }

    public function getMetadata()
    {
        $subjs = CcSubjsQuery::create()->findPK($this->webstream->getDbCreatorId());

        $username = $subjs->getDbLogin();

        return array(
            "name"        => $this->webstream->getDbName(),
            "length"      => $this->webstream->getDbLength(),
            "description" => $this->webstream->getDbDescription(),
            "login"       => $username,
            "url"         => $this->webstream->getDbUrl(),
        );
    }

    public static function deleteStreams($p_ids, $p_userId)
    {
        $userInfo = Zend_Auth::getInstance()->getStorage()->read();
        $user = new Application_Model_User($userInfo->id);
        $isAdminOrPM = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
        
        if (!$isAdminOrPM) {
            //Make sure the user has ownership of ALL the selected webstreams before
            $leftOver = self::streamsNotOwnedByUser($p_ids, $p_userId);
            if (count($leftOver) == 0) {
                CcWebstreamQuery::create()->findPKs($p_ids)->delete();
            } else {
                throw new WebstreamNoPermissionException;
            }
        } else {
            CcWebstreamQuery::create()->findPKs($p_ids)->delete();
        }
    }

    // This function returns that are not owen by $p_user_id among $p_ids
    private static function streamsNotOwnedByUser($p_ids, $p_userId)
    {
        $ownedByUser = CcWebstreamQuery::create()->filterByDbCreatorId($p_userId)->find()->getData();
        $ownedStreams = array();
        foreach ($ownedByUser as $pl) {
            if (in_array($pl->getDbId(), $p_ids)) {
                $ownedStreams[] = $pl->getDbId();
            }
        }

        $leftOvers = array_diff($p_ids, $ownedStreams);

        return $leftOvers;
    }

    public static function analyzeFormData($parameters)
    {
        $valid = array("length" => array(true, ''),
            "url" => array(true, ''),
            "name" => array(true, ''));

        $di = null;
        $length = $parameters["length"];
        $result = preg_match("/^(?:([0-9]{1,2})h)?\s*(?:([0-9]{1,2})m)?$/", $length, $matches);

        $invalid_date_interval = false;
        if ($result == 1 && count($matches) == 2) {
            $hours = $matches[1];
            $minutes = 0;
        } elseif ($result == 1 && count($matches) == 3) {
            $hours = $matches[1];
            $minutes = $matches[2];
        } else {
            $invalid_date_interval = true;
        }

        if (!$invalid_date_interval) {

            //Due to the way our Regular Expression is set up, we could have $minutes or $hours
            //not set. Do simple test here
            if (!is_numeric($hours)) {
                $hours = 0;
            }
            if (!is_numeric($minutes)) {
                $minutes = 0;
            }

            //minutes cannot be over 59. Need to convert anything > 59 minutes into hours.
            $hours += intval($minutes/60);
            $minutes = $minutes%60;

            $di = new DateInterval("PT{$hours}H{$minutes}M");

            $totalMinutes = $di->h * 60 + $di->i;

            if ($totalMinutes == 0) {
                $valid['length'][0] = false;
                $valid['length'][1] = _('Length needs to be greater than 0 minutes');
            }

        } else {
            $valid['length'][0] = false;
            $valid['length'][1] = _('Length should be of form "00h 00m"');
        }

        $url = $parameters["url"];
        //simple validator that checks to make sure that the url starts with
        //http(s),
        //and that the domain is at least 1 letter long
        $result = preg_match("/^(http|https):\/\/.+/", $url, $matches);

        $mime = null;
        $mediaUrl = null;
        if ($result == 0) {
            $valid['url'][0] = false;
            $valid['url'][1] = _('URL should be of form "http://domain"');
        } elseif (strlen($url) > 512) {
            $valid['url'][0] = false;
            $valid['url'][1] = _('URL should be 512 characters or less');
        } else {

            try {
                list($mime, $content_length_found) = self::discoverStreamMime($url);
                if (is_null($mime)) {
                    throw new Exception(_("No MIME type found for webstream."));
                }
                $mediaUrl = self::getMediaUrl($url, $mime, $content_length_found);

                if (preg_match("/(x-mpegurl)|(xspf\+xml)|(pls\+xml)|(x-scpls)/", $mime)) {
                     list($mime, $content_length_found) = self::discoverStreamMime($mediaUrl);
                }
            } catch (Exception $e) {
                $valid['url'][0] = false;
                $valid['url'][1] = $e->getMessage();
            }
        }

        $name = $parameters["name"];
        if (strlen($name) == 0) {
            $valid['name'][0] = false;
            $valid['name'][1] = _('Webstream name cannot be empty');
        }

        $id = $parameters["id"];

        return array($valid, $mime, $mediaUrl, $di);
    }

    public static function isValid($analysis)
    {
        foreach ($analysis as $k => $v) {
            if ($v[0] === false) {
                return false;
            }
        }
        return true;
    }

    // TODO : Fix this interface
    //This function should not be defined in the interface.
    public function setMetadata($key, $val)
    {
        throw new Exception("Not implemented.");
    }

    public function setName($name)
    {
        $this->webstream->setDbName($name);
    }
    
    public function setLastPlayed($timestamp)
    {
        $this->webstream->setDbLPtime($timestamp);
        $this->webstream->save();
    }

    private static function getUrlData($url)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        // grab URL and pass it to the browser
        //TODO: What if invalid url?
        $content = curl_exec($ch);

        // close cURL resource, and free up system resources
        curl_close($ch);
        
        return $content;
    }

    private static function getXspfUrl($url)
    {
        $content = self::getUrlData($url);

        $dom = new DOMDocument;
        //TODO: What if invalid xml?
        $dom->loadXML($content);
        $tracks = $dom->getElementsByTagName('track');

        foreach ($tracks as $track) {
            $locations = $track->getElementsByTagName('location');
            foreach ($locations as $loc) {
                return $loc->nodeValue;
            }
        }

        throw new Exception(_("Could not parse XSPF playlist"));
    }
    
    private static function getPlsUrl($url)
    {
        $content = self::getUrlData($url);

        $matches = array();
        $numStreams = 0; //Number of streams explicitly listed in the PLS.
        
        if (preg_match("/NumberOfEntries=([0-9]*)/", $content, $matches) !== FALSE) {
            $numStreams = $matches[1];
        }

        //Find all the stream URLs in the playlist
        if (preg_match_all("/File[0-9]*=(.*)/", $content, $matches) !== FALSE) {
            //This array contains all the streams! If we need fallback stream URLs in the future,
            //they're already in this array...
            return $matches[1][0];
        } else {
            throw new Exception(_("Could not parse PLS playlist"));
        }
    }

    private static function getM3uUrl($url)
    {
        $content = self::getUrlData($url);

        //split into lines:
        $delim = "\n";
        if (strpos($content, "\r\n") !== false) {
            $delim = "\r\n";
        }
        $lines = explode("$delim", $content);
        #$lines = preg_split('/$\R?^/m', $content);

        if (count($lines) > 0) {
            return $lines[0];
        }

        throw new Exception(_("Could not parse M3U playlist"));
    }

    private static function getMediaUrl($url, $mime, $content_length_found)
    {

        if (preg_match("/x-mpegurl/", $mime)) {
            $media_url = self::getM3uUrl($url);
        } elseif (preg_match("/xspf\+xml/", $mime)) {
            $media_url = self::getXspfUrl($url);
        } elseif (preg_match("/pls\+xml/", $mime) || preg_match("/x-scpls/", $mime)) {
            $media_url = self::getPlsUrl($url);
        } elseif (preg_match("/(mpeg|ogg|audio\/aacp|audio\/aac)/", $mime)) {
            if ($content_length_found) {
                throw new Exception(_("Invalid webstream - This appears to be a file download."));
            }
            $media_url = $url;
        } else {
            throw new Exception(sprintf(_("Unrecognized stream type: %s"), $mime));
        }

        return $media_url;

    }

    /* PHP get_headers has an annoying property where if the passed in URL is 
     * a redirect, then it goes to the new URL, and returns headers from both
     * requests. We only want the headers from the final request. Here's an
     * example:
     *
     * 0 => "HTTP/1.1 302 Moved Temporarily",
     * 1 => "X-Powered-By: Servlet/3.0 JSP/2.2 (GlassFish Server Open Source Edition 3.1.1 Java/Sun Microsystems Inc./1.6)",
     * 2 => "Server: GlassFish Server Open Source Edition 3.1.1",
     * 3 => "Location: http://3043.live.streamtheworld.com:80/SAM04AAC89_SC",
     * 4 => "Content-Type: text/html;charset=ISO-8859-1",
     * 5 => "Content-Language: en-US",
     * 6 => "Content-Length: 202",
     * 7 => "Date: Thu, 27 Dec 2012 21:52:59 GMT",
     * 8 => "Connection: close",
     * 9 => "HTTP/1.0 200 OK",
     * 10 => "Expires: Thu, 01 Dec 2003 16:00:00 GMT",
     * 11 => "Cache-Control: no-cache, must-revalidate",
     * 12 => "Pragma: no-cache",
     * 13 => "Content-Type: audio/aacp",
     * 14 => "icy-br: 68",
     * 15 => "Server: MediaGateway 3.2.1-04",
     * */
    private static function cleanHeaders($headers) {
        //find the position of HTTP/1  200 OK
        //
        $position = 0;
        foreach ($headers as $i => $v) {
            if (preg_match("/^HTTP.*200 OK$/i", $v)) {
                $position = $i;
                break;
            }
        }

        return array_slice($headers, $position); 
    }

    private static function discoverStreamMime($url)
    {
        try {
            $headers = @get_headers($url);

            $mime = null;
            $content_length_found = false;
    
            if ($headers !== false) {
                $headers = self::cleanHeaders($headers);
                foreach ($headers as $h) {
                    if (preg_match("/^content-type:/i", $h)) {
                        list(, $value) = explode(":", $h, 2);
                        $mime = trim($value);
                    }
                    if (preg_match("/^content-length:/i", $h)) {
                        $content_length_found = true;
                    }
                }
            }
        } catch (Exception $e) {
            Logging::info("Invalid stream URL");
            Logging::info($e->getMessage());
        }

        return array($mime, $content_length_found);
    }

    public static function save($parameters, $mime, $mediaUrl, $di)
    {
        $userInfo = Zend_Auth::getInstance()->getStorage()->read();

        $id = $parameters['id'];
        if ($id != -1) {
            $webstream = CcWebstreamQuery::create()->findPK($id);
        } else {
            $webstream = new CcWebstream();
        }

        $webstream->setDbName($parameters["name"]);
        $webstream->setDbDescription($parameters["description"]);
        $webstream->setDbUrl($mediaUrl);

        $dblength = $di->format("%H:%I");
        $webstream->setDbLength($dblength);
        $webstream->setDbCreatorId($userInfo->id);
        $webstream->setDbUtime(new DateTime("now", new DateTimeZone('UTC')));
        $webstream->setDbMtime(new DateTime("now", new DateTimeZone('UTC')));

        $ws = new Application_Model_Webstream($webstream);

        $webstream->setDbMime($mime);
        $webstream->save();

        return $webstream->getDbId();
    }
    
    /*
     * method is not used, webstreams aren't currently kept track of for isScheduled.
     */
    public static function setIsScheduled($p_webstreamId, $p_status) {
    
    	$webstream = CcWebstreamQuery::create()->findPK($p_webstreamId);
    	$updateIsScheduled = false;
    
    	if (isset($webstream) && !in_array($p_webstreamId,
    			Application_Model_Schedule::getAllFutureScheduledWebstreams())) {
    		//$webstream->setDbIsScheduled($p_status)->save();
    		$updateIsScheduled = true;
    	}
    
    	return $updateIsScheduled;
    }
}

class WebstreamNoPermissionException extends Exception {}

