angular.module("core-config-setup", ['angularFileUpload'])
  .directive('configSetupTool', function() {
    var directiveDefinitionObject = {
      priority: 1,
      templateUrl: '/static/directives/config/config-setup-tool.html',
      replace: true,
      transclude: true,
      restrict: 'C',
      scope: {
        'isActive': '=isActive',
        'configurationSaved': '&configurationSaved'
      },
      controller: function($rootScope, $scope, $element, $timeout, ApiService) {
        $scope.HOSTNAME_REGEX = '^[a-zA-Z-0-9\.]+(:[0-9]+)?$';
        $scope.GITHOST_REGEX = '^https?://([a-zA-Z0-9]+\.?\/?)+$';

        $scope.SERVICES = [
          {'id': 'redis', 'title': 'Redis'},

          {'id': 'registry-storage', 'title': 'Registry Storage'},

          {'id': 'ssl', 'title': 'SSL certificate and key', 'condition': function(config) {
            return config.PREFERRED_URL_SCHEME == 'https';
          }},

          {'id': 'ldap', 'title': 'LDAP Authentication', 'condition': function(config) {
            return config.AUTHENTICATION_TYPE == 'LDAP';
          }, 'password': true},

          {'id': 'jwt', 'title': 'JWT Authentication', 'condition': function(config) {
            return config.AUTHENTICATION_TYPE == 'JWT';
          }, 'password': true},

          {'id': 'keystone', 'title': 'Keystone Authentication', 'condition': function(config) {
            return config.AUTHENTICATION_TYPE == 'Keystone';
          }, 'password': true},

          {'id': 'mail', 'title': 'E-mail Support', 'condition': function(config) {
            return config.FEATURE_MAILING;
          }},

          {'id': 'github-login', 'title': 'Github (Enterprise) Authentication', 'condition': function(config) {
            return config.FEATURE_GITHUB_LOGIN;
          }},

          {'id': 'google-login', 'title': 'Google Authentication', 'condition': function(config) {
            return config.FEATURE_GOOGLE_LOGIN;
          }},

          {'id': 'github-trigger', 'title': 'GitHub (Enterprise) Build Triggers', 'condition': function(config) {
            return config.FEATURE_GITHUB_BUILD;
          }},

          {'id': 'bitbucket-trigger', 'title': 'BitBucket Build Triggers', 'condition': function(config) {
            return config.FEATURE_BITBUCKET_BUILD;
          }},

          {'id': 'gitlab-trigger', 'title': 'GitLab Build Triggers', 'condition': function(config) {
            return config.FEATURE_GITLAB_BUILD;
          }}
        ];

        $scope.STORAGE_CONFIG_FIELDS = {
          'LocalStorage': [
            {'name': 'storage_path', 'title': 'Storage Directory', 'placeholder': '/some/directory', 'kind': 'text'}
          ],

          'S3Storage': [
            {'name': 's3_access_key', 'title': 'AWS Access Key', 'placeholder': 'accesskeyhere', 'kind': 'text'},
            {'name': 's3_secret_key', 'title': 'AWS Secret Key', 'placeholder': 'secretkeyhere', 'kind': 'text'},
            {'name': 's3_bucket', 'title': 'S3 Bucket',  'placeholder': 'my-cool-bucket', 'kind': 'text'},
            {'name': 'storage_path', 'title': 'Storage Directory', 'placeholder': '/path/inside/bucket', 'kind': 'text'}
          ],

          'GoogleCloudStorage': [
            {'name': 'access_key', 'title': 'Cloud Access Key', 'placeholder': 'accesskeyhere', 'kind': 'text'},
            {'name': 'secret_key', 'title': 'Cloud Secret Key', 'placeholder': 'secretkeyhere', 'kind': 'text'},
            {'name': 'bucket_name', 'title': 'GCS Bucket',  'placeholder': 'my-cool-bucket', 'kind': 'text'},
            {'name': 'storage_path', 'title': 'Storage Directory', 'placeholder': '/path/inside/bucket', 'kind': 'text'}
          ],

          'RadosGWStorage': [
            {'name': 'hostname', 'title': 'Rados Server Hostname', 'placeholder': 'my.rados.hostname', 'kind': 'text'},
            {'name': 'is_secure', 'title': 'Is Secure', 'placeholder': 'Require SSL', 'kind': 'bool'},
            {'name': 'access_key', 'title': 'Access Key', 'placeholder': 'accesskeyhere', 'kind': 'text', 'help_url': 'http://ceph.com/docs/master/radosgw/admin/'},
            {'name': 'secret_key', 'title': 'Secret Key', 'placeholder': 'secretkeyhere', 'kind': 'text'},
            {'name': 'bucket_name', 'title': 'Bucket Name',  'placeholder': 'my-cool-bucket', 'kind': 'text'},
            {'name': 'storage_path', 'title': 'Storage Directory', 'placeholder': '/path/inside/bucket', 'kind': 'text'}
          ],

          'SwiftStorage': [
            {'name': 'auth_url', 'title': 'Swift Auth URL', 'placeholder': '', 'kind': 'text'},
            {'name': 'swift_container', 'title': 'Swift Container Name', 'placeholder': 'mycontainer', 'kind': 'text'},
            {'name': 'storage_path', 'title': 'Storage Path', 'placeholder': '/path/inside/container', 'kind': 'text'},

            {'name': 'swift_user', 'title': 'Username', 'placeholder': 'accesskeyhere', 'kind': 'text'},
            {'name': 'swift_password', 'title': 'Password/Key', 'placeholder': 'secretkeyhere', 'kind': 'text'},

            {'name': 'ca_cert_path', 'title': 'CA Cert Filename', 'placeholder': 'conf/stack/swift.cert', 'kind': 'text', 'optional': true},
            {'name': 'os_options', 'title': 'OS Options', 'kind': 'map',
             'keys': ['tenant_id', 'auth_token', 'service_type', 'endpoint_type', 'tenant_name', 'object_storage_url', 'region_name']}
          ]
        };

        $scope.validateHostname = function(hostname) {
          if (hostname.indexOf('127.0.0.1') == 0 || hostname.indexOf('localhost') == 0) {
            return 'Please specify a non-localhost hostname. "localhost" will refer to the container, not your machine.'
          }

          return null;
        };

        $scope.config = null;
        $scope.mapped = {
          '$hasChanges': false
        };

        $scope.validating = null;
        $scope.savingConfiguration = false;

        $scope.getServices = function(config) {
          var services = [];
          if (!config) { return services; }

          for (var i = 0; i < $scope.SERVICES.length; ++i) {
            var service = $scope.SERVICES[i];
            if (!service.condition || service.condition(config)) {
              services.push({
                'service': service,
                'status': 'validating'
              });
            }
          }

          return services;
        };

        $scope.validationStatus = function(serviceInfos) {
          if (!serviceInfos) { return 'validating'; }

          var hasError = false;
          for (var i = 0; i < serviceInfos.length; ++i) {
            if (serviceInfos[i].status == 'validating') {
              return 'validating';
            }
            if (serviceInfos[i].status == 'error') {
              hasError = true;
            }
          }

          return hasError ? 'failed' : 'success';
        };

        $scope.cancelValidation = function() {
          $('#validateAndSaveModal').modal('hide');
          $scope.validating = null;
          $scope.savingConfiguration = false;
        };

        $scope.validateService = function(serviceInfo, opt_password) {
          var params = {
            'service': serviceInfo.service.id
          };

          var data = {
            'config': $scope.config,
            'password': opt_password || ''
          };

          ApiService.scValidateConfig(data, params).then(function(resp) {
            serviceInfo.status = resp.status ? 'success' : 'error';
            serviceInfo.errorMessage = $.trim(resp.reason || '');
          }, ApiService.errorDisplay('Could not validate configuration. Please report this error.'));
        };

        $scope.checkValidateAndSave = function() {
          if ($scope.configform.$valid) {
            $scope.validateAndSave();
            return;
          }

          $element.find("input.ng-invalid:first")[0].scrollIntoView();
          $element.find("input.ng-invalid:first").focus();
        };

        $scope.validateAndSave = function() {
          $scope.validating = $scope.getServices($scope.config);

          var requirePassword = false;
          for (var i = 0; i < $scope.validating.length; ++i) {
            var serviceInfo = $scope.validating[i];
            if (serviceInfo.service.password) {
              requirePassword = true;
              break;
            }
          }

          if (!requirePassword) {
            $scope.performValidateAndSave();
            return;
          }

          var box = bootbox.dialog({
            "message": 'Please enter your superuser password to validate your auth configuration:' +
              '<form style="margin-top: 10px" action="javascript:void(0)">' +
              '<input id="validatePassword" class="form-control" type="password" placeholder="Password">' +
              '</form>',
            "title": 'Enter Password',
            "buttons": {
              "success": {
                "label": "Validate Config",
                "className": "btn-success",
                "callback": function() {
                  $scope.performValidateAndSave($('#validatePassword').val());
                }
              },
              "close": {
                "label": "Cancel",
                "className": "btn-default",
                "callback": function() {
                }
              }
            }
          });

          box.bind('shown.bs.modal', function(){
            box.find("input").focus();
            box.find("form").submit(function() {
              if (!$('#validatePassword').val()) { return; }

              box.modal('hide');
              verifyNow();
            });
          });
        };

        $scope.performValidateAndSave = function(opt_password) {
          $scope.savingConfiguration = false;
          $scope.validating = $scope.getServices($scope.config);

          $('#validateAndSaveModal').modal({
            keyboard: false,
            backdrop: 'static'
          });

          for (var i = 0; i < $scope.validating.length; ++i) {
            var serviceInfo = $scope.validating[i];
            $scope.validateService(serviceInfo, opt_password);
          }
        };

        $scope.saveConfiguration = function() {
          $scope.savingConfiguration = true;

          // Make sure to note that fully verified setup is completed. We use this as a signal
          // in the setup tool.
          $scope.config['SETUP_COMPLETE'] = true;

          var data = {
            'config': $scope.config,
            'hostname': window.location.host
          };

          ApiService.scUpdateConfig(data).then(function(resp) {
            $scope.savingConfiguration = false;
            $scope.mapped.$hasChanges = false;
            $('#validateAndSaveModal').modal('hide');
            $scope.configurationSaved({'config': $scope.config});
          }, ApiService.errorDisplay('Could not save configuration. Please report this error.'));
        };

        var gitlabSelector = function(key) {
          return function(value) {
            if (!value || !$scope.config) { return; }

            if (!$scope.config[key]) {
              $scope.config[key] = {};
            }

            if (value == 'enterprise') {
              if ($scope.config[key]['GITLAB_ENDPOINT'] == 'https://gitlab.com/') {
                $scope.config[key]['GITLAB_ENDPOINT'] = '';
              }
            } else if (value == 'hosted') {
              $scope.config[key]['GITLAB_ENDPOINT'] = 'https://gitlab.com/';
            }
          };
        };

        var githubSelector = function(key) {
          return function(value) {
            if (!value || !$scope.config) { return; }

            if (!$scope.config[key]) {
              $scope.config[key] = {};
            }

            if (value == 'enterprise') {
              if ($scope.config[key]['GITHUB_ENDPOINT'] == 'https://github.com/') {
                $scope.config[key]['GITHUB_ENDPOINT'] = '';
              }
              delete $scope.config[key]['API_ENDPOINT'];
            } else if (value == 'hosted') {
              $scope.config[key]['GITHUB_ENDPOINT'] = 'https://github.com/';
              $scope.config[key]['API_ENDPOINT'] = 'https://api.github.com/';
            }
          };
        };

        var getKey = function(config, path) {
          if (!config) {
            return null;
          }

          var parts = path.split('.');
          var current = config;
          for (var i = 0; i < parts.length; ++i) {
            var part = parts[i];
            if (!current[part]) { return null; }
            current = current[part];
          }
          return current;
        };

        var initializeMappedLogic = function(config) {
          var gle = getKey(config, 'GITHUB_LOGIN_CONFIG.GITHUB_ENDPOINT');
          var gte = getKey(config, 'GITHUB_TRIGGER_CONFIG.GITHUB_ENDPOINT');

          $scope.mapped['GITHUB_LOGIN_KIND'] = gle == 'https://github.com/' ? 'hosted' : 'enterprise';
          $scope.mapped['GITHUB_TRIGGER_KIND'] = gte == 'https://github.com/' ? 'hosted' : 'enterprise';

          var glabe = getKey(config, 'GITLAB_TRIGGER_KIND.GITHUB_ENDPOINT');
          $scope.mapped['GITLAB_TRIGGER_KIND'] = glabe == 'https://gitlab.com/' ? 'hosted' : 'enterprise';

          $scope.mapped['redis'] = {};
          $scope.mapped['redis']['host'] = getKey(config, 'BUILDLOGS_REDIS.host') || getKey(config, 'USER_EVENTS_REDIS.host');
          $scope.mapped['redis']['port'] = getKey(config, 'BUILDLOGS_REDIS.port') || getKey(config, 'USER_EVENTS_REDIS.port');
          $scope.mapped['redis']['password'] = getKey(config, 'BUILDLOGS_REDIS.password') || getKey(config, 'USER_EVENTS_REDIS.password');
        };

        var redisSetter = function(keyname) {
          return function(value) {
            if (value == null || !$scope.config) { return; }

            if (!$scope.config['BUILDLOGS_REDIS']) {
              $scope.config['BUILDLOGS_REDIS'] = {};
            }

            if (!$scope.config['USER_EVENTS_REDIS']) {
              $scope.config['USER_EVENTS_REDIS'] = {};
            }

            if (!value) {
              delete $scope.config['BUILDLOGS_REDIS'][keyname];
              delete $scope.config['USER_EVENTS_REDIS'][keyname];
              return;
            }

            $scope.config['BUILDLOGS_REDIS'][keyname] = value;
            $scope.config['USER_EVENTS_REDIS'][keyname] = value;
          };
        };

        // Add mapped logic.
        $scope.$watch('mapped.GITHUB_LOGIN_KIND', githubSelector('GITHUB_LOGIN_CONFIG'));
        $scope.$watch('mapped.GITHUB_TRIGGER_KIND', githubSelector('GITHUB_TRIGGER_CONFIG'));
        $scope.$watch('mapped.GITLAB_TRIGGER_KIND', gitlabSelector('GITLAB_TRIGGER_KIND'));

        $scope.$watch('mapped.redis.host', redisSetter('host'));
        $scope.$watch('mapped.redis.port', redisSetter('port'));
        $scope.$watch('mapped.redis.password', redisSetter('password'));

        // Add a watch to remove any fields not allowed by the current storage configuration.
        // We have to do this otherwise extra fields (which are not allowed) can end up in the
        // configuration.
        $scope.$watch('config.DISTRIBUTED_STORAGE_CONFIG.local[0]', function(value) {
          // Remove any fields not associated with the current kind.
          if (!value || !$scope.STORAGE_CONFIG_FIELDS[value]
              || !$scope.config.DISTRIBUTED_STORAGE_CONFIG
              || !$scope.config.DISTRIBUTED_STORAGE_CONFIG.local
              || !$scope.config.DISTRIBUTED_STORAGE_CONFIG.local[1]) { return; }

          var allowedFields = $scope.STORAGE_CONFIG_FIELDS[value];
          var configObject = $scope.config.DISTRIBUTED_STORAGE_CONFIG.local[1];

          // Remove any fields not allowed.
          for (var fieldName in configObject) {
            if (!configObject.hasOwnProperty(fieldName)) {
              continue;
            }

            var isValidField = $.grep(allowedFields, function(field) {
              return field.name == fieldName;
            }).length > 0;

            if (!isValidField) {
              delete configObject[fieldName];
            }
          }

          // Set any boolean fields to false.
          for (var i = 0; i < allowedFields.length; ++i) {
            if (allowedFields[i].kind == 'bool') {
              configObject[allowedFields[i].name] = false;
            }
          }
        });

        $scope.$watch('config', function(value) {
          $scope.mapped['$hasChanges'] = true;
        }, true);

        $scope.$watch('isActive', function(value) {
          if (!value) { return; }

          ApiService.scGetConfig().then(function(resp) {
            $scope.config = resp['config'] || {};
            initializeMappedLogic($scope.config);
            $scope.mapped['$hasChanges'] = false;
          }, ApiService.errorDisplay('Could not load config'));
        });
      }
    };

    return directiveDefinitionObject;
  })

  .directive('configParsedField', function ($timeout) {
    var directiveDefinitionObject = {
      priority: 0,
      templateUrl: '/static/directives/config/config-parsed-field.html',
      replace: false,
      transclude: true,
      restrict: 'C',
      scope: {
        'binding': '=binding',
        'parser': '&parser',
        'serializer': '&serializer'
      },
      controller: function($scope, $element, $transclude) {
        $scope.childScope = null;

        $transclude(function(clone, scope) {
          $scope.childScope = scope;
          $scope.childScope['fields'] = {};
          $element.append(clone);
        });

        $scope.childScope.$watch('fields', function(value) {
          // Note: We need the timeout here because Angular starts the digest of the
          // parent scope AFTER the child scope, which means it can end up one action
          // behind. The timeout ensures that the parent scope will be fully digest-ed
          // and then we update the binding. Yes, this is a hack :-/.
          $timeout(function() {
            $scope.binding = $scope.serializer({'fields': value});
          });
        }, true);

        $scope.$watch('binding', function(value) {
          var parsed = $scope.parser({'value': value});
          for (var key in parsed) {
            if (parsed.hasOwnProperty(key)) {
             $scope.childScope['fields'][key] = parsed[key];
            }
          }
        });
      }
    };
    return directiveDefinitionObject;
  })

  .directive('configVariableField', function () {
    var directiveDefinitionObject = {
      priority: 0,
      templateUrl: '/static/directives/config/config-variable-field.html',
      replace: false,
      transclude: true,
      restrict: 'C',
      scope: {
        'binding': '=binding'
      },
      controller: function($scope, $element) {
        $scope.sections = {};
        $scope.currentSection = null;

        $scope.setSection = function(section) {
          $scope.binding = section.value;
        };

        this.addSection = function(section, element) {
          $scope.sections[section.value] = {
            'title': section.valueTitle,
            'value': section.value,
            'element': element
          };

          element.hide();

          if (!$scope.binding) {
            $scope.binding = section.value;
          }
        };

        $scope.$watch('binding', function(binding) {
          if (!binding) { return; }

          if ($scope.currentSection) {
            $scope.currentSection.element.hide();
          }

          if ($scope.sections[binding]) {
            $scope.sections[binding].element.show();
            $scope.currentSection = $scope.sections[binding];
          }
        });
      }
    };
    return directiveDefinitionObject;
  })

  .directive('variableSection', function () {
    var directiveDefinitionObject = {
      priority: 0,
      templateUrl: '/static/directives/config/config-variable-field.html',
      priority: 1,
      require: '^configVariableField',
      replace: false,
      transclude: true,
      restrict: 'C',
      scope: {
        'value': '@value',
        'valueTitle': '@valueTitle'
      },
      controller: function($scope, $element) {
        var parentCtrl = $element.parent().controller('configVariableField');
        parentCtrl.addSection($scope, $element);
      }
    };
    return directiveDefinitionObject;
  })

  .directive('configListField', function () {
    var directiveDefinitionObject = {
      priority: 0,
      templateUrl: '/static/directives/config/config-list-field.html',
      replace: false,
      transclude: false,
      restrict: 'C',
      scope: {
        'binding': '=binding',
        'placeholder': '@placeholder',
        'defaultValue': '@defaultValue',
        'itemTitle': '@itemTitle'
      },
      controller: function($scope, $element) {
        $scope.removeItem = function(item) {
          var index = $scope.binding.indexOf(item);
          if (index >= 0) {
            $scope.binding.splice(index, 1);
          }
        };

        $scope.addItem = function() {
          if (!$scope.newItemName) {
            return;
          }

          if (!$scope.binding) {
            $scope.binding = [];
          }

          if ($scope.binding.indexOf($scope.newItemName) >= 0) {
            return;
          }

          $scope.binding.push($scope.newItemName);
          $scope.newItemName = null;
        };

        $scope.$watch('binding', function(binding) {
          if (!binding && $scope.defaultValue) {
            $scope.binding = eval($scope.defaultValue);
          }
        });
      }
    };
    return directiveDefinitionObject;
  })

  .directive('configFileField', function () {
    var directiveDefinitionObject = {
      priority: 0,
      templateUrl: '/static/directives/config/config-file-field.html',
      replace: false,
      transclude: false,
      restrict: 'C',
      scope: {
        'filename': '@filename',
        'skipCheckFile': '@skipCheckFile',
        'hasFile': '=hasFile'
      },
      controller: function($scope, $element, Restangular, $upload) {
        $scope.hasFile = false;

        $scope.onFileSelect = function(files) {
          if (files.length < 1) {
            $scope.hasFile = false;
            return;
          }

          $scope.uploadProgress = 0;
          $scope.upload = $upload.upload({
            url: '/api/v1/superuser/config/file/' + $scope.filename,
            method: 'POST',
            data: {'_csrf_token': window.__token},
            file: files[0],
          }).progress(function(evt) {
            $scope.uploadProgress = parseInt(100.0 * evt.loaded / evt.total);
            if ($scope.uploadProgress == 100) {
              $scope.uploadProgress = null;
              $scope.hasFile = true;
            }
          }).success(function(data, status, headers, config) {
            $scope.uploadProgress = null;
            $scope.hasFile = true;
          });
        };

        var loadStatus = function(filename) {
          Restangular.one('superuser/config/file/' + filename).get().then(function(resp) {
            $scope.hasFile = resp['exists'];
          });
        };

        if ($scope.filename && $scope.skipCheckFile != "true") {
          loadStatus($scope.filename);
        }
      }
    };
    return directiveDefinitionObject;
  })

  .directive('configBoolField', function () {
    var directiveDefinitionObject = {
      priority: 0,
      templateUrl: '/static/directives/config/config-bool-field.html',
      replace: false,
      transclude: false,
      restrict: 'C',
      scope: {
        'binding': '=binding'
      },
      controller: function($scope, $element) {
      }
    };
    return directiveDefinitionObject;
  })

  .directive('configNumericField', function () {
    var directiveDefinitionObject = {
      priority: 0,
      templateUrl: '/static/directives/config/config-numeric-field.html',
      replace: false,
      transclude: false,
      restrict: 'C',
      scope: {
        'binding': '=binding',
        'placeholder': '@placeholder',
        'defaultValue': '@defaultValue'
      },
      controller: function($scope, $element) {
        $scope.bindinginternal = 0;

        $scope.$watch('binding', function(binding) {
          if ($scope.binding == 0 && $scope.defaultValue) {
            $scope.binding = $scope.defaultValue * 1;
          }

          $scope.bindinginternal = $scope.binding;
        });

        $scope.$watch('bindinginternal', function(binding) {
          var newValue = $scope.bindinginternal * 1;
          if (isNaN(newValue)) {
            newValue = 0;
          }
          $scope.binding = newValue;
        });
      }
    };
    return directiveDefinitionObject;
  })

  .directive('configContactsField', function () {
    var directiveDefinitionObject = {
      priority: 0,
      templateUrl: '/static/directives/config/config-contacts-field.html',
      priority: 1,
      replace: false,
      transclude: false,
      restrict: 'C',
      scope: {
        'binding': '=binding'
      },
      controller: function($scope, $element) {
        var padItems = function(items) {
          // Remove the last item if both it and the second to last items are empty.
          if (items.length > 1 && !items[items.length - 2].value && !items[items.length - 1].value) {
            items.splice(items.length - 1, 1);
            return;
          }

          // If the last item is non-empty, add a new item.
          if (items.length == 0 || items[items.length - 1].value) {
            items.push({'value': ''});
            return;
          }
        };

        $scope.itemHash = null;
        $scope.$watch('items', function(items) {
          if (!items) { return; }
          padItems(items);

          var itemHash = '';
          var binding = [];
          for (var i = 0; i < items.length; ++i) {
            var item = items[i];
            if (item.value && (URI(item.value).host() || URI(item.value).path())) {
              binding.push(item.value);
              itemHash += item.value;
            }
          }

          $scope.itemHash = itemHash;
          $scope.binding = binding;
        }, true);

        $scope.$watch('binding', function(binding) {
          var current = binding || [];
          var items = [];
          var itemHash = '';
          for (var i = 0; i < current.length; ++i) {
            items.push({'value': current[i]})
            itemHash += current[i];
          }

          if ($scope.itemHash != itemHash) {
            $scope.items = items;
          }
        });
      }
    };
    return directiveDefinitionObject;
  })

  .directive('configContactField', function () {
    var directiveDefinitionObject = {
      priority: 0,
      templateUrl: '/static/directives/config/config-contact-field.html',
      priority: 1,
      replace: false,
      transclude: true,
      restrict: 'C',
      scope: {
        'binding': '=binding'
      },
      controller: function($scope, $element) {
        $scope.kind = null;
        $scope.value = null;

        var updateBinding = function() {
          if ($scope.value == null) { return; }
          var value = $scope.value || '';

          switch ($scope.kind) {
            case 'mailto':
              $scope.binding = 'mailto:' + value;
              return;

            case 'tel':
              $scope.binding = 'tel:' + value;
              return;

            case 'irc':
              $scope.binding = 'irc://' + value;
              return;

            default:
              $scope.binding = value;
              return;
          }
        };

        $scope.$watch('kind', updateBinding);
        $scope.$watch('value', updateBinding);

        $scope.$watch('binding', function(value) {
          if (!value) {
            $scope.kind = null;
            $scope.value = null;
            return;
          }

          var uri = URI(value);
          $scope.kind = uri.scheme();

          switch ($scope.kind) {
            case 'mailto':
            case 'tel':
              $scope.value = uri.path();
              break;

            case 'irc':
              $scope.value = value.substr('irc://'.length);
              break;

            default:
              $scope.kind = 'http';
              $scope.value = value;
              break;
          }
        });

        $scope.getPlaceholder = function(kind) {
          switch (kind) {
            case 'mailto':
              return 'some@example.com';

            case 'tel':
              return '555-555-5555';

            case 'irc':
              return 'myserver:port/somechannel';

            default:
              return 'http://some/url';
          }
        };
      }
    };
    return directiveDefinitionObject;
  })

 .directive('configMapField', function () {
    var directiveDefinitionObject = {
      priority: 0,
      templateUrl: '/static/directives/config/config-map-field.html',
      replace: false,
      transclude: false,
      restrict: 'C',
      scope: {
        'binding': '=binding',
        'keys': '=keys'
      },
      controller: function($scope, $element) {
        $scope.newKey = null;
        $scope.newValue = null;

        $scope.hasValues = function(binding) {
          return binding && Object.keys(binding).length;
        };

        $scope.removeKey = function(key) {
          delete $scope.binding[key];
        };

        $scope.addEntry = function() {
          if (!$scope.newKey || !$scope.newValue) { return; }

          $scope.binding = $scope.binding || {};
          $scope.binding[$scope.newKey] = $scope.newValue;
          $scope.newKey = null;
          $scope.newValue = null;
        }
      }
    };
    return directiveDefinitionObject;
  })

  .directive('configStringField', function () {
    var directiveDefinitionObject = {
      priority: 0,
      templateUrl: '/static/directives/config/config-string-field.html',
      replace: false,
      transclude: false,
      restrict: 'C',
      scope: {
        'binding': '=binding',
        'placeholder': '@placeholder',
        'pattern': '@pattern',
        'defaultValue': '@defaultValue',
        'validator': '&validator',
        'isOptional': '=isOptional'
      },
      controller: function($scope, $element) {
        $scope.getRegexp = function(pattern) {
          if (!pattern) {
            pattern = '.*';
          }
          return new RegExp(pattern);
        };

        $scope.$watch('binding', function(binding) {
          if (!binding && $scope.defaultValue) {
            $scope.binding = $scope.defaultValue;
          }

          $scope.errorMessage = $scope.validator({'value': binding || ''});
        });
      }
    };
    return directiveDefinitionObject;
  });