async () => {
        try {
            lastMessage = await getLastMessage();
        } catch (err) {
            return false;
        }
        // stop polling when found the expected message
        return lastMessage &&
            lastMessage.body === expectedMessage.body &&
            lastMessage.sender === expectedMessage.sender;
    }


(eventTile) => {
        return getMessageFromEventTile(eventTile);
    }


(m) => !!m


(prevSender: string, m) => {
        if (m.continuation) {
            m.sender = prevSender;
            return prevSender;
        } else {
            return m.sender;
        }
    }


(expectedMessage) => {
        const foundMessage = timelineMessages.find((message) => {
            return message.sender === expectedMessage.sender &&
                message.body === expectedMessage.body;
        });
        try {
            assertMessage(foundMessage, expectedMessage);
        } catch (err) {
            console.log("timelineMessages", timelineMessages);
            throw err;
        }
    }


(message) => {
            return message.sender === expectedMessage.sender &&
                message.body === expectedMessage.body;
        }


interface Message {
    sender: string;
    encrypted?: boolean;
    body: string;
    continuation?: boolean;
}


() => this.disconnected()


(s,d) => s += (d & 0x0F).toString(16) + ((d >> 4) & 0x0F).toString(16)


export class BaseChip {
  protected chipData: ChipObject
  protected chipId: Uint8Array
  protected owDevice: OWDevice
  private chipType: ChipType
  private disposed: boolean = false
  private onDisconnectListener: Disposable | null = null
  private onDisconnectEvent = new TypedEvent<null>()
  protected _onDataEvent = new TypedEvent<ChipObject>()

  constructor (chipId: Uint8Array, owDevice: OWDevice, onDisconnect: (listener: Listener<null>) => Disposable) {
    this.chipId = chipId
    this.owDevice = owDevice
    this.chipType = getChipType(this.chipId[0])
    this.chipData = new ChipObject(this.chipType)
    this.onDisconnectListener = onDisconnect(() => this.disconnected())

    Logger.info(`${getChipLabel(this.type)} connected: ${this.id}`)
  }

  get data () {
    return this.chipData
  }

  get diposed () {
    return this.disposed
  }

  onData (listener: Listener<ChipObject>) {
    return this._onDataEvent.on(listener)
  }

  protected setChipData (data: ChipObject) {
    this.chipData = data
    this._onDataEvent.emit(data)
  }

  onDisconnect (listener: Listener<null>) {
    return this.onDisconnectEvent.on(listener)
  }

  protected disconnected () {
    this.onDisconnectEvent.emit(null)
    this.dispose()
  }

  protected dispose () {
    if (this.onDisconnectListener) {
      this.onDisconnectListener.dispose()
      this.onDisconnectListener = null
    }
    this.disposed = true

    Logger.info(`${getChipLabel(this.type)} disconnected: ${this.id}`)
  }

  get id () {
    return this.chipId.reduce((s,d) => s += (d & 0x0F).toString(16) + ((d >> 4) & 0x0F).toString(16), '').split('').reverse().join('')
  }

  get type () {
    return this.chipType
  }

  destroy () {
    this.disconnected()
  }
}


onData (listener: Listener<ChipObject>) {
    return this._onDataEvent.on(listener)
  }


protected setChipData (data: ChipObject) {
    this.chipData = data
    this._onDataEvent.emit(data)
  }


onDisconnect (listener: Listener<null>) {
    return this.onDisconnectEvent.on(listener)
  }


protected disconnected () {
    this.onDisconnectEvent.emit(null)
    this.dispose()
  }


protected dispose () {
    if (this.onDisconnectListener) {
      this.onDisconnectListener.dispose()
      this.onDisconnectListener = null
    }
    this.disposed = true

    Logger.info(`${getChipLabel(this.type)} disconnected: ${this.id}`)
  }


destroy () {
    this.disconnected()
  }


export class DateLocalizer {
    formats: Formats;
    propType: Validator<any>;
    startOfWeek: (culture: Culture) => number;

    constructor(spec: DateLocalizerSpec);

    format(value: FormatInput, format: string, culture: Culture): string;
}


export default class BigCalendar<T extends Event = Event> extends React.Component<BigCalendarProps<T>> {
    components: {
        dateCellWrapper: React.ComponentType,
        dayWrapper: React.ComponentType,
        eventWrapper: React.ComponentType,
    };
    /**
     * create DateLocalizer from globalize
     */
    static globalizeLocalizer(globalizeInstance: object): DateLocalizer;
    /**
     * create DateLocalizer from a moment
     */
    static momentLocalizer(momentInstance: object): DateLocalizer;
    /**
     * action constants for Navigate
     */
    static Navigate: {
        PREVIOUS: 'PREV',
        NEXT: 'NEXT',
        TODAY: 'TODAY',
        DATE: 'DATE',
    };
    /**
     * action constants for View
     */
    static Views: Record<ViewKey, View>;
    static move(View: ViewStatic | ViewKey, options: MoveOptions): Date;
}


export interface DateRange {
    start: Date;
    end: Date;
}


export interface Formats {
    /**
     * Format for the day of the month heading in the Month view.
     * e.g. "01", "02", "03", etc
     */
    dateFormat?: DateFormat;

    /**
     * A day of the week format for Week and Day headings,
     * e.g. "Wed 01/04"
     *
     */
    dayFormat?: DateFormat;

    /**
     * Week day name format for the Month week day headings,
     * e.g: "Sun", "Mon", "Tue", etc
     *
     */
    weekdayFormat?: DateFormat;

    /**
     * The timestamp cell formats in Week and Time views, e.g. "4:00 AM"
     */
    timeGutterFormat?: DateFormat;

    /**
     * Toolbar header format for the Month view, e.g "2015 April"
     *
     */
    monthHeaderFormat?: DateFormat;

    /**
     * Toolbar header format for the Week views, e.g. "Mar 29 - Apr 04"
     */
    dayRangeHeaderFormat?: DateRangeFormatFunction;

    /**
     * Toolbar header format for the Day view, e.g. "Wednesday Apr 01"
     */
    dayHeaderFormat?: DateFormat;

    /**
     * Toolbar header format for the Agenda view, e.g. "4/1/2015 — 5/1/2015"
     */
    agendaHeaderFormat?: DateRangeFormatFunction;

    /**
     * A time range format for selecting time slots, e.g "8:00am — 2:00pm"
     */
    selectRangeFormat?: DateRangeFormatFunction;

    agendaDateFormat?: DateFormat;
    agendaTimeFormat?: DateFormat;
    agendaTimeRangeFormat?: DateRangeFormatFunction;

    /**
     * Time range displayed on events.
     */
    eventTimeRangeFormat?: DateRangeFormatFunction;

    /**
     * An optional event time range for events that continue onto another day
     */
    eventTimeRangeStartFormat?: DateRangeFormatFunction;

    /**
     * An optional event time range for events that continue from another day
     */
    eventTimeRangeEndFormat?: DateRangeFormatFunction;
}


export interface HeaderProps {
    date: Date;
    label: string;
    localizer: DateLocalizer;
}


export interface Components {
    event?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
    eventWrapper?: React.ComponentType<EventWrapperProps>;
    dayWrapper?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
    dateCellWrapper?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
    /**
     * component used as a header for each column in the TimeGridHeader
     */
    header?: React.ComponentType<HeaderProps>;
    toolbar?: React.ComponentType<ToolbarProps>;
    agenda?: {
        date?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
        time?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
        event?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
    };
    day?: {
        header?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
        event?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
    };
    week?: {
        header?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
        event?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
    };
    month?: {
        header?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
        dateHeader?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
        event?: React.SFC | React.Component | React.ComponentClass | JSX.Element;
    };
}


export interface ToolbarProps {
    date: Date;
    view: View;
    views: Views;
    label: string;
    localizer: { messages: Messages };
    onNavigate: (navigate: Navigate, date?: Date) => void;
    onView: (view: View) => void;
    children?: React.ReactNode;
}


export interface EventWrapperProps<T extends Event = Event> {
    // https://github.com/intljusticemission/react-big-calendar/blob/27a2656b40ac8729634d24376dff8ea781a66d50/src/TimeGridEvent.js#L28
    style?: React.CSSProperties & { xOffset: number };
    className: string;
    event: T;
    isRtl: boolean;
    getters: {
        eventProp?: EventPropGetter<T>;
        slotProp?: SlotPropGetter;
        dayProp?: DayPropGetter;
    };
    onClick: (e: React.MouseEvent<HTMLElement>) => void;
    onDoubleClick: (e: React.MouseEvent<HTMLElement>) => void;
    accessors: {
        title?: (event: T) => string;
        tooltip?: (event: T) => string;
        end?: (event: T) => Date;
        start?: (event: T) => Date;
    };
    selected: boolean;
    label: string;
    continuesEarlier: boolean;
    continuesLater: boolean;
}


export interface Messages {
    date?: string;
    time?: string;
    event?: string;
    allDay?: string;
    week?: string;
    work_week?: string;
    day?: string;
    month?: string;
    previous?: string;
    next?: string;
    yesterday?: string;
    tomorrow?: string;
    today?: string;
    agenda?: string;
    showMore?: (count: number) => string;
}


export interface DateLocalizerSpec {
    firstOfWeek: (culture: Culture) => number;
    format: (value: FormatInput, format: string, culture: Culture) => string;
    formats: Formats;
    propType?: Validator<any>;
}


export interface BigCalendarProps<T extends Event = Event> extends React.Props<BigCalendar<T>> {
    localizer: DateLocalizer;

    date?: stringOrDate;
    now?: Date;
    view?: View;
    events?: T[];
    onNavigate?: (newDate: Date, action: Navigate) => void;
    onView?: (view: View) => void;
    onDrillDown?: (date: Date, view: View) => void;
    onSelectSlot?: (slotInfo: { start: stringOrDate, end: stringOrDate, slots: Date[] | string[], action: 'select' | 'click' | 'doubleClick' }) => void;
    onDoubleClickEvent?: (event: T, e: React.SyntheticEvent<HTMLElement>) => void;
    onSelectEvent?: (event: T, e: React.SyntheticEvent<HTMLElement>) => void;
    onSelecting?: (range: { start: stringOrDate, end: stringOrDate }) => boolean | undefined | null;
    selected?: any;
    views?: Views;
    drilldownView?: View | null;
    getDrilldownView?: ((targetDate: Date, currentViewName: View, configuredViewNames: View[]) => void) | null;
    length?: number;
    toolbar?: boolean;
    popup?: boolean;
    popupOffset?: number | { x: number, y: number };
    selectable?: boolean | 'ignoreEvents';
    longPressThreshold?: number;
    step?: number;
    timeslots?: number;
    rtl?: boolean;
    eventPropGetter?: EventPropGetter<T>;
    slotPropGetter?: SlotPropGetter;
    dayPropGetter?: DayPropGetter;
    showMultiDayTimes?: boolean;
    min?: stringOrDate;
    max?: stringOrDate;
    scrollToTime?: Date;
    culture?: string;
    formats?: Formats;
    components?: Components;
    messages?: Messages;
    titleAccessor?: keyof T | ((event: T) => string);
    allDayAccessor?: keyof T | ((event: T) => boolean);
    startAccessor?: keyof T | ((event: T) => Date);
    endAccessor?: keyof T | ((event: T) => Date);
    resourceAccessor?: keyof T | ((event: T) => any);
    resources?: any[];
    resourceIdAccessor?: keyof T | ((event: T) => any);
    resourceTitleAccessor?: keyof T | ((event: T) => string);
    defaultView?: View;
    defaultDate?: Date;
    className?: string;
    elementProps?: React.HTMLAttributes<HTMLElement>;
}


export interface ViewStatic {
    navigate(date: Date, action: Navigate, props: any): Date;
}


export interface MoveOptions {
    action: Navigate;
    date: Date;
    today: Date;
}


export type DayPropGetter = (date: Date) => { className?: string, style?: React.CSSProperties };


export type EventPropGetter<T> = (event: T, start: stringOrDate, end: stringOrDate, isSelected: boolean) => { className?: string, style?: React.CSSProperties };


export type SlotPropGetter = (date: Date) => { className?: string, style?: React.CSSProperties };


export type stringOrDate = string | Date;


export type ViewKey = 'MONTH' | 'WEEK' | 'WORK_WEEK' | 'DAY' | 'AGENDA';


export type View = 'month' | 'week' | 'work_week' | 'day' | 'agenda';


export type Views = View[] | {
    month: boolean | React.SFC | React.Component,
    week: boolean | React.SFC | React.Component,
    myweek: boolean | React.SFC | React.Component,
};


export type Navigate = 'PREV' | 'NEXT' | 'TODAY' | 'DATE';


export type Event = object;


export type DateFormatFunction = (date: Date, culture?: string, localizer?: object) => string;


export type DateRangeFormatFunction = (range: DateRange, culture?: string, localizer?: object) => string;


export type DateFormat = string | DateFormatFunction;


export type Culture = string | string[];


export type FormatInput = number | string | Date;


format(value: FormatInput, format: string, culture: Culture): string;


/**
     * create DateLocalizer from globalize
     */
static globalizeLocalizer(globalizeInstance: object): DateLocalizer;


/**
     * create DateLocalizer from a moment
     */
static momentLocalizer(momentInstance: object): DateLocalizer;


static move(View: ViewStatic | ViewKey, options: MoveOptions): Date;


res => {
         if(res){
           this.setUsrName()
         }
       }


data => {

     this.turnos = data.map(e => {
       return {
         id: e.payload.doc.id,
         DiaTurno: e.payload.doc.data()['fechaej'],
         apellidoPaciente: e.payload.doc.data()['apellidoPaciente'],
         horaTurno: e.payload.doc.data()['horaTurno'],
         nombrePaciente: e.payload.doc.data()['nombrePaciente'],
         profesional: e.payload.doc.data()['profesional'],
         email: e.payload.doc.data()['email'],

       };
     })

   }


e => {
       return {
         id: e.payload.doc.id,
         DiaTurno: e.payload.doc.data()['fechaej'],
         apellidoPaciente: e.payload.doc.data()['apellidoPaciente'],
         horaTurno: e.payload.doc.data()['horaTurno'],
         nombrePaciente: e.payload.doc.data()['nombrePaciente'],
         profesional: e.payload.doc.data()['profesional'],
         email: e.payload.doc.data()['email'],

       };
     }


data => {

     this.profesionales = data.map(e => {
       return {
         id: e.payload.doc.id,
         name: e.payload.doc.data()['name'],
         avatar: e.payload.doc.data()['avatar'],
         especialidad: e.payload.doc.data()['especialidad'],
         horario: e.payload.doc.data()['horario'],
         diasDeTrabajo: e.payload.doc.data()['diasDeTrabajo'],
         surname: e.payload.doc.data()['surname'],
         uid: e.payload.doc.data()['uid'],

       };
     })

   }


e => {
       return {
         id: e.payload.doc.id,
         name: e.payload.doc.data()['name'],
         avatar: e.payload.doc.data()['avatar'],
         especialidad: e.payload.doc.data()['especialidad'],
         horario: e.payload.doc.data()['horario'],
         diasDeTrabajo: e.payload.doc.data()['diasDeTrabajo'],
         surname: e.payload.doc.data()['surname'],
         uid: e.payload.doc.data()['uid'],

       };
     }


(e: any) => this.imgSrc = e.target.result


res => {
        this.resetFields();
        this.router.navigate(['/home']);
      }


element => {
       console.log(element.diasDeTrabajo)
       console.log(dia);
         if(element.diasDeTrabajo == dia)
         {
            this.profesional = new profesional(element.age,element.avatar,element.DiasDeTrabajo,element.especialidad,element.name,element.uid);
           this.listadoespecialistaspordia.push(this.profesional);
         }
         }


element => {

    if(element.especialidad == this.especialidades)
    {
      this.filtrarlistaporespecialidad.push(element);
    }
    }


element => {
              this.turnos.forEach(item =>
             {///////////////////////////primer foreach

                    if(item.horaTurno == this.horaTurno && item.DiaTurno == this.fechavalidacion && element.name == item.profesional )
                    {
                     // this.profesional = new profesional(item.age,item.avatar,item.DiasDeTrabajo,item.especialidad,item.profesiona,item.uid);
                      this.profesional = new profesional(element.age,element.avatar,element.DiasDeTrabajo,element.especialidad,element.name,element.uid )
                      this.listadoespecialistasconTurnos.push(this.profesional);
                    }
                    else
                    {
                      //ESPECIALISTA EN TURNO LIBRE

                    }
             });
          }


item =>
             {///////////////////////////primer foreach

                    if(item.horaTurno == this.horaTurno && item.DiaTurno == this.fechavalidacion && element.name == item.profesional )
                    {
                     // this.profesional = new profesional(item.age,item.avatar,item.DiasDeTrabajo,item.especialidad,item.profesiona,item.uid);
                      this.profesional = new profesional(element.age,element.avatar,element.DiasDeTrabajo,element.especialidad,element.name,element.uid )
                      this.listadoespecialistasconTurnos.push(this.profesional);
                    }
                    else
                    {
                      //ESPECIALISTA EN TURNO LIBRE

                    }
             }


element => {
              this.listadoespecialistasconTurnos.forEach(item => {///////////////////////////primer foreach

              console.log(item.name);
              console.log(element.name);
              if(element.name == item.name )
              {

                   }
              else
              {
                //ESPECIALISTA EN TURNO LIBRE
                this.listadoFinal.push( element);

              }
           });
          }


item => {///////////////////////////primer foreach

              console.log(item.name);
              console.log(element.name);
              if(element.name == item.name )
              {

                   }
              else
              {
                //ESPECIALISTA EN TURNO LIBRE
                this.listadoFinal.push( element);

              }
           }


element => {

                //ESPECIALISTA EN TURNO LIBRE
                this.listadoFinal.push( element);

          }


userProfileSnapshot => {
              this.userProfile = userProfileSnapshot.data();
               console.log(this.userProfile);
              this.email = userProfileSnapshot.data().email;
              this.perfil= userProfileSnapshot.data().perfil;
              //console.log(this.perfil);

            }


user => {

              this.currentUser = user;
              this.uidUsuario = user.uid}


class profesional {
  age: String;
  avatar: String;
  DiasDeTrabajo: String;
  especialidad: String;
  name: String;

  surname: String;
  uidProfesional:String;

  ingresarperfil=false;

//DETECTAR USUARIO
  isLoggedIn$: Observable<boolean>;
  usrName = '';
  Logueado= false;
  public userProfile: any;
  public birthDate: Date;
  public perfil:string;

  constructor(age:String,avatar:String,DiasDeTrabajo:String,especialidad:String,name:String,uidProfesional:String )
  {
    this.age=age;
    this.avatar=avatar;
    this.DiasDeTrabajo=DiasDeTrabajo;
    this.especialidad=especialidad;
    this.name=name;
    this.uidProfesional=uidProfesional;

  }
}


async traerturnos()
 {
   this.firebaseService.getTurnos().subscribe(data => {

     this.turnos = data.map(e => {
       return {
         id: e.payload.doc.id,
         DiaTurno: e.payload.doc.data()['fechaej'],
         apellidoPaciente: e.payload.doc.data()['apellidoPaciente'],
         horaTurno: e.payload.doc.data()['horaTurno'],
         nombrePaciente: e.payload.doc.data()['nombrePaciente'],
         profesional: e.payload.doc.data()['profesional'],
         email: e.payload.doc.data()['email'],

       };
     })

   });
   return this.turnos;
 }


async traerprofesional()
 {
   this.servicioProfesionales.getUsers().subscribe(data => {

     this.profesionales = data.map(e => {
       return {
         id: e.payload.doc.id,
         name: e.payload.doc.data()['name'],
         avatar: e.payload.doc.data()['avatar'],
         especialidad: e.payload.doc.data()['especialidad'],
         horario: e.payload.doc.data()['horario'],
         diasDeTrabajo: e.payload.doc.data()['diasDeTrabajo'],
         surname: e.payload.doc.data()['surname'],
         uid: e.payload.doc.data()['uid'],

       };
     })

   });

   return this.profesionales;
 }


createForm() {
  this.exampleForm = this.fb.group({
    nombrePaciente: ['', Validators.required ],
//    apellidoPaciente: ['', Validators.required ],
    cliente: this.uidUsuario,
    DiaTurno: this.fechatotal,
    horaTurno: this.horaTurno,
    profesional: ['', Validators.required ],
    consultorio: "3F",
    especialidad: ['', Validators.required ]
  });
}


resetFields(){
  this.avatarLink = "https://s3.amazonaws.com/uifaces/faces/twitter/adellecharles/128.jpg";
  this.exampleForm = this.fb.group({
    name: new FormControl('', Validators.required),
    surname: new FormControl('', Validators.required),
    age: new FormControl('', Validators.required),
    especialidad : new FormControl('', Validators.required),

  });

  this.imgSrc = '/assets/img/image_placeholder.jpg';
  this.selectedImage = null;
  this.isSubmitted = false;
}


ngOnInit() {

       ///////////////////////////////////////////////////////////////////////////////////////////////observable
       this.isLoggedIn$ = this.auth.isLoggedIn;
       console.log(this.isLoggedIn$)
       this.isLoggedIn$.subscribe(res => {
         if(res){
           this.setUsrName()
         }
       });

}


////////////////////////imagen
showPreview(event: any) {
    if (event.target.files && event.target.files[0]) {
      const reader = new FileReader();
      reader.onload = (e: any) => this.imgSrc = e.target.result;
      reader.readAsDataURL(event.target.files[0]);
      this.selectedImage = event.target.files[0];
    }
    else {
      this.imgSrc = '/assets/imagenes/image_placeholder.jpg';
      this.selectedImage = null;
    }
  }


onSubmit(value){
    console.log("_________________________________________");
    console.log(this.usuarioactual);
    console.log(this.nombreEspecialistaelegido);

    this.firebaseService.createTurno(this.nombreEspecialistaelegido,this.uidUsuario,value,this.DiaTurno.toString(),this.makeRandom(),this.email,this.uidProfesional,this.DiaTurno.toString())
    .then(
      res => {
        this.resetFields();
        this.router.navigate(['/home']);
      }
    )
  }


makeRandom() {
    var lengthOfCode = 5;
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    let text = "";
    for (let i = 0; i < lengthOfCode; i++) {
      text += possible.charAt(Math.floor(Math.random() * possible.length));
    }
      return text;
  }


eventoCalendario(type: string, event: MatDatepickerInputEvent<Date>) {

  //this.datosCliente();
  this.mostrarListado=true;
  this.mostrarListadoFinal=false;
  this.events.push(`${event.value}`);

 var fecha= this.events.toString();
this.fechavalidacion = parseInt(fecha);
var fechaHoy = parseInt( Date.now().toString())

//  alert(fechavalidacion);
//  alert(fechaHoy);

 if(this.fechavalidacion < fechaHoy)
 {
  alert("la fecha debe ser mayor");
 }

 this.fechatotal=fecha;
 //console.log("esta es l fecha"+fecha);
 //var fechaAdate =Date.parse(this.event);
 var valor=event.value.toString().split(" ",1);
 var color: string;

switch (valor.toString()) {
  case "Mon":
      var buscrcomo= "Lunes,Miercoles,Viernes";
      this.especialistaPorDia(buscrcomo);
  break;
  case "Tue":
   var buscrcomo= "Martes,Jueves";
   this.especialistaPorDia(buscrcomo);
   break;
  case "Wed":
      var buscrcomo= "Lunes,Miercoles,Viernes";

    this.especialistaPorDia(buscrcomo);
  //  this.compararFecha();
    break;
  case "Thu":
      var buscrcomo= "Martes,Jueves";

    this.especialistaPorDia(buscrcomo);
    break;
  case "Fri":
      var buscrcomo= "Lunes,Miercoles,Viernes";

    this.especialistaPorDia(buscrcomo);
    break;
    case "Sat":
        var buscrcomo= "Jueves,Sabados";

      this.especialistaPorDia(buscrcomo);
      break;
  default:
   // confirm("Sorry, that color is not in the system yet!");
}

  this.events.length=0;
}


especialistaPorDia (dia: String)///////////////////////////////////////////////////////////CLASIFICA POR DIA
{
       this.listadoespecialistaspordia = [];

       this.filtrarlistaporespecialidad.forEach(element => {
       console.log(element.diasDeTrabajo)
       console.log(dia);
         if(element.diasDeTrabajo == dia)
         {
            this.profesional = new profesional(element.age,element.avatar,element.DiasDeTrabajo,element.especialidad,element.name,element.uid);
           this.listadoespecialistaspordia.push(this.profesional);
         }
         });

}


filtrarEspecialidad(){

  this.filtrarlistaporespecialidad = [];

  this.profesionales.forEach(element => {

    if(element.especialidad == this.especialidades)
    {
      this.filtrarlistaporespecialidad.push(element);
    }
    });

 }


seleccionandoHoraDeturno()//AL CAMBIAR EL HORARIO INGRESA ACA SELECTHORARIO
          {
            this.mostrarListado=false;
            this.listadoFinal.length=0;
           // this.datosCliente();

            this.mostrarbotonSeleccionar=true;
            this.mostrarListadoFinal=true;

            this.listadoespecialistasconTurnos=[];

            console.log(this.listadoespecialistasconTurnos);

            this.listadoespecialistaspordia.forEach(element => {
              this.turnos.forEach(item =>
             {///////////////////////////primer foreach

                    if(item.horaTurno == this.horaTurno && item.DiaTurno == this.fechavalidacion && element.name == item.profesional )
                    {
                     // this.profesional = new profesional(item.age,item.avatar,item.DiasDeTrabajo,item.especialidad,item.profesiona,item.uid);
                      this.profesional = new profesional(element.age,element.avatar,element.DiasDeTrabajo,element.especialidad,element.name,element.uid )
                      this.listadoespecialistasconTurnos.push(this.profesional);
                    }
                    else
                    {
                      //ESPECIALISTA EN TURNO LIBRE

                    }
             });
          });

          //SEGUNDO FOR TOMA LOS ESPECIALISTAS POR DIA QUE NO ESTAN RESERVADOS
          console.log(this.listadoespecialistaspordia);
          console.log( this.listadoespecialistasconTurnos);

          this.listadoespecialistaspordia.forEach(element => {
              this.listadoespecialistasconTurnos.forEach(item => {///////////////////////////primer foreach

              console.log(item.name);
              console.log(element.name);
              if(element.name == item.name )
              {

                   }
              else
              {
                //ESPECIALISTA EN TURNO LIBRE
                this.listadoFinal.push( element);

              }
           });
          });

          if(this.listadoespecialistasconTurnos.length == 0)
          {
            console.log("entor");
            this.listadoespecialistaspordia.forEach(element => {

                //ESPECIALISTA EN TURNO LIBRE
                this.listadoFinal.push( element);

          });
          }

          }


viewDetails(item)
         {

          // filtro la lista por especialidad y  por dia (lunes, martes )
          // y lo guardo en una nueva lista (a) listadoespec
          //3 selecciono el horario y que consulte en turnos si algun especialista a esa hs tiene turno.. si tiene
          //(B)guardo en una tercera lista los especialistas que no estan ocupados y muestro el boton seleccionar

           this.mostrarListado=false;
           this.mostrarListadoFinal=false;
           this.mostrarEspecialistaelegido=true;

           this.fotoespecialistElegido= item.avatar;
           this.nombreEspecialistaelegido=item.name;
           this.uidProfesional=item.uidProfesional;

         }


Detectar(){
         // this.Logueado=true;
      console.log("entro a detectar");
              this.profileService
            .getUserProfile()
            .get()
            .then( userProfileSnapshot => {
              this.userProfile = userProfileSnapshot.data();
               console.log(this.userProfile);
              this.email = userProfileSnapshot.data().email;
              this.perfil= userProfileSnapshot.data().perfil;
              //console.log(this.perfil);

            });

            firebase.auth().onAuthStateChanged(user => {

              this.currentUser = user;
              this.uidUsuario = user.uid});
          // }

          console.log(this.currentUser);
          console.log(this.email);

          console.log(this.uidUsuario);

        }


setUsrName(){
        // let usr = this.auth.getCurrentUser();
          //this.usrName = usr.email;
          this.Detectar();
        }


usuarioes()
        {
          // alert(this.userProfile);

          // alert(this.uidUsuario);
        }


(endDate: moment.Moment): moment.Moment =>
      endDate.clone().subtract(10, 'days')


(endDate: moment.Moment): moment.Moment =>
      endDate.clone().subtract(1, 'months')


(endDate: moment.Moment): moment.Moment =>
      endDate.clone().subtract(3, 'months')


(endDate: moment.Moment): moment.Moment =>
      endDate.clone().subtract(1, 'years')


@Injectable()
export class ReportQueryService {

  intervals: TimeIntervals[] = [
    { name: 'Last 10 days', findStartDate: (endDate: moment.Moment): moment.Moment =>
      endDate.clone().subtract(10, 'days')},
    { name: 'Last month', findStartDate: (endDate: moment.Moment): moment.Moment =>
      endDate.clone().subtract(1, 'months')},
    { name: 'Last 3 months', findStartDate: (endDate: moment.Moment): moment.Moment =>
      endDate.clone().subtract(3, 'months')},
    { name: 'Last year', findStartDate: (endDate: moment.Moment): moment.Moment =>
      endDate.clone().subtract(1, 'years')}
  ];

  private idToTitle: Map<string, string> = new Map<string, string>();

  state: BehaviorSubject<ReportQuery> = new BehaviorSubject(this.initialReportQueryState());

  private initialReportQueryState() {
    const endDate = moment().utc().startOf('day').add(12, 'hours');
    return {
      startDate: this.findTimeIntervalStartDate(0, endDate),
      endDate: endDate,
      interval: 0,
      filters: []
    };
  }

  getReportQuery(): ReportQuery {
    const reportQuery = this.state.getValue();
    return {
      startDate: reportQuery.startDate.clone(),
      endDate: reportQuery.endDate.clone(),
      interval: reportQuery.interval,
      filters: [...reportQuery.filters]
    };
  }

  setState(newState: ReportQuery) {
    this.state.next(newState);
  }

  findTimeIntervalStartDate(interval: number, endDate: moment.Moment): moment.Moment {
    return this.intervals[interval].findStartDate(endDate.clone());
  }

  setFilterTitle(type: string, id: string, title: string) {
    this.idToTitle.set(type + '-' + id, title);
  }

  getFilterTitle(type: string, id: string): string {
    return this.idToTitle.get(type + '-' + id);
  }
}


export interface ReportQuery {
  startDate: moment.Moment;
  endDate: moment.Moment;
  interval: number;
  filters: FilterC[];
}


interface TimeIntervals {
  name: string;
  findStartDate: (endDate: moment.Moment) => moment.Moment;
}


private initialReportQueryState() {
    const endDate = moment().utc().startOf('day').add(12, 'hours');
    return {
      startDate: this.findTimeIntervalStartDate(0, endDate),
      endDate: endDate,
      interval: 0,
      filters: []
    };
  }


getReportQuery(): ReportQuery {
    const reportQuery = this.state.getValue();
    return {
      startDate: reportQuery.startDate.clone(),
      endDate: reportQuery.endDate.clone(),
      interval: reportQuery.interval,
      filters: [...reportQuery.filters]
    };
  }


setState(newState: ReportQuery) {
    this.state.next(newState);
  }


findTimeIntervalStartDate(interval: number, endDate: moment.Moment): moment.Moment {
    return this.intervals[interval].findStartDate(endDate.clone());
  }


setFilterTitle(type: string, id: string, title: string) {
    this.idToTitle.set(type + '-' + id, title);
  }


getFilterTitle(type: string, id: string): string {
    return this.idToTitle.get(type + '-' + id);
  }


@Component({
  selector: 'youtube-videos',
  styleUrls: ['./youtube-videos.scss'],
  template: `
    <loader [message]="'Loading '" [loading]="loading$ | async"></loader>
    <youtube-list
      [list]="videos$ | async"
      [queued]="playlistVideos$ | async"
      (play)="playSelectedVideo($event)"
      (queue)="queueSelectedVideo($event)"
      (unqueue)="removeVideoFromPlaylist($event)"
    ></youtube-list>
  `
})
export class YoutubeVideosComponent implements OnInit {
  videos$ = this.store.let(getPlayerSearchResults$);
  playlistVideos$ = this.store.let(getPlaylistVideos$);
  loading$ = this.store.let(getIsSearching$);

  constructor(
    private store: Store<EchoesState>,
    private appPlayerApi: AppPlayerApi,
    private playerSearchActions: PlayerSearchActions
  ) {}

  ngOnInit() {
    this.store.dispatch(this.playerSearchActions.updateSearchType(CSearchTypes.VIDEO));
    this.store.dispatch(this.playerSearchActions.searchCurrentQuery());
  }

  playSelectedVideo(media: GoogleApiYouTubeVideoResource) {
    this.appPlayerApi.playVideo(media);
  }

  queueSelectedVideo(media: GoogleApiYouTubeVideoResource) {
    this.appPlayerApi.queueVideo(media);
  }

  removeVideoFromPlaylist(media: GoogleApiYouTubeVideoResource) {
    this.appPlayerApi.removeVideoFromPlaylist(media);
  }
}


ngOnInit() {
    this.store.dispatch(this.playerSearchActions.updateSearchType(CSearchTypes.VIDEO));
    this.store.dispatch(this.playerSearchActions.searchCurrentQuery());
  }


playSelectedVideo(media: GoogleApiYouTubeVideoResource) {
    this.appPlayerApi.playVideo(media);
  }


queueSelectedVideo(media: GoogleApiYouTubeVideoResource) {
    this.appPlayerApi.queueVideo(media);
  }


removeVideoFromPlaylist(media: GoogleApiYouTubeVideoResource) {
    this.appPlayerApi.removeVideoFromPlaylist(media);
  }


(iconProps: IFluentIconsProps, props: React.HTMLAttributes<HTMLElement>): JSX.Element => {
  const {
    primaryFill,
    className
  } = iconProps;
  return <svg {...props} width={12} height={12} viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" className={className}><path d="M7.53 2.22c.3.3.3.77 0 1.06L4.81 6l2.72 2.72a.75.75 0 01-1.06 1.06L3.22 6.53a.75.75 0 010-1.06l3.25-3.25c.3-.3.77-.3 1.06 0z" fill={primaryFill} /></svg>;
}


(def, typeName) => ({ ...parsedConfigs[typeName], ...def })


t => t !== standardObjectTypeName


t => `${SETTINGS_TYPE_PREFIX}${t}`


export default class ZuoraAdapter implements AdapterOperations {
  private filtersRunner: Required<Filter>
  private client: ZuoraClient
  private paginator: clientUtils.Paginator
  private userConfig: ZuoraConfig

  public constructor({
    filterCreators = DEFAULT_FILTERS,
    client,
    config,
  }: ZuoraAdapterParams) {
    this.userConfig = config
    this.client = client
    this.paginator = createPaginator({
      client: this.client,
      paginationFunc: paginate,
    })
    this.filtersRunner = filtersRunner(
      {
        client: this.client,
        paginator: this.paginator,
        config,
      },
      filterCreators,
    )
  }

  private apiDefinitions(
    parsedConfigs: Record<string, configUtils.RequestableTypeSwaggerConfig>,
  ): ZuoraApiConfig {
    return {
      ...this.userConfig[API_DEFINITIONS_CONFIG],
      // user config takes precedence over parsed config
      types: {
        ...parsedConfigs,
        ..._.mapValues(
          this.userConfig[API_DEFINITIONS_CONFIG].types,
          (def, typeName) => ({ ...parsedConfigs[typeName], ...def })
        ),
      },
    }
  }

  @logDuration('generating types from swagger')
  private async getSwaggerTypes(): Promise<elementUtils.swagger.ParsedTypes> {
    return generateTypes(
      ZUORA_BILLING,
      this.userConfig[API_DEFINITIONS_CONFIG]
    )
  }

  @logDuration('generating types for billing settings')
  private async getBillingSettingsTypes({
    parsedConfigs, allTypes,
  }: elementUtils.swagger.ParsedTypes): Promise<elementUtils.swagger.ParsedTypes> {
    if (this.userConfig[FETCH_CONFIG].settingsIncludeTypes === undefined) {
      return { allTypes: {}, parsedConfigs: {} }
    }

    const apiDefs = this.apiDefinitions(parsedConfigs)
    const settingsOpInfoInstances = await getAllInstances({
      paginator: this.paginator,
      objectTypes: _.pickBy(allTypes, isObjectType),
      apiConfig: apiDefs,
      fetchConfig: { includeTypes: [LIST_ALL_SETTINGS_TYPE] },
    })
    if (_.isEmpty(settingsOpInfoInstances)) {
      throw new Error('could not find any settings definitions - remove settingsFetchTypes and fetch again')
    }
    return generateBillingSettingsTypes(settingsOpInfoInstances, apiDefs)
  }

  @logDuration('generating type and instances for standard objects')
  private async getStandardObjectElements({
    parsedConfigs, allTypes,
  }: elementUtils.swagger.ParsedTypes): Promise<Element[]> {
    const apiConfig = this.apiDefinitions(parsedConfigs)
    const standardObjectTypeName = getStandardObjectTypeName(apiConfig)
    if (
      standardObjectTypeName === undefined
      || !this.userConfig[FETCH_CONFIG].includeTypes.includes(standardObjectTypeName)
    ) {
      return []
    }
    const standardObjectWrapperType = allTypes[standardObjectTypeName]
    const customObjectDefType = allTypes[CUSTOM_OBJECT_DEFINITION_TYPE]
    if (!isObjectType(standardObjectWrapperType) || !isObjectType(customObjectDefType)) {
      log.warn('Could not find object types %s / %s', standardObjectTypeName, CUSTOM_OBJECT_DEFINITION_TYPE)
      return []
    }
    return getStandardObjectElements({
      standardObjectWrapperType,
      customObjectDefType,
      paginator: this.paginator,
      apiConfig,
    })
  }

  @logDuration('getting instances from service')
  private async getInstances(
    allTypes: TypeMap,
    parsedConfigs: Record<string, configUtils.RequestableTypeSwaggerConfig>,
  ): Promise<InstanceElement[]> {
    // standard objects are not included in the swagger and need special handling - done in a filter
    const standardObjectTypeName = getStandardObjectTypeName(this.apiDefinitions(parsedConfigs))
    const swaggerIncludeTypes = this.userConfig[FETCH_CONFIG].includeTypes.filter(
      t => t !== standardObjectTypeName
    )
    // settings include types can be fetched with the regular include types, since their types
    // were already generatedטוב
    const settingsIncludeTypes = (this.userConfig[FETCH_CONFIG].settingsIncludeTypes ?? []).map(
      t => `${SETTINGS_TYPE_PREFIX}${t}`
    )
    const fetchConfig = {
      ...this.userConfig[FETCH_CONFIG],
      includeTypes: [...swaggerIncludeTypes, ...settingsIncludeTypes],
    }

    return getAllInstances({
      paginator: this.paginator,
      objectTypes: _.pickBy(allTypes, isObjectType),
      apiConfig: this.apiDefinitions(parsedConfigs),
      fetchConfig,
    })
  }

  /**
   * Fetch configuration elements in the given zuora account.
   * Account credentials were given in the constructor.
   */
  @logDuration('fetching account configuration')
  async fetch({ progressReporter }: FetchOptions): Promise<FetchResult> {
    log.debug('going to fetch zuora account configuration..')
    progressReporter.reportProgress({ message: 'Fetching types' })
    const swaggerTypes = await this.getSwaggerTypes()
    // the billing settings types are not listed in the swagger, so we fetch them separately
    // and give them a custom prefix to avoid conflicts
    const settingsTypes = await this.getBillingSettingsTypes(swaggerTypes)

    const { allTypes, parsedConfigs } = {
      allTypes: { ...swaggerTypes.allTypes, ...settingsTypes.allTypes },
      parsedConfigs: { ...swaggerTypes.parsedConfigs, ...settingsTypes.parsedConfigs },
    }
    progressReporter.reportProgress({ message: 'Fetching instances' })

    const instances = await this.getInstances(allTypes, parsedConfigs)
    const standardObjectElements = await this.getStandardObjectElements({ allTypes, parsedConfigs })

    const elements = [
      ...Object.values(allTypes),
      ...instances,
      ...standardObjectElements,
    ]

    log.debug('going to run filters on %d fetched elements', elements.length)
    progressReporter.reportProgress({ message: 'Running filters for additional information' })

    await this.filtersRunner.onFetch(elements)
    return { elements }
  }

  /**
   * Deploy configuration elements to the given account.
   */
  @logDuration('deploying account configuration')
  // eslint-disable-next-line class-methods-use-this
  async deploy(): Promise<DeployResult> {
    throw new Error('Not implemented.')
  }

  // eslint-disable-next-line class-methods-use-this
  public get deployModifiers(): DeployModifiers {
    return {
      changeValidator,
    }
  }
}


export interface ZuoraAdapterParams {
  filterCreators?: FilterCreator[]
  client: ZuoraClient
  config: ZuoraConfig
}


private apiDefinitions(
    parsedConfigs: Record<string, configUtils.RequestableTypeSwaggerConfig>,
  ): ZuoraApiConfig {
    return {
      ...this.userConfig[API_DEFINITIONS_CONFIG],
      // user config takes precedence over parsed config
      types: {
        ...parsedConfigs,
        ..._.mapValues(
          this.userConfig[API_DEFINITIONS_CONFIG].types,
          (def, typeName) => ({ ...parsedConfigs[typeName], ...def })
        ),
      },
    }
  }


@logDuration('generating types from swagger')
  private async getSwaggerTypes(): Promise<elementUtils.swagger.ParsedTypes> {
    return generateTypes(
      ZUORA_BILLING,
      this.userConfig[API_DEFINITIONS_CONFIG]
    )
  }


@logDuration('generating types for billing settings')
  private async getBillingSettingsTypes({
    parsedConfigs, allTypes,
  }: elementUtils.swagger.ParsedTypes): Promise<elementUtils.swagger.ParsedTypes> {
    if (this.userConfig[FETCH_CONFIG].settingsIncludeTypes === undefined) {
      return { allTypes: {}, parsedConfigs: {} }
    }

    const apiDefs = this.apiDefinitions(parsedConfigs)
    const settingsOpInfoInstances = await getAllInstances({
      paginator: this.paginator,
      objectTypes: _.pickBy(allTypes, isObjectType),
      apiConfig: apiDefs,
      fetchConfig: { includeTypes: [LIST_ALL_SETTINGS_TYPE] },
    })
    if (_.isEmpty(settingsOpInfoInstances)) {
      throw new Error('could not find any settings definitions - remove settingsFetchTypes and fetch again')
    }
    return generateBillingSettingsTypes(settingsOpInfoInstances, apiDefs)
  }


@logDuration('generating type and instances for standard objects')
  private async getStandardObjectElements({
    parsedConfigs, allTypes,
  }: elementUtils.swagger.ParsedTypes): Promise<Element[]> {
    const apiConfig = this.apiDefinitions(parsedConfigs)
    const standardObjectTypeName = getStandardObjectTypeName(apiConfig)
    if (
      standardObjectTypeName === undefined
      || !this.userConfig[FETCH_CONFIG].includeTypes.includes(standardObjectTypeName)
    ) {
      return []
    }
    const standardObjectWrapperType = allTypes[standardObjectTypeName]
    const customObjectDefType = allTypes[CUSTOM_OBJECT_DEFINITION_TYPE]
    if (!isObjectType(standardObjectWrapperType) || !isObjectType(customObjectDefType)) {
      log.warn('Could not find object types %s / %s', standardObjectTypeName, CUSTOM_OBJECT_DEFINITION_TYPE)
      return []
    }
    return getStandardObjectElements({
      standardObjectWrapperType,
      customObjectDefType,
      paginator: this.paginator,
      apiConfig,
    })
  }


@logDuration('getting instances from service')
  private async getInstances(
    allTypes: TypeMap,
    parsedConfigs: Record<string, configUtils.RequestableTypeSwaggerConfig>,
  ): Promise<InstanceElement[]> {
    // standard objects are not included in the swagger and need special handling - done in a filter
    const standardObjectTypeName = getStandardObjectTypeName(this.apiDefinitions(parsedConfigs))
    const swaggerIncludeTypes = this.userConfig[FETCH_CONFIG].includeTypes.filter(
      t => t !== standardObjectTypeName
    )
    // settings include types can be fetched with the regular include types, since their types
    // were already generatedטוב
    const settingsIncludeTypes = (this.userConfig[FETCH_CONFIG].settingsIncludeTypes ?? []).map(
      t => `${SETTINGS_TYPE_PREFIX}${t}`
    )
    const fetchConfig = {
      ...this.userConfig[FETCH_CONFIG],
      includeTypes: [...swaggerIncludeTypes, ...settingsIncludeTypes],
    }

    return getAllInstances({
      paginator: this.paginator,
      objectTypes: _.pickBy(allTypes, isObjectType),
      apiConfig: this.apiDefinitions(parsedConfigs),
      fetchConfig,
    })
  }


/**
   * Fetch configuration elements in the given zuora account.
   * Account credentials were given in the constructor.
   */
@logDuration('fetching account configuration')
  async fetch({ progressReporter }: FetchOptions): Promise<FetchResult> {
    log.debug('going to fetch zuora account configuration..')
    progressReporter.reportProgress({ message: 'Fetching types' })
    const swaggerTypes = await this.getSwaggerTypes()
    // the billing settings types are not listed in the swagger, so we fetch them separately
    // and give them a custom prefix to avoid conflicts
    const settingsTypes = await this.getBillingSettingsTypes(swaggerTypes)

    const { allTypes, parsedConfigs } = {
      allTypes: { ...swaggerTypes.allTypes, ...settingsTypes.allTypes },
      parsedConfigs: { ...swaggerTypes.parsedConfigs, ...settingsTypes.parsedConfigs },
    }
    progressReporter.reportProgress({ message: 'Fetching instances' })

    const instances = await this.getInstances(allTypes, parsedConfigs)
    const standardObjectElements = await this.getStandardObjectElements({ allTypes, parsedConfigs })

    const elements = [
      ...Object.values(allTypes),
      ...instances,
      ...standardObjectElements,
    ]

    log.debug('going to run filters on %d fetched elements', elements.length)
    progressReporter.reportProgress({ message: 'Running filters for additional information' })

    await this.filtersRunner.onFetch(elements)
    return { elements }
  }


/**
   * Deploy configuration elements to the given account.
   */
@logDuration('deploying account configuration')
  // eslint-disable-next-line class-methods-use-this
  async deploy(): Promise<DeployResult> {
    throw new Error('Not implemented.')
  }


/**
 * A method decorator that delays the execution of the class method
 * by the provided time in milliseconds.
 */
export function debounce(delay: number): MethodDecorator {
  return (target, property, descriptor) => {
    if (
      process.env.NODE_ENV !== 'production' &&
      (!isMethod(target, property, descriptor) || !('value' in descriptor && typeof descriptor.value === 'function'))
    ) {
      throw new TypeError(`\`@debounce\` may only be applied to class methods.`);
    }

    // We must use a map as all class instances would share the
    // same timer value otherwise.
    const timers = new WeakMap<Function, NodeJS.Timeout>();

    // Overwrite the value function with a new debounced function
    const func = descriptor.value;

    // @ts-expect-error Override generic
    // eslint-disable-next-line @typescript-eslint/no-shadow
    descriptor.value = function debounce(this: Function, ...args: unknown[]) {
      const timer = timers.get(this);

      if (timer) {
        clearTimeout(timer);
        timers.delete(this);
      }

      timers.set(
        this,
        setTimeout(() => {
          (func as unknown as Function).apply(this, args);
        }, delay),
      );
    };
  };
}


(target, property, descriptor) => {
    if (
      process.env.NODE_ENV !== 'production' &&
      (!isMethod(target, property, descriptor) || !('value' in descriptor && typeof descriptor.value === 'function'))
    ) {
      throw new TypeError(`\`@debounce\` may only be applied to class methods.`);
    }

    // We must use a map as all class instances would share the
    // same timer value otherwise.
    const timers = new WeakMap<Function, NodeJS.Timeout>();

    // Overwrite the value function with a new debounced function
    const func = descriptor.value;

    // @ts-expect-error Override generic
    // eslint-disable-next-line @typescript-eslint/no-shadow
    descriptor.value = function debounce(this: Function, ...args: unknown[]) {
      const timer = timers.get(this);

      if (timer) {
        clearTimeout(timer);
        timers.delete(this);
      }

      timers.set(
        this,
        setTimeout(() => {
          (func as unknown as Function).apply(this, args);
        }, delay),
      );
    };
  }


() => {
          (func as unknown as Function).apply(this, args);
        }


() => {
  let component: TelescopeBackgroundComponent;
  let fixture: ComponentFixture<TelescopeBackgroundComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [TelescopeBackgroundComponent]
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(TelescopeBackgroundComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
}


() => {
    TestBed.configureTestingModule({
      declarations: [TelescopeBackgroundComponent]
    }).compileComponents();
  }


() => {
    fixture = TestBed.createComponent(TelescopeBackgroundComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  }


() => (
    <List>
        {_.map(navData, (item, index) => (
            <li key={`productLink-${index}`}>
                <Link to={item.url}>
                    <Heading asElement


(item, index) => (
            <li key={`productLink-${index}`


export function createTaskGroupId(): number {
            System.$taskGroupId++;
            return System.$taskGroupId;
        }


/**
         * 判断指定模块是否己停止
         * export
         */
export function isModuleStopped(mod: ModuleEnum): boolean {
            if (mod === ModuleEnum.TIMELINE) {
                if (M.timeline === null || M.timeline.stopped === true) {
                    return true;
                }
            }
            else if (mod === ModuleEnum.CUSTOM) {
                if (M.timeStamp === null || M.timeStamp.stopped === true) {
                    return true;
                }
            }
            else if (M.engine === null) {
                return true;
            }
            return false;
        }


/**
         * 判断指定模块是否己暂停
         * export
         */
export function isModulePaused(mod: ModuleEnum): boolean {
            if (isModuleStopped(mod) === true) {
                return true;
            }
            if (mod === ModuleEnum.TIMELINE) {
                return M.timeline.paused;
            }
            else if (mod === ModuleEnum.CUSTOM) {
                return M.timeStamp.paused;
            }
            return false;
        }


/**
         * 获取时间间隔（所有模块共享）
         * export
         */
export function getDelta(): number {
            if (isModuleStopped(ModuleEnum.SYSTEM) === false) {
                return M.engine.getDelta();
            }
            else {
                suncom.Logger.error(`尝试获取帧时间间隔，但系统模块己停止！！！`);
            }
        }


/**
         * 获取指定模块的时间戳
         * export
         */
export function getModuleTimestamp(mod: ModuleEnum): number {
            if (isModuleStopped(mod) === false) {
                if (mod === ModuleEnum.TIMELINE) {
                    return M.timeline.getTime();
                }
                else if (mod === ModuleEnum.CUSTOM) {
                    return M.timeStamp.getTime();
                }
                return M.engine.getTime();
            }
            else {
                suncom.Logger.error(`尝试获取时间戳，但模块 ${ModuleEnum[mod]} 己停止！！！`);
            }
        }


/**
         * 添加任务
         * @groupId: 不同编组并行执行，若为-1，则自动给预一个groupId，默认为: 0
         * @return: 返回任务的groupId，若为-1，则说明任务添加失败
         * 说明：
         * 1. 自定义的groupId的值不允许超过1000
         * export
         */
export function addTask(mod: ModuleEnum, task: ITask, groupId: number = 0): number {
            if (System.isModuleStopped(mod) === false) {
                if (groupId === -1) {
                    groupId = System.createTaskGroupId();
                }
                else if (groupId > 1000) {
                    throw Error(`自定义的Task GroupId不允许超过1000`);
                }
                const message: IMessage = suncom.Pool.getItemByClass("suncore.Message", Message);
                message.mod = mod;
                message.task = task;
                message.groupId = groupId;
                message.priority = MessagePriorityEnum.PRIORITY_TASK;
                M.messageManager.putMessage(message);
            }
            else {
                groupId = -1;
                suncom.Logger.error(`尝试添加任务，但模块 ${ModuleEnum[mod]} 己停止！！！`);
            }
            return groupId;
        }


/**
         * 取消任务
         * export
         */
export function cancelTaskByGroupId(mod: ModuleEnum, groupId: number): void {
            M.messageManager.cancelTaskByGroupId(mod, groupId);
        }


/**
         * 添加触发器
         * export
         */
export function addTrigger(mod: ModuleEnum, delay: number, caller: Object, method: Function, args: any[] = null): void {
            if (System.isModuleStopped(mod) === false) {
                const message: IMessage = suncom.Pool.getItemByClass("suncore.Message", Message);
                message.mod = mod;
                message.args = args;
                message.caller = caller;
                message.method = method;
                message.timeout = System.getModuleTimestamp(mod) + delay;
                message.priority = MessagePriorityEnum.PRIORITY_TRIGGER;
                M.messageManager.putMessage(message);
            }
            else {
                suncom.Logger.error(`尝试添加触发器，但模块 ${ModuleEnum[mod]} 己停止！！！`);
            }
        }


/**
         * 添加承诺
         * @method: 此方法被调用时，第一个参数必然是resolve方法，你应当在method方法执行完毕时调用resolve方法，否则该promise将永远不会结束
         * export
         */
export function addPromise(mod: ModuleEnum, caller: Object, method: Function, args: any[] = null): void {
            if (System.isModuleStopped(mod) === false) {
                const message: IMessage = suncom.Pool.getItemByClass("suncore.Message", Message);
                message.mod = mod;
                message.task = new PromiseTask(caller, method, args);
                message.priority = MessagePriorityEnum.PRIORITY_PROMISE;
                M.messageManager.putMessage(message);
            }
            else {
                suncom.Logger.error(`尝试添加Promise消息，但模块 ${ModuleEnum[mod]} 己停止！！！`);
            }
        }


/**
         * 添加消息
         * export
         */
export function addMessage(mod: ModuleEnum, priority: MessagePriorityEnum, caller: Object, method: Function, args: any[] = null): void {
            if (System.isModuleStopped(mod) === false) {
                const message: IMessage = suncom.Pool.getItemByClass("suncore.Message", Message);
                message.mod = mod;
                message.args = args;
                message.caller = caller;
                message.method = method;
                message.priority = priority;
                M.messageManager.putMessage(message);
            }
            else {
                suncom.Logger.error(`尝试添加Message消息，但模块 ${ModuleEnum[mod]} 己停止！！！`);
            }
        }


/**
         * 添加自定义消息
         * @messageId: 再三考虑，这个值应当作为形参传入，否则不利于问题调试
         * @message: 消息日志
         * 说明：
         * 1. 通过此接口注册的 MessageId 会无条件限制 MessagePriorityEnum.PRIORITY_LAZY 的执行，直到 MessageId 被移除
         * 2. 有时候你没法借助 System 的其它接口来限制 MessagePriorityEnum.PRIORITY_LAZY 的执行，此接口会帮助到你
         * export
         */
export function addCustomMessageId(mod: ModuleEnum, messageId: number, message: string = null): void {
            if (System.isModuleStopped(mod) === false) {
                M.messageManager.addCustomMessageId(mod, messageId, message);
            }
            else {
                suncom.Logger.error(`尝试添加自定义消息，但模块 ${ModuleEnum[mod]} 己停止！！！`);
            }
        }


/**
         * 移除自定义消息
         * @message: 消息日志
         * 说明
         * 1. 当所有 MessageId 均被移除时，对 MessagePriorityEnum.PRIORITY_LAZY 的执行限制将被解除
         * export
         */
export function removeCustomMessageId(mod: ModuleEnum, messageId: number): void {
            if (System.isModuleStopped(mod) === false) {
                M.messageManager.removeCustomMessageId(mod, messageId);
            }
            else {
                suncom.Logger.error(`尝试移除自定义消息，但模块 ${ModuleEnum[mod]} 己停止！！！`);
            }
        }


/**
         * 添加自定义定时器
         * @mod: 所属模块
         * @delay: 响应间隔，若为数组，则第二个参数表示首次响应延时，且默认为：0，若首次响应延时为0，则定时器会立即执行一次
         * @method: 回调函数，默认参数：{ count: number, loops: number }
         * @caller: 回调对象
         * @args[]: 参数列表
         * @loops: 响应次数，默认为1
         * @jumpFrame: 时钟是否跳帧，若为 true ，则在单位时间间隔内，回调会连续执行多次，出于性能考虑，默认为： false
         * export
         */
export function addTimer(mod: ModuleEnum, delay: number | number[], method: Function, caller: Object, args?: any[], loops: number = 1, jumpFrame: boolean = false): number {
            if (System.isModuleStopped(mod) === false) {
                return M.timerManager.addTimer(mod, delay, method, caller, args, loops, jumpFrame);
            }
            else {
                suncom.Logger.error(`尝试添加定时器，但模块 ${ModuleEnum[mod]} 己停止！！！`);
            }
        }


/**
         * 移除定时器
         * export
         */
export function removeTimer(timerId: number): number {
            return M.timerManager.removeTimer(timerId);
        }


l => l.uri.toString() === previousSelectedRoot


debugType => this.debugAdapterFactories.set(debugType, debugAdapterLauncher)


() => {
				debugTypes.forEach(debugType => this.debugAdapterFactories.delete(debugType));
			}


debugType => this.debugAdapterFactories.delete(debugType)


() => {
				this.unregisterDebugAdapterDescriptorFactory(debugAdapterProvider);
			}


p => p.type === config.type && p.debugAdapterExecutable


p => p.type === config.type && p.createDebugAdapterDescriptor


() => {
				this.unregisterDebugConfigurationProvider(debugConfigurationProvider);
			}


p => p.provideDebugConfigurations && (p.type === debugType) && (p.triggerKind === triggerKind)


p => p.type === type && p.resolveDebugConfiguration


p => p.type === '*' && p.resolveDebugConfiguration


provider => async () => {
			// If any provider returned undefined or null make sure to respect that and do not pass the result to more resolver
			if (result) {
				result = await provider.resolveDebugConfiguration!(folderUri, result, token);
			}
		}


async () => {
			// If any provider returned undefined or null make sure to respect that and do not pass the result to more resolver
			if (result) {
				result = await provider.resolveDebugConfiguration!(folderUri, result, token);
			}
		}


p => p.type === type && p.resolveDebugConfigurationWithSubstitutedVariables


p => p.type === '*' && p.resolveDebugConfigurationWithSubstitutedVariables


provider => async () => {
			// If any provider returned undefined or null make sure to respect that and do not pass the result to more resolver
			if (result) {
				result = await provider.resolveDebugConfigurationWithSubstitutedVariables!(folderUri, result, token);
			}
		}


async () => {
			// If any provider returned undefined or null make sure to respect that and do not pass the result to more resolver
			if (result) {
				result = await provider.resolveDebugConfigurationWithSubstitutedVariables!(folderUri, result, token);
			}
		}


p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Initial && p.provideDebugConfigurations


p => p.provideDebugConfigurations!(folderUri, token)


e => {
			const activationEvent = e.activationEvents && e.activationEvents.find(e => e.includes(onDebugDynamicConfigurationsName));
			if (activationEvent) {
				const type = activationEvent.substr(onDebugDynamicConfigurationsName.length);
				return type || (e.contributes && e.contributes.debuggers && e.contributes.debuggers.length ? e.contributes.debuggers[0].type : undefined);
			}

			return undefined;
		}


e => e.includes(onDebugDynamicConfigurationsName)


e => typeof e === 'string'


type => {
			return {
				label: this.getDebuggerLabel(type)!,
				pick: async () => {
					await this.activateDebuggers(onDebugDynamicConfigurationsName, type);
					const token = new CancellationTokenSource();
					const picks: Promise<{ label: string, launch: ILaunch, config: IConfig }[]>[] = [];
					const provider = this.configProviders.filter(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations)[0];
					this.getLaunches().forEach(launch => {
						if (launch.workspace && provider) {
							picks.push(provider.provideDebugConfigurations!(launch.workspace.uri, token.token).then(configurations => configurations.map(config => ({
								label: config.name,
								config,
								buttons: [{
									iconClass: 'codicon-gear',
									tooltip: nls.localize('editLaunchConfig', "Edit Debug Configuration in launch.json")
								}],
								launch
							}))));
						}
					});
					const promiseOfPicks = Promise.all(picks).then(result => result.reduce((first, second) => first.concat(second), []));

					const result = await this.quickInputService.pick<{ label: string, launch: ILaunch, config: IConfig }>(promiseOfPicks, {
						placeHolder: nls.localize('selectConfiguration', "Select Launch Configuration"),
						onDidTriggerItemButton: async (context) => {
							await this.quickInputService.cancel();
							const { launch, config } = context.item;
							await launch.openConfigFile(false, config.type);
							// Only Launch have a pin trigger button
							await (launch as Launch).writeConfiguration(config);
							this.selectConfiguration(launch, config.name);
						}
					});
					if (!result) {
						// User canceled quick input we should notify the provider to cancel computing configurations
						token.cancel();
					}

					return result;
				}
			};
		}


async () => {
					await this.activateDebuggers(onDebugDynamicConfigurationsName, type);
					const token = new CancellationTokenSource();
					const picks: Promise<{ label: string, launch: ILaunch, config: IConfig }[]>[] = [];
					const provider = this.configProviders.filter(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations)[0];
					this.getLaunches().forEach(launch => {
						if (launch.workspace && provider) {
							picks.push(provider.provideDebugConfigurations!(launch.workspace.uri, token.token).then(configurations => configurations.map(config => ({
								label: config.name,
								config,
								buttons: [{
									iconClass: 'codicon-gear',
									tooltip: nls.localize('editLaunchConfig', "Edit Debug Configuration in launch.json")
								}],
								launch
							}))));
						}
					});
					const promiseOfPicks = Promise.all(picks).then(result => result.reduce((first, second) => first.concat(second), []));

					const result = await this.quickInputService.pick<{ label: string, launch: ILaunch, config: IConfig }>(promiseOfPicks, {
						placeHolder: nls.localize('selectConfiguration', "Select Launch Configuration"),
						onDidTriggerItemButton: async (context) => {
							await this.quickInputService.cancel();
							const { launch, config } = context.item;
							await launch.openConfigFile(false, config.type);
							// Only Launch have a pin trigger button
							await (launch as Launch).writeConfiguration(config);
							this.selectConfiguration(launch, config.name);
						}
					});
					if (!result) {
						// User canceled quick input we should notify the provider to cancel computing configurations
						token.cancel();
					}

					return result;
				}


p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations


launch => {
						if (launch.workspace && provider) {
							picks.push(provider.provideDebugConfigurations!(launch.workspace.uri, token.token).then(configurations => configurations.map(config => ({
								label: config.name,
								config,
								buttons: [{
									iconClass: 'codicon-gear',
									tooltip: nls.localize('editLaunchConfig', "Edit Debug Configuration in launch.json")
								}],
								launch
							}))));
						}
					}


configurations => configurations.map(config => ({
								label: config.name,
								config,
								buttons: [{
									iconClass: 'codicon-gear',
									tooltip: nls.localize('editLaunchConfig', "Edit Debug Configuration in launch.json")
								}],
								launch
							}))


config => ({
								label: config.name,
								config,
								buttons: [{
									iconClass: 'codicon-gear',
									tooltip: nls.localize('editLaunchConfig', "Edit Debug Configuration in launch.json")
								}],
								launch
							})


result => result.reduce((first, second) => first.concat(second), [])


async (context) => {
							await this.quickInputService.cancel();
							const { launch, config } = context.item;
							await launch.openConfigFile(false, config.type);
							// Only Launch have a pin trigger button
							await (launch as Launch).writeConfiguration(config);
							this.selectConfiguration(launch, config.name);
						}


(extensions, delta) => {
			delta.added.forEach(added => {
				added.value.forEach(rawAdapter => {
					if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) {
						added.collector.error(nls.localize('debugNoType', "Debugger 'type' can not be omitted and must be of type 'string'."));
					}
					if (rawAdapter.enableBreakpointsFor) {
						rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => {
							this.breakpointModeIdsSet.add(modeId);
						});
					}

					if (rawAdapter.type !== '*') {
						const existing = this.getDebugger(rawAdapter.type);
						if (existing) {
							existing.merge(rawAdapter, added.description);
						} else {
							this.debuggers.push(this.instantiationService.createInstance(Debugger, this, rawAdapter, added.description));
						}
					}
				});
			});

			// take care of all wildcard contributions
			extensions.forEach(extension => {
				extension.value.forEach(rawAdapter => {
					if (rawAdapter.type === '*') {
						this.debuggers.forEach(dbg => dbg.merge(rawAdapter, extension.description));
					}
				});
			});

			delta.removed.forEach(removed => {
				const removedTypes = removed.value.map(rawAdapter => rawAdapter.type);
				this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1);
			});

			// update the schema to include all attributes, snippets and types from extensions.
			this.debuggers.forEach(adapter => {
				const items = (<IJSONSchema>launchSchema.properties!['configurations'].items);
				const schemaAttributes = adapter.getSchemaAttributes();
				if (schemaAttributes && items.oneOf) {
					items.oneOf.push(...schemaAttributes);
				}
				const configurationSnippets = adapter.configurationSnippets;
				if (configurationSnippets && items.defaultSnippets) {
					items.defaultSnippets.push(...configurationSnippets);
				}
			});

			this.setCompoundSchemaValues();
			this._onDidRegisterDebugger.fire();
		}


added => {
				added.value.forEach(rawAdapter => {
					if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) {
						added.collector.error(nls.localize('debugNoType', "Debugger 'type' can not be omitted and must be of type 'string'."));
					}
					if (rawAdapter.enableBreakpointsFor) {
						rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => {
							this.breakpointModeIdsSet.add(modeId);
						});
					}

					if (rawAdapter.type !== '*') {
						const existing = this.getDebugger(rawAdapter.type);
						if (existing) {
							existing.merge(rawAdapter, added.description);
						} else {
							this.debuggers.push(this.instantiationService.createInstance(Debugger, this, rawAdapter, added.description));
						}
					}
				});
			}


rawAdapter => {
					if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) {
						added.collector.error(nls.localize('debugNoType', "Debugger 'type' can not be omitted and must be of type 'string'."));
					}
					if (rawAdapter.enableBreakpointsFor) {
						rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => {
							this.breakpointModeIdsSet.add(modeId);
						});
					}

					if (rawAdapter.type !== '*') {
						const existing = this.getDebugger(rawAdapter.type);
						if (existing) {
							existing.merge(rawAdapter, added.description);
						} else {
							this.debuggers.push(this.instantiationService.createInstance(Debugger, this, rawAdapter, added.description));
						}
					}
				}


modeId => {
							this.breakpointModeIdsSet.add(modeId);
						}


extension => {
				extension.value.forEach(rawAdapter => {
					if (rawAdapter.type === '*') {
						this.debuggers.forEach(dbg => dbg.merge(rawAdapter, extension.description));
					}
				});
			}


rawAdapter => {
					if (rawAdapter.type === '*') {
						this.debuggers.forEach(dbg => dbg.merge(rawAdapter, extension.description));
					}
				}


dbg => dbg.merge(rawAdapter, extension.description)


removed => {
				const removedTypes = removed.value.map(rawAdapter => rawAdapter.type);
				this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1);
			}


rawAdapter => rawAdapter.type


d => removedTypes.indexOf(d.type) === -1


adapter => {
				const items = (<IJSONSchema>launchSchema.properties!['configurations'].items);
				const schemaAttributes = adapter.getSchemaAttributes();
				if (schemaAttributes && items.oneOf) {
					items.oneOf.push(...schemaAttributes);
				}
				const configurationSnippets = adapter.configurationSnippets;
				if (configurationSnippets && items.defaultSnippets) {
					items.defaultSnippets.push(...configurationSnippets);
				}
			}


(extensions, delta) => {
			delta.removed.forEach(removed => {
				removed.value.forEach(breakpoints => this.breakpointModeIdsSet.delete(breakpoints.language));
			});
			delta.added.forEach(added => {
				added.value.forEach(breakpoints => this.breakpointModeIdsSet.add(breakpoints.language));
			});
		}


removed => {
				removed.value.forEach(breakpoints => this.breakpointModeIdsSet.delete(breakpoints.language));
			}


breakpoints => this.breakpointModeIdsSet.delete(breakpoints.language)


added => {
				added.value.forEach(breakpoints => this.breakpointModeIdsSet.add(breakpoints.language));
			}


breakpoints => this.breakpointModeIdsSet.add(breakpoints.language)


() => {
			this.initLaunches();
			this.selectConfiguration(undefined);
			this.setCompoundSchemaValues();
		}


e => {
			if (e.affectsConfiguration('launch')) {
				// A change happen in the launch.json. If there is already a launch configuration selected, do not change the selection.
				this.selectConfiguration(undefined);
				this.setCompoundSchemaValues();
			}
		}


folder => this.instantiationService.createInstance(Launch, this, folder)


l =>
			l.getConfigurationNames(true)


f => f.name


l => l.workspace && l.workspace.uri.toString() === workspaceUri.toString()


l => !!(l && l.getConfigurationNames().length)


dbg => strings.equalsIgnoreCase(dbg.type, type)


a => language && a.languages && a.languages.indexOf(language) >= 0


dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider()


(first, second) => first.label.localeCompare(second.label)


c => ({ label: c.label, debugger: c })


picked => {
				if (picked && picked.debugger) {
					return picked.debugger;
				}
				if (picked) {
					this.commandService.executeCommand('debug.installAdditionalDebuggers');
				}
				return undefined;
			}


compound => compound.name === name


cfg => cfg && typeof cfg.name === 'string'


c => c.name


compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length


config => config && config.name === name


abstract class AbstractLaunch {
	protected abstract getConfig(): IGlobalConfig | undefined;

	constructor(protected configurationManager: ConfigurationManager) {
	}

	getCompound(name: string): ICompound | undefined {
		const config = this.getConfig();
		if (!config || !config.compounds) {
			return undefined;
		}

		return config.compounds.find(compound => compound.name === name);
	}

	getConfigurationNames(ignoreCompoundsAndPresentation = false): string[] {
		const config = this.getConfig();
		if (!config || (!Array.isArray(config.configurations) && !Array.isArray(config.compounds))) {
			return [];
		} else {
			const configurations: (IConfig | ICompound)[] = [];
			if (config.configurations) {
				configurations.push(...config.configurations.filter(cfg => cfg && typeof cfg.name === 'string'));
			}

			if (ignoreCompoundsAndPresentation) {
				return configurations.map(c => c.name);
			}

			if (config.compounds) {
				configurations.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length));
			}
			return getVisibleAndSorted(configurations).map(c => c.name);
		}
	}

	getConfiguration(name: string): IConfig | undefined {
		// We need to clone the configuration in order to be able to make changes to it #42198
		const config = objects.deepClone(this.getConfig());
		if (!config || !config.configurations) {
			return undefined;
		}

		return config.configurations.find(config => config && config.name === name);
	}

	async getInitialConfigurationContent(folderUri?: uri, type?: string, token?: CancellationToken): Promise<string> {
		let content = '';
		const adapter = await this.configurationManager.guessDebugger(type);
		if (adapter) {
			const initialConfigs = await this.configurationManager.provideDebugConfigurations(folderUri, adapter.type, token || CancellationToken.None);
			content = await adapter.getInitialConfigurationContent(initialConfigs);
		}
		return content;
	}

	get hidden(): boolean {
		return false;
	}
}


class Launch extends AbstractLaunch implements ILaunch {

	constructor(
		configurationManager: ConfigurationManager,
		public workspace: IWorkspaceFolder,
		@IFileService private readonly fileService: IFileService,
		@ITextFileService private readonly textFileService: ITextFileService,
		@IEditorService private readonly editorService: IEditorService,
		@IConfigurationService private readonly configurationService: IConfigurationService
	) {
		super(configurationManager);
	}

	get uri(): uri {
		return resources.joinPath(this.workspace.uri, '/.vscode/launch.json');
	}

	get name(): string {
		return this.workspace.name;
	}

	protected getConfig(): IGlobalConfig | undefined {
		return this.configurationService.inspect<IGlobalConfig>('launch', { resource: this.workspace.uri }).workspaceFolderValue;
	}

	async openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }> {
		const resource = this.uri;
		let created = false;
		let content = '';
		try {
			const fileContent = await this.fileService.readFile(resource);
			content = fileContent.value.toString();
		} catch {
			// launch.json not found: create one by collecting launch configs from debugConfigProviders
			content = await this.getInitialConfigurationContent(this.workspace.uri, type, token);
			if (content) {
				created = true; // pin only if config file is created #8727
				try {
					await this.textFileService.write(resource, content);
				} catch (error) {
					throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message));
				}
			}
		}

		if (content === '') {
			return { editor: null, created: false };
		}

		const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`);
		let startLineNumber = 1;
		for (let i = 0; i < index; i++) {
			if (content.charAt(i) === '\n') {
				startLineNumber++;
			}
		}
		const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined;

		const editor = await this.editorService.openEditor({
			resource,
			options: {
				selection,
				preserveFocus,
				pinned: created,
				revealIfVisible: true
			},
		}, ACTIVE_GROUP);

		return ({
			editor: withUndefinedAsNull(editor),
			created
		});
	}

	async writeConfiguration(configuration: IConfig): Promise<void> {
		const fullConfig = objects.deepClone(this.getConfig()!);
		fullConfig.configurations.push(configuration);
		await this.configurationService.updateValue('launch', fullConfig, { resource: this.workspace.uri }, ConfigurationTarget.WORKSPACE_FOLDER);
	}
}


class WorkspaceLaunch extends AbstractLaunch implements ILaunch {
	constructor(
		configurationManager: ConfigurationManager,
		@IEditorService private readonly editorService: IEditorService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
	) {
		super(configurationManager);
	}

	get workspace(): undefined {
		return undefined;
	}

	get uri(): uri {
		return this.contextService.getWorkspace().configuration!;
	}

	get name(): string {
		return nls.localize('workspace', "workspace");
	}

	protected getConfig(): IGlobalConfig | undefined {
		return this.configurationService.inspect<IGlobalConfig>('launch').workspaceValue;
	}

	async openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }> {
		let launchExistInFile = !!this.getConfig();
		if (!launchExistInFile) {
			// Launch property in workspace config not found: create one by collecting launch configs from debugConfigProviders
			let content = await this.getInitialConfigurationContent(undefined, type, token);
			if (content) {
				await this.configurationService.updateValue('launch', json.parse(content), ConfigurationTarget.WORKSPACE);
			} else {
				return { editor: null, created: false };
			}
		}

		const editor = await this.editorService.openEditor({
			resource: this.contextService.getWorkspace().configuration!,
			options: { preserveFocus }
		}, ACTIVE_GROUP);

		return ({
			editor: withUndefinedAsNull(editor),
			created: false
		});
	}
}


class UserLaunch extends AbstractLaunch implements ILaunch {

	constructor(
		configurationManager: ConfigurationManager,
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IPreferencesService private readonly preferencesService: IPreferencesService
	) {
		super(configurationManager);
	}

	get workspace(): undefined {
		return undefined;
	}

	get uri(): uri {
		return this.preferencesService.userSettingsResource;
	}

	get name(): string {
		return nls.localize('user settings', "user settings");
	}

	get hidden(): boolean {
		return true;
	}

	protected getConfig(): IGlobalConfig | undefined {
		return this.configurationService.inspect<IGlobalConfig>('launch').userValue;
	}

	async openConfigFile(preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> {
		const editor = await this.preferencesService.openGlobalSettings(true, { preserveFocus });
		return ({
			editor: withUndefinedAsNull(editor),
			created: false
		});
	}
}


// debuggers
registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable {
		debugTypes.forEach(debugType => this.debugAdapterFactories.set(debugType, debugAdapterLauncher));
		return {
			dispose: () => {
				debugTypes.forEach(debugType => this.debugAdapterFactories.delete(debugType));
			}
		};
	}


createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined {
		let dap = this.debugAdapterFactories.get(session.configuration.type);
		if (dap) {
			return dap.createDebugAdapter(session);
		}
		return undefined;
	}


substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig> {
		let dap = this.debugAdapterFactories.get(debugType);
		if (dap) {
			return dap.substituteVariables(folder, config);
		}
		return Promise.resolve(config);
	}


runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined> {
		let tl = this.debugAdapterFactories.get(debugType);
		if (tl) {
			return tl.runInTerminal(args);
		}
		return Promise.resolve(void 0);
	}


// debug adapter
registerDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): IDisposable {
		this.adapterDescriptorFactories.push(debugAdapterProvider);
		return {
			dispose: () => {
				this.unregisterDebugAdapterDescriptorFactory(debugAdapterProvider);
			}
		};
	}


unregisterDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): void {
		const ix = this.adapterDescriptorFactories.indexOf(debugAdapterProvider);
		if (ix >= 0) {
			this.adapterDescriptorFactories.splice(ix, 1);
		}
	}


getDebugAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor | undefined> {

		const config = session.configuration;

		// first try legacy proposed API: DebugConfigurationProvider.debugAdapterExecutable
		const providers0 = this.configProviders.filter(p => p.type === config.type && p.debugAdapterExecutable);
		if (providers0.length === 1 && providers0[0].debugAdapterExecutable) {
			return providers0[0].debugAdapterExecutable(session.root ? session.root.uri : undefined);
		} else {
			// TODO@AW handle n > 1 case
		}

		// new API
		const providers = this.adapterDescriptorFactories.filter(p => p.type === config.type && p.createDebugAdapterDescriptor);
		if (providers.length === 1) {
			return providers[0].createDebugAdapterDescriptor(session);
		} else {
			// TODO@AW handle n > 1 case
		}
		return Promise.resolve(undefined);
	}


getDebuggerLabel(type: string): string | undefined {
		const dbgr = this.getDebugger(type);
		if (dbgr) {
			return dbgr.label;
		}

		return undefined;
	}


// debug configurations
registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable {
		this.configProviders.push(debugConfigurationProvider);
		return {
			dispose: () => {
				this.unregisterDebugConfigurationProvider(debugConfigurationProvider);
			}
		};
	}


unregisterDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): void {
		const ix = this.configProviders.indexOf(debugConfigurationProvider);
		if (ix >= 0) {
			this.configProviders.splice(ix, 1);
		}
	}


/**
	 * if scope is not specified,a value of DebugConfigurationProvideTrigger.Initial is assumed.
	 */
hasDebugConfigurationProvider(debugType: string, triggerKind?: DebugConfigurationProviderTriggerKind): boolean {
		if (triggerKind === undefined) {
			triggerKind = DebugConfigurationProviderTriggerKind.Initial;
		}
		// check if there are providers for the given type that contribute a provideDebugConfigurations method
		const provider = this.configProviders.find(p => p.provideDebugConfigurations && (p.type === debugType) && (p.triggerKind === triggerKind));
		return !!provider;
	}


async resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, config: IConfig, token: CancellationToken): Promise<IConfig | null | undefined> {
		await this.activateDebuggers('onDebugResolve', type);
		// pipe the config through the promises sequentially. Append at the end the '*' types
		const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration)
			.concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration));

		let result: IConfig | null | undefined = config;
		await sequence(providers.map(provider => async () => {
			// If any provider returned undefined or null make sure to respect that and do not pass the result to more resolver
			if (result) {
				result = await provider.resolveDebugConfiguration!(folderUri, result, token);
			}
		}));

		return result;
	}


async resolveDebugConfigurationWithSubstitutedVariables(folderUri: uri | undefined, type: string | undefined, config: IConfig, token: CancellationToken): Promise<IConfig | null | undefined> {
		// pipe the config through the promises sequentially. Append at the end the '*' types
		const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfigurationWithSubstitutedVariables)
			.concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfigurationWithSubstitutedVariables));

		let result: IConfig | null | undefined = config;
		await sequence(providers.map(provider => async () => {
			// If any provider returned undefined or null make sure to respect that and do not pass the result to more resolver
			if (result) {
				result = await provider.resolveDebugConfigurationWithSubstitutedVariables!(folderUri, result, token);
			}
		}));

		return result;
	}


async provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise<any[]> {
		await this.activateDebuggers('onDebugInitialConfigurations');
		const results = await Promise.all(this.configProviders.filter(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Initial && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token)));

		return results.reduce((first, second) => first.concat(second), []);
	}


async getDynamicProviders(): Promise<{ label: string, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]> {
		const extensions = await this.extensionService.getExtensions();
		const onDebugDynamicConfigurationsName = 'onDebugDynamicConfigurations';
		const debugDynamicExtensionsTypes = extensions.map(e => {
			const activationEvent = e.activationEvents && e.activationEvents.find(e => e.includes(onDebugDynamicConfigurationsName));
			if (activationEvent) {
				const type = activationEvent.substr(onDebugDynamicConfigurationsName.length);
				return type || (e.contributes && e.contributes.debuggers && e.contributes.debuggers.length ? e.contributes.debuggers[0].type : undefined);
			}

			return undefined;
		}).filter(e => typeof e === 'string') as string[];

		return debugDynamicExtensionsTypes.map(type => {
			return {
				label: this.getDebuggerLabel(type)!,
				pick: async () => {
					await this.activateDebuggers(onDebugDynamicConfigurationsName, type);
					const token = new CancellationTokenSource();
					const picks: Promise<{ label: string, launch: ILaunch, config: IConfig }[]>[] = [];
					const provider = this.configProviders.filter(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations)[0];
					this.getLaunches().forEach(launch => {
						if (launch.workspace && provider) {
							picks.push(provider.provideDebugConfigurations!(launch.workspace.uri, token.token).then(configurations => configurations.map(config => ({
								label: config.name,
								config,
								buttons: [{
									iconClass: 'codicon-gear',
									tooltip: nls.localize('editLaunchConfig', "Edit Debug Configuration in launch.json")
								}],
								launch
							}))));
						}
					});
					const promiseOfPicks = Promise.all(picks).then(result => result.reduce((first, second) => first.concat(second), []));

					const result = await this.quickInputService.pick<{ label: string, launch: ILaunch, config: IConfig }>(promiseOfPicks, {
						placeHolder: nls.localize('selectConfiguration', "Select Launch Configuration"),
						onDidTriggerItemButton: async (context) => {
							await this.quickInputService.cancel();
							const { launch, config } = context.item;
							await launch.openConfigFile(false, config.type);
							// Only Launch have a pin trigger button
							await (launch as Launch).writeConfiguration(config);
							this.selectConfiguration(launch, config.name);
						}
					});
					if (!result) {
						// User canceled quick input we should notify the provider to cancel computing configurations
						token.cancel();
					}

					return result;
				}
			};
		});
	}


getAllConfigurations(): { launch: ILaunch; name: string; presentation?: IConfigPresentation }[] {
		const all: { launch: ILaunch, name: string, presentation?: IConfigPresentation }[] = [];
		for (let l of this.launches) {
			for (let name of l.getConfigurationNames()) {
				const config = l.getConfiguration(name) || l.getCompound(name);
				if (config) {
					all.push({ launch: l, name, presentation: config.presentation });
				}
			}
		}

		return getVisibleAndSorted(all);
	}


private registerListeners(): void {
		debuggersExtPoint.setHandler((extensions, delta) => {
			delta.added.forEach(added => {
				added.value.forEach(rawAdapter => {
					if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) {
						added.collector.error(nls.localize('debugNoType', "Debugger 'type' can not be omitted and must be of type 'string'."));
					}
					if (rawAdapter.enableBreakpointsFor) {
						rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => {
							this.breakpointModeIdsSet.add(modeId);
						});
					}

					if (rawAdapter.type !== '*') {
						const existing = this.getDebugger(rawAdapter.type);
						if (existing) {
							existing.merge(rawAdapter, added.description);
						} else {
							this.debuggers.push(this.instantiationService.createInstance(Debugger, this, rawAdapter, added.description));
						}
					}
				});
			});

			// take care of all wildcard contributions
			extensions.forEach(extension => {
				extension.value.forEach(rawAdapter => {
					if (rawAdapter.type === '*') {
						this.debuggers.forEach(dbg => dbg.merge(rawAdapter, extension.description));
					}
				});
			});

			delta.removed.forEach(removed => {
				const removedTypes = removed.value.map(rawAdapter => rawAdapter.type);
				this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1);
			});

			// update the schema to include all attributes, snippets and types from extensions.
			this.debuggers.forEach(adapter => {
				const items = (<IJSONSchema>launchSchema.properties!['configurations'].items);
				const schemaAttributes = adapter.getSchemaAttributes();
				if (schemaAttributes && items.oneOf) {
					items.oneOf.push(...schemaAttributes);
				}
				const configurationSnippets = adapter.configurationSnippets;
				if (configurationSnippets && items.defaultSnippets) {
					items.defaultSnippets.push(...configurationSnippets);
				}
			});

			this.setCompoundSchemaValues();
			this._onDidRegisterDebugger.fire();
		});

		breakpointsExtPoint.setHandler((extensions, delta) => {
			delta.removed.forEach(removed => {
				removed.value.forEach(breakpoints => this.breakpointModeIdsSet.delete(breakpoints.language));
			});
			delta.added.forEach(added => {
				added.value.forEach(breakpoints => this.breakpointModeIdsSet.add(breakpoints.language));
			});
		});

		this.toDispose.push(Event.any<IWorkspaceFoldersChangeEvent | WorkbenchState>(this.contextService.onDidChangeWorkspaceFolders, this.contextService.onDidChangeWorkbenchState)(() => {
			this.initLaunches();
			this.selectConfiguration(undefined);
			this.setCompoundSchemaValues();
		}));
		this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {
			if (e.affectsConfiguration('launch')) {
				// A change happen in the launch.json. If there is already a launch configuration selected, do not change the selection.
				this.selectConfiguration(undefined);
				this.setCompoundSchemaValues();
			}
		}));
	}


private initLaunches(): void {
		this.launches = this.contextService.getWorkspace().folders.map(folder => this.instantiationService.createInstance(Launch, this, folder));
		if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
			this.launches.push(this.instantiationService.createInstance(WorkspaceLaunch, this));
		}
		this.launches.push(this.instantiationService.createInstance(UserLaunch, this));

		if (this.selectedLaunch && this.launches.indexOf(this.selectedLaunch) === -1) {
			this.selectConfiguration(undefined);
		}
	}


private setCompoundSchemaValues(): void {
		const compoundConfigurationsSchema = (<IJSONSchema>launchSchema.properties!['compounds'].items).properties!['configurations'];
		const launchNames = this.launches.map(l =>
			l.getConfigurationNames(true)).reduce((first, second) => first.concat(second), []);
		(<IJSONSchema>compoundConfigurationsSchema.items).oneOf![0].enum = launchNames;
		(<IJSONSchema>compoundConfigurationsSchema.items).oneOf![1].properties!.name.enum = launchNames;

		const folderNames = this.contextService.getWorkspace().folders.map(f => f.name);
		(<IJSONSchema>compoundConfigurationsSchema.items).oneOf![1].properties!.folder.enum = folderNames;

		jsonRegistry.registerSchema(launchSchemaId, launchSchema);
	}


getLaunches(): ILaunch[] {
		return this.launches;
	}


getLaunch(workspaceUri: uri | undefined): ILaunch | undefined {
		if (!uri.isUri(workspaceUri)) {
			return undefined;
		}

		return this.launches.find(l => l.workspace && l.workspace.uri.toString() === workspaceUri.toString());
	}


getWorkspaceLaunch(): ILaunch | undefined {
		if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
			return this.launches[this.launches.length - 1];
		}

		return undefined;
	}


selectConfiguration(launch: ILaunch | undefined, name?: string, config?: IConfig): void {
		if (typeof launch === 'undefined') {
			const rootUri = this.historyService.getLastActiveWorkspaceRoot();
			launch = this.getLaunch(rootUri);
			if (!launch || launch.getConfigurationNames().length === 0) {
				launch = first(this.launches, l => !!(l && l.getConfigurationNames().length), launch) || this.launches[0];
			}
		}

		const previousLaunch = this.selectedLaunch;
		const previousName = this.selectedName;
		this.selectedLaunch = launch;

		if (this.selectedLaunch) {
			this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE);
		} else {
			this.storageService.remove(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE);
		}
		const names = launch ? launch.getConfigurationNames() : [];
		if ((name && names.indexOf(name) >= 0) || config) {
			this.setSelectedLaunchName(name);
		} else if (!this.selectedName || names.indexOf(this.selectedName) === -1) {
			this.setSelectedLaunchName(names.length ? names[0] : undefined);
		}

		this.selectedConfig = config || (this.selectedLaunch && this.selectedName ? this.selectedLaunch.getConfiguration(this.selectedName) : undefined);
		if (this.selectedConfig) {
			this.debugConfigurationTypeContext.set(this.selectedConfig.type);
			this.storageService.store(DEBUG_SELECTED_CONFIG, JSON.stringify(this.selectedConfig), StorageScope.WORKSPACE);
		} else {
			this.debugConfigurationTypeContext.reset();
			this.storageService.remove(DEBUG_SELECTED_CONFIG, StorageScope.WORKSPACE);
		}

		if (this.selectedLaunch !== previousLaunch || this.selectedName !== previousName) {
			this._onDidSelectConfigurationName.fire();
		}
	}


canSetBreakpointsIn(model: ITextModel): boolean {
		const modeId = model.getLanguageIdentifier().language;
		if (!modeId || modeId === 'jsonc' || modeId === 'log') {
			// do not allow breakpoints in our settings files and output
			return false;
		}
		if (this.configurationService.getValue<IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
			return true;
		}

		return this.breakpointModeIdsSet.has(modeId);
	}


getDebugger(type: string): Debugger | undefined {
		return this.debuggers.find(dbg => strings.equalsIgnoreCase(dbg.type, type));
	}


isDebuggerInterestedInLanguage(language: string): boolean {
		return !!this.debuggers.find(a => language && a.languages && a.languages.indexOf(language) >= 0);
	}


async guessDebugger(type?: string): Promise<Debugger | undefined> {
		if (type) {
			const adapter = this.getDebugger(type);
			return Promise.resolve(adapter);
		}

		const activeTextEditorControl = this.editorService.activeTextEditorControl;
		let candidates: Debugger[] | undefined;
		if (isCodeEditor(activeTextEditorControl)) {
			const model = activeTextEditorControl.getModel();
			const language = model ? model.getLanguageIdentifier().language : undefined;
			const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0);
			if (adapters.length === 1) {
				return adapters[0];
			}
			if (adapters.length > 1) {
				candidates = adapters;
			}
		}

		if (!candidates) {
			await this.activateDebuggers('onDebugInitialConfigurations');
			candidates = this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider());
		}

		candidates.sort((first, second) => first.label.localeCompare(second.label));
		const picks = candidates.map(c => ({ label: c.label, debugger: c }));
		return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: nls.localize('more', "More..."), debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
			.then(picked => {
				if (picked && picked.debugger) {
					return picked.debugger;
				}
				if (picked) {
					this.commandService.executeCommand('debug.installAdditionalDebuggers');
				}
				return undefined;
			});
	}


async activateDebuggers(activationEvent: string, debugType?: string): Promise<void> {
		const promises: Promise<any>[] = [
			this.extensionService.activateByEvent(activationEvent),
			this.extensionService.activateByEvent('onDebug')
		];
		if (debugType) {
			promises.push(this.extensionService.activateByEvent(`${activationEvent}:${debugType}`));
		}
		await Promise.all(promises);
	}


private setSelectedLaunchName(selectedName: string | undefined): void {
		this.selectedName = selectedName;

		if (this.selectedName) {
			this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE);
		} else {
			this.storageService.remove(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE);
		}
	}


dispose(): void {
		this.toDispose = dispose(this.toDispose);
	}


protected abstract getConfig(): IGlobalConfig | undefined;


getCompound(name: string): ICompound | undefined {
		const config = this.getConfig();
		if (!config || !config.compounds) {
			return undefined;
		}

		return config.compounds.find(compound => compound.name === name);
	}