import { Injectable } from '@angular/core';
import { Observable, throwError, of } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { catchError, tap } from 'rxjs/operators';
import { Subject, BehaviorSubject } from 'rxjs';

import { CourseData } from '../structures/course-data';
import { FreeScopeData } from '../structures/free-scope-data';
import { CourseInstructorData } from '../structures/course-instructor-data';
import { QuestionnaireData } from '../structures/questionnaire-data';
import { EvaluationData } from '../structures/evaluation-data';

import * as Globals from '../globals';
import { INFERRED_TYPE } from '@angular/compiler/src/output/output_ast';

@Injectable({
  providedIn: 'root'
})
export class CourseService {

  private courses = new BehaviorSubject<CourseData[] | null>(null);
  courses$ = this.courses.asObservable();

  // stav ukladani informaci o kurzu
  private courseSaveStatus = new BehaviorSubject<number>(Globals.savingStatus.none);
  courseSaveStatus$ = this.courseSaveStatus.asObservable();

  // stav ukladani stavu kurzu
  private reportSaveStatus = new BehaviorSubject<number>(Globals.savingStatus.none);
  reportSaveStatus$ = this.reportSaveStatus.asObservable();

  // stav ukladani instruktoru propojenych s akci
  private courseInstructorsSaveStatus = new BehaviorSubject<number>(Globals.savingStatus.none);
  courseInstructorsSaveStatus$ = this.courseInstructorsSaveStatus.asObservable();

  // stav ukladani dotazniku pred kurzem
  private questionnaireSaveStatus = new BehaviorSubject<number>(Globals.savingStatus.none);
  questionnaireSaveStatus$ = this.questionnaireSaveStatus.asObservable();

  // stav ukladani hodnoceni instruktoru
  private evaluationSaveStatus = new BehaviorSubject<number>(Globals.savingStatus.none);
  evaluationSaveStatus$ = this.evaluationSaveStatus.asObservable();

  // stav ukladani zapisu z kurzu
  private courseStateSaveStatus = new BehaviorSubject<number>(Globals.savingStatus.none);
  courseStateSaveStatus$ = this.courseStateSaveStatus.asObservable();

  constructor(private http: HttpClient) { 
  }

  saveStatusReset(): void {
    if(this.courseSaveStatus.value != Globals.savingStatus.inProgress) {
      this.courseSaveStatus.next(Globals.savingStatus.none);
    }
    if(this.courseInstructorsSaveStatus.value != Globals.savingStatus.inProgress) {
      this.courseInstructorsSaveStatus.next(Globals.savingStatus.none);
    }
    if(this.questionnaireSaveStatus.value != Globals.savingStatus.inProgress) {
      this.questionnaireSaveStatus.next(Globals.savingStatus.none);
    }
    if(this.evaluationSaveStatus.value != Globals.savingStatus.inProgress) {
      this.evaluationSaveStatus.next(Globals.savingStatus.none);
    }
    if(this.reportSaveStatus.value != Globals.savingStatus.inProgress) {
      this.reportSaveStatus.next(Globals.savingStatus.none);
    }
    if(this.courseStateSaveStatus.value != Globals.savingStatus.inProgress) {
      this.courseStateSaveStatus.next(Globals.savingStatus.none);
    }
  }

  addLoadedCourses(newCourses: CourseData[]): void {
    var allCourses = this.courses.value;
    newCourses.forEach(newCourse => {
      if(allCourses != null) {
        var index = allCourses.findIndex(course => { return course.ID == newCourse.ID; } );
        if(index == -1) {
          // kurz s timto ID jeste nebyl nacten, proto jej pridame
          allCourses.push(newCourse);
        } else {
          // kurz s timto ID je jiz nacten, proto jej pouze aktualizujeme
          allCourses[index] = newCourse;
        }
      } else {
        // nebyly jeste nacteny zadne kurzy, vkladame prvni
        allCourses = [newCourse];
      }
    });
    // pokud je uloziste kurzu null a nacetl jsem prazdne pole, nastavim prazdne pole i do uloziste, aby bylo jasne ze se jedna jiz o realnou hodnotu
    if(newCourses.length == 0 && this.courses.value == null) {
      this.courses.next([]);
    } else {
      // serazeni pole
      if(allCourses != null){
        allCourses.sort(function(a,b){ 
          if(a.dateStart != null && b.dateStart != null) {
            return (a.dateStart < b.dateStart ? -1 : 1);
          }
          return 0;
        });
      }
      this.courses.next(allCourses);
    }
  }

  actualizeActualYearCourses(): void {
    this.http.get<CourseData[]>(Globals.api.courseGetActualYearCourses).pipe( catchError(this.handleError)).subscribe(
      //data => this.courses.next(data)
      data => this.addLoadedCourses(data)
    );
  }

  actualizeMyCourses(): void {
    this.http.get<CourseData[]>(Globals.api.courseGetMyCourses).pipe( catchError(this.handleError)).subscribe(
      //data => this.myCourses.next(data)
      data => this.addLoadedCourses(data)
    );
  }

  actualizeSchoolCourses(): void {
    this.http.get<CourseData[]>(Globals.api.courseGetSchoolCourses).pipe( catchError(this.handleError)).subscribe(
      data => this.addLoadedCourses(data)
    );
  }

  actualizeInstructorCourses(id: number): void {
    this.http.get<CourseData[]>(Globals.api.courseGetInstructorCourses + '?personId=' + id).pipe( catchError(this.handleError)).subscribe(
      data => this.addLoadedCourses(data)
    );
  }

  actualizeRequestedCourses(): void {
    this.http.get<CourseData[]>(Globals.api.courseGetRequestedCourses).pipe( catchError(this.handleError)).subscribe(
      data => this.addLoadedCourses(data)
    );
  }

  async getCourseById(id: number): Promise<CourseData | null> {
    // zkousim hledat mezi mymi kurzy
    if(this.courses.value != null) {
      var found = this.courses.value.find(course => course.ID == id)
      if(found != undefined) {
        var courseData = found;
        return new Promise(resolve => { resolve(courseData); });
      }
    }
    // musim kurz ziskat ze serveru
    var courseData = await this.http.get<CourseData>(Globals.api.courseGetById + '?courseId=' + id).pipe( catchError(this.handleError)).toPromise();
    return new Promise(resolve => { resolve(courseData); });
  }

  async saveCourseData(data: CourseData): Promise<CourseData | null> {
    var result = null;
    this.courseSaveStatus.next(Globals.savingStatus.inProgress);
    if(data.ID == 0) {
      // vytvoreni noveho kurzu
      await this.http.post<CourseData>(Globals.api.createCourse, data, Globals.httpOptions).pipe( catchError(this.handleError)).toPromise().then(
        (val) => {
          this.addLoadedCourses([val]),
          this.courseSaveStatus.next(Globals.savingStatus.success),
          result = val
        },
        (err) => this.courseSaveStatus.next(Globals.savingStatus.failed)
      );
    } else {
      // ulozeni zmen
      await this.http.post<CourseData>(Globals.api.updateCourse, data, Globals.httpOptions).pipe( catchError(this.handleError)).toPromise().then(
        (val) => {
          this.addLoadedCourses([val]),
          this.courseSaveStatus.next(Globals.savingStatus.success),
          result = val
        },
        (err) => this.courseSaveStatus.next(Globals.savingStatus.failed)
      );
    }
    return result;
  }

  async changeCourseState(courseId: number, newState: string): Promise<void> {
    this.courseStateSaveStatus.next(Globals.savingStatus.inProgress);
    await this.http.post<CourseData>(Globals.api.changeCourseState + '?courseId=' + courseId, { newState: newState }, Globals.httpOptions).pipe( catchError(this.handleError)).toPromise().then(
      (val) => {
        this.addLoadedCourses([val]),
        this.courseStateSaveStatus.next(Globals.savingStatus.success);
      },
      (err) => this.courseStateSaveStatus.next(Globals.savingStatus.failed)
    );
  }

  async enlistCourseData(data: CourseData): Promise<CourseData | null> {
    var result = null;
    this.courseSaveStatus.next(Globals.savingStatus.inProgress);
    if(data.ID == 0) {
      // vytvoreni noveho kurzu
      await this.http.post<CourseData>(Globals.api.enlistNewCourse, data, Globals.httpOptions).pipe( catchError(this.handleError)).toPromise().then(
        (val) => {
          this.addLoadedCourses([val]),
          this.courseSaveStatus.next(Globals.savingStatus.success),
          result = val
        },
        (err) => this.courseSaveStatus.next(Globals.savingStatus.failed)
      );
    } else {
      // ulozeni zmen
      await this.http.post<CourseData>(Globals.api.enlistModifiedCourse, data, Globals.httpOptions).pipe( catchError(this.handleError)).toPromise().then(
        (val) => {
          this.addLoadedCourses([val]),
          this.courseSaveStatus.next(Globals.savingStatus.success),
          result = val
        },
        (err) => this.courseSaveStatus.next(Globals.savingStatus.failed)
      );
    }
    return result;
  }

  async saveQuestionnaireData(data: QuestionnaireData): Promise<QuestionnaireData | null> {
    var result = null;
    this.questionnaireSaveStatus.next(Globals.savingStatus.inProgress);
    if(data.filledAt == null) {
      // vytvoreni noveho dotazniku
      await this.http.post<CourseData>(Globals.api.createQuestionnaire, data, Globals.httpOptions).pipe( catchError(this.handleError)).toPromise().then(
        (val) => {
          this.addLoadedCourses([val]),
          this.questionnaireSaveStatus.next(Globals.savingStatus.success),
          result = val
        },
        (err) => this.questionnaireSaveStatus.next(Globals.savingStatus.failed)
      );
    } else {
      // ulozeni zmen
      await this.http.post<CourseData>(Globals.api.updateQuestionnaire, data, Globals.httpOptions).pipe( catchError(this.handleError)).toPromise().then(
        (val) => {
          this.addLoadedCourses([val]),
          this.questionnaireSaveStatus.next(Globals.savingStatus.success),
          result = val
        },
        (err) => this.questionnaireSaveStatus.next(Globals.savingStatus.failed)
      );
    }
    return result;
  }

  async updateCourseInstructors(data: CourseInstructorData[], courseId: number, roleId: number): Promise<void> {
    this.courseInstructorsSaveStatus.next(Globals.savingStatus.inProgress);

    await this.http.post<any>(Globals.api.updateCourseInstructors + '?courseId=' + courseId + '&roleId=' + roleId, data, Globals.httpOptions).pipe( catchError(this.handleError)).toPromise().then(
      (val) => {
        this.courseInstructorsSaveStatus.next(Globals.savingStatus.success);
      },
      (err) => this.courseInstructorsSaveStatus.next(Globals.savingStatus.failed)
    );
  }

  async saveInstructorEvaluation(data: EvaluationData): Promise<void> {
    var result = null;
    this.evaluationSaveStatus.next(Globals.savingStatus.inProgress);
    if(data.ID == 0) {
      // vytvoreni noveho kurzu
      await this.http.post<CourseData>(Globals.api.createInstructorEvaluation, data, Globals.httpOptions).pipe( catchError(this.handleError)).toPromise().then(
        (val) => {
          this.addLoadedCourses([val]),
          this.evaluationSaveStatus.next(Globals.savingStatus.success),
          result = val
        },
        (err) => this.evaluationSaveStatus.next(Globals.savingStatus.failed)
      );
    } else {
      // ulozeni zmen
      await this.http.post<CourseData>(Globals.api.updateInstructorEvaluation, data, Globals.httpOptions).pipe( catchError(this.handleError)).toPromise().then(
        (val) => {
          this.addLoadedCourses([val]),
          this.evaluationSaveStatus.next(Globals.savingStatus.success),
          result = val
        },
        (err) => this.evaluationSaveStatus.next(Globals.savingStatus.failed)
      );
    }
  }

  async saveCourseReport(courseId: number, report: string): Promise<void> {
    this.reportSaveStatus.next(Globals.savingStatus.inProgress);
    // vytvoreni noveho kurzu
    await this.http.post<CourseData>(Globals.api.saveCourseReport + '?courseId=' + courseId, { reportText: report }, Globals.httpOptions).pipe( catchError(this.handleError)).toPromise().then(
      (val) => {
        this.addLoadedCourses([val]),
        this.reportSaveStatus.next(Globals.savingStatus.success)
      },
      (err) => this.reportSaveStatus.next(Globals.savingStatus.failed)
    );
  }

  async getFreeScopesFromServer(): Promise<FreeScopeData[]> {
    return this.http.get<FreeScopeData[]>(Globals.api.getFreeScopes).pipe(catchError(this.handleError)).toPromise();
  }

  // pro pouziti pri odhlaseni
  clearAll(): void {
    // this.myCourses.next(null);
    // this.actualYearCourses.next(null);
    this.courses.next(null);
  }

  private handleError(error: HttpErrorResponse) {
    if (error.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      console.error('An error occurred:', error.error.message);
    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong.
      console.error(
        `Backend returned code ${error.status}, ` +
        `body was: ${error.error}`);
    }
    // Return an observable with a user-facing error message.
    return throwError(
      'Something bad happened; please try again later.');
  }
}
