/*******************************************************************************
 * Copyright 2013 The MITRE Corporation 
 *   and the MIT Kerberos and Internet Trust Consortium
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
var ClientModel = Backbone.Model.extend({

    idAttribute: "id",

    initialize: function () {

        // bind validation errors to dom elements
        // this will display form elements in red if they are not valid
        this.bind('error', function(model, errs) {
            _.map(errs, function (val, elID) {
                $('#' + elID).addClass('error');
            });
        });

    },

    // We can pass it default values.
    defaults:{
        id:null,
        
        clientId:"",
        clientSecret:"",
        redirectUris:[],
        clientName:null,
        clientUri:"",
        logoUri:"",
        contacts:[],
        tosUri:"",
        tokenEndpointAuthMethod:null,
        scope:[],
        grantTypes:["authorization_code"],
        responseTypes:[],
        policyUri:"",
        jwksUri:"",
        
        applicationType:null,
        sectorIdentifierUri:"",
        subjectType:null,
        
        requestObjectSigningAlg:null,
        
        userInfoSignedResponseAlg:null,
        userInfoEncryptedResponseAlg:null,
        userInfoEncryptedResponseEnc:null,
        
        idTokenSignedResponseAlg:null,
        idTokenEncryptedResponseAlg:null,
        idTokenEncryptedResponseEnc:null,
        
        defaultMaxAge:60000,
        requireAuthTime:false,
        defaultACRvalues:null,
        
        initiateLoginUri:"",
        postLogoutRedirectUri:"",
        
        requestUris:[],
        
        authorities:[],
        accessTokenValiditySeconds: 3600,
        refreshTokenValiditySeconds: 604800,
        resourceIds:[],
        //additionalInformation?
        
        clientDescription:"",
        reuseRefreshToken:true,
        dynamicallyRegistered:false,
        allowIntrospection:false,
        idTokenValiditySeconds: 600,
        createdAt:null,     

        allowRefresh:false,
        displayClientSecret: false,
        generateClientSecret: false,
        requireClientSecret: true,
    },

    urlRoot:"api/clients"

});

var ClientCollection = Backbone.Collection.extend({

    initialize: function() {
        //this.fetch();
    },

    model:ClientModel,
    url:"api/clients",
    
    getByClientId: function(clientId) {
		var clients = this.where({clientId: clientId});
		if (clients.length == 1) {
			return clients[0];
		} else {
			return null;
		}
    }
});

var ClientView = Backbone.View.extend({

    tagName: 'tr',

    initialize:function () {

        if (!this.template) {
            this.template = _.template($('#tmpl-client').html());
        }

        if (!this.scopeTemplate) {
        	this.scopeTemplate = _.template($('#tmpl-scope-list').html());
        }

        this.model.bind('change', this.render, this);
        
    },

    render:function (eventName) {
    	var json = {client: this.model.toJSON(), count: this.options.count};
        this.$el.html(this.template(json));

        $('.scope-list', this.el).html(this.scopeTemplate({scopes: this.model.get('scope'), systemScopes: app.systemScopeList}));
        
        this.$('.dynamically-registered').tooltip({title: 'This client was dynamically registered'});
        
        return this;
    },

    events:{
        "click .btn-edit":"editClient",
        "click .btn-delete":"deleteClient",
        "click .btn-whitelist":"whiteListClient"
    },

    editClient:function () {
        app.navigate('admin/client/' + this.model.id, {trigger: true});
    },

    whiteListClient:function() {
    	var whiteList = app.whiteListList.getByClientId(this.model.get('clientId'));
    	if (whiteList == null) {
    		// create a new one
    		app.navigate('admin/whitelist/new/' + this.model.id, {trigger: true});
    	} else {
    		// edit the existing one
    		app.navigate('admin/whitelist/' + whiteList.id, {trigger: true});
    	}
    },
    
    deleteClient:function () {

        if (confirm("Are you sure sure you would like to delete this client?")) {
            var self = this;

            this.model.destroy({
                success:function () {
                    self.$el.fadeTo("fast", 0.00, function () { //fade
                        $(this).slideUp("fast", function () { //slide up
                            $(this).remove(); //then remove from the DOM
                            app.clientListView.togglePlaceholder();
                        });
                    });
                },
                error:function (error, response) {
            		console.log("An error occurred when deleting a client");
    
					//Pull out the response text.
					var responseJson = JSON.parse(response.responseText);
            		
            		//Display an alert with an error message
            		$('#modalAlert div.modal-body').html(responseJson.errorMessage);
            		
        			 $("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
        				 "backdrop" : "static",
        				 "keyboard" : true,
        				 "show" : true // ensure the modal is shown immediately
        			 });
            	}
            });

            app.clientListView.delegateEvents();
        }

        return false;
    },

    close:function () {
        $(this.el).unbind();
        $(this.el).empty();
    }
});

var ClientListView = Backbone.View.extend({

    tagName: 'span',

    initialize:function () {
        //this.model.bind("reset", this.render, this);
    },

    events:{
        "click .new-client":"newClient",
        "click .refresh-table":"refreshTable"
    },

    newClient:function () {
        this.remove();
        app.navigate('admin/client/new', {trigger: true});
    },

    render:function (eventName) {

        // append and render table structure
        $(this.el).html($('#tmpl-client-table').html());

        _.each(this.model.models, function (client) {
            $("#client-table",this.el).append(
            		new ClientView({
            				model:client, 
            				count:this.options.stats.get(client.get('id'))
            			}).render().el);
        }, this);

        this.togglePlaceholder();
        
        return this;
    },
    
	togglePlaceholder:function() {
		if (this.model.length > 0) {
			$('#client-table', this.el).show();
			$('#client-table-empty', this.el).hide();
		} else {
			$('#client-table', this.el).hide();
			$('#client-table-empty', this.el).show();
		}
	},
	
    refreshTable:function() {
    	var _self = this;
    	_self.model.fetch({
    		success: function() {
    			_self.options.stats.fetch({
    				success: function () {
    					_self.render();
    				}
    			});
    		}
    	});
    }
});

var ClientFormView = Backbone.View.extend({

    tagName:"span",

    initialize:function () {

        if (!this.template) {
            this.template = _.template($('#tmpl-client-form').html());
        }

        this.redirectUrisCollection = new Backbone.Collection();
        this.scopeCollection = new Backbone.Collection();
        this.contactsCollection = new Backbone.Collection();
        this.defaultAcrValuesCollection = new Backbone.Collection();
        this.requestUrisCollection = new Backbone.Collection();
        // TODO: add Spring authorities collection and resource IDs collection?
    },

    events:{
        "click .btn-save":"saveClient",
        "click #allowRefresh" : "toggleRefreshTokenTimeout",
        "click #disableAccessTokenTimeout" : function() { 
        	$("#access-token-timeout-time", this.$el).prop('disabled',!$("#access-token-timeout-time", this.$el).prop('disabled')); 
        	$("#access-token-timeout-unit", this.$el).prop('disabled',!$("#access-token-timeout-unit", this.$el).prop('disabled')); 
        	document.getElementById("access-token-timeout-time").value = '';
        	},
        "click #disableIDTokenTimeout" : function() { 
        	$("#id-token-timeout-time", this.$el).prop('disabled',!$("#id-token-timeout-time", this.$el).prop('disabled')); 
        	$("#id-token-timeout-unit", this.$el).prop('disabled',!$("#id-token-timeout-unit", this.$el).prop('disabled')); 
        	document.getElementById("id-token-timeout-time").value = ''; 
        	},
        "click #disableRefreshTokenTimeout" : function() { 
        	$("#refresh-token-timeout-time", this.$el).prop('disabled',!$("#refresh-token-timeout-time", this.$el).prop('disabled'));
        	$("#refresh-token-timeout-unit", this.$el).prop('disabled',!$("#refresh-token-timeout-unit", this.$el).prop('disabled')); 
        	document.getElementById("refresh-token-timeout-time").value = ''; 	
        	},
        "click .btn-cancel": function() { window.history.back(); return false; },
        "change #requireClientSecret":"toggleRequireClientSecret",
        "change #displayClientSecret":"toggleDisplayClientSecret",
        "change #generateClientSecret":"toggleGenerateClientSecret",
        "change #logoUri input":"previewLogo"
    },

    toggleRefreshTokenTimeout:function () {
        $("#refreshTokenValidityTime", this.$el).toggle();
    },
    
    previewLogo:function(event) {
    	if ($('#logoUri input', this.el).val()) {
    		$('#logoPreview', this.el).empty();
    		$('#logoPreview', this.el).attr('src', $('#logoUri input').val());
    	} else {
    		$('#logoBlock', this.el).hide();
    	}
    },

    /**
     * Set up the form based on the current state of the requireClientSecret checkbox parameter
     * @param event
     */
    toggleRequireClientSecret:function(event) {
    	
    	if ($('#requireClientSecret input', this.el).is(':checked')) {
    		// client secret is required, show all the bits
    		$('#clientSecretPanel', this.el).show();
    		// this function sets up the display portions
    		this.toggleGenerateClientSecret();
    	} else {
    		// no client secret, hide all the bits
    		$('#clientSecretPanel', this.el).hide();        		
    	}
    },
    
    /**
     * Set up the form based on the "Generate" checkbox
     * @param event
     */
    toggleGenerateClientSecret:function(event) {

    	if ($('#generateClientSecret input', this.el).is(':checked')) {
    		// show the "generated" block, hide the "display" checkbox
    		$('#displayClientSecret', this.el).hide();
    		$('#clientSecret', this.el).hide();
    		$('#clientSecretGenerated', this.el).show();
    		$('#clientSecretHidden', this.el).hide();
    	} else {
    		// show the display checkbox, fall back to the "display" logic
    		$('#displayClientSecret', this.el).show();
    		this.toggleDisplayClientSecret(event);
    	}
    },
    
    /**
     * Handle whether or not to display the client secret
     * @param event
     */
    toggleDisplayClientSecret:function(event) {
    	
    	if ($('#displayClientSecret input').is(':checked')) {
    		// want to display it
    		$('#clientSecret', this.el).show();
    		$('#clientSecretHidden', this.el).hide();
    		$('#clientSecretGenerated', this.el).hide();
    	} else {
    		// want to hide it
    		$('#clientSecret', this.el).hide();
    		$('#clientSecretHidden', this.el).show();
    		$('#clientSecretGenerated', this.el).hide();
    	}
    },

    // rounds down to the nearest integer value in seconds.
    getFormTokenNumberValue:function(value, timeUnit) {
        if (value == "") {
        	return null;
        } else if (timeUnit == 'hours') {
        	return parseInt(parseFloat(value) * 3600);
        } else if (timeUnit == 'minutes') {
        	return parseInt(parseFloat(value) * 60);
        } else { // seconds
        	return parseInt(value);
        }
    },

    // maps from a form-friendly name to the real grant parameter name
    grantMap:{
    	'authorization_code': 'authorization_code',
    	'password': 'password',
    	'implicit': 'implicit',
    	'client_credentials': 'client_credentials',
    	'redelegate': 'urn:ietf:params:oauth:grant_type:redelegate',
    	'refresh_token': 'refresh_token'
    },
    
    // maps from a form-friendly name to the real response type parameter name
    responseMap:{
    	'code': 'code',
    	'token': 'token',
    	'idtoken': 'id_token',
    	'token-idtoken': 'token id_token',
    	'code-idtoken': 'code id_token',
    	'code-token': 'code token',
    	'code-token-idtoken': 'code token id_token'
    },
    
    saveClient:function (event) {

        $('.control-group').removeClass('error');

        // build the scope object
        var scopes = this.scopeCollection.pluck("item");
        
        // build the grant type object
        var grantTypes = [];
        $.each(this.grantMap, function(index,type) {
            if ($('#grantTypes-' + index).is(':checked')) {
                grantTypes.push(type);
            }
        });
        
        // build the response type object
        var responseTypes = [];
        $.each(this.responseMap, function(index,type) {
        	if ($('#responseTypes-' + index).is(':checked')) {
        		responseTypes.push(type);
        	}
        });

        var requireClientSecret = $('#requireClientSecret input').is(':checked');
        var generateClientSecret = $('#generateClientSecret input').is(':checked');
        var clientSecret = null;
        
        if (requireClientSecret && !generateClientSecret) {
        	// if it's required but we're not generating it, send the value to preserve it
        	clientSecret = $('#clientSecret input').val();
        }

        var accessTokenValiditySeconds = null;
        if (!$('disableAccessTokenTimeout').is(':checked')) {
        	accessTokenValiditySeconds = this.getFormTokenNumberValue($('#accessTokenValidityTime input[type=text]').val(), $('#accessTokenValidityTime select').val()); 
        }
        
        var idTokenValiditySeconds = null;
        if (!$('disableIDTokenTimeout').is(':checked')) {
        	idTokenValiditySeconds = this.getFormTokenNumberValue($('#idTokenValidityTime input[type=text]').val(), $('#idTokenValidityTime select').val()); 
        }
        
        var refreshTokenValiditySeconds = null;
        if ($('#allowRefresh').is(':checked')) {

        	if ($.inArray('refresh_token', grantTypes) == -1) {
        		grantTypes.push('refresh_token');
        	}

        	if ($.inArray('offline_access', scopes) == -1) {
            	scopes.push("offline_access");            		
        	}

        	if (!$('disableRefreshTokenTimeout').is(':checked')) {
        		refreshTokenValiditySeconds = this.getFormTokenNumberValue($('#refreshTokenValidityTime input[type=text]').val(), $('#refreshTokenValidityTime select').val());
        	}
        }
        
        var attrs = {
            clientName:$('#clientName input').val(),
            clientId:$('#clientId input').val(),
            clientSecret: clientSecret,
            generateClientSecret:generateClientSecret,
            redirectUris: this.redirectUrisCollection.pluck("item"),
            clientDescription:$('#clientDescription textarea').val(),
            logoUri:$('#logoUri input').val(),
            grantTypes: grantTypes,
            accessTokenValiditySeconds: accessTokenValiditySeconds,
            refreshTokenValiditySeconds: refreshTokenValiditySeconds,
            idTokenValiditySeconds: idTokenValiditySeconds,
            allowRefresh: $('#allowRefresh').is(':checked'),
            allowIntrospection: $('#allowIntrospection input').is(':checked'), // <-- And here? --^
            scope: scopes,
            
            // TODO: items below this line are untested
            tosUri: $('#tosUri input').val(),
            policyUri: $('#policyUri input').val(),
            clientUri: $('#clientUri input').val(),
            applicationType: $('#applicationType input').filter(':checked').val(),
            jwksUri: $('#jwksUri input').val(),
            subjectType: $('#applicationType input').filter(':checked').val(),
            tokenEndpointAuthMethod: $('#tokenEndpointAuthMethod input').filter(':checked').val(),
            responseTypes: responseTypes,
            sectorIdentifierUri: $('#sectorIdentifierUri input').val(),
            initiateLoginUri: $('#initiateLoginUri input').val(),
            postLogoutRedirectUri: $('#postLogoutRedirectUri input').val(),
            reuseRefreshToken: $('#reuseRefreshToken').is(':checked'),
            requireAuthTime: $('#requireAuthTime input').is(':checked'),
            defaultMaxAge: parseInt($('#defaultMaxAge input').val()),
            contacts: this.contactsCollection.pluck('item'),
            requestUris: this.requestUrisCollection.pluck('item'),
            defaultAcrValues: this.defaultAcrValuesCollection.pluck('item'),
            requestObjectSigningAlg: $('#requestObjectSigningAlg select').val(),
            userInfoSignedResponseAlg: $('#userInfoSignedResponseAlg select').val(),
            userInfoEncryptedResponseAlg: $('#userInfoEncryptedResponseAlg select').val(),
            userInfoEncryptedResponseEnc: $('#userInfoEncryptedResponseEnc select').val(),
            idTokenSignedResponseAlg: $('#idTokenSignedResponseAlg select').val(),
            idTokenEncryptedResponseAlg: $('#idTokenEncryptedResponseAlg select').val(),
            idTokenEncryptedResponseEnc: $('#idTokenEncryptedResponseEnc select').val()
        };

        // post-validate
        if (attrs["allowRefresh"] == false) {
            attrs["refreshTokenValiditySeconds"] = null;
        }

        if ($('#disableIDTokenTimeout').is(':checked')) {
             attrs["idTokenValiditySeconds"] = null;
        }

        if ($('#disableAccessTokenTimeout').is(':checked')) {
            attrs["accessTokenValiditySeconds"] = null;
        }

        if ($('#disableRefreshTokenTimeout').is(':checked')) {
            attrs["refreshTokenValiditySeconds"] = null;
        }

        // set all empty strings to nulls
        for (var key in attrs) {
        	if (attrs[key] === "") {
        		attrs[key] = null;
        	}
        }
        
        var _self = this;
        this.model.save(attrs, {
            success:function () {
                app.clientList.add(_self.model);
                app.navigate('admin/clients', {trigger:true});
            },
            error:function (error, response) {
        		console.log("An error occurred when deleting from a list widget");

				//Pull out the response text.
				var responseJson = JSON.parse(response.responseText);
        		
        		//Display an alert with an error message
        		$('#modalAlert div.modal-body').html(responseJson.errorMessage);
        		
    			 $("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
    				 "backdrop" : "static",
    				 "keyboard" : true,
    				 "show" : true // ensure the modal is shown immediately
    			 });
        	}
        });

        return false;
    },

    render:function (eventName) {

        $(this.el).html(this.template(this.model.toJSON()));

        
        var _self = this;

        // build and bind registered redirect URI collection and view
        _.each(this.model.get("redirectUris"), function (redirectUri) {
            _self.redirectUrisCollection.add(new URIModel({item:redirectUri}));
        });

        $("#redirectUris .controls",this.el).html(new ListWidgetView({
        	type:'uri', 
        	placeholder: 'http://',
        	collection: this.redirectUrisCollection}).render().el);
        
        // build and bind scopes
        _.each(this.model.get("scope"), function (scope) {
            _self.scopeCollection.add(new Backbone.Model({item:scope}));
        });

        $("#scope .controls",this.el).html(new ListWidgetView({
        	placeholder: 'new scope', 
        	autocomplete: _.uniq(_.flatten(app.systemScopeList.pluck("value"))), 
            collection: this.scopeCollection}).render().el);

        // build and bind contacts
        _.each(this.model.get('contacts'), function (contact) {
        	_self.contactsCollection.add(new Backbone.Model({item:contact}));
        });
        
        $("#contacts .controls", this.el).html(new ListWidgetView({
        	placeholder: 'new contact',
        	collection: this.contactsCollection}).render().el);
        
        
        // build and bind request URIs
        _.each(this.model.get('requestUris'), function (requestUri) {
        	_self.requestUrisCollection.add(new URIModel({item:requestUri}));
        });
        
        $('#requestUris .controls', this.el).html(new ListWidgetView({
        	type: 'uri',
        	placeholder: 'http://',
        	collection: this.requestUrisCollection}).render().el);
        
        // build and bind default ACR values
        _.each(this.model.get('defaultAcrValues'), function (defaultAcrValue) {
        	_self.defaultAcrValuesCollection.add(new Backbone.Model({item:defaultAcrValue}));
        });
        
        $('#defaultAcrValues .controls', this.el).html(new ListWidgetView({
        	placeholder: 'new ACR value',
        	// TODO: autocomplete from spec
        	collection: this.defaultAcrValuesCollection}).render().el);
        
        // build and bind 
        
        // set up token  fields
        if (!this.model.get("allowRefresh")) {
            $("#refreshTokenValidityTime", this.$el).hide();
        }

        if (this.model.get("accessTokenValiditySeconds") == null) {
            $("#access-token-timeout-time", this.$el).prop('disabled',true);
        }

        if (this.model.get("refreshTokenValiditySeconds") == null) {
            $("#refresh-token-timeout-time", this.$el).prop('disabled',true);
        }

        if (this.model.get("idTokenValiditySeconds") == null) {
            $("#id-token-timeout-time", this.$el).prop('disabled',true);
        }

        // toggle other dynamic fields
        this.toggleRequireClientSecret();
        this.previewLogo();
        
        this.$('.nyi').clickover({
        	placement: 'right', 
        	title: 'Not Yet Implemented', 
        	content: 'The value of this field will be saved with the client, '
        		+'but the server does not currently process anything with it. '
        		+'Future versions of the server library will make use of this.'
        	});
        
       return this;
    }
});


