<template>
    <div class="sso">
        <div class="sso-container">
            <div class="sso-logo">
                <picture>
                    <!-- Use full logo if width > 768; use only knot otherwise -->
                    <source :srcset="staticContentHost + '/images/psu_knot.png'" media="(max-width: 768px)">
                    <img :src="staticContentHost + '/images/psu_logo.png'" alt="PSU Logo" />
                </picture>
            </div>
            <div class="sso-title">
                <!-- {{ appTitle }} -->
            </div>
            <div class="sso-developer">
                <!-- DEVELOPMENT INFO (via status icons, or plain text) -->
                <div v-if="envName !== 'prod' && (hasRole('developer') || isProxying)">
                    <div class="sso-developer-info">
                        <span v-if="fontAwesomePresent">
                            <span class="fa fa-info-circle sso-icon" :title="`${appName} -- ${envName}`"></span>
                            <span class="fa fa-plug sso-icon" :title="`SSO Plugin ${ssoVersion}`"></span>
                            <span class="fa fa-font-awesome sso-icon" title="Font Awesome is available!"></span>
                            <span class="fa fa-code sso-icon" :title="`jQuery is ${(jQueryPresent ? '' : 'not')} available`"></span>
                            <span class="fa fa-users sso-icon" title="Proxy" v-if="canProxy" @click="selectProxy" :style="{color: activateProxy || isProxying ? 'yellow' : 'inherit'}"></span>
                            <span v-if="isCurrent">
                                <span class="fa fa-clock-o sso-icon" style="margin-right: 3px;" title="Token will expire in:"></span>
                                <span style="font-size: xx-small">{{ timeRemaining }}</span>
                            </span>
                        </span>
                        <span v-else>
                            {{appName}} -- Using SSO {{ssoVersion}} in {{envName}} --
                            <span style="margin-left: 10px;" v-bind:style="{ color: jQueryPresent ? 'black' : '#AAA' }" :title="`${ jQueryPresent ? '' : 'not' } available`">jQuery</span>
                            <span style="margin-left: 10px;" v-bind:style="{ color: fontAwesomePresent ? 'black' : '#AAA' }" :title="`${ fontAwesomePresent ? '' : 'not' } available`">FontAwesome</span>
                            <!-- NON-FA PROXY BUTTON -->
                            <span style="display:inline;float:right;margin-right:10px;" v-if="canProxy && envName !== 'prod' && !fontAwesomePresent">
                                <img alt="Proxy: " :src="staticContent('images/proxy.png')" class="sso-proxy-img" aria-label="Proxy:" @click="selectProxy">
                            </span>
                            <span v-if="isCurrent">
                                <span style="font-size: xx-small">{{ timeRemaining }}</span>
                            </span>
                        </span>
                    </div>

                    <!-- PROXY FORM -->
                    <div class="sso-developer-proxy" v-if="activateProxy" transition="expand">
                        <span v-if="isProxying">
                            <s style="font-size: .75em; cursor:pointer;" @click="removeProxy">{{jwtData.real.first}} {{jwtData.real.last}}</s>
                        </span>
                        <span v-else>
                            <input id="proxy-input" type="text" @keyup.enter="setProxy" @keydown.tab.prevent="setProxy" @change="setProxy" placeholder="Username or PSU ID" size="10" aria-labelledby="proxy-icon" v-model="proxyInfo" />
                        </span>
                        <span class="proxy-action-spinner" style="display:none;">
                            <span v-if="fontAwesomePresent"><i class="fa fa-spinner fa-spin fa-fw sso-icon" title="Loading..."></i></span>
                            <em v-else>Loading...</em>
                        </span>
                    </div>
                </div>

            </div>
            <div class="sso-status">
                <span v-if="isCurrent">
                    <span class="sso-name">
                        {{ name }} |
                    </span>
                    <a href="#" @click="ssoLogout">Sign Out</a>
                </span>
                <span v-else>
                    <a href="#" @click="ssoRedirect">Log In</a>
                </span>
                <div v-if="isExpiring" class="sso-expiration sso-expiring">
                    <b>Your session is about to expire!</b><br />
                    <button @click="renewJWT" v-if="jQueryPresent">I Need More Time!</button>
                    <button @click="ssoLogout" v-if="jQueryPresent">Sign Out</button>
                </div>
                <div v-else-if="hasJWT && isExpired" class="sso-expiration sso-expired">
                    <b>Your session has expired!</b><br />
                    <button @click="ssoRedirect" v-if="jQueryPresent">Sign In</button>
                </div>
            </div>
        </div>
        <div class="sso-spacer">&nbsp;</div>
    </div>
</template>

<script>

import _jwt from './jwt'
import {mapActions} from 'vuex';

export default {
    name: 'sso',

    //PROPERTIES -- passed in from outside (do not modify)
    props: {
        app: String,    // A short, unique, upper-case code
        env: String,    // {local, dev, test, prod}
        appRoles: String,
        additionalIdentity: String,
        title: String,
        autoClaim: String,
        loginRequired: String,
        eventBus: Object
    },

    //DATA -- updated within this component
    data: function(){
        return {
            ssoVersion: "0.8.x",
            jwt: undefined,
            secondsLeft: undefined,
            interval: undefined,
            jQueryPresent: undefined,
            fontAwesomePresent: undefined,
            activateProxy: undefined,
            proxyInfo: undefined,
            fintiHost: 'https://ws-test.oit.pdx.edu',
            ssoHost: 'https://jwt-stage.campus.wdt.pdx.edu',
            staticContentHost: 'https://content.oit.pdx.edu',
            otuToken: undefined,
            callCount: 0,
        }
    },

    //COMPUTED -- Calculated values cached until relied-upon data changes
    computed: {

        /** APP METADATA
         *********************************************************************
         *********************************************************************/
        //Get the current application name
        appName: function(){
            //If not passed in as property
            if(typeof this.app === 'undefined'){
                //It must be defined globally
                return this.$appName.toUpperCase().trim();
            }
            //Use the given property
            return this.app.toUpperCase().trim();
        },

        //Get the current application name
        appTitle: function(){
            //If not passed in as property
            if(typeof this.title === 'undefined'){
                return "Portland State University"
            }
            //Use the given property
            return this.title;
        },

        //Get the current environment name
        envName: function(){
            //If not passed in as property
            if(typeof this.env === 'undefined'){
                //If not defined globally, use dev by default
                if(typeof this.$envName === 'undefined'){
                    return 'dev';
                }
                //Use the global definition
                else{
                    return this.$envName.toLowerCase().trim();
                }
            }
            //Use the given property
            return this.env.toLowerCase().trim();
        },

        //Get the current application's base URL
        appURL: function(){
            try{
                let pieces = document.location.href.split('/');
                if(pieces.length > 3){
                    pieces.length = 3;
                }
                return pieces.join('/');
            }
            catch(ee){
                return document.location.href;
            }
        },
        signInURL: function() {
            let ssoUrl = this.ssoHost + '?app='+this.appName;
            if(this.additionalIdentity){
                ssoUrl += '&get_idb=Y';
            }
            if(this.appRoles){
                ssoUrl += '&get_app=Y';
            }
            return ssoUrl + '&next='+this.appURL;
        },
        signOutURL: function() {
            return this.ssoHost + '/accounts/logout';
        },

        /** JWT STATUS
         *********************************************************************
         *********************************************************************/

        //Does JWT hold a string that looks like a JWT?
        hasJWT: function(){
            return (this.jwt !== null) && (typeof this.jwt !== 'undefined') && (this.jwt.search(/\S+\.\S+\.\S+/) === 0);
        },
        timeRemaining: function(){
            let mins = Math.floor(this.secondsLeft/60);
            let secs = this.secondsLeft%60;
            return `${(mins ? mins : 0)}:${("00" + secs).slice(-2)}`
        },
        isCurrent: function(){
            return this.secondsLeft > 0;
        },
        isExpiring: function(){
            return (this.secondsLeft <= 60) && (this.secondsLeft > 0);
        },
        isExpired: function(){
            return this.secondsLeft <= 0;
        },




        /** JWT DATA
         *********************************************************************
         *********************************************************************/

        //Get the data from the JWT
        //Note that this data is unverified, and could have been tampered with
        jwtData: function() {
            return _jwt.readString(this.jwt);
        },

        //Name of authenticated user
        name: function () {
            //If preferred name was selected, use it
            if('pref_first' in this.jwtData && this.jwtData.pref_first !== null) {
                return this.jwtData.pref_first + ' ' + this.jwtData.pref_last;
            }
            else if('first' in this.jwtData){
                return this.jwtData.first + ' ' + this.jwtData.last;
            }
            else{
                return "Anonymous"
            }
        },

        //Is the current user proxying
        isProxying: function(){
            return (this.jwtData && this.jwtData.hasOwnProperty('real'));
        },

        //Can the current user proxy?
        canProxy: function(){
            //Must be authenticated
            if(this.hasJWT && this.isCurrent){
                //Only in non-production
                if((!('sid' in this.jwtData)) || this.jwtData['sid'] === 'oprd'){
                    return false;
                }
                //If has proxy role
                if(this.hasRole('proxy')){
                    return true;
                }
                //If already proxying
                if(this.isProxying){
                    return true;
                }
            }
            return false;
        }
    },


    /** LIFECYCLE HOOKS
     *********************************************************************
     *********************************************************************/
    created() {
        //Look for the JWT (params or cookie)
        let foundJWT = this.findJWT();

        // Using eventbus allows triggering events and pass information between components
        if (this.eventBus !== undefined) {
            console.log("Using Event Bus");
        }

        if (this.autoClaim === "N") {
            console.log("No auto-claim");
        }

        //Redirect to SSO page if no JWT
        if (this.loginRequired == 'Y' && !foundJWT) {
            console.log("No JWT found. Redirecting to login...");
            this.require_login();
        }

        //Configure Finti and SSO URLs based on environment
        if(this.envName === 'prod'){
            this.fintiHost = 'https://ws.oit.pdx.edu';
            this.ssoHost = 'https://jwt.campus.wdt.pdx.edu';
            this.staticContentHost = 'https://content.oit.pdx.edu';
        }
        else if(this.envName === 'test'){
            this.fintiHost = 'https://ws-test.oit.pdx.edu';
            this.ssoHost = 'https://jwt-stage.campus.wdt.pdx.edu';
            this.staticContentHost = 'https://content.oit.pdx.edu/nonprod';
        }
        else if(this.envName === 'local'){
            this.fintiHost = 'http://localhost:8888';
            this.ssoHost = 'http://localhost:8000';
            this.staticContentHost = 'https://content.oit.pdx.edu/nonprod';
        }
        else{  // dev (local Finti instance with stage SSO)
            this.fintiHost = 'http://localhost:8888';
            this.ssoHost = 'https://jwt-stage.campus.wdt.pdx.edu';
            this.staticContentHost = 'https://content.oit.pdx.edu/nonprod';
        }

        if(foundJWT){
            this.initializeJWT(foundJWT);
            //If the JWT is a short-lived, one-time-use token, claim it
            let disableAutoClaim = (this.autoClaim === "N");
            if(this.$jwt.secondsLeft(foundJWT) <= 60 && !disableAutoClaim){

                //Has this token already been processed
                if(this.otuToken === foundJWT){
                }
                else{
                    if('otu' in this.jwtData && this.jwtData['otu']){
                        console.log("Auto-claim token");
                        this.otuToken = foundJWT;
                        this.renewJWT();
                    }
                }

            }
        }

        //Configure auto-renew when provided by Finti
        //This only works if jQuery is installed
        this.jQueryPresent = (window.jQuery ? true : false);
        if(this.jQueryPresent){
            let self = this;
            $( document ).ajaxComplete(function(event, xhr, settings) {
                try{
                    if(xhr && xhr.hasOwnProperty("responseJSON") && xhr.responseJSON.hasOwnProperty("jwt")){
                        self.onAutoRenew(xhr.responseJSON);
                    }
                }
                catch(ee){
                    console.log('Auto-renew failed');
                    console.log(ee);
                }
            });
        }

        //Check to see if Font Awesome is available
        this.fontAwesomePresent = this.checkFontAwesome();
    },
    //Lifecycle hook - on update
    updated() {
        this.updateStatusOfJWT();
    },
    //Lifecycle hook - mounted
    mounted(){
        this.updateStatusOfJWT();

        if (this.eventBus !== undefined) {
            this.eventBus.$on('submitted', function(payload) {
                console.log("Got " + payload + "!");
            });

            this.eventBus.$on('new-jwt', (jwt) => {
                console.log("new-jwt: ..." + jwt.slice(-10));
                this.callCount += 1;
                if (jwt) {
                    this.jwt = jwt;
                }
                console.log("sso : Call count " + this.callCount);
            });
        } else if (this.autoClaim === "N") {
            console.warn("Auto-claim is disabled and EventBus is not used. Make sure you implemented a way to update JWT in your app.");
        }
    },

    //FUNCTIONS
    methods: {
        ...mapActions({
            updateIdentity: 'identity/updateIdentity'
        }),

        //Find JWT on initial page load (from cookie or parameter)
        findJWT: function(){
            // FROM COOKIE
            let jwtCookieContent = _jwt.get(this.appName);
            if(jwtCookieContent){
                //Ignore cookie if its JWT has already expired
                if(this.$jwt.secondsLeft(jwtCookieContent) > 0){
                    return jwtCookieContent;
                }
            }

            // FROM ROUTER PARAMETER (if using Vue Router)
            try{
                //If router has a JWT parameter
                if(this.$route.query.jwt){
                    //Get the JWT
                    let jwtParameterContent = this.$route.query.jwt;
                    //Remove the parameter
                    let query = Object.assign({}, this.$route.query);
                    delete query.jwt;
                    this.$router.replace({ query });
                    //Return the JWT
                    return jwtParameterContent;
                }
            }
            catch(ee){ }

            // FROM URL STRING PARAMETER
            try{
                let thisURL = window.location.href;
                let regex = new RegExp('[?&]jwt(=([^&#]*)|&|#|$)');
                let results = regex.exec(thisURL);
                if (!results){
                    return null;
                }
                if (!results[2]){
                    return '';
                }
                else{
                    let jwtContent = decodeURIComponent(results[2].replace(/\+/g, ' '));
                    //Put JWT into cookie because page reloads when removing the JWT parameter
                    this.$jwt.store(jwtContent, this.appName);
                    //Remove the parameter
                    try{
                        let baseURL = thisURL.substring(0, thisURL.indexOf('?'));
                        //Refreshing the page breaks the JWT claim process
                        //window.location.href = baseURL;
                    }
                    catch(ee){ console.log(ee); }

                    //If parameter removal failed, return the JWT content
                    return jwtContent
                }
            }
            catch(ee){ console.log(ee); }
        },

        // PROXY FUNCTIONS
        selectProxy: function(){
            this.activateProxy = !this.activateProxy;
            if(this.jQueryPresent) {

                setTimeout(function(){ $('#proxy-input').focus(); }, 25)
            }
        },


        //Component-provided function to reset a JWT expiration via AJAX call
        renewJWT: function(){
            let self = this;

            $.ajax({
                type:   "GET",
                url:    self.fintiHost + '/wdt/v1/sso_proxy/renew',
                data:   { },
                headers: { 'Authorization':'Bearer ' + self.jwt },
                dataType: "json",
                beforeSend:function(){ },
                success:function(data){
                    self.initializeJWT(data.message);
                },
                error:function(error){
                    console.log(error);
                },
                complete:function(){ }
            });
        },


        hasRole: function(roleName){
            //console.log(this.jwtData);
            return (this.isCurrent && 'app_roles' in this.jwtData && this.jwtData['app_roles'].indexOf(roleName) >= 0);
        },

        //Component-provided function to proxy as another user in non-production
        setProxy: function(){
            let self = this;
            let proxyParameter = self.proxyInfo ? self.proxyInfo : '-';

            if(this.jQueryPresent){
                $.ajax({
                    type:   "GET",
                    url:    self.fintiHost + '/wdt/v1/sso_proxy/proxy',
                    data:   { proxy: proxyParameter},
                    headers: { 'Authorization':'Bearer ' + self.jwt },
                    dataType: "json",
                    beforeSend:function(){
                        $('.proxy-action-spinner').show();
                        $('.proxy-action-button').hide();
                     },
                    success:function(data){
                        self.initializeJWT(data.message);
                        self.activateProxy = self.isProxying;
                        self.proxyInfo = "";
                        //Refresh the page to "start over" as the new user
                        location.reload();
                    },
                    error:function(error){
                        console.log(error);
                    },
                    complete:function(){
                        $('.proxy-action-spinner').hide();
                        $('.proxy-action-button').show();
                    }
                });
            }
            else{
                console.log("Proxying only works if jQuery is installed.");
            }
        },
        removeProxy: function(){
            this.proxyInfo = "";
            //this.activateProxy = false;
            this.setProxy();
        },


        //Globally-defined response to handle an auto-renew on any jQuery AJAX call
        onAutoRenew: function(apiResponse){
            try{
                let newJWT = apiResponse.jwt;
                if((newJWT !== null) && (typeof newJWT !== 'undefined')){  // only renew if jwt is not undefined or null
                    this.initializeJWT(newJWT);
                }
            }
            catch(ee){
                console.log(ee);
            }
        },


        /* When a new or updated JWT value is given to us */
        initializeJWT(jwtContent){
            this.jwt = jwtContent;
            this.updateStatusOfJWT();

            // If JWT has not expired, save it in a cookie
            if(this.isCurrent){
                _jwt.store(jwtContent, this.appName);
            }
        },

        //Determine the status of the JWT (expired or not expired; how long until expiration)
        updateStatusOfJWT: function(){
            if(this.hasJWT){
                //Stop the secondsLeft decrement counter if it is already running
                if(this.interval){
                    clearInterval(this.interval);
                }

                //Determine how many seconds are left until the JWT expires
                this.secondsLeft = _jwt.secondsLeft(this.jwt);

                //Decrement secondsLeft every second
                if(this.secondsLeft > 0){
                    //Start a counter to fire the decrement event every second
                    let self = this;
                    this.interval = setInterval(function(){ self.decrementSeconds() }, 1000);
                }
                else{
                    //Clear cookie, but keep in the component
                    this.$jwt.clear();
                }

                //Update state
                this.updateIdentity(this)

            }
            else{
                this.secondsLeft = 0;
            }
        },

        //Called every second to keep number of seconds up to date
        decrementSeconds: function(){
            //Re-check the seconds left every minute
            if(this.secondsLeft > 59 && this.secondsLeft % 60 === 0){
                this.updateStatusOfJWT();
            }
            else if(this.secondsLeft > 0){
                this.secondsLeft -= 1;
            }
            else{
                this.secondsLeft = 0;
                //Stop the counter
                clearInterval(this.interval);
                //Clear cookie, but keep in the component
                this.$jwt.clear();
            }
        },

        forget: function(){
            _jwt.clear(this.appName);
            this.jwt = undefined
        },



        require_login: function(){
            //Is there an existing valid JWT?
            if(_jwt.isCurrent(this.jwt)){
                return; //Already logged in
            }

            else{
                //Send to login page
                this.ssoRedirect();
            }
        },

        ssoRedirect: function(){
            this.forget();
            console.log("SSO URL: " + this.signInURL);
            document.location.href= this.signInURL;
        },

        ssoLogout: function(){
            this.forget();
            document.location.href=this.signOutURL;
        },

        staticContent: function(path){
            return `${this.staticContentHost}/${path}`;
        },

        checkFontAwesome: function () {
            //Create a hidden .fa icon on the page
            let span = document.createElement('span');
            span.className = 'fa';
            span.style.display = 'none';
            document.body.insertBefore(span, document.body.firstChild);

            //Check if the resluting element has the FontAwesome font family
            let answer = (window.getComputedStyle(span, null).getPropertyValue('font-family') === 'FontAwesome')

            //Remove the hidden "test" element
            document.body.removeChild(span);

            //Return the answer
            return answer;
        }
    }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
    .sso-container{
        position: fixed;
        top: 0;
        left:0;
        right: 0;
        display: grid;
        grid-template-columns: 36px repeat( auto-fit, minmax(175px, 1fr) );
        background-color: #6A7F10;
        color: whitesmoke;
        align-items: center;
        grid-gap: 20px;
        padding: 15px 60px;
        font-size: 1.4em;
        z-index: 999;
    }
    .sso-logo{

    }
    .sso-title{
        justify-self: start;
        font-weight: bold;
    }
    .sso-developer{
        font-size: .75em;
        justify-self: center;
        padding-right: 35px;
    }
    .sso-developer-info{
        line-height: 12px;
    }
    .sso-developer-proxy{
    }
    .sso-developer-proxy input{
        height: 10px;
    }
    .sso-status{
        justify-self: end;
    }
    .sso-expiration{
        position: fixed;
        top: 5px;
        right: 10px;
        padding: 10px;
        border: 1px solid black;
        border-radius: 5px;
        background-color: #6A7F10;
        box-shadow: 5px 10px 8px forestgreen;
        font-size: .75em;
    }
    .sso-expiring{
        background-color: palegoldenrod;
        border: 1px solid darkgoldenrod;
        box-shadow: 5px 10px 8px darkgoldenrod;
        color: saddlebrown;
    }
    .sso-expired{
        background-color: palegoldenrod;
        border: 1px solid maroon;
        box-shadow: 5px 10px 8px maroon;
        color: maroon
    }
    .sso-expiration button{
        font-size: 1em;
    }

    .sso-container a{
        color: whitesmoke;
        text-decoration: underline;
        cursor: pointer;
        white-space: nowrap;
    }
    .sso-spacer{
        height: 10px;
    }

    @media screen and (max-width: 1050px) {
        .sso-developer{
            display: none;
        }
    }
    @media screen and (max-width: 800px) {
        .sso-container{
            padding: 15px 20px;
        }
    }

    @media screen and (max-width: 700px) {
        .sso-status{
            grid-column-end: -1;
            font-size: .7em;
        }
        .sso-container{
            grid-row-gap: 3px;
        }
    }
    @media screen and (max-width: 600px) {
        .sso-status{
            font-size: .6em;
        }
    }
    @media screen and (max-width: 500px) {
        .sso-name{
            display:none;
        }
        .sso-container{
            grid-gap: 5px;
            grid-template-columns: 36px 175px 1fr;
        }
    }
    @media screen and (max-width: 350px) {
        .sso-status{
            grid-column: -1;
            font-size: .5em;
        }
        .sso-container{
            grid-gap: 3px;
            padding: 10px 10px;
        }
        .sso-spacer{
            height: 3px;
        }
    }


.sso-icon{
    margin-right: 10px;
    font-size: 12px;
}
.sso-proxy-img{
    margin: 0;
    max-height: 20px;
    border: 0;
}
</style>
