import { Injectable } from '@angular/core';

import { Observable, Subject } from 'rxjs';
import { map, take, takeWhile, share, tap, filter } from 'rxjs/operators';

import { ProtocolService } from '@ucap-webmessenger/protocol';
import {
  SVC_TYPE_ROOM,
  SSVC_TYPE_ROOM_OPEN_REQ,
  SSVC_TYPE_ROOM_OPEN2_REQ,
  SSVC_TYPE_ROOM_OPEN3_REQ,
  SSVC_TYPE_ROOM_OPEN4_REQ,
  SSVC_TYPE_ROOM_INVITE_REQ,
  SSVC_TYPE_ROOM_INFO_ROOM,
  SSVC_TYPE_ROOM_INFO_RES,
  SSVC_TYPE_ROOM_INFO_REQ,
  SSVC_TYPE_ROOM_INFO_USER,
  SSVC_TYPE_ROOM_INFO_USER2,
  SSVC_TYPE_ROOM_EXIT_REQ,
  SSVC_TYPE_ROOM_EXIT_ALL_REQ,
  SSVC_TYPE_ROOM_EXIT_FORCING_REQ,
  SSVC_TYPE_ROOM_USER_STATUS_REQ,
  SSVC_TYPE_ROOM_UPD_REQ,
  SSVC_TYPE_ROOM_INFO_UPD_REQ,
  SSVC_TYPE_ROOM_FONT_UPD_REQ,
  SSVC_TYPE_ROOM_INVITE_NOTI,
  SSVC_TYPE_ROOM_EXIT_NOTI,
  SSVC_TYPE_ROOM_EXIT_FORCING_NOTI,
  SSVC_TYPE_ROOM_FONT_UPD_NOTI,
  SSVC_TYPE_ROOM_UPD_RES,
  SSVC_TYPE_ROOM_EXIT_FORCING_RES,
  SSVC_TYPE_ROOM_EXIT_RES,
  SSVC_TYPE_ROOM_INVITE_RES
} from '../types/service';
import {
  OpenRequest,
  encodeOpen,
  decodeOpen,
  OpenResponse,
  Open2Request,
  Open2Response,
  decodeOpen2,
  encodeOpen2,
  Open3Request,
  Open3Response,
  encodeOpen3,
  decodeOpen3,
  Open4Request,
  Open4Response,
  encodeOpen4,
  decodeOpen4
} from '../protocols/open';
import {
  InviteRequest,
  InviteResponse,
  encodeInvite,
  decodeInvite,
  decodeInviteNotification,
  InviteNotification
} from '../protocols/invite';
import {
  InfoRequest,
  InfoResponse,
  InfoData,
  UserShortData,
  UserData,
  encodeInfo,
  decodeInfoData,
  decodeInfo,
  decodeUserShortData,
  decodeUserData,
  UserStatusOfflineRequest,
  UserStatusOfflineResponse,
  encodeUserStatusOffline,
  decodeUserStatusOffline
} from '../protocols/info';
import {
  ExitRequest,
  ExitResponse,
  encodeExit,
  decodeExit,
  ExitAllRequest,
  encodeAllExit,
  ExitForcingRequest,
  ExitForcingResponse,
  encodeExitForcing,
  decodeExitForcing,
  decodeAllExit,
  ExitAllResponse,
  decodeExitForcingNotification,
  decodeExitNotification,
  ExitNotification,
  ExitForcingNotification
} from '../protocols/exit';
import {
  UpdateRequest,
  UpdateResponse,
  encodeUpdate,
  decodeUpdate,
  UpdateTimerSetRequest,
  UpdateTimerSetResponse,
  encodeUpdateTimerSet,
  decodeUpdateTimerSet,
  UpdateFontRequest,
  UpdateFontResponse,
  encodeUpdateFont,
  decodeUpdateFont,
  decodeUpdateFontNotification,
  UpdateFontNotification,
  decodeUpdateNotification,
  UpdateNotification
} from '../protocols/update';

type Notifications =
  | UpdateFontNotification
  | UpdateNotification
  | InviteNotification
  | ExitNotification
  | ExitForcingNotification;

@Injectable({
  providedIn: 'root'
})
export class RoomProtocolService {
  private notificationSubject: Subject<Notifications>;
  public notification$: Observable<Notifications>;

  constructor(private protocolService: ProtocolService) {
    this.notificationSubject = new Subject();
    this.notification$ = this.notificationSubject.asObservable().pipe(share());

    this.protocolService.serverMessage
      .pipe(
        filter(message => message.serviceType === SVC_TYPE_ROOM),
        tap(message => {
          switch (message.subServiceType) {
            case SSVC_TYPE_ROOM_INVITE_RES:
              {
                this.notificationSubject.next(
                  decodeInviteNotification(message)
                );
              }
              break;
            case SSVC_TYPE_ROOM_UPD_RES:
              {
                this.notificationSubject.next(
                  decodeUpdateNotification(message)
                );
              }
              break;
            case SSVC_TYPE_ROOM_INVITE_NOTI:
              {
                this.notificationSubject.next(
                  decodeInviteNotification(message)
                );
              }
              break;
            case SSVC_TYPE_ROOM_EXIT_RES:
            case SSVC_TYPE_ROOM_EXIT_NOTI:
              {
                this.notificationSubject.next(decodeExitNotification(message));
              }
              break;
            case SSVC_TYPE_ROOM_EXIT_FORCING_RES:
              {
                this.notificationSubject.next(
                  decodeExitForcingNotification(message)
                );
              }
              break;
            case SSVC_TYPE_ROOM_EXIT_FORCING_NOTI:
              {
                this.notificationSubject.next(
                  decodeExitForcingNotification(message)
                );
              }
              break;
            case SSVC_TYPE_ROOM_FONT_UPD_NOTI:
              {
                this.notificationSubject.next(
                  decodeUpdateFontNotification(message)
                );
              }
              break;

            default:
              break;
          }
        })
      )
      .subscribe();
  }

  public open(req: OpenRequest): Observable<OpenResponse> {
    return this.protocolService
      .call(SVC_TYPE_ROOM, SSVC_TYPE_ROOM_OPEN_REQ, ...encodeOpen(req))
      .pipe(
        take(1),
        map(res => decodeOpen(res))
      );
  }
  public open2(req: Open2Request): Observable<Open2Response> {
    return this.protocolService
      .call(SVC_TYPE_ROOM, SSVC_TYPE_ROOM_OPEN2_REQ, ...encodeOpen2(req))
      .pipe(
        take(1),
        map(res => decodeOpen2(res))
      );
  }
  public open3(req: Open3Request): Observable<Open3Response> {
    return this.protocolService
      .call(SVC_TYPE_ROOM, SSVC_TYPE_ROOM_OPEN3_REQ, ...encodeOpen3(req))
      .pipe(
        take(1),
        map(res => decodeOpen3(res))
      );
  }
  public open4(req: Open4Request): Observable<Open4Response> {
    return this.protocolService
      .call(SVC_TYPE_ROOM, SSVC_TYPE_ROOM_OPEN4_REQ, ...encodeOpen4(req))
      .pipe(
        take(1),
        map(res => decodeOpen4(res))
      );
  }

  public invite(req: InviteRequest): Observable<InviteResponse> {
    return this.protocolService
      .call(SVC_TYPE_ROOM, SSVC_TYPE_ROOM_INVITE_REQ, ...encodeInvite(req))
      .pipe(
        take(1),
        map(res => decodeInvite(res))
      );
  }

  public info(
    req: InfoRequest
  ): Observable<InfoResponse | InfoData | UserShortData | UserData> {
    return this.protocolService
      .call(SVC_TYPE_ROOM, SSVC_TYPE_ROOM_INFO_REQ, ...encodeInfo(req))
      .pipe(
        takeWhile(res => SSVC_TYPE_ROOM_INFO_RES !== res.subServiceType, true),
        map(res => {
          if (SSVC_TYPE_ROOM_INFO_ROOM === res.subServiceType) {
            return decodeInfoData(res);
          } else if (SSVC_TYPE_ROOM_INFO_USER === res.subServiceType) {
            return decodeUserShortData(res);
          } else if (SSVC_TYPE_ROOM_INFO_USER2 === res.subServiceType) {
            return decodeUserData(res);
          }

          return decodeInfo(res);
        })
      );
  }
  public userStatusOffline(
    req: UserStatusOfflineRequest
  ): Observable<UserStatusOfflineResponse> {
    return this.protocolService
      .call(
        SVC_TYPE_ROOM,
        SSVC_TYPE_ROOM_USER_STATUS_REQ,
        ...encodeUserStatusOffline(req)
      )
      .pipe(
        take(1),
        map(res => {
          return decodeUserStatusOffline(res);
        })
      );
  }

  public exit(req: ExitRequest): Observable<ExitResponse> {
    return this.protocolService
      .call(SVC_TYPE_ROOM, SSVC_TYPE_ROOM_EXIT_REQ, ...encodeExit(req))
      .pipe(
        take(1),
        map(res => {
          return decodeExit(res);
        })
      );
  }
  public exitAll(req: ExitAllRequest): Observable<ExitAllResponse> {
    return this.protocolService
      .call(SVC_TYPE_ROOM, SSVC_TYPE_ROOM_EXIT_ALL_REQ, ...encodeAllExit(req))
      .pipe(
        take(1),
        map(res => {
          return decodeAllExit(res);
        })
      );
  }
  public exitForcing(req: ExitForcingRequest): Observable<ExitForcingResponse> {
    return this.protocolService
      .call(
        SVC_TYPE_ROOM,
        SSVC_TYPE_ROOM_EXIT_FORCING_REQ,
        ...encodeExitForcing(req)
      )
      .pipe(
        take(1),
        map(res => {
          return decodeExitForcing(res);
        })
      );
  }

  public update(req: UpdateRequest): Observable<UpdateResponse> {
    return this.protocolService
      .call(SVC_TYPE_ROOM, SSVC_TYPE_ROOM_UPD_REQ, ...encodeUpdate(req))
      .pipe(
        take(1),
        map(res => {
          return decodeUpdate(res);
        })
      );
  }

  public updateTimerSet(
    req: UpdateTimerSetRequest
  ): Observable<UpdateTimerSetResponse> {
    return this.protocolService
      .call(
        SVC_TYPE_ROOM,
        SSVC_TYPE_ROOM_INFO_UPD_REQ,
        ...encodeUpdateTimerSet(req)
      )
      .pipe(
        take(1),
        map(res => {
          return decodeUpdateTimerSet(res);
        })
      );
  }

  public updateFont(req: UpdateFontRequest): Observable<UpdateFontResponse> {
    return this.protocolService
      .call(
        SVC_TYPE_ROOM,
        SSVC_TYPE_ROOM_FONT_UPD_REQ,
        ...encodeUpdateFont(req)
      )
      .pipe(
        take(1),
        map(res => {
          return decodeUpdateFont(res);
        })
      );
  }
}
