import { Input, Output, Component } from 'angular-ts-decorators';
import * as moment from 'moment';


/**
 * A component that lets the user set up a build trigger for a public Git repository host service.
 */
@Component({
  selector: 'manageTriggerGithost',
  templateUrl: '/static/js/directives/ui/manage-trigger-githost/manage-trigger-githost.component.html'
})
export class ManageTriggerGithostComponent implements ng.IComponentController {

  // FIXME: Use one-way data binding
  @Input('=') public repository: any;
  @Input('=') public trigger: Trigger;
  @Output() public activateTrigger: (trigger: {config: any, pull_robot: any}) => void;
  private config: any;
  private local: any = {
    namespaceOptions: {
      filter: '',
      predicate: 'score',
      reverse: false,
      page: 0
    },
    repositoryOptions: {
      filter: '',
      predicate: 'score',
      reverse: false,
      page: 0,
      hideStale: true
    },
    robotOptions: {
      filter: '',
      predicate: 'score',
      reverse: false,
      page: 0
    }
  };
  private currentState: any | null;
  private namespacesPerPage: number = 10;
  private repositoriesPerPage: number = 10;
  private robotsPerPage: number = 10;
  private namespaceTitle: string;
  private namespace: any;

  constructor(private ApiService: any,
              private TableService: any,
              private TriggerService: any,
              private RolesService: any,
              private $scope: ng.IScope) {
    // FIXME: Here binding methods to class context in order to pass them as arguments to $scope.$watch
    this.buildOrderedNamespaces = this.buildOrderedNamespaces.bind(this);
    this.loadNamespaces = this.loadNamespaces.bind(this);
    this.buildOrderedRepositories = this.buildOrderedRepositories.bind(this);
    this.loadRepositories = this.loadRepositories.bind(this);
    this.loadRepositoryRefs = this.loadRepositoryRefs.bind(this);
    this.buildOrderedRobotAccounts = this.buildOrderedRobotAccounts.bind(this);
    this.loadDockerfileLocations = this.loadDockerfileLocations.bind(this);
    this.checkDockerfilePath = this.checkDockerfilePath.bind(this);
  }

  public $onInit(): void {
    // TODO: Replace $scope.$watch with @Output methods for child component mutations or $onChanges for parent mutations
    this.$scope.$watch(() => this.trigger, this.initialSetup.bind(this));
    this.$scope.$watch(() => this.repository, this.initialSetup.bind(this));

    this.$scope.$watch(() => this.local.selectedNamespace, (namespace) => {
      if (namespace) {
        this.loadRepositories(namespace);
      }
    });

    this.$scope.$watch(() => this.local.selectedRepository, (repository) => {
      if (repository) {
        this.loadRepositoryRefs(repository);
        this.loadDockerfileLocations(repository);
      }
    });

    this.$scope.$watch(() => this.local.dockerfilePath, (path) => {
      if (path && this.local.selectedRepository) {
        this.setPossibleContexts(path);
        this.checkDockerfilePath(this.local.selectedRepository, path, this.local.dockerContext);
      }
    });

    this.$scope.$watch(() => this.local.dockerContext, (context) => {
      if (context && this.local.selectedRepository) {
        this.checkDockerfilePath(this.local.selectedRepository, this.local.dockerfilePath, context);
      }
    });

    this.$scope.$watch(() => this.local.namespaceOptions.predicate, this.buildOrderedNamespaces);
    this.$scope.$watch(() => this.local.namespaceOptions.reverse, this.buildOrderedNamespaces);
    this.$scope.$watch(() => this.local.namespaceOptions.filter, this.buildOrderedNamespaces);
    this.$scope.$watch(() => this.local.repositoryOptions.predicate, this.buildOrderedRepositories);
    this.$scope.$watch(() => this.local.repositoryOptions.reverse, this.buildOrderedRepositories);
    this.$scope.$watch(() => this.local.repositoryOptions.filter, this.buildOrderedRepositories);
    this.$scope.$watch(() => this.local.repositoryOptions.hideStale, this.buildOrderedRepositories);
    this.$scope.$watch(() => this.local.robotOptions.predicate, this.buildOrderedRobotAccounts);
    this.$scope.$watch(() => this.local.robotOptions.reverse, this.buildOrderedRobotAccounts);
    this.$scope.$watch(() => this.local.robotOptions.filter, this.buildOrderedRobotAccounts);
  }

  private initialSetup(): void {
    if (!this.repository || !this.trigger) { return; }

    if (this.namespaceTitle) {
      // Already setup.
      return;
    }

    this.config = this.trigger['config'] || {};
    this.namespaceTitle = 'organization';
    this.local.selectedNamespace = null;
    this.loadNamespaces();
  }

  public getTriggerIcon(): any {
    return this.TriggerService.getIcon(this.trigger.service);
  }

  public createTrigger(): void {
    var config: any = {
      build_source: this.local.selectedRepository.full_name,
      dockerfile_path: this.local.dockerfilePath,
      context: this.local.dockerContext
    };

    if (this.local.triggerOptions.hasBranchTagFilter &&
        this.local.triggerOptions.branchTagFilter) {
      config['branchtag_regex'] = this.local.triggerOptions.branchTagFilter;
    }

    var activate = () => {
      this.activateTrigger({'config': config, 'pull_robot': this.local.robotAccount});
    };

    if (this.local.robotAccount && this.local.triggerAnalysis.status == 'requiresrobot') {
      if (this.local.robotAccount.can_read) {
        activate();
      } else {
        // Add read permission onto the base repository for the robot and then activate the
        // trigger.
        var robot_name = this.local.robotAccount.name;
        this.RolesService.setRepositoryRole(this.repository, 'read', 'robot', robot_name, activate);
      }
    } else {
      activate();
    }
  }

  private buildOrderedNamespaces(): void {
    if (!this.local.namespaces) {
      return;
    }

    var namespaces = this.local.namespaces || [];
    this.local.orderedNamespaces = this.TableService.buildOrderedItems(namespaces,
      this.local.namespaceOptions,
      ['id'],
      ['score']);

    this.local.maxScore = 0;
    namespaces.forEach((namespace) => {
      this.local.maxScore = Math.max(namespace.score, this.local.maxScore);
    });
  }

  private loadNamespaces(): void {
    this.local.namespaces = null;
    this.local.selectedNamespace = null;
    this.local.orderedNamespaces = null;

    this.local.selectedRepository = null;
    this.local.orderedRepositories = null;

    var params = {
      'repository': this.repository.namespace + '/' + this.repository.name,
      'trigger_uuid': this.trigger.id
    };

    this.ApiService.listTriggerBuildSourceNamespaces(null, params)
      .then((resp) => {
        this.local.namespaces = resp['namespaces'];
        this.local.repositories = null;
        this.buildOrderedNamespaces();
    }, this.ApiService.errorDisplay('Could not retrieve the list of ' + this.namespaceTitle));
  }

  private buildOrderedRepositories(): void {
    if (!this.local.repositories) {
      return;
    }

    var repositories = this.local.repositories || [];
    repositories.forEach((repository) => {
      repository['last_updated_datetime'] = new Date(repository['last_updated'] * 1000);
    });

    if (this.local.repositoryOptions.hideStale) {
      var existingRepositories = repositories;

      repositories = repositories.filter((repository) => {
        var older_date = moment(repository['last_updated_datetime']).add(1, 'months');
        return !moment().isAfter(older_date);
      });

      if (existingRepositories.length > 0 && repositories.length == 0) {
        repositories = existingRepositories;
      }
    }

    this.local.orderedRepositories = this.TableService.buildOrderedItems(repositories,
      this.local.repositoryOptions,
      ['name', 'description'],
      []);
  }

  private loadRepositories(namespace: any): void {
    this.local.repositories = null;
    this.local.selectedRepository = null;
    this.local.repositoryRefs = null;
    this.local.triggerOptions = {
      'hasBranchTagFilter': false
    };

    this.local.orderedRepositories = null;

    var params = {
      'repository': this.repository.namespace + '/' + this.repository.name,
      'trigger_uuid': this.trigger.id
    };

    var data = {
      'namespace': namespace.id
    };

    this.ApiService.listTriggerBuildSources(data, params).then((resp) => {
      if (namespace == this.local.selectedNamespace) {
        this.local.repositories = resp['sources'];
        this.buildOrderedRepositories();
      }
    }, this.ApiService.errorDisplay('Could not retrieve repositories'));
  }

  private loadRepositoryRefs(repository: any): void {
    this.local.repositoryRefs = null;
    this.local.triggerOptions = {
      'hasBranchTagFilter': false
    };

    var params = {
      'repository': this.repository.namespace + '/' + this.repository.name,
      'trigger_uuid': this.trigger.id,
      'field_name': 'refs'
    };

    var config = {
      'build_source': repository.full_name
    };

    this.ApiService.listTriggerFieldValues(config, params).then((resp) => {
      if (repository == this.local.selectedRepository) {
        this.local.repositoryRefs = resp['values'];
        this.local.repositoryFullRefs = resp['values'].map((ref) => {
          var kind = ref.kind == 'branch' ? 'heads' : 'tags';
          var icon = ref.kind == 'branch' ? 'fa-code-fork' : 'fa-tag';
          return {
            'value': kind + '/' + ref.name,
            'icon': icon,
            'title': ref.name
          };
        });
      }
    }, this.ApiService.errorDisplay('Could not retrieve repository refs'));
  }

  private loadDockerfileLocations(repository: any): void {
    this.local.dockerfilePath = null;
    this.local.dockerContext = null;

    var params = {
      'repository': this.repository.namespace + '/' + this.repository.name,
      'trigger_uuid': this.trigger.id
    };

    var config = {
      'build_source': repository.full_name
    };

    this.ApiService.listBuildTriggerSubdirs(config, params)
      .then((resp) => {
        if (repository == this.local.selectedRepository) {
          this.local.dockerfileLocations = resp;
        }
      }, this.ApiService.errorDisplay('Could not retrieve Dockerfile locations'));
  }

  private buildOrderedRobotAccounts(): void {
    if (!this.local.triggerAnalysis || !this.local.triggerAnalysis.robots) {
      return;
    }

    var robots = this.local.triggerAnalysis.robots;
    this.local.orderedRobotAccounts = this.TableService.buildOrderedItems(robots,
      this.local.robotOptions,
      ['name'],
      []);
  }

  private checkDockerfilePath(repository: any, path: string, context: string): void {
    this.local.triggerAnalysis = null;
    this.local.robotAccount = null;

    var params = {
      'repository': this.repository.namespace + '/' + this.repository.name,
      'trigger_uuid': this.trigger.id
    };

    var config = {
      'build_source': repository.full_name,
      'dockerfile_path': path.substr(1),
      'context': context
    };

    var data = {
      'config': config
    };

    this.ApiService.analyzeBuildTrigger(data, params)
      .then((resp) => {
        this.local.triggerAnalysis = resp;
        this.buildOrderedRobotAccounts();
      }, this.ApiService.errorDisplay('Could not analyze trigger'));
  }

  private setPossibleContexts(path){
    if(this.local.dockerfileLocations.contextMap){
      this.local.contexts = this.local.dockerfileLocations.contextMap[path] || [];
    }
  }
}


/**
 * A type representing local application data.
 */
export type Local = {
  namespaceOptions: {
    filter: string;
    predicate: string;
    reverse: boolean;
    page: number;
  };
  repositoryOptions: {
    filter: string;
    predicate: string;
    reverse: boolean;
    page: number;
    hideStale: boolean;
  };
  robotOptions: {
    filter: string;
    predicate: string;
    reverse: boolean;
    page: number;
  };
};


/**
 * A type representing a trigger.
 */
export type Trigger = {
  id: number;
  service: any;
};