<?php

namespace EspressoDev;

class InstagramBasicDisplay
{
    const API_URL = 'https://graph.instagram.com/';

    const API_OAUTH_URL = 'https://api.instagram.com/oauth/authorize';

    const API_OAUTH_TOKEN_URL = 'https://api.instagram.com/oauth/access_token';
    
    const API_TOKEN_EXCHANGE_URL = 'https://graph.instagram.com/access_token';
    
    const API_TOKEN_REFRESH_URL = 'https://graph.instagram.com/refresh_access_token';

    private $_appId;

    private $_appSecret;

    private $_redirectUri;

    private $_accesstoken;

    private $_scopes = ['user_profile', 'user_media'];

    private $_userFields = 'account_type,id,media_count,username';

    private $_mediaFields = 'caption,id,media_type,media_url,permalink,thumbnail_url,timestamp,username';

    private $_mediaChildrenFields = 'id,media_type,media_url,permalink,thumbnail_url,timestamp,username';

    private $_timeout = 90000;

    private $_connectTimeout = 20000;

    public function __construct($config = null) 
    {
        if (is_array($config)) {
            $this->setAppId($config['appId']);
            $this->setAppSecret($config['appSecret']);
            $this->setRedirectUri($config['redirectUri']);
            
            if (isset($config['timeout'])) {
                $this->setTimeout($config['timeout']);    
            }
            
            if (isset($config['connectTimeout'])) {
                $this->setConnectTimeout($config['connectTimeout']);    
            }
        } elseif (is_string($config)) {
            // For read-only
            $this->setAccessToken($config);
        } else {
            throw new InstagramBasicDisplayException('Error: __construct() - Configuration data is missing.');
        }
    }

    public function getLoginUrl($scopes = ['user_profile', 'user_media'], $state = '')
    {
        if (is_array($scopes) && count(array_intersect($scopes, $this->_scopes)) === count($scopes)) {
            return self::API_OAUTH_URL . '?client_id=' . $this->getAppId() . '&redirect_uri=' . urlencode($this->getRedirectUri()) . '&scope=' . implode(',',
                $scopes) . '&response_type=code' . ($state != '' ? '&state=' . $state : '');
        }

        throw new InstagramBasicDisplayException("Error: getLoginUrl() - The parameter isn't an array or invalid scope permissions used.");
    }

    public function getUserProfile($id = 0)
    {
        if ($id === 0) {
            $id = 'me';
        }

        return $this->_makeCall($id, ['fields' => $this->_userFields]);
    }

    public function getUserMedia($id = 'me', $limit = 0, $before = null, $after = null)
    {
        $params = [
            'fields' => $this->_mediaFields
        ];

        if ($limit > 0) {
            $params['limit'] = $limit;
        }
        if (isset($before)) {
            $params['before'] = $before;
        }
        if (isset($after)) {
            $params['after'] = $after;
        }

        return $this->_makeCall($id . '/media', $params);
    }

    public function getMedia($id)
    {
        return $this->_makeCall($id, ['fields' => $this->_mediaFields]);
    }

    public function getMediaChildren($id)
    {
        return $this->_makeCall($id . '/children', ['fields' => $this->_mediaChildrenFields]);
    }

    public function pagination($obj)
    {
        if (is_object($obj) && !is_null($obj->paging)) {
            if (!isset($obj->paging->next)) {
                return;
            }

            $apiCall = explode('?', $obj->paging->next);

            if (count($apiCall) < 2) {
                return;
            }

            $function = str_replace(self::API_URL, '', $apiCall[0]);
            parse_str($apiCall[1], $params);

            // No need to include access token as this will be handled by _makeCall
            unset($params['access_token']);

            return $this->_makeCall($function, $params);
        }

        throw new InstagramBasicDisplayException("Error: pagination() | This method doesn't support pagination.");
    }

    public function getOAuthToken($code, $tokenOnly = false)
    {
        $apiData = array(
            'app_id' => $this->getAppId(),
            'app_secret' => $this->getAppSecret(),
            'grant_type' => 'authorization_code',
            'redirect_uri' => $this->getRedirectUri(),
            'code' => $code
        );

        $result = $this->_makeOAuthCall(self::API_OAUTH_TOKEN_URL, $apiData);

        return !$tokenOnly ? $result : $result->access_token;
    }

    public function getLongLivedToken($token, $tokenOnly = false)
    {
        $apiData = array(
            'client_secret' => $this->getAppSecret(),
            'grant_type' => 'ig_exchange_token',
            'access_token' => $token
        );

        $result = $this->_makeOAuthCall(self::API_TOKEN_EXCHANGE_URL, $apiData, 'GET');

        return !$tokenOnly ? $result : $result->access_token;
    }

    public function refreshToken($token, $tokenOnly = false)
    {
        $apiData = array(
            'grant_type' => 'ig_refresh_token',
            'access_token' => $token
        );

        $result = $this->_makeOAuthCall(self::API_TOKEN_REFRESH_URL, $apiData, 'GET');

        return !$tokenOnly ? $result : $result->access_token;
    }

    protected function _makeCall($function, $params = null, $method = 'GET')
    {
        if (!isset($this->_accesstoken)) {
            throw new InstagramBasicDisplayException("Error: _makeCall() | $function - This method requires an authenticated users access token.");
        }

        $authMethod = '?access_token=' . $this->getAccessToken();

        $paramString = null;

        if (isset($params) && is_array($params)) {
            $paramString = '&' . http_build_query($params);
        }

        $apiCall = self::API_URL . $function . $authMethod . (('GET' === $method) ? $paramString : null);
        //replace encoded comma since api return error if not
        $apiCall = str_replace('%2C', ',', $apiCall);

        $headerData = array('Accept: application/json');

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $apiCall);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headerData);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->_connectTimeout);
        curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->_timeout);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_HEADER, true);

        $jsonData = curl_exec($ch);

        if (!$jsonData) {
            throw new InstagramBasicDisplayException('Error: _makeCall() - cURL error: ' . curl_error($ch), curl_errno($ch));
        }

        list($headerContent, $jsonData) = explode("\r\n\r\n", $jsonData, 2);

        curl_close($ch);

        return json_decode($jsonData);
    }

    private function _makeOAuthCall($apiHost, $params, $method = 'POST')
    {
        $paramString = null;

        if (isset($params) && is_array($params)) {
            $paramString = '?' . http_build_query($params);
        }

        $apiCall = $apiHost . (('GET' === $method) ? $paramString : null);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $apiCall);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json'));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->_timeout);
        
        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, count($params));
            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
        }

        $jsonData = curl_exec($ch);

        if (!$jsonData) {
            throw new InstagramBasicDisplayException('Error: _makeOAuthCall() - cURL error: ' . curl_error($ch));
        }

        curl_close($ch);

        return json_decode($jsonData);
    }

    public function setAccessToken($token)
    {
        $this->_accesstoken = $token;
    }

    public function getAccessToken()
    {
        return $this->_accesstoken;
    }

    public function setAppId($appId)
    {
        $this->_appId = $appId;
    }

    public function getAppId()
    {
        return $this->_appId;
    }

    public function setAppSecret($appSecret)
    {
        $this->_appSecret = $appSecret;
    }

    public function getAppSecret()
    {
        return $this->_appSecret;
    }

    public function setRedirectUri($redirectUri)
    {
        $this->_redirectUri = $redirectUri;
    }

    public function getRedirectUri()
    {
        return $this->_redirectUri;
    }

    public function setUserFields($fields)
    {
        $this->_userFields = $fields;
    }

    public function setMediaFields($fields)
    {
        $this->_mediaFields = $fields;
    }

    public function setMediaChildrenFields($fields)
    {
        $this->_mediaChildrenFields = $fields;
    }

    public function setTimeout($timeout)
    {
        $this->_timeout = $timeout;
    }

    public function setConnectTimeout($connectTimeout)
    {
        $this->_connectTimeout = $connectTimeout;
    }
}
