() => {
    beforeEach(() => {
      instance.enable();
    });

    it('removes disabled state class', () => {
      expect(
        instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState),
      ).to.equal(false);
      instance.disable();
      expect(
        instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState),
      ).to.equal(true);
    });

    it('removes aria-disabled attribute', () => {
      expect(instance.element.getAttribute('aria-disabled')).to.equal(null);
      instance.disable();
      expect(instance.element.getAttribute('aria-disabled')).to.equal('true');
    });

    it('sets isDisabled flag to true', () => {
      instance.disable();
      expect(instance.isDisabled).to.equal(true);
    });

    describe('select one element', () => {
      beforeEach(() => {
        instance.type = 'select-one';
        instance.disable();
      });

      it('sets tabindex attribute', () => {
        expect(instance.element.getAttribute('tabindex')).to.equal('-1');
      });
    });
  }


() => {
      instance.enable();
    }


() => {
      expect(
        instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState),
      ).to.equal(false);
      instance.disable();
      expect(
        instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState),
      ).to.equal(true);
    }


() => {
      expect(instance.element.getAttribute('aria-disabled')).to.equal(null);
      instance.disable();
      expect(instance.element.getAttribute('aria-disabled')).to.equal('true');
    }


() => {
      instance.disable();
      expect(instance.isDisabled).to.equal(true);
    }


() => {
      beforeEach(() => {
        instance.type = 'select-one';
        instance.disable();
      });

      it('sets tabindex attribute', () => {
        expect(instance.element.getAttribute('tabindex')).to.equal('-1');
      });
    }


() => {
        instance.type = 'select-one';
        instance.disable();
      }


() => {
        expect(instance.element.getAttribute('tabindex')).to.equal('-1');
      }


() => {
    let elementToWrap;

    beforeEach(() => {
      elementToWrap = document.createElement('div');
      elementToWrap.id = 'wrap-test';
      document.body.appendChild(elementToWrap);
    });

    afterEach(() => {
      document.getElementById('wrap-test')!.remove();
    });

    it('wraps passed element inside element', () => {
      expect(instance.element.querySelector('div#wrap-test')).to.equal(null);
      instance.wrap(document.querySelector('div#wrap-test'));
      expect(instance.element.querySelector('div#wrap-test')).to.equal(
        elementToWrap,
      );
    });
  }


() => {
      elementToWrap = document.createElement('div');
      elementToWrap.id = 'wrap-test';
      document.body.appendChild(elementToWrap);
    }


() => {
      document.getElementById('wrap-test')!.remove();
    }


() => {
      expect(instance.element.querySelector('div#wrap-test')).to.equal(null);
      instance.wrap(document.querySelector('div#wrap-test'));
      expect(instance.element.querySelector('div#wrap-test')).to.equal(
        elementToWrap,
      );
    }


() => {
    let elementToUnwrap;

    beforeEach(() => {
      elementToUnwrap = document.createElement('div');
      elementToUnwrap.id = 'unwrap-test';
      document.body.appendChild(elementToUnwrap);
      instance.wrap(document.getElementById('unwrap-test'));
    });

    afterEach(() => {
      document.body.removeChild(document.getElementById('unwrap-test') as Node);
    });

    it('moves wrapped element outside of element', () => {
      expect(
        instance.element.querySelector('div#unwrap-test'),
      ).to.be.instanceof(HTMLElement);
      instance.unwrap(elementToUnwrap);
      expect(instance.element.querySelector('div#unwrap-test')).to.equal(null);
      expect(document.querySelector('div#unwrap-test')).to.be.instanceof(
        HTMLElement,
      );
    });

    it('removes element from DOM', () => {
      expect(document.getElementById('container')).to.not.equal(null);
      instance.unwrap(elementToUnwrap);
      expect(document.getElementById('container')).to.equal(null);
    });
  }


() => {
      elementToUnwrap = document.createElement('div');
      elementToUnwrap.id = 'unwrap-test';
      document.body.appendChild(elementToUnwrap);
      instance.wrap(document.getElementById('unwrap-test'));
    }


() => {
      document.body.removeChild(document.getElementById('unwrap-test') as Node);
    }


() => {
      expect(
        instance.element.querySelector('div#unwrap-test'),
      ).to.be.instanceof(HTMLElement);
      instance.unwrap(elementToUnwrap);
      expect(instance.element.querySelector('div#unwrap-test')).to.equal(null);
      expect(document.querySelector('div#unwrap-test')).to.be.instanceof(
        HTMLElement,
      );
    }


() => {
      expect(document.getElementById('container')).to.not.equal(null);
      instance.unwrap(elementToUnwrap);
      expect(document.getElementById('container')).to.equal(null);
    }


() => {
    beforeEach(() => {
      instance.removeLoadingState();
    });

    it('adds loading state class', () => {
      expect(
        instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState),
      ).to.equal(false);
      instance.addLoadingState();
      expect(
        instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState),
      ).to.equal(true);
    });

    it('sets aria-busy attribute to true', () => {
      expect(instance.element.getAttribute('aria-busy')).to.equal(null);
      instance.addLoadingState();
      expect(instance.element.getAttribute('aria-busy')).to.equal('true');
    });

    it('sets isLoading flag to false', () => {
      expect(instance.isLoading).to.equal(false);
      instance.addLoadingState();
      expect(instance.isLoading).to.equal(true);
    });
  }


() => {
      expect(
        instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState),
      ).to.equal(false);
      instance.addLoadingState();
      expect(
        instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState),
      ).to.equal(true);
    }


() => {
      expect(instance.element.getAttribute('aria-busy')).to.equal(null);
      instance.addLoadingState();
      expect(instance.element.getAttribute('aria-busy')).to.equal('true');
    }


() => {
      expect(instance.isLoading).to.equal(false);
      instance.addLoadingState();
      expect(instance.isLoading).to.equal(true);
    }


() => {
    beforeEach(() => {
      instance.addLoadingState();
    });

    it('removes loading state class', () => {
      expect(
        instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState),
      ).to.equal(true);
      instance.removeLoadingState();
      expect(
        instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState),
      ).to.equal(false);
    });

    it('removes aria-busy attribute', () => {
      expect(instance.element.getAttribute('aria-busy')).to.equal('true');
      instance.removeLoadingState();
      expect(instance.element.getAttribute('aria-busy')).to.equal(null);
    });

    it('sets isLoading flag to true', () => {
      expect(instance.isLoading).to.equal(true);
      instance.removeLoadingState();
      expect(instance.isLoading).to.equal(false);
    });
  }


() => {
      instance.addLoadingState();
    }


() => {
      expect(
        instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState),
      ).to.equal(true);
      instance.removeLoadingState();
      expect(
        instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState),
      ).to.equal(false);
    }


() => {
      expect(instance.element.getAttribute('aria-busy')).to.equal('true');
      instance.removeLoadingState();
      expect(instance.element.getAttribute('aria-busy')).to.equal(null);
    }


() => {
      expect(instance.isLoading).to.equal(true);
      instance.removeLoadingState();
      expect(instance.isLoading).to.equal(false);
    }


@injectable()
export class DdlViewerService {
  private ddlMetadata = new Map<string, Promise<string>>();

  constructor(private graphQLService: GraphQLService) {
    makeObservable<DdlViewerService, 'ddlMetadata'>(this, {
      ddlMetadata: observable,
    });
  }

  getMetadata(nodeId: string) {
    return this.ddlMetadata.get(nodeId);
  }

  async loadDdlMetadata(nodeId: string): Promise<string> {
    const cached = this.ddlMetadata.get(nodeId);
    if (cached) {
      return cached;
    }
    const metadata = this.fetchDdlMetadata(nodeId);
    this.ddlMetadata.set(nodeId, metadata);
    return metadata;
  }

  resetMetadata(nodeId: string): void {
    this.ddlMetadata.delete(nodeId);
  }

  private async fetchDdlMetadata(nodeId: string): Promise<string> {
    const response = await this.graphQLService.sdk.metadataGetNodeDDL({ nodeId });
    return response.metadataGetNodeDDL || '';
  }
}


getMetadata(nodeId: string) {
    return this.ddlMetadata.get(nodeId);
  }


async loadDdlMetadata(nodeId: string): Promise<string> {
    const cached = this.ddlMetadata.get(nodeId);
    if (cached) {
      return cached;
    }
    const metadata = this.fetchDdlMetadata(nodeId);
    this.ddlMetadata.set(nodeId, metadata);
    return metadata;
  }


resetMetadata(nodeId: string): void {
    this.ddlMetadata.delete(nodeId);
  }


private async fetchDdlMetadata(nodeId: string): Promise<string> {
    const response = await this.graphQLService.sdk.metadataGetNodeDDL({ nodeId });
    return response.metadataGetNodeDDL || '';
  }


export interface DateTimePickerTabsProps extends WithStyles<typeof styles, true> {
    view: DateTimePickerViewType;
    onChange: (view: DateTimePickerView) => void;
    dateRangeIcon: React.ReactNode;
    timeIcon: React.ReactNode;
}


/**
 * Compute role.
 */
export function getRole(args: GetRoleArgs, opts?: pulumi.InvokeOptions): Promise<GetRoleResult> {
    if (!opts) {
        opts = {}
    }

    if (!opts.version) {
        opts.version = utilities.getVersion();
    }
    return pulumi.runtime.invoke("azure-native:databoxedge/v20210201preview:getRole", {
        "deviceName": args.deviceName,
        "name": args.name,
        "resourceGroupName": args.resourceGroupName,
    }, opts);
}


/**
 * Compute role.
 */
export interface GetRoleResult {
    /**
     * The path ID that uniquely identifies the object.
     */
    readonly id: string;
    /**
     * Role type.
     */
    readonly kind: string;
    /**
     * The object name.
     */
    readonly name: string;
    /**
     * Role configured on ASE resource
     */
    readonly systemData: outputs.databoxedge.v20210201preview.SystemDataResponse;
    /**
     * The hierarchical type of the object.
     */
    readonly type: string;
}


export default async function getRoute(dep: string, arr: string, rawType: string) {
  let type = (rawType === 'B788' || rawType === 'B789' || rawType === 'B78X' || rawType === 'B77W') ? 'B773' : rawType;
  let res = await axios.request({
    url: `http://rfinder.asalink.net/planner.php?op=genroute&adep=${dep}&ades=${arr}&reqfl=350&arctyp=${type}&metmode=NONE&nats=Y&rra=Y&usesid=Y&usestar=Y&tfr=Y&force&avoid`,
    method: 'GET',
    xsrfCookieName: 'PHPSESSID',
    headers: {
      'Cookie': `PHPSESSID=${myCookie}`
    }
  });
  return res.data;
}


(nick) => {
        this.numeroParticipantes++;
        if (this.sonido) {
          sound.volume (0.1);
          sound.play();
        }
        console.log ('se ha conectado ' + nick);
        this.participantes.push ({
          nickName: nick,
          contestado: false
        });
    }


(respuesta) => {
        this.numeroRespuestas++;
        if (this.sonido) {
          sound.volume (0.1);
          sound.play();
        }
        console.log ('ha contestado ' + respuesta.nick);
        console.log ('respuestas ');
        console.log (respuesta.respuestas);
        this.respuestas.push (respuesta.respuestas);

        // Actualizo el histograma y los donuts

        let aciertos = 0;
        for (let i = 0; i < respuesta.respuestas.Preguntas.length; i++) {
          const pregunta = this.preguntas.filter (p => p.id === respuesta.respuestas.Preguntas[i])[0];
          console.log ('pregunta ' + i);
          console.log (pregunta);
          const indexDonut = this.misDonuts.findIndex (elemento => elemento[0].preguntaId === pregunta.id);
          console.log ('donut de la pregunta');
          console.log (this.misDonuts[indexDonut]);
          if (pregunta.tipo === 'Emparejamiento') {
            // primero actualizamos el histograma
            if (respuesta.respuestas.Respuestas[i] !== undefined) {
              let n = 0;
              for (let j = 0; j < pregunta.emparejamientos.length; j++) {
                if (pregunta.emparejamientos[j].r === respuesta.respuestas.Respuestas[i][j]) {
                  n++;
                }
              }
              if (n === pregunta.emparejamientos.length) {
                aciertos++;
                this.misDonuts[indexDonut][1].cont++; // respuesta correcta
              } else {
                console.log ('voy a modificar el donut de emparejamientos');
                this.misDonuts[indexDonut][2].cont++; // respuesta incorecta
              }
            } else {
              this.misDonuts[indexDonut][3].cont++; // respuesta en blanco
            }

          } else if (pregunta.tipo === 'Cuatro opciones') {
            // actualizo histograma
            if (pregunta.respuestaCorrecta === respuesta.respuestas.Respuestas[i][0]) {
              aciertos++;
            }
            console.log ('voy a modificar el donut de 4 opciones');
            this.misDonuts[indexDonut].filter (entrada => entrada.respuesta === respuesta.respuestas.Respuestas[i][0])[0].cont++;
          } else {
            console.log ('pregunta de respuesta abierta o verdadero o falso');
            if (pregunta.respuestaCorrecta === respuesta.respuestas.Respuestas[i][0]) {
              aciertos++;
              this.misDonuts[indexDonut][1].cont++;  // respuesta correcta
            } else if (respuesta.respuestas.Respuestas[i][0] === '-') {
                console.log ('voy a modificar el donut verdadero o falso o de abierta');
                this.misDonuts[indexDonut][3].cont++;  // respuesta en blanco
              } else {
                this.misDonuts[indexDonut][2].cont++;  // respuesta mal
              }
          }
          console.log ('nuevo donut');
          console.log (this.misDonuts[indexDonut]);
        }
        this.histogramaAciertos[aciertos]++;

        // // voy a contar los aciertos de este alumno
        // for (let i = 0; i < this.preguntas.length; i++) {
        //   const pregunta = this.preguntas.filter (p => p.id === respuesta.respuestas.Preguntas[i])[0];
        //   console.log ('pregunta ' + i);
        //   console.log (pregunta);
        //   if (pregunta.Tipo === 'Emparejamiento') {
        //     if (respuesta.respuestas.Respuestas[i] !== undefined) {
        //       let n = 0;
        //       for (let j = 0; j < pregunta.Emparejamientos.length; j++) {
        //         if (pregunta.Emparejamientos[j].r === respuesta.respuestas.Respuestas[i][j]) {
        //           n++;
        //         }
        //       }
        //       if (n === pregunta.Emparejamientos.length) {
        //         aciertos++;
        //       }
        //     }

        //   } else {
        //     if (pregunta.RespuestaCorrecta === respuesta.respuestas.Respuestas[i][0]) {
        //       aciertos++;
        //     }
        //   }
        // }
        // this.histogramaAciertos[aciertos]++;
        // console.log ('histograma de aciertos');
        // console.log (this.histogramaAciertos);

        // actualizo los donuts

        // let aciertos = 0;
        // let j;
        // for (j = 0; j < respuesta.respuestas.Preguntas.length; j++) {
        //   // tslint:disable-next-line:max-line-length
        //   const respuestaCorrecta = this.preguntas.filter (pregunta => pregunta.id === respuesta.respuestas.Preguntas[j])[0].RespuestaCorrecta;
        //   if (respuestaCorrecta === respuesta.respuestas.Respuestas[j]) {
        //       aciertos++;
        //   }
        // }
        // this.histogramaAciertos[aciertos]++;

        // // actualizo los donuts
        // console.log ('actualizo donuts');
        // console.log (this.misDonuts);
        // for (j = 0; j < respuesta.respuestas.Preguntas.length; j++) {
        //   const e = this.misDonuts.filter (elemento => elemento.id === respuesta.respuestas.Preguntas[j]);
        //   const donut = e[0].donut;
        //   console.log ('donut ' + j);
        //   console.log (donut);
        //   donut.filter (p => p.respuesta === respuesta.respuestas.Respuestas [j])[0].cont++;

        // }

        this.clasificacion.push ({
          nick: respuesta.nick,
          nota: respuesta.respuestas.Nota,
          tiempo: respuesta.respuestas.Tiempo
        });

        // tslint:disable-next-line:only-arrow-functions
        this.clasificacion = this.clasificacion.sort(function(a, b) {
          if (b.nota !== a.nota) {
            return b.nota - a.nota;
          } else {
            // en caso de empate en la nota, gana el que empleó menos tiempo
            return a.tiempo - b.tiempo;
          }
        });

        this.dataSource = new MatTableDataSource(this.clasificacion);

        this.participantes.filter (participante => participante.nickName === respuesta.nick)[0].contestado = true;
        this.PrepararGraficos();
    }


p => p.id === respuesta.respuestas.Preguntas[i]


elemento => elemento[0].preguntaId === pregunta.id


entrada => entrada.respuesta === respuesta.respuestas.Respuestas[i][0]


participante => participante.nickName === respuesta.nick


pregunta => {
          let miDonut: any;
          miDonut = [];
          // preparo los datos del donut
          // primero meto el tipo de pregunta
          miDonut.push ( { preguntaId: pregunta.id, Tipo: pregunta.tipo});
          if (pregunta.tipo === 'Cuatro opciones') {
            miDonut.push ( { respuesta: pregunta.respuestaCorrecta, cont: 0});
            miDonut.push ( { respuesta: pregunta.respuestaIncorrecta1, cont: 0});
            miDonut.push ( { respuesta: pregunta.respuestaIncorrecta2, cont: 0});
            miDonut.push ( { respuesta: pregunta.respuestaIncorrecta3, cont: 0});
            // esto es para el caso de respuesta en blando
            miDonut.push ( { respuesta: '-', cont: 0});

          } else if (pregunta.tipo === 'Respuesta abierta') {
            miDonut.push ( { respuesta: pregunta.respuestaCorrecta, cont: 0});
            miDonut.push ( { respuesta: 'Otras respuestas', cont: 0});
            // esto es para el caso de respuesta en blando
            miDonut.push ( { respuesta: '-', cont: 0});
          } else if (pregunta.tipo === 'Verdadero o falso') {
            miDonut.push ( { respuesta: pregunta.respuestaCorrecta, cont: 0});
            miDonut.push ( { respuesta: 'Mal', cont: 0});
            // esto es para el caso de respuesta en blando
            miDonut.push ( { respuesta: '-', cont: 0});
          } else {
            miDonut.push ( { respuesta: 'Emparejamientos correctos', cont: 0});
            miDonut.push ( { respuesta: 'Otros emparejamientos incorrectos', cont: 0});
            // esto es para el caso de respuesta en blando
            miDonut.push ( { respuesta: '-', cont: 0});
          }

          this.misDonuts.push (miDonut);
        }


(result) => {
      if (result.value) {
        this.peticionesAPI.BorraJuegoDeCuestionarioRapido (this.juegoSeleccionado.id)
        .subscribe(res => {
            if (res !== undefined) {
              Swal.fire('El juego se ha eliminado correctamente');
              this.router.navigate(['/inicio/' + this.profesorId]);
            }
        });
      }
    }


res => {
            if (res !== undefined) {
              Swal.fire('El juego se ha eliminado correctamente');
              this.router.navigate(['/inicio/' + this.profesorId]);
            }
        }


respuesta => {

      let aciertos = 0;
      for (let i = 0; i < respuesta.respuestas.Preguntas.length; i++) {
        const pregunta = this.preguntas.filter (p => p.id === respuesta.respuestas.Preguntas[i])[0];
        console.log ('pregunta ' + i);
        console.log (pregunta);
        const donut = this.misDonuts.filter (elemento => elemento[0].preguntaId === pregunta.id)[0];
        console.log ('donut de la pregunta');
        console.log (donut);
        if (pregunta.tipo === 'Emparejamiento') {
          // primero actualizamos el histograma
          if (respuesta.respuestas.Respuestas[i] !== undefined) {
            let n = 0;
            for (let j = 0; j < pregunta.emparejamientos.length; j++) {
              if (pregunta.emparejamientos[j].r === respuesta.respuestas.Respuestas[i][j]) {
                n++;
              }
            }
            if (n === pregunta.emparejamientos.length) {
              aciertos++;
              donut[1].cont++; // respuesta correcta
            } else {
              donut[2].cont++; // respuesta incorecta
            }
          } else {
            donut[3].cont++; // respuesta en blanco
          }

        } else if (pregunta.tipo === 'Cuatro opciones') {
          // actualizo histograma
          if (pregunta.respuestaCorrecta === respuesta.respuestas.Respuestas[i][0]) {
            aciertos++;
          }
          donut.filter (entrada => entrada.respuesta === respuesta.respuestas.Respuestas[i][0])[0].cont++;
        } else {
          console.log ('pregunta de respuesta abierta o verdadero o falso');
          if (pregunta.respuestaCorrecta === respuesta.respuestas.Respuestas[i][0]) {
            aciertos++;
            donut[1].cont++;  // respuesta correcta
          } else if (respuesta.respuestas.Respuestas[i][0] === '-') {
              donut[3].cont++;  // respuesta en blanco
            } else {
              donut[2].cont++;  // respuesta mal
            }
        }
      }
      this.histogramaAciertos[aciertos]++;
      this.clasificacion.push ({
        nick: respuesta.nick,
        nota: respuesta.respuestas.Nota,
        tiempo: respuesta.respuestas.Tiempo
      });

      // tslint:disable-next-line:only-arrow-functions
      this.clasificacion = this.clasificacion.sort(function(a, b) {
        if (b.nota !== a.nota) {
          return b.nota - a.nota;
        } else {
          // en caso de empate en la nota, gana el que empleó menos tiempo
          return a.tiempo - b.tiempo;
        }
      });
    }


miDonut => {
      if (miDonut[0].Tipo === 'Cuatro opciones') {
        const datos = [
          // las respuestas correctas siempre en verde
          {value: miDonut[1].cont, name: miDonut[1].respuesta, itemStyle: {color: 'green'}},
          {value: miDonut[2].cont, name: miDonut[2].respuesta, itemStyle: {color: 'rgb(50,50,50)'}},
          {value: miDonut[3].cont, name: miDonut[3].respuesta, itemStyle: {color: 'rgb(100,100,100)'}},
          {value: miDonut[4].cont, name: miDonut[4].respuesta, itemStyle: {color: 'rgb(125,125,125)'}},
          {value: miDonut[5].cont, name: 'No contesta ' , itemStyle: {color: 'rgb(150,150,150)'}}
        ];
        const  donut = {
          title: {
            text: 'Pregunta ' + i++,
            subtext: 'Respuesta correcta: ' + miDonut[1].respuesta ,
            left: 'center'
          },
          tooltip: {
              trigger: 'item',
              formatter: '{c} alumnos
 ({d}%)'
          },
          series: [
              {
                  name: '',
                  type: 'pie',
                  radius: ['50%', '70%'],
                  avoidLabelOverlap: false,
                  label: {
                      show: false,
                      position: 'center'
                  },
                  emphasis: {
                      label: {
                          show: true,
                          fontSize: '30',
                          fontWeight: 'bold'
                      }
                  },
                  labelLine: {
                      show: false
                  },
                  data: datos
              }
          ]
        };
        this.donuts.push (donut);
      } else if (miDonut[0].Tipo === 'Respuesta abierta') {
        const datos = [
          // las respuestas correctas siempre en verde
          {value: miDonut[1].cont, name: miDonut[1].respuesta, itemStyle: {color: 'green'}},
          {value: miDonut[2].cont, name: 'Otras respuestas', itemStyle: {color: 'rgb(50,50,50)'}},
          {value: miDonut[3].cont, name: 'No contesta ' , itemStyle: {color: 'rgb(150,150,150)'}}
        ];
        const  donut = {
          title: {
            text: 'Pregunta ' + i++,
            subtext: 'Respuesta correcta: ' + miDonut[1].respuesta ,
            left: 'center'
          },
          tooltip: {
              trigger: 'item',
              formatter: '{c} alumnos
 ({d}%)'
          },
          series: [
              {
                  name: '',
                  type: 'pie',
                  radius: ['50%', '70%'],
                  avoidLabelOverlap: false,
                  label: {
                      show: false,
                      position: 'center'
                  },
                  emphasis: {
                      label: {
                          show: true,
                          fontSize: '30',
                          fontWeight: 'bold'
                      }
                  },
                  labelLine: {
                      show: false
                  },
                  data: datos
              }
          ]
        };
        this.donuts.push (donut);
      } else if (miDonut[0].Tipo === 'Verdadero o falso') {
        const datos = [
          // las respuestas correctas siempre en verde
          {value: miDonut[1].cont, name: miDonut[1].respuesta, itemStyle: {color: 'green'}},
          {value: miDonut[2].cont, name: 'Mal', itemStyle: {color: 'rgb(50,50,50)'}},
          {value: miDonut[3].cont, name: 'No contesta ' , itemStyle: {color: 'rgb(150,150,150)'}}
        ];
        const  donut = {
          title: {
            text: 'Pregunta ' + i++,
            subtext: 'Respuesta correcta: ' + miDonut[1].respuesta ,
            left: 'center'
          },
          tooltip: {
              trigger: 'item',
              formatter: '{c} alumnos
 ({d}%)'
          },
          series: [
              {
                  name: '',
                  type: 'pie',
                  radius: ['50%', '70%'],
                  avoidLabelOverlap: false,
                  label: {
                      show: false,
                      position: 'center'
                  },
                  emphasis: {
                      label: {
                          show: true,
                          fontSize: '30',
                          fontWeight: 'bold'
                      }
                  },
                  labelLine: {
                      show: false
                  },
                  data: datos
              }
          ]
        };
        this.donuts.push (donut);
      } else {
        const datos = [
          // las respuestas correctas siempre en verde
          {value: miDonut[1].cont, name: 'Emparejamientos correctos', itemStyle: {color: 'green'}},
          {value: miDonut[2].cont, name: 'Otros emparejamientos incorrectos', itemStyle: {color: 'rgb(50,50,50)'}},
          {value: miDonut[3].cont, name: 'No contesta ' , itemStyle: {color: 'rgb(150,150,150)'}}
        ];
        const  donut = {
          title: {
            text: 'Pregunta ' + i++,
            subtext: 'Respuesta correcta: ' + miDonut[1].respuesta ,
            left: 'center'
          },
          tooltip: {
              trigger: 'item',
              formatter: '{c} alumnos
 ({d}%)'
          },
          series: [
              {
                  name: '',
                  type: 'pie',
                  radius: ['50%', '70%'],
                  avoidLabelOverlap: false,
                  label: {
                      show: false,
                      position: 'center'
                  },
                  emphasis: {
                      label: {
                          show: true,
                          fontSize: '30',
                          fontWeight: 'bold'
                      }
                  },
                  labelLine: {
                      show: false
                  },
                  data: datos
              }
          ]
        };
        this.donuts.push (donut);
      }
      i++;
    }


obs => {
      let mensaje;
      if (!this.ficheroGenerado) {
        mensaje = 'No has generado el fichero PDF.';
      }

      Swal.fire({
          title: '¿Seguro que quieres salir?',
          html: mensaje,
          icon: 'warning',
          showCancelButton: true,
          confirmButtonColor: '#3085d6',
          cancelButtonColor: '#d33',
          confirmButtonText: 'Si, estoy seguro'
        }).then((result) => {
          if (result.value) {
            this.sonido = false;
          }
          obs.next (result.value);
      });
    }


(result) => {
          if (result.value) {
            this.sonido = false;
          }
          obs.next (result.value);
      }


EliminarJuegoRapido() {
    Swal.fire({
      title: '¿Seguro que quieres eliminar este juego rápido?',
      icon: 'warning',
      showCancelButton: true,
      confirmButtonColor: '#3085d6',
      cancelButtonColor: '#d33',
      confirmButtonText: 'Si, estoy seguro'
    }).then((result) => {
      if (result.value) {
        this.peticionesAPI.BorraJuegoDeCuestionarioRapido (this.juegoSeleccionado.id)
        .subscribe(res => {
            if (res !== undefined) {
              Swal.fire('El juego se ha eliminado correctamente');
              this.router.navigate(['/inicio/' + this.profesorId]);
            }
        });
      }
    });

  }


// Esto es para cuando el juego ya tenia respuestas de otras sesiones
PrepararHitogramaYDonutsIniciales() {

    console.log ('voy a preparar el histograma');

    console.log (this.respuestas);
    console.log ('preguntas');
    console.log (this.preguntas);

    console.log ('histograma de aciertos');
    console.log (this.histogramaAciertos);

    this.respuestas.forEach (respuesta => {

      let aciertos = 0;
      for (let i = 0; i < respuesta.respuestas.Preguntas.length; i++) {
        const pregunta = this.preguntas.filter (p => p.id === respuesta.respuestas.Preguntas[i])[0];
        console.log ('pregunta ' + i);
        console.log (pregunta);
        const donut = this.misDonuts.filter (elemento => elemento[0].preguntaId === pregunta.id)[0];
        console.log ('donut de la pregunta');
        console.log (donut);
        if (pregunta.tipo === 'Emparejamiento') {
          // primero actualizamos el histograma
          if (respuesta.respuestas.Respuestas[i] !== undefined) {
            let n = 0;
            for (let j = 0; j < pregunta.emparejamientos.length; j++) {
              if (pregunta.emparejamientos[j].r === respuesta.respuestas.Respuestas[i][j]) {
                n++;
              }
            }
            if (n === pregunta.emparejamientos.length) {
              aciertos++;
              donut[1].cont++; // respuesta correcta
            } else {
              donut[2].cont++; // respuesta incorecta
            }
          } else {
            donut[3].cont++; // respuesta en blanco
          }

        } else if (pregunta.tipo === 'Cuatro opciones') {
          // actualizo histograma
          if (pregunta.respuestaCorrecta === respuesta.respuestas.Respuestas[i][0]) {
            aciertos++;
          }
          donut.filter (entrada => entrada.respuesta === respuesta.respuestas.Respuestas[i][0])[0].cont++;
        } else {
          console.log ('pregunta de respuesta abierta o verdadero o falso');
          if (pregunta.respuestaCorrecta === respuesta.respuestas.Respuestas[i][0]) {
            aciertos++;
            donut[1].cont++;  // respuesta correcta
          } else if (respuesta.respuestas.Respuestas[i][0] === '-') {
              donut[3].cont++;  // respuesta en blanco
            } else {
              donut[2].cont++;  // respuesta mal
            }
        }
      }
      this.histogramaAciertos[aciertos]++;
      this.clasificacion.push ({
        nick: respuesta.nick,
        nota: respuesta.respuestas.Nota,
        tiempo: respuesta.respuestas.Tiempo
      });

      // tslint:disable-next-line:only-arrow-functions
      this.clasificacion = this.clasificacion.sort(function(a, b) {
        if (b.nota !== a.nota) {
          return b.nota - a.nota;
        } else {
          // en caso de empate en la nota, gana el que empleó menos tiempo
          return a.tiempo - b.tiempo;
        }
      });
    });
    console.log ('histograma de aciertos');
    console.log (this.histogramaAciertos);
    console.log ('donuts');
    console.log (this.misDonuts);

    this.dataSource = new MatTableDataSource(this.clasificacion);

  }


canExit(): Observable <boolean> {
  // esta función se llamará cada vez que quedamos salir de la página
    const confirmacionObservable = new Observable <boolean>( obs => {
      let mensaje;
      if (!this.ficheroGenerado) {
        mensaje = 'No has generado el fichero PDF.';
      }

      Swal.fire({
          title: '¿Seguro que quieres salir?',
          html: mensaje,
          icon: 'warning',
          showCancelButton: true,
          confirmButtonColor: '#3085d6',
          cancelButtonColor: '#d33',
          confirmButtonText: 'Si, estoy seguro'
        }).then((result) => {
          if (result.value) {
            this.sonido = false;
          }
          obs.next (result.value);
      });
    });

    return confirmacionObservable;
}


GenerarInforme() {
  Swal.fire ('Atención', 'Esta funcionalidad aún no está operativa' , 'error');

  // const margenIzquierdo = 15;
  // const margenSuperior = 20;
  // const interlineado = 5;
  // const doc = new jsPDF();

  // doc.setFontSize(20);
  // doc.setFont('arial', 'normal');
  // doc.setTextColor("blue");
  // doc.text('Resultado de la asignación de turnos', margenIzquierdo, margenSuperior);
  // doc.line(margenIzquierdo, margenSuperior + 5, margenIzquierdo + 150, margenSuperior + 5);
  // const fecha = new Date().toLocaleDateString();

  // doc.setTextColor("black");
  // doc.setFontSize(14);
  // const splittedText = doc.splitTextToSize('Presentación: ' + this.juegoSeleccionado.Presentacion,  180);
  // doc.text (splittedText, margenIzquierdo, margenSuperior + 20);
  // doc.text('Fecha: ' + fecha, margenIzquierdo, margenSuperior + 40);

  // autoTable(doc, { html: '#tabla',  startY:  margenSuperior + 70 });

  // const pageCount = doc.getNumberOfPages(); //Total Page Number
  // let i;
  // for (i = 0; i < pageCount; i++) {
  //   doc.setPage(i);
  //   const pageCurrent = doc.getCurrentPageInfo().pageNumber; //Current Page
  //   doc.setFontSize(12);
  //   doc.text('página: ' + pageCurrent + '/' + pageCount, 180,  margenSuperior);
  // }

  // doc.save('informe.pdf');
  // this.ficheroGenerado = true;
}


export default class PackageEmptyChecker
{
  public static isToBreakBuildForEmptyDirectory(
    projectDir: string,
    sourceDirectory: string,
    isToBreakBuildIfEmpty: boolean
  ): {
    message: string;
    result: string;
  } {
    let directoryToCheck;
    let status: { message: string; result: string } = {
      message: "",
      result: "",
    };

    if (projectDir!=null) {
      directoryToCheck = path.join(projectDir, sourceDirectory);
    } else directoryToCheck = sourceDirectory;

    try {
      if (!existsSync(directoryToCheck)) {
        //Folder do not exists, break build
        if (isToBreakBuildIfEmpty) {
          status.message = `Folder not Found , Stopping build as isToBreakBuildIfEmpty is ${isToBreakBuildIfEmpty}`;
          status.result = "break";
        } else {
          status.message = `Folder not Found , Skipping task as isToBreakBuildIfEmpty is ${isToBreakBuildIfEmpty}`;
          status.result = "skip";
        }
        return status;
      } else if (
        PackageEmptyChecker.isEmptyFolder(projectDir, sourceDirectory)
      ) {
        if (isToBreakBuildIfEmpty) {
          status.message = `Folder is Empty , Stopping build as isToBreakBuildIfEmpty is ${isToBreakBuildIfEmpty}`;
          status.result = "break";
        } else {
          status.message = `Folder is Empty, Skipping task as isToBreakBuildIfEmpty is ${isToBreakBuildIfEmpty}`;
          status.result = "skip";
        }
        return status;
      } else {
        status.result = "continue";
        return status;
      }
    } catch (err) {
      if (err.code === "ENOENT") {
        throw new Error(`No such file or directory ${err.path}`); // Re-throw error if .forceignore does not exist
      } else if (!isToBreakBuildIfEmpty) {
        status.message = `Something wrong with the path provided  ${directoryToCheck},,but skipping, The exception is ${err}`;
        status.result = "skip";
        return status;
      } else throw err;
    }
  }

  public static isEmptyFolder(
    projectDirectory: string,
    sourceDirectory: string
  ): boolean {
    let dirToCheck;

    if (projectDirectory!=null) {
      dirToCheck = path.join(projectDirectory, sourceDirectory);
    } else {
      dirToCheck = sourceDirectory;
    }

    let files: string[] = FileSystem.readdirRecursive(dirToCheck);

    let forceignorePath;
    if (projectDirectory!=null)
      forceignorePath = path.join(projectDirectory, ".forceignore");
    else forceignorePath = path.join(process.cwd(), ".forceignore");

    // Ignore files that are listed in .forceignore
    files = ignore()
      .add(readFileSync(forceignorePath).toString()) // Add ignore patterns from '.forceignore'.
      .filter(files);

    if (files == null || files.length === 0) return true;
    else return false;
  }

}


public static isToBreakBuildForEmptyDirectory(
    projectDir: string,
    sourceDirectory: string,
    isToBreakBuildIfEmpty: boolean
  ): {
    message: string;
    result: string;
  } {
    let directoryToCheck;
    let status: { message: string; result: string } = {
      message: "",
      result: "",
    };

    if (projectDir!=null) {
      directoryToCheck = path.join(projectDir, sourceDirectory);
    } else directoryToCheck = sourceDirectory;

    try {
      if (!existsSync(directoryToCheck)) {
        //Folder do not exists, break build
        if (isToBreakBuildIfEmpty) {
          status.message = `Folder not Found , Stopping build as isToBreakBuildIfEmpty is ${isToBreakBuildIfEmpty}`;
          status.result = "break";
        } else {
          status.message = `Folder not Found , Skipping task as isToBreakBuildIfEmpty is ${isToBreakBuildIfEmpty}`;
          status.result = "skip";
        }
        return status;
      } else if (
        PackageEmptyChecker.isEmptyFolder(projectDir, sourceDirectory)
      ) {
        if (isToBreakBuildIfEmpty) {
          status.message = `Folder is Empty , Stopping build as isToBreakBuildIfEmpty is ${isToBreakBuildIfEmpty}`;
          status.result = "break";
        } else {
          status.message = `Folder is Empty, Skipping task as isToBreakBuildIfEmpty is ${isToBreakBuildIfEmpty}`;
          status.result = "skip";
        }
        return status;
      } else {
        status.result = "continue";
        return status;
      }
    } catch (err) {
      if (err.code === "ENOENT") {
        throw new Error(`No such file or directory ${err.path}`); // Re-throw error if .forceignore does not exist
      } else if (!isToBreakBuildIfEmpty) {
        status.message = `Something wrong with the path provided  ${directoryToCheck},,but skipping, The exception is ${err}`;
        status.result = "skip";
        return status;
      } else throw err;
    }
  }


public static isEmptyFolder(
    projectDirectory: string,
    sourceDirectory: string
  ): boolean {
    let dirToCheck;

    if (projectDirectory!=null) {
      dirToCheck = path.join(projectDirectory, sourceDirectory);
    } else {
      dirToCheck = sourceDirectory;
    }

    let files: string[] = FileSystem.readdirRecursive(dirToCheck);

    let forceignorePath;
    if (projectDirectory!=null)
      forceignorePath = path.join(projectDirectory, ".forceignore");
    else forceignorePath = path.join(process.cwd(), ".forceignore");

    // Ignore files that are listed in .forceignore
    files = ignore()
      .add(readFileSync(forceignorePath).toString()) // Add ignore patterns from '.forceignore'.
      .filter(files);

    if (files == null || files.length === 0) return true;
    else return false;
  }


<R>(f: any): (baseUrl: string | IGraphQueryable, path?: string) => R => {
    return invokableFactory<R>(f);
}


(v: [string, string]) => v[0] + "=" + v[1]


/**
 * Queryable Base Class
 *
 */
export class _GraphQueryable<GetType = any> extends Queryable<GetType> implements IGraphQueryable<GetType> {

    /**
     * Creates a new instance of the Queryable class
     *
     * @constructor
     * @param baseUrl A string or Queryable that should form the base part of the url
     *
     */
    constructor(baseUrl: string | IGraphQueryable, path?: string) {

        let url = "";
        let parentUrl = "";
        const query = new Map<string, string>();

        if (typeof baseUrl === "string") {
            parentUrl = baseUrl;
            url = combine(parentUrl, path);
        } else {
            parentUrl = baseUrl.toUrl();
            url = combine(parentUrl, path);
        }

        super({
            parentUrl,
            query,
            url,
        });

        // post init actions
        if (typeof baseUrl !== "string") {
            this.configureFrom(baseUrl);
        }
    }

    /**
     * Choose which fields to return
     *
     * @param selects One or more fields to return
     */
    public select(...selects: string[]): this {
        if (selects.length > 0) {
            this.query.set("$select", selects.map(encodeURIComponent).join(","));
        }
        return this;
    }

    /**
     * Expands fields such as lookups to get additional data
     *
     * @param expands The Fields for which to expand the values
     */
    public expand(...expands: string[]): this {
        if (expands.length > 0) {
            this.query.set("$expand", expands.map(encodeURIComponent).join(","));
        }
        return this;
    }

    public defaultAction(options?: IFetchOptions): Promise<GetType> {
        return graphGet(this, options);
    }

    public get<T = GetType>(options?: IFetchOptions): Promise<T> {
        return graphGet<T>(<any>this, options);
    }

    /**
     * Gets the full url with query information
     *
     */
    public toUrlAndQuery(): string {

        let url = this.toUrl();

        if (this.query.size > 0) {
            const char = url.indexOf("?") > -1 ? "&" : "?";
            url += `${char}${Array.from(this.query).map((v: [string, string]) => v[0] + "=" + v[1]).join("&")}`;
        }

        return url;
    }

    public setEndpoint(endpoint: "beta" | "v1.0"): this {
        this.data.url = GraphEndpoints.ensure(this.data.url, endpoint);
        return this;
    }

    /**
     * Gets a parent for this instance as specified
     *
     * @param factory The contructor for the class to create
     */
    protected getParent<T extends _GraphQueryable>(
        factory: IGraphQueryableConstructor<T>,
        baseUrl: string | IGraphQueryable = this.parentUrl,
        path?: string): T {

        return new factory(baseUrl, path);
    }

    /**
     * Clones this queryable into a new queryable instance of T
     * @param factory Constructor used to create the new instance
     * @param additionalPath Any additional path to include in the clone
     * @param includeBatch If true this instance's batch will be added to the cloned instance
     * @param includeQuery If true all of the query values will be copied to the cloned instance
     */
    protected clone<T extends IGraphQueryable>(factory: (...args: any[]) => T, additionalPath?: string, includeBatch = true, includeQuery = false): T {

        return super.cloneTo<T>(factory(this, additionalPath), { includeBatch, includeQuery });
    }
}


/**
 * Represents a REST collection which can be filtered, paged, and selected
 *
 */
export class _GraphQueryableCollection<GetType = any[]> extends _GraphQueryable<GetType> implements IGraphQueryableCollection<GetType> {

    /**
     *
     * @param filter The string representing the filter query
     */
    public filter(filter: string): this {
        this.query.set("$filter", filter);
        return this;
    }

    /**
     * Orders based on the supplied fields
     *
     * @param orderby The name of the field on which to sort
     * @param ascending If false DESC is appended, otherwise ASC (default)
     */
    public orderBy(orderBy: string, ascending = true): this {
        const o = "$orderby";
        const query = this.query.has(o) ? this.query.get(o).split(",") : [];
        query.push(`${encodeURIComponent(orderBy)} ${ascending ? "asc" : "desc"}`);
        this.query.set(o, query.join(","));
        return this;
    }

    /**
     * Limits the query to only return the specified number of items
     *
     * @param top The query row limit
     */
    public top(top: number): this {
        this.query.set("$top", top.toString());
        return this;
    }

    /**
     * Skips a set number of items in the return set
     *
     * @param num Number of items to skip
     */
    public skip(num: number): this {
        this.query.set("$skip", num.toString());
        return this;
    }

    /**
     * 	To request second and subsequent pages of Graph data
     */
    public skipToken(token: string): this {
        this.query.set("$skiptoken", token);
        return this;
    }

    /**
     * 	Retrieves the total count of matching resources
     */
    public get count(): this {
        this.query.set("$count", "true");
        return this;
    }
}


export class _GraphQueryableSearchableCollection extends _GraphQueryableCollection implements IGraphQueryableSearchableCollection {

    /**
     * 	To request second and subsequent pages of Graph data
     */
    public search(query: string): this {
        this.query.set("$search", query);
        return this;
    }
}


/**
 * Represents an instance that can be selected
 *
 */
export class _GraphQueryableInstance<GetType = any> extends _GraphQueryable<GetType> { }


export interface IGraphQueryableConstructor<T> {
    new(baseUrl: string | IGraphQueryable, path?: string): T;
}


export interface IGraphQueryable<GetType = any> extends IInvokable, IQueryable<GetType> {

    /**
     * Choose which fields to return
     *
     * @param selects One or more fields to return
     */
    select(...selects: string[]): this;

    /**
     * Expands fields such as lookups to get additional data
     *
     * @param expands The Fields for which to expand the values
     */
    expand(...expands: string[]): this;

    /**
     *
     * @param options
     */
    defaultAction(options?: IFetchOptions): Promise<GetType>;

    /**
     * Allows you to set the graph endpoint version string
     *
     * @param endpoint The string either beta of v1.0
     */
    setEndpoint(endpoint: "beta" | "v1.0"): this;

    /**
     * Gets the full url with query information
     *
     */
    toUrlAndQuery(): string;

}


export interface _GraphQueryable extends IInvokable { }


export interface IGraphQueryableCollection<GetType = any[]> extends IInvokable, IGraphQueryable<GetType> {

    /**
     * 	Retrieves the total count of matching resources
     */
    count: this;

    /**
     *
     * @param filter The string representing the filter query
     */
    filter(filter: string): this;

    /**
     * Orders based on the supplied fields
     *
     * @param orderby The name of the field on which to sort
     * @param ascending If false DESC is appended, otherwise ASC (default)
     */
    orderBy(orderBy: string, ascending?: boolean): this;

    /**
     * Limits the query to only return the specified number of items
     *
     * @param top The query row limit
     */
    top(top: number): this;

    /**
     * Skips a set number of items in the return set
     *
     * @param num Number of items to skip
     */
    skip(num: number): this;

    /**
     * 	To request second and subsequent pages of Graph data
     */
    skipToken(token: string): this;
}


export interface _GraphQueryableCollection extends IInvokable { }


export interface IGraphQueryableSearchableCollection<GetType = any> extends IInvokable, IGraphQueryable<GetType> {
    search(query: string): this;
}


export interface _GraphQueryableSearchableCollection extends IInvokable { }


export interface IGraphQueryableInstance<GetType = any> extends IInvokable, IGraphQueryable<GetType> { }


export interface _GraphQueryableInstance extends IInvokable { }


/**
     * Choose which fields to return
     *
     * @param selects One or more fields to return
     */
public select(...selects: string[]): this {
        if (selects.length > 0) {
            this.query.set("$select", selects.map(encodeURIComponent).join(","));
        }
        return this;
    }


/**
     * Expands fields such as lookups to get additional data
     *
     * @param expands The Fields for which to expand the values
     */
public expand(...expands: string[]): this {
        if (expands.length > 0) {
            this.query.set("$expand", expands.map(encodeURIComponent).join(","));
        }
        return this;
    }


public defaultAction(options?: IFetchOptions): Promise<GetType> {
        return graphGet(this, options);
    }


public get<T = GetType>(options?: IFetchOptions): Promise<T> {
        return graphGet<T>(<any>this, options);
    }


/**
     * Gets the full url with query information
     *
     */
public toUrlAndQuery(): string {

        let url = this.toUrl();

        if (this.query.size > 0) {
            const char = url.indexOf("?") > -1 ? "&" : "?";
            url += `${char}${Array.from(this.query).map((v: [string, string]) => v[0] + "=" + v[1]).join("&")}`;
        }

        return url;
    }


public setEndpoint(endpoint: "beta" | "v1.0"): this {
        this.data.url = GraphEndpoints.ensure(this.data.url, endpoint);
        return this;
    }


/**
     * Gets a parent for this instance as specified
     *
     * @param factory The contructor for the class to create
     */
protected getParent<T extends _GraphQueryable>(
        factory: IGraphQueryableConstructor<T>,
        baseUrl: string | IGraphQueryable = this.parentUrl,
        path?: string): T {

        return new factory(baseUrl, path);
    }


/**
     * Clones this queryable into a new queryable instance of T
     * @param factory Constructor used to create the new instance
     * @param additionalPath Any additional path to include in the clone
     * @param includeBatch If true this instance's batch will be added to the cloned instance
     * @param includeQuery If true all of the query values will be copied to the cloned instance
     */
protected clone<T extends IGraphQueryable>(factory: (...args: any[]) => T, additionalPath?: string, includeBatch = true, includeQuery = false): T {

        return super.cloneTo<T>(factory(this, additionalPath), { includeBatch, includeQuery });
    }


/**
     *
     * @param filter The string representing the filter query
     */
public filter(filter: string): this {
        this.query.set("$filter", filter);
        return this;
    }


/**
     * Orders based on the supplied fields
     *
     * @param orderby The name of the field on which to sort
     * @param ascending If false DESC is appended, otherwise ASC (default)
     */
public orderBy(orderBy: string, ascending = true): this {
        const o = "$orderby";
        const query = this.query.has(o) ? this.query.get(o).split(",") : [];
        query.push(`${encodeURIComponent(orderBy)} ${ascending ? "asc" : "desc"}`);
        this.query.set(o, query.join(","));
        return this;
    }


/**
     * Limits the query to only return the specified number of items
     *
     * @param top The query row limit
     */
public top(top: number): this {
        this.query.set("$top", top.toString());
        return this;
    }


/**
     * Skips a set number of items in the return set
     *
     * @param num Number of items to skip
     */
public skip(num: number): this {
        this.query.set("$skip", num.toString());
        return this;
    }


/**
     * 	To request second and subsequent pages of Graph data
     */
public skipToken(token: string): this {
        this.query.set("$skiptoken", token);
        return this;
    }


/**
     * 	To request second and subsequent pages of Graph data
     */
public search(query: string): this {
        this.query.set("$search", query);
        return this;
    }


function assertLegalScore(score: number) {
  if (score < MIN_ALLOWED_SCORE || score > MAX_ALLOWED_SCORE) {
    throw new Error('Illegal score=' + score + ', it must be between ' + MIN_ALLOWED_SCORE + ' - ' + MAX_ALLOWED_SCORE);
  }
}


/**
 * Returns the move that the computer player should do for the given state.
 * alphaBetaLimits is an object that sets a limit on the alpha-beta search,
 * and it has either a millisecondsLimit or maxDepth field:
 * millisecondsLimit is a time limit, and maxDepth is a depth limit.
 */
export function createComputerMove<T>(
  startingState: T,
  turnIndex: number,
  alphaBetaLimits: IAlphaBetaLimits,
  aiService: AiService<T>
): IMove<T> {
  // We use alpha-beta search, where the search states are TicTacToe moves.
  return alphaBetaDecision(startingState, turnIndex, aiService, alphaBetaLimits);
}


/**
 * Does alpha-beta search, starting from startingState,
 * where the first move is done by playerIndex (playerIndex is either 0 or 1),
 * then the next move is done by 1-playerIndex, etc.
 *
 * getPossibleMoves(state, playerIndex) should return an array of the following states
 * and if state is a terminal state it should return an empty array.
 *
 * getStateScoreForIndex0(state, playerIndex) should return a score for
 * the state as viewed by player index 0, i.e.,
 * if player index 0 is probably winning then the score should be high.
 * Return MAX_ALLOWED_SCORE is player index 0 is definitely winning,
 * and MIN_ALLOWED_SCORE if player index 0 is definitely losing.
 *
 * alphaBetaLimits is an object that sets a limit on the alpha-beta search,
 * and it has either a millisecondsLimit or maxDepth field:
 * millisecondsLimit is a time limit, and maxDepth is a depth limit.
 */
export function alphaBetaDecision<T>(
  startingState: T,
  playerIndex: number,
  aiService: AiService<T>,
  alphaBetaLimits: IAlphaBetaLimits
): IMove<T> {
  const move = alphaBetaDecisionMayReturnNull(startingState, playerIndex, aiService, alphaBetaLimits);
  if (move) {
    return move;
  }
  // We run out of time, but we have to return a non-null move (no matter what).
  return aiService.getPossibleMoves(startingState, playerIndex)[0];
}


function alphaBetaDecisionMayReturnNull<T>(
  startingState: T,
  playerIndex: number,
  aiService: AiService<T>,
  alphaBetaLimits: IAlphaBetaLimits
): IMove<T> | null {
  // Checking input
  if (!startingState || !aiService) {
    throw new Error('startingState or aiService is null/undefined');
  }
  if (playerIndex !== 0 && playerIndex !== 1) {
    throw new Error('playerIndex must be either 0 or 1');
  }

  const startTime = new Date().getTime(); // used for the time limit
  if (alphaBetaLimits.maxDepth) {
    return getScoreForIndex0(startingState, playerIndex, aiService, alphaBetaLimits, startTime, 0, MIN_SCORE, MAX_SCORE)
      .bestState;
  }
  if (!alphaBetaLimits.millisecondsLimit) {
    throw new Error('alphaBetaLimits must have either millisecondsLimit or maxDepth');
  }
  const millisecondsLimit = alphaBetaLimits.millisecondsLimit;
  // For time limits (without maxDepth), we do iterative deepening (A* search).
  if (debugAlphaBetaService) {
    console.log('Doing iterative-deepeninh (A*) until we run out of time or find a certain win/lose move.');
  }
  let maxDepth = 1;
  let bestState: IMove<T> | null = null;
  // eslint-disable-next-line no-constant-condition
  while (true) {
    if (debugAlphaBetaService) {
      console.log('Alpha-beta search until maxDepth=' + maxDepth);
    }
    const nextBestStateAndScore = getScoreForIndex0(
      startingState,
      playerIndex,
      aiService,
      {maxDepth: maxDepth, millisecondsLimit: millisecondsLimit},
      startTime,
      0,
      MIN_SCORE,
      MAX_SCORE
    );
    const nextBestScore = nextBestStateAndScore.bestScore;
    const nextBestState = nextBestStateAndScore.bestState;
    if (nextBestScore === MAX_SCORE || nextBestScore === MIN_SCORE) {
      if (debugAlphaBetaService) {
        const isWin = nextBestScore === (playerIndex === 0 ? MAX_SCORE : MIN_SCORE);
        console.log('Discovered that AI is going to ' + (isWin ? 'win' : 'lose') + ' with maxDepth=' + maxDepth);
        console.log('Best state is ', nextBestState);
      }
      return nextBestState;
    }
    const isHalfTimePassed = isTimeout({millisecondsLimit: millisecondsLimit / 2}, startTime);
    const isAllTimePassed = isTimeout(alphaBetaLimits, startTime);
    if (isHalfTimePassed || isAllTimePassed) {
      // If we run out of half the time, then no point of starting a new search that
      // will most likely take more time than all previous searches.
      // It's more accurate to return the best state for the previous alpha-beta search
      // if we run out of time, because we finished traversing all
      // immediate children of the starting state.
      const result = !isAllTimePassed || maxDepth === 1 || !bestState ? nextBestState : bestState;
      if (isAllTimePassed) {
        if (debugAlphaBetaService) {
          console.log(
            'Run out of time when maxDepth=' +
              maxDepth +
              ', so returning the best state for maxDepth=' +
              (maxDepth === 1 ? 1 : maxDepth - 1)
          );
        }
      } else {
        if (debugAlphaBetaService) {
          console.log(
            'Run out of half the time when maxDepth=' + maxDepth + ', so no point of exploring the next depth.'
          );
        }
      }
      if (debugAlphaBetaService) {
        console.log('Best state is ', result);
      }
      return result;
    }
    bestState = nextBestState;
    maxDepth++;
    if (maxDepth >= MAX_ALLOWED_DEPTH) {
      return nextBestState;
    }
  }
}


function isTimeout(alphaBetaLimits: IAlphaBetaLimits, startTime: number): boolean {
  return alphaBetaLimits.millisecondsLimit
    ? new Date().getTime() - startTime > alphaBetaLimits.millisecondsLimit
    : false;
}


function expandMovesTillNextTurn<T>(aiService: AiService<T>, moves: IMove<T>[], playerIndex: number) {
  const nextTurn = 1 - playerIndex;
  const okMove = (move: IMove<T>) => move.endMatchScores || move.turnIndex == nextTurn;

  if (moves.every(okMove)) return moves;

  const badMoves = moves.filter((m) => !okMove(m));
  const okMoves = moves.filter(okMove);
  while (badMoves.length > 0) {
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    const badMove = badMoves.pop()!;
    const moves = aiService.getPossibleMoves(badMove.state, playerIndex);
    moves.forEach((m) => (okMove(m) ? okMoves.push(m) : badMoves.push(m)));
  }
  return okMoves;
}


function getScoreForIndex0<T>(
  startingState: T,
  playerIndex: number,
  aiService: AiService<T>,
  alphaBetaLimits: IAlphaBetaLimits,
  startTime: number,
  depth: number,
  alpha: number,
  beta: number
): ScoreState<T> {
  let bestScore: number | null = null;
  let bestState: IMove<T> | null = null;
  if (isTimeout(alphaBetaLimits, startTime)) {
    if (debugAlphaBetaService) {
      console.log('Run out of time, just quitting from this traversal.');
    }
    return {bestScore: 0, bestState: null}; // This traversal is "ruined" anyway because we ran out of time.
  }
  if (depth === alphaBetaLimits.maxDepth) {
    bestScore = aiService.getStateScoreForIndex0(startingState, playerIndex);
    assertLegalScore(bestScore);
    if (debugAlphaBetaService) {
      console.log('Max depth reached, score is ' + bestScore);
    }
    return {bestScore: bestScore, bestState: null};
  }
  const moves = aiService.getPossibleMoves(startingState, playerIndex);
  if (debugAlphaBetaService) {
    console.log('startingState=', startingState, ' has ' + moves.length + ' next states');
  }
  if (moves.length === 0) {
    bestScore = aiService.getStateScoreForIndex0(startingState, playerIndex);
    assertLegalScore(bestScore);
    if (debugAlphaBetaService) {
      console.log('Terminal state, score is ' + bestScore);
    }
    return {bestScore: bestScore, bestState: null};
  }
  for (const move of expandMovesTillNextTurn(aiService, moves, playerIndex)) {
    const endMatchScores = move.endMatchScores;
    let scoreForIndex0: number;
    if (endMatchScores) {
      if (move.turnIndex != -1) {
        throw new Error('When game ends with endMatchScores=' + endMatchScores + ' you must set turnIndex to -1');
      }
      // prefer to do as many moves as possible for riddles
      // (so the AI tries to block, even if it's going to lose)
      scoreForIndex0 =
        endMatchScores[0] > endMatchScores[1]
          ? MAX_SCORE - depth
          : endMatchScores[0] < endMatchScores[1]
          ? MIN_SCORE + depth
          : 0;
    } else {
      const nextTurnIndex = 1 - playerIndex;
      if (move.turnIndex != nextTurnIndex) {
        // getPossibleMoves must return moves that switch turns...
        throw new Error(
          'Call to getPossibleMoves with playerIndex=' +
            playerIndex +
            ' returned a state with turnIndex=' +
            move.turnIndex
        );
      }
      scoreForIndex0 = getScoreForIndex0(
        move.state,
        nextTurnIndex,
        aiService,
        alphaBetaLimits,
        startTime,
        depth + 1,
        alpha,
        beta
      ).bestScore;
    }

    if (debugAlphaBetaService) {
      console.log('Score of ', move, ' is ', scoreForIndex0);
    }
    if (
      bestScore === null ||
      (playerIndex === 0 && scoreForIndex0 > bestScore) ||
      (playerIndex === 1 && scoreForIndex0 < bestScore)
    ) {
      bestScore = scoreForIndex0;
      bestState = move;
    }
    if (playerIndex === 0) {
      if (bestScore >= beta) {
        return {bestScore: bestScore, bestState: bestState};
      }
      alpha = Math.max(alpha, bestScore);
    } else {
      if (bestScore <= alpha) {
        return {bestScore: bestScore, bestState: bestState};
      }
      beta = Math.min(beta, bestScore);
    }
  }
  if (debugAlphaBetaService) {
    console.log('Best next state for playerIndex ' + playerIndex + ' is ', bestState, ' with score of ' + bestScore);
  }
  return {bestScore: bestScore ? bestScore : 0, bestState: bestState};
}


(move: IMove<T>) => move.endMatchScores || move.turnIndex == nextTurn


(m) => !okMove(m)


(m) => (okMove(m) ? okMoves.push(m) : badMoves.push(m))


export interface IAlphaBetaLimits {
  millisecondsLimit?: number;
  maxDepth?: number;
}


interface ScoreState<T> {
  bestScore: number;
  bestState: IMove<T> | null;
}


() => {
  const { register, handleSubmit, errors, setValue, triggerValidation, formState } = useForm({
    mode: 'onBlur',
  });

  const onSubmit = data => {
    // eslint-disable-next-line no-console
    console.log('errors: ', errors);
    // eslint-disable-next-line no-console
    console.log('data: ', data);
  };

  React.useEffect(() => {
    register(
      { name: 'city' },
      {
        required: { value: true, message: 'city is required' },
      },
    );
  }, [register]);

  return (
    <Form onSubmit={handleSubmit(onSubmit)}>
      <FormInput
        label="First Name"
        id="first-name-hooks"
        name="firstName"
        ref={register({
          required: { value: true, message: 'first name is required' },
          validate: val


data => {
    // eslint-disable-next-line no-console
    console.log('errors: ', errors);
    // eslint-disable-next-line no-console
    console.log('data: ', data);
  }


() => {
    register(
      { name: 'city' },
      {
        required: { value: true, message: 'city is required' },
      },
    );
  }


register({
          required: { value: true, message: 'first name is required' },
          validate: val


(re = defaultRe) =>
  regexpTokeniser(re, (offset, text, match) => {
    const charBefore = match.input.charAt(offset - 1);
    const textAfter = match.input.substring(offset + text.length);
    if (charBefore !== '' && !adjacentChars.test(charBefore)) {
      return null;
    }
    // Allow textAfter to start with '-' with the following non-adjanced
    // characters. It is useful for russian @username-ных endings.
    if (textAfter.length >= 2 && textAfter.charAt(0) === '-') {
      if (adjacentChars.test(textAfter.charAt(1))) {
        return null;
      }
    } else if (textAfter !== '' && !adjacentChars.test(textAfter.charAt(0))) {
      return null;
    }
    return tokenMaker(offset, text, match);
  })


(offset, text, match) => {
    const charBefore = match.input.charAt(offset - 1);
    const textAfter = match.input.substring(offset + text.length);
    if (charBefore !== '' && !adjacentChars.test(charBefore)) {
      return null;
    }
    // Allow textAfter to start with '-' with the following non-adjanced
    // characters. It is useful for russian @username-ных endings.
    if (textAfter.length >= 2 && textAfter.charAt(0) === '-') {
      if (adjacentChars.test(textAfter.charAt(1))) {
        return null;
      }
    } else if (textAfter !== '' && !adjacentChars.test(textAfter.charAt(0))) {
      return null;
    }
    return tokenMaker(offset, text, match);
  }


export class Mention extends Token {}


export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
	return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined);
}


export function workbenchInstantiationService(overrides?: { textFileService?: (instantiationService: IInstantiationService) => ITextFileService }): ITestInstantiationService {
	const instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()]));

	instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService());
	instantiationService.stub(IEnvironmentService, TestEnvironmentService);
	const contextKeyService = <IContextKeyService>instantiationService.createInstance(MockContextKeyService);
	instantiationService.stub(IContextKeyService, contextKeyService);
	instantiationService.stub(IProgressService, new TestProgressService());
	const workspaceContextService = new TestContextService(TestWorkspace);
	instantiationService.stub(IWorkspaceContextService, workspaceContextService);
	const configService = new TestConfigurationService();
	instantiationService.stub(IConfigurationService, configService);
	instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService));
	instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService));
	instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService));
	instantiationService.stub(IStorageService, new TestStorageService());
	instantiationService.stub(IRemotePathService, new TestRemotePathService(TestEnvironmentService));
	const layoutService = new TestLayoutService();
	instantiationService.stub(IWorkbenchLayoutService, layoutService);
	instantiationService.stub(IDialogService, new TestDialogService());
	const accessibilityService = new TestAccessibilityService();
	instantiationService.stub(IAccessibilityService, accessibilityService);
	instantiationService.stub(IFileDialogService, new TestFileDialogService());
	instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl));
	instantiationService.stub(IHistoryService, new TestHistoryService());
	instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(configService));
	instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService));
	const themeService = new TestThemeService();
	instantiationService.stub(IThemeService, themeService);
	instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl));
	instantiationService.stub(IFileService, new TestFileService());
	instantiationService.stub(IBackupFileService, new TestBackupFileService());
	instantiationService.stub(ITelemetryService, NullTelemetryService);
	instantiationService.stub(INotificationService, new TestNotificationService());
	instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService));
	instantiationService.stub(IMenuService, new TestMenuService());
	const keybindingService = new MockKeybindingService();
	instantiationService.stub(IKeybindingService, keybindingService);
	instantiationService.stub(IDecorationsService, new TestDecorationsService());
	instantiationService.stub(IExtensionService, new TestExtensionService());
	instantiationService.stub(IWorkingCopyFileService, instantiationService.createInstance(WorkingCopyFileService));
	instantiationService.stub(ITextFileService, overrides?.textFileService ? overrides.textFileService(instantiationService) : <ITextFileService>instantiationService.createInstance(TestTextFileService));
	instantiationService.stub(IHostService, <IHostService>instantiationService.createInstance(TestHostService));
	instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
	instantiationService.stub(ILogService, new NullLogService());
	const editorGroupService = new TestEditorGroupsService([new TestEditorGroupView(0)]);
	instantiationService.stub(IEditorGroupsService, editorGroupService);
	instantiationService.stub(ILabelService, <ILabelService>instantiationService.createInstance(LabelService));
	const editorService = new TestEditorService(editorGroupService);
	instantiationService.stub(IEditorService, editorService);
	instantiationService.stub(ICodeEditorService, new CodeEditorService(editorService, themeService));
	instantiationService.stub(IViewletService, new TestViewletService());
	instantiationService.stub(IListService, new TestListService());
	instantiationService.stub(IQuickInputService, new QuickInputService(TestEnvironmentService, configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService));

	return instantiationService;
}


export function registerTestEditor(id: string, inputs: SyncDescriptor<EditorInput>[], factoryInputId?: string): IDisposable {
	class TestEditorControl extends BaseEditor {

		constructor() { super(id, NullTelemetryService, new TestThemeService(), new TestStorageService()); }

		async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
			super.setInput(input, options, token);

			await input.resolve();
		}

		getId(): string { return id; }
		layout(): void { }
		createEditor(): void { }
	}

	const disposables = new DisposableStore();

	disposables.add(Registry.as<IEditorRegistry>(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, id, 'Test Editor Control'), inputs));

	if (factoryInputId) {

		interface ISerializedTestInput {
			resource: string;
		}

		class EditorsObserverTestEditorInputFactory implements IEditorInputFactory {

			canSerialize(editorInput: EditorInput): boolean {
				return true;
			}

			serialize(editorInput: EditorInput): string {
				let testEditorInput = <TestFileEditorInput>editorInput;
				let testInput: ISerializedTestInput = {
					resource: testEditorInput.resource.toString()
				};

				return JSON.stringify(testInput);
			}

			deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
				let testInput: ISerializedTestInput = JSON.parse(serializedEditorInput);

				return new TestFileEditorInput(URI.parse(testInput.resource), factoryInputId!);
			}
		}

		disposables.add(Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputFactory(factoryInputId, EditorsObserverTestEditorInputFactory));
	}

	return disposables;
}


group => group.id === identifier


resourceAndOption => this.resolve(resourceAndOption.resource, resourceAndOption.options)


stat => ({ stat, success: true })


(event: string, callback: Function): void => {
					if (event === 'data') {
						callback(this.content);
					}
					if (event === 'end') {
						callback();
					}
				}


() => this.providers.delete(scheme)


() => this.watches.splice(this.watches.indexOf(_resource), 1)


changes => changes.map((c): IFileChange => {
		return {
			type: c.type,
			resource: c.resource.with({ scheme: Schemas.vscodeRemote, authority: this.remoteAuthority }),
		};
	})


(c): IFileChange => {
		return {
			type: c.type,
			resource: c.resource.with({ scheme: Schemas.vscodeRemote, authority: this.remoteAuthority }),
		};
	}


export class TestServiceAccessor {
	constructor(
		@ILifecycleService public lifecycleService: TestLifecycleService,
		@ITextFileService public textFileService: TestTextFileService,
		@IWorkingCopyFileService public workingCopyFileService: IWorkingCopyFileService,
		@IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService,
		@IWorkspaceContextService public contextService: TestContextService,
		@IModelService public modelService: ModelServiceImpl,
		@IFileService public fileService: TestFileService,
		@IFileDialogService public fileDialogService: TestFileDialogService,
		@IWorkingCopyService public workingCopyService: IWorkingCopyService,
		@IEditorService public editorService: TestEditorService,
		@IEditorGroupsService public editorGroupService: IEditorGroupsService,
		@IModeService public modeService: IModeService,
		@ITextModelService public textModelResolverService: ITextModelService,
		@IUntitledTextEditorService public untitledTextEditorService: UntitledTextEditorService,
		@IConfigurationService public testConfigurationService: TestConfigurationService,
		@IBackupFileService public backupFileService: TestBackupFileService,
		@IHostService public hostService: TestHostService,
		@IQuickInputService public quickInputService: IQuickInputService
	) { }
}


export class TestTextFileService extends BrowserTextFileService {
	private resolveTextContentError!: FileOperationError | null;

	constructor(
		@IFileService protected fileService: IFileService,
		@IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService,
		@ILifecycleService lifecycleService: ILifecycleService,
		@IInstantiationService instantiationService: IInstantiationService,
		@IModelService modelService: IModelService,
		@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
		@IDialogService dialogService: IDialogService,
		@IFileDialogService fileDialogService: IFileDialogService,
		@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
		@IProductService productService: IProductService,
		@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
		@ITextModelService textModelService: ITextModelService,
		@ICodeEditorService codeEditorService: ICodeEditorService,
		@IRemotePathService remotePathService: IRemotePathService,
		@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
	) {
		super(
			fileService,
			untitledTextEditorService,
			lifecycleService,
			instantiationService,
			modelService,
			environmentService,
			dialogService,
			fileDialogService,
			textResourceConfigurationService,
			filesConfigurationService,
			textModelService,
			codeEditorService,
			remotePathService,
			workingCopyFileService
		);
	}

	setResolveTextContentErrorOnce(error: FileOperationError): void {
		this.resolveTextContentError = error;
	}

	async readStream(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileStreamContent> {
		if (this.resolveTextContentError) {
			const error = this.resolveTextContentError;
			this.resolveTextContentError = null;

			throw error;
		}

		const content = await this.fileService.readFileStream(resource, options);
		return {
			resource: content.resource,
			name: content.name,
			mtime: content.mtime,
			ctime: content.ctime,
			etag: content.etag,
			encoding: 'utf8',
			value: await createTextBufferFactoryFromStream(content.value),
			size: 10
		};
	}
}


export class TestProgressService implements IProgressService {

	_serviceBrand: undefined;

	withProgress(
		options: IProgressOptions | IProgressWindowOptions | IProgressNotificationOptions | IProgressCompositeOptions,
		task: (progress: IProgress<IProgressStep>) => Promise<any>,
		onDidCancel?: ((choice?: number | undefined) => void) | undefined
	): Promise<any> {
		return task(Progress.None);
	}
}


export class TestAccessibilityService implements IAccessibilityService {

	_serviceBrand: undefined;

	onDidChangeScreenReaderOptimized = Event.None;

	isScreenReaderOptimized(): boolean { return false; }
	alwaysUnderlineAccessKeys(): Promise<boolean> { return Promise.resolve(false); }
	setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { }
	getAccessibilitySupport(): AccessibilitySupport { return AccessibilitySupport.Unknown; }
}


export class TestDecorationsService implements IDecorationsService {

	_serviceBrand: undefined;

	onDidChangeDecorations: Event<IResourceDecorationChangeEvent> = Event.None;

	registerDecorationsProvider(_provider: IDecorationsProvider): IDisposable { return Disposable.None; }
	getDecoration(_uri: URI, _includeChildren: boolean, _overwrite?: IDecorationData): IDecoration | undefined { return undefined; }
}


export class TestExtensionService extends NullExtensionService { }


export class TestMenuService implements IMenuService {

	_serviceBrand: undefined;

	createMenu(_id: MenuId, _scopedKeybindingService: IContextKeyService): IMenu {
		return {
			onDidChange: Event.None,
			dispose: () => undefined,
			getActions: () => []
		};
	}
}


export class TestHistoryService implements IHistoryService {

	_serviceBrand: undefined;

	constructor(private root?: URI) { }

	reopenLastClosedEditor(): void { }
	forward(): void { }
	back(): void { }
	last(): void { }
	remove(_input: IEditorInput | IResourceEditorInput): void { }
	clear(): void { }
	clearRecentlyOpened(): void { }
	getHistory(): ReadonlyArray<IEditorInput | IResourceEditorInput> { return []; }
	openNextRecentlyUsedEditor(group?: GroupIdentifier): void { }
	openPreviouslyUsedEditor(group?: GroupIdentifier): void { }
	getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; }
	getLastActiveFile(_schemeFilter: string): URI | undefined { return undefined; }
	openLastEditLocation(): void { }
}


export class TestFileDialogService implements IFileDialogService {

	_serviceBrand: undefined;

	private confirmResult!: ConfirmResult;

	defaultFilePath(_schemeFilter?: string): URI | undefined { return undefined; }
	defaultFolderPath(_schemeFilter?: string): URI | undefined { return undefined; }
	defaultWorkspacePath(_schemeFilter?: string): URI | undefined { return undefined; }
	pickFileFolderAndOpen(_options: IPickAndOpenOptions): Promise<any> { return Promise.resolve(0); }
	pickFileAndOpen(_options: IPickAndOpenOptions): Promise<any> { return Promise.resolve(0); }
	pickFolderAndOpen(_options: IPickAndOpenOptions): Promise<any> { return Promise.resolve(0); }
	pickWorkspaceAndOpen(_options: IPickAndOpenOptions): Promise<any> { return Promise.resolve(0); }

	private fileToSave!: URI;
	setPickFileToSave(path: URI): void { this.fileToSave = path; }
	pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise<URI | undefined> { return Promise.resolve(this.fileToSave); }

	showSaveDialog(_options: ISaveDialogOptions): Promise<URI | undefined> { return Promise.resolve(undefined); }
	showOpenDialog(_options: IOpenDialogOptions): Promise<URI[] | undefined> { return Promise.resolve(undefined); }

	setConfirmResult(result: ConfirmResult): void { this.confirmResult = result; }
	showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise<ConfirmResult> { return Promise.resolve(this.confirmResult); }
}


export class TestLayoutService implements IWorkbenchLayoutService {

	_serviceBrand: undefined;

	dimension: IDimension = { width: 800, height: 600 };

	container: HTMLElement = window.document.body;

	onZenModeChange: Event<boolean> = Event.None;
	onCenteredLayoutChange: Event<boolean> = Event.None;
	onFullscreenChange: Event<boolean> = Event.None;
	onMaximizeChange: Event<boolean> = Event.None;
	onPanelPositionChange: Event<string> = Event.None;
	onPartVisibilityChange: Event<void> = Event.None;
	onLayout = Event.None;

	private readonly _onMenubarVisibilityChange = new Emitter<Dimension>();
	get onMenubarVisibilityChange(): Event<Dimension> { return this._onMenubarVisibilityChange.event; }

	isRestored(): boolean { return true; }
	hasFocus(_part: Parts): boolean { return false; }
	hasWindowBorder(): boolean { return false; }
	getWindowBorderRadius(): string | undefined { return undefined; }
	isVisible(_part: Parts): boolean { return true; }
	getDimension(_part: Parts): Dimension { return new Dimension(0, 0); }
	getContainer(_part: Parts): HTMLElement { return null!; }
	isTitleBarHidden(): boolean { return false; }
	isStatusBarHidden(): boolean { return false; }
	isActivityBarHidden(): boolean { return false; }
	setActivityBarHidden(_hidden: boolean): void { }
	isSideBarHidden(): boolean { return false; }
	setEditorHidden(_hidden: boolean): Promise<void> { return Promise.resolve(); }
	setSideBarHidden(_hidden: boolean): Promise<void> { return Promise.resolve(); }
	isPanelHidden(): boolean { return false; }
	setPanelHidden(_hidden: boolean): Promise<void> { return Promise.resolve(); }
	toggleMaximizedPanel(): void { }
	isPanelMaximized(): boolean { return false; }
	getMenubarVisibility(): MenuBarVisibility { throw new Error('not implemented'); }
	getSideBarPosition() { return 0; }
	getPanelPosition() { return 0; }
	setPanelPosition(_position: PartPosition): Promise<void> { return Promise.resolve(); }
	addClass(_clazz: string): void { }
	removeClass(_clazz: string): void { }
	getMaximumEditorDimensions(): Dimension { throw new Error('not implemented'); }
	getWorkbenchContainer(): HTMLElement { throw new Error('not implemented'); }
	toggleZenMode(): void { }
	isEditorLayoutCentered(): boolean { return false; }
	centerEditorLayout(_active: boolean): void { }
	resizePart(_part: Parts, _sizeChange: number): void { }
	registerPart(part: Part): void { }
	isWindowMaximized() { return false; }
	updateWindowMaximizedState(maximized: boolean): void { }
	getVisibleNeighborPart(part: Parts, direction: Direction): Parts | undefined { return undefined; }
	focus() { }
}


export class TestViewletService implements IViewletService {
	_serviceBrand: undefined;

	onDidViewletRegisterEmitter = new Emitter<ViewletDescriptor>();
	onDidViewletDeregisterEmitter = new Emitter<ViewletDescriptor>();
	onDidViewletOpenEmitter = new Emitter<IViewlet>();
	onDidViewletCloseEmitter = new Emitter<IViewlet>();

	onDidViewletRegister = this.onDidViewletRegisterEmitter.event;
	onDidViewletDeregister = this.onDidViewletDeregisterEmitter.event;
	onDidViewletOpen = this.onDidViewletOpenEmitter.event;
	onDidViewletClose = this.onDidViewletCloseEmitter.event;

	openViewlet(id: string, focus?: boolean): Promise<IViewlet | undefined> { return Promise.resolve(undefined); }
	getViewlets(): ViewletDescriptor[] { return []; }
	getAllViewlets(): ViewletDescriptor[] { return []; }
	getActiveViewlet(): IViewlet { return activeViewlet; }
	getDefaultViewletId(): string { return 'workbench.view.explorer'; }
	getViewlet(id: string): ViewletDescriptor | undefined { return undefined; }
	getProgressIndicator(id: string) { return undefined; }
	hideActiveViewlet(): void { }
	getLastActiveViewletId(): string { return undefined!; }
	dispose() { }
}


export class TestPanelService implements IPanelService {
	_serviceBrand: undefined;

	onDidPanelOpen = new Emitter<{ panel: IPanel, focus: boolean }>().event;
	onDidPanelClose = new Emitter<IPanel>().event;

	async openPanel(id?: string, focus?: boolean): Promise<undefined> { return undefined; }
	getPanel(id: string): any { return activeViewlet; }
	getPanels() { return []; }
	getPinnedPanels() { return []; }
	getActivePanel(): IPanel { return activeViewlet; }
	setPanelEnablement(id: string, enabled: boolean): void { }
	dispose() { }
	showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { throw new Error('Method not implemented.'); }
	getProgressIndicator(id: string) { return null!; }
	hideActivePanel(): void { }
	getLastActivePanelId(): string { return undefined!; }
}


export class TestViewsService implements IViewsService {
	_serviceBrand: undefined;

	onDidChangeViewVisibilityEmitter = new Emitter<{ id: string; visible: boolean; }>();

	onDidChangeViewVisibility = this.onDidChangeViewVisibilityEmitter.event;
	isViewVisible(id: string): boolean { return true; }
	getActiveViewWithId<T extends IView>(id: string): T | null { return null; }
	openView<T extends IView>(id: string, focus?: boolean | undefined): Promise<T | null> { return Promise.resolve(null); }
	closeView(id: string): void { }
	getProgressIndicator(id: string) { return null!; }
}


export class TestEditorGroupsService implements IEditorGroupsService {

	_serviceBrand: undefined;

	constructor(public groups: TestEditorGroupView[] = []) { }

	onDidActiveGroupChange: Event<IEditorGroup> = Event.None;
	onDidActivateGroup: Event<IEditorGroup> = Event.None;
	onDidAddGroup: Event<IEditorGroup> = Event.None;
	onDidRemoveGroup: Event<IEditorGroup> = Event.None;
	onDidMoveGroup: Event<IEditorGroup> = Event.None;
	onDidGroupIndexChange: Event<IEditorGroup> = Event.None;
	onDidLayout: Event<IDimension> = Event.None;
	onDidEditorPartOptionsChange = Event.None;

	orientation = GroupOrientation.HORIZONTAL;
	whenRestored: Promise<void> = Promise.resolve(undefined);
	willRestoreEditors = false;

	contentDimension = { width: 800, height: 600 };

	get activeGroup(): IEditorGroup { return this.groups[0]; }
	get count(): number { return this.groups.length; }

	getGroups(_order?: GroupsOrder): ReadonlyArray<IEditorGroup> { return this.groups; }
	getGroup(identifier: number): IEditorGroup | undefined { return find(this.groups, group => group.id === identifier); }
	getLabel(_identifier: number): string { return 'Group 1'; }
	findGroup(_scope: IFindGroupScope, _source?: number | IEditorGroup, _wrap?: boolean): IEditorGroup { throw new Error('not implemented'); }
	activateGroup(_group: number | IEditorGroup): IEditorGroup { throw new Error('not implemented'); }
	restoreGroup(_group: number | IEditorGroup): IEditorGroup { throw new Error('not implemented'); }
	getSize(_group: number | IEditorGroup): { width: number, height: number } { return { width: 100, height: 100 }; }
	setSize(_group: number | IEditorGroup, _size: { width: number, height: number }): void { }
	arrangeGroups(_arrangement: GroupsArrangement): void { }
	applyLayout(_layout: EditorGroupLayout): void { }
	setGroupOrientation(_orientation: GroupOrientation): void { }
	addGroup(_location: number | IEditorGroup, _direction: GroupDirection, _options?: IAddGroupOptions): IEditorGroup { throw new Error('not implemented'); }
	removeGroup(_group: number | IEditorGroup): void { }
	moveGroup(_group: number | IEditorGroup, _location: number | IEditorGroup, _direction: GroupDirection): IEditorGroup { throw new Error('not implemented'); }
	mergeGroup(_group: number | IEditorGroup, _target: number | IEditorGroup, _options?: IMergeGroupOptions): IEditorGroup { throw new Error('not implemented'); }
	copyGroup(_group: number | IEditorGroup, _location: number | IEditorGroup, _direction: GroupDirection): IEditorGroup { throw new Error('not implemented'); }
	centerLayout(active: boolean): void { }
	isLayoutCentered(): boolean { return false; }

	partOptions!: IEditorPartOptions;
	enforcePartOptions(options: IEditorPartOptions): IDisposable { return Disposable.None; }
}


export class TestEditorGroupView implements IEditorGroupView {

	constructor(public id: number) { }

	get group(): EditorGroup { throw new Error('not implemented'); }
	activeEditorPane!: IVisibleEditorPane;
	activeEditor!: IEditorInput;
	previewEditor!: IEditorInput;
	count!: number;
	disposed!: boolean;
	editors: ReadonlyArray<IEditorInput> = [];
	label!: string;
	ariaLabel!: string;
	index!: number;
	whenRestored: Promise<void> = Promise.resolve(undefined);
	element!: HTMLElement;
	minimumWidth!: number;
	maximumWidth!: number;
	minimumHeight!: number;
	maximumHeight!: number;

	isEmpty = true;
	isMinimized = false;

	onWillDispose: Event<void> = Event.None;
	onDidGroupChange: Event<IGroupChangeEvent> = Event.None;
	onWillCloseEditor: Event<IEditorCloseEvent> = Event.None;
	onDidCloseEditor: Event<IEditorCloseEvent> = Event.None;
	onWillOpenEditor: Event<IEditorOpeningEvent> = Event.None;
	onDidOpenEditorFail: Event<IEditorInput> = Event.None;
	onDidFocus: Event<void> = Event.None;
	onDidChange: Event<{ width: number; height: number; }> = Event.None;

	getEditors(_order?: EditorsOrder): ReadonlyArray<IEditorInput> { return []; }
	getEditorByIndex(_index: number): IEditorInput { throw new Error('not implemented'); }
	getIndexOfEditor(_editor: IEditorInput): number { return -1; }
	openEditor(_editor: IEditorInput, _options?: IEditorOptions): Promise<IEditorPane> { throw new Error('not implemented'); }
	openEditors(_editors: IEditorInputWithOptions[]): Promise<IEditorPane> { throw new Error('not implemented'); }
	isOpened(_editor: IEditorInput | IResourceEditorInput): boolean { return false; }
	isPinned(_editor: IEditorInput): boolean { return false; }
	isActive(_editor: IEditorInput): boolean { return false; }
	moveEditor(_editor: IEditorInput, _target: IEditorGroup, _options?: IMoveEditorOptions): void { }
	copyEditor(_editor: IEditorInput, _target: IEditorGroup, _options?: ICopyEditorOptions): void { }
	closeEditor(_editor?: IEditorInput, options?: ICloseEditorOptions): Promise<void> { return Promise.resolve(); }
	closeEditors(_editors: IEditorInput[] | { except?: IEditorInput; direction?: CloseDirection; savedOnly?: boolean; }, options?: ICloseEditorOptions): Promise<void> { return Promise.resolve(); }
	closeAllEditors(): Promise<void> { return Promise.resolve(); }
	replaceEditors(_editors: IEditorReplacement[]): Promise<void> { return Promise.resolve(); }
	pinEditor(_editor?: IEditorInput): void { }
	focus(): void { }
	invokeWithinContext<T>(fn: (accessor: ServicesAccessor) => T): T { throw new Error('not implemented'); }
	setActive(_isActive: boolean): void { }
	notifyIndexChanged(_index: number): void { }
	dispose(): void { }
	toJSON(): object { return Object.create(null); }
	layout(_width: number, _height: number): void { }
	relayout() { }
}


export class TestEditorGroupAccessor implements IEditorGroupsAccessor {

	groups: IEditorGroupView[] = [];
	activeGroup!: IEditorGroupView;

	partOptions: IEditorPartOptions = {};

	onDidEditorPartOptionsChange = Event.None;
	onDidVisibilityChange = Event.None;

	getGroup(identifier: number): IEditorGroupView | undefined { throw new Error('Method not implemented.'); }
	getGroups(order: GroupsOrder): IEditorGroupView[] { throw new Error('Method not implemented.'); }
	activateGroup(identifier: number | IEditorGroupView): IEditorGroupView { throw new Error('Method not implemented.'); }
	restoreGroup(identifier: number | IEditorGroupView): IEditorGroupView { throw new Error('Method not implemented.'); }
	addGroup(location: number | IEditorGroupView, direction: GroupDirection, options?: IAddGroupOptions | undefined): IEditorGroupView { throw new Error('Method not implemented.'); }
	mergeGroup(group: number | IEditorGroupView, target: number | IEditorGroupView, options?: IMergeGroupOptions | undefined): IEditorGroupView { throw new Error('Method not implemented.'); }
	moveGroup(group: number | IEditorGroupView, location: number | IEditorGroupView, direction: GroupDirection): IEditorGroupView { throw new Error('Method not implemented.'); }
	copyGroup(group: number | IEditorGroupView, location: number | IEditorGroupView, direction: GroupDirection): IEditorGroupView { throw new Error('Method not implemented.'); }
	removeGroup(group: number | IEditorGroupView): void { throw new Error('Method not implemented.'); }
	arrangeGroups(arrangement: GroupsArrangement, target?: number | IEditorGroupView | undefined): void { throw new Error('Method not implemented.'); }
}


export class TestEditorService implements EditorServiceImpl {

	_serviceBrand: undefined;

	onDidActiveEditorChange: Event<void> = Event.None;
	onDidVisibleEditorsChange: Event<void> = Event.None;
	onDidCloseEditor: Event<IEditorCloseEvent> = Event.None;
	onDidOpenEditorFail: Event<IEditorIdentifier> = Event.None;
	onDidMostRecentlyActiveEditorsChange: Event<void> = Event.None;

	activeEditorPane: IVisibleEditorPane | undefined;
	activeTextEditorControl: ICodeEditor | IDiffEditor | undefined;
	activeTextEditorMode: string | undefined;
	activeEditor: IEditorInput | undefined;
	editors: ReadonlyArray<IEditorInput> = [];
	mostRecentlyActiveEditors: ReadonlyArray<IEditorIdentifier> = [];
	visibleEditorPanes: ReadonlyArray<IVisibleEditorPane> = [];
	visibleTextEditorControls = [];
	visibleEditors: ReadonlyArray<IEditorInput> = [];
	count = this.editors.length;

	constructor(private editorGroupService?: IEditorGroupsService) { }

	getEditors() { return []; }
	overrideOpenEditor(_handler: IOpenEditorOverrideHandler): IDisposable { return toDisposable(() => undefined); }
	openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<IEditorPane | undefined>;
	openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextEditorPane | undefined>;
	openEditor(editor: IResourceDiffEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextDiffEditorPane | undefined>;
	async openEditor(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | ITextEditorOptions | IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<IEditorPane | undefined> {
		throw new Error('not implemented');
	}
	doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditorInputType): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined {
		if (!this.editorGroupService) {
			return undefined;
		}

		return [this.editorGroupService.activeGroup, editor as EditorInput, undefined];
	}
	openEditors(_editors: any, _group?: any): Promise<IEditorPane[]> { throw new Error('not implemented'); }
	isOpen(_editor: IEditorInput | IResourceEditorInput): boolean { return false; }
	replaceEditors(_editors: any, _group: any) { return Promise.resolve(undefined); }
	invokeWithinEditorContext<T>(fn: (accessor: ServicesAccessor) => T): T { throw new Error('not implemented'); }
	createEditorInput(_input: IResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput): EditorInput { throw new Error('not implemented'); }
	save(editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise<boolean> { throw new Error('Method not implemented.'); }
	saveAll(options?: ISaveEditorsOptions): Promise<boolean> { throw new Error('Method not implemented.'); }
	revert(editors: IEditorIdentifier[], options?: IRevertOptions): Promise<void> { throw new Error('Method not implemented.'); }
	revertAll(options?: IRevertAllEditorsOptions): Promise<void> { throw new Error('Method not implemented.'); }
}