@Component({
  selector: 'app-paypal-stub',
  templateUrl: './paypalstub.component.html',
  styleUrls: ['./paypalstub.component.css']
})
export class PaypalStubComponent implements OnInit {
  @Output() paymentStatusChange = new EventEmitter<boolean>();
  paymentStatus: string;
  MAX_ALLOWED_ATTEMPTS = environment.MAX_PAYMENT_ALLOWED_ATTEMPTS;
  timerCheck: any;
  orderId: string;
  closeResult = '';
  @ViewChild('modalContent') public modalContentRef: TemplateRef<any>;
  @Input() stubUserChoice: '';
  @ViewChild(MerchantResponseComponent) merchantResponseComponent;
  oscConfig: string;
  amountTotal: number;

  constructor(private modalService: NgbModal,
              private gd: GlobalAppData,
              private paypalService: PaypalService,
              private sharedService: SharedService) {}

  ngOnInit(): void {
    this.oscConfig = this.gd.oscConfig.oscCode;
    this.amountTotal = this.gd.orderTransaction.amount;
  }

  openPaypalPopup(): void {
    this.createPaypalOrder().then((orderId: string) => {
      if (orderId) {
        this.orderId = orderId;
        this.openModal(this.modalContentRef);
      }
      return orderId;
    }).catch((err) => {
      return null;
    });
  }

  openModal(modalContent){
    this.modalService.open(modalContent, {ariaLabelledBy: 'modal-basic-title'}).result.then((result) => {
      this.closeResult = `Closed with: ${result}`;
      // call PSL service
      const requestBody = {
        payPalOrderId: this.orderId,
        paymentResult: this.stubUserChoice // 'accept' : 'decline'
      };
      this.paypalService.postPaypalStubData(JSON.stringify(requestBody)).then((res: string) => {
        if (JSON.stringify(res) === 'true') {
            this.capturePayPalOrder(JSON.stringify(res));
        }
      }).catch((error) => {
        this.handlePaypalDecline(this.gd.attemptCount);
      });
    }, (reason) => {
      this.handlePaypalDecline(this.gd.attemptCount);
      this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
    });
  }

  private getDismissReason(reason: any): string {
    if (reason === ModalDismissReasons.ESC) {
      return 'by pressing ESC';
    } else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
      return 'by clicking on a backdrop';
    } else {
      return `with: ${reason}`;
    }
  }

  capturePayPalOrder(data: any): void {
    try{
        this.paypalService.capturePaypalOrder(this.gd.transId)
        .then((res: PaypalResponse) => {
        if (res.status === 'COMPLETED') {
            // this.paymentStatusChange.emit(true);
          this.redirectToMerchantURL();
            } else {
            this.handlePaypalDecline(this.gd.attemptCount);

            }
        }).catch((err) => {

        });
    }catch (err){

    }
  }

  createPaypalOrder(): Promise<string> {
    return new Promise((resolve, reject) => {
      if (this.gd.attemptCount > 1 && this.gd.attemptCount <= environment.MAX_PAYMENT_ALLOWED_ATTEMPTS) {
        this.sharedService.getNewTransactionId(this.gd.transId)
          .then((newTransId: string) => {
            this.gd.transId = newTransId;
            updateQueryStringParam('t', newTransId);
            return this.paypalService.getPaypalOrderId(newTransId)
              .then((ppRes: PaypalResponse) => {
                this.gd.attemptCount += 1;
                resolve(ppRes.paypalOrderId);
              })
              .catch((err) => {
                reject(JSON.stringify(err));
              });
          }).catch((error) => {

            if (error && error.status && error.error) {
              if (error.status === 400 && error.error === 'No of retries exhausted') {
                this.paymentStatusChange.emit(false);
              }
            }
            reject(JSON.stringify(error));
          });
      } else {
        return this.paypalService.getPaypalOrderId(this.gd.transId)
          .then((ppRes: PaypalResponse) => {
            this.gd.attemptCount += 1;
            resolve(ppRes.paypalOrderId);
          })
          .catch((err) => {
            reject(JSON.stringify(err));
          });
      }
    });
  }

  handlePaypalDecline(pmtAttemptCount: number): void {
    if (pmtAttemptCount <= this.MAX_ALLOWED_ATTEMPTS) {
      this.paymentStatus = 'declined';
    } else {
      this.redirectToMerchantRejectURL();
    }
  }

  redirectToMerchantURL(): void {

    const paymentReq: MerchantPaymentReq = {

    }

    let merchantSuccessURL = this.gd.orderTransaction.order.merchantSuccessURL;
    if (!merchantSuccessURL) {
      merchantSuccessURL = this.gd.oscConfig.successUrl;
    }
    this.redirectToMerchant(merchantSuccessURL);
  }

  redirectToMerchantRejectURL(): void {

    let merchantSuccessURL = this.gd.orderTransaction.order.merchantRejectURL;
    if (!merchantSuccessURL) {
      merchantSuccessURL = this.gd.oscConfig.rejectUrl;
    }

    this.redirectToMerchant(merchantSuccessURL);
  }

  redirectToMerchant(url: string): void {

    const paymentActionData = {
      url: url,
      orderTransaction: this.gd.orderTransaction,
      merchantPaymentReq: {},
    } as MerchantResponseData ;

    this.merchantResponseComponent.sendResponseToMerchant('PAYPAL', paymentActionData);
  }
}


ngOnInit(): void {
    this.oscConfig = this.gd.oscConfig.oscCode;
    this.amountTotal = this.gd.orderTransaction.amount;
  }


openPaypalPopup(): void {
    this.createPaypalOrder().then((orderId: string) => {
      if (orderId) {
        this.orderId = orderId;
        this.openModal(this.modalContentRef);
      }
      return orderId;
    }).catch((err) => {
      return null;
    });
  }


openModal(modalContent){
    this.modalService.open(modalContent, {ariaLabelledBy: 'modal-basic-title'}).result.then((result) => {
      this.closeResult = `Closed with: ${result}`;
      // call PSL service
      const requestBody = {
        payPalOrderId: this.orderId,
        paymentResult: this.stubUserChoice // 'accept' : 'decline'
      };
      this.paypalService.postPaypalStubData(JSON.stringify(requestBody)).then((res: string) => {
        if (JSON.stringify(res) === 'true') {
            this.capturePayPalOrder(JSON.stringify(res));
        }
      }).catch((error) => {
        this.handlePaypalDecline(this.gd.attemptCount);
      });
    }, (reason) => {
      this.handlePaypalDecline(this.gd.attemptCount);
      this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
    });
  }


private getDismissReason(reason: any): string {
    if (reason === ModalDismissReasons.ESC) {
      return 'by pressing ESC';
    } else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
      return 'by clicking on a backdrop';
    } else {
      return `with: ${reason}`;
    }
  }


capturePayPalOrder(data: any): void {
    try{
        this.paypalService.capturePaypalOrder(this.gd.transId)
        .then((res: PaypalResponse) => {
        if (res.status === 'COMPLETED') {
            // this.paymentStatusChange.emit(true);
          this.redirectToMerchantURL();
            } else {
            this.handlePaypalDecline(this.gd.attemptCount);

            }
        }).catch((err) => {

        });
    }catch (err){

    }
  }


createPaypalOrder(): Promise<string> {
    return new Promise((resolve, reject) => {
      if (this.gd.attemptCount > 1 && this.gd.attemptCount <= environment.MAX_PAYMENT_ALLOWED_ATTEMPTS) {
        this.sharedService.getNewTransactionId(this.gd.transId)
          .then((newTransId: string) => {
            this.gd.transId = newTransId;
            updateQueryStringParam('t', newTransId);
            return this.paypalService.getPaypalOrderId(newTransId)
              .then((ppRes: PaypalResponse) => {
                this.gd.attemptCount += 1;
                resolve(ppRes.paypalOrderId);
              })
              .catch((err) => {
                reject(JSON.stringify(err));
              });
          }).catch((error) => {

            if (error && error.status && error.error) {
              if (error.status === 400 && error.error === 'No of retries exhausted') {
                this.paymentStatusChange.emit(false);
              }
            }
            reject(JSON.stringify(error));
          });
      } else {
        return this.paypalService.getPaypalOrderId(this.gd.transId)
          .then((ppRes: PaypalResponse) => {
            this.gd.attemptCount += 1;
            resolve(ppRes.paypalOrderId);
          })
          .catch((err) => {
            reject(JSON.stringify(err));
          });
      }
    });
  }


handlePaypalDecline(pmtAttemptCount: number): void {
    if (pmtAttemptCount <= this.MAX_ALLOWED_ATTEMPTS) {
      this.paymentStatus = 'declined';
    } else {
      this.redirectToMerchantRejectURL();
    }
  }


redirectToMerchantURL(): void {

    const paymentReq: MerchantPaymentReq = {

    }

    let merchantSuccessURL = this.gd.orderTransaction.order.merchantSuccessURL;
    if (!merchantSuccessURL) {
      merchantSuccessURL = this.gd.oscConfig.successUrl;
    }
    this.redirectToMerchant(merchantSuccessURL);
  }


redirectToMerchantRejectURL(): void {

    let merchantSuccessURL = this.gd.orderTransaction.order.merchantRejectURL;
    if (!merchantSuccessURL) {
      merchantSuccessURL = this.gd.oscConfig.rejectUrl;
    }

    this.redirectToMerchant(merchantSuccessURL);
  }


redirectToMerchant(url: string): void {

    const paymentActionData = {
      url: url,
      orderTransaction: this.gd.orderTransaction,
      merchantPaymentReq: {},
    } as MerchantResponseData ;

    this.merchantResponseComponent.sendResponseToMerchant('PAYPAL', paymentActionData);
  }


export interface SuperBatchWebResources {
  uris?: {
    all?: string;
    css?: string;
    js?: string;
  };
  tags?: {
    all?: string;
    css?: string;
    data?: string;
    js?: string;
  };
  metatags?: string;
}


/**
 * @class PISPBackendRequests
 * @description tiny wrapper dedicated to make requests to DFSP backend endpoint
 */
export class PISPBackendRequests extends HttpRequests {
  // we want this constructor for better code support
  // eslint-disable-next-line no-useless-constructor
  constructor (config: PISPBackendConfig) {
    super(config)
  }

  // getters

  // config getter
  // polymorphism for getters can be handy and saves a lot of type casting
  protected get config (): PISPBackendConfig {
    return super.config as unknown as PISPBackendConfig
  }

  get signAuthorizationPath (): string {
    return this.config.signAuthorizationPath
  }

  // used by handler in POST /authorizations @ Ingoing
  // requests signing of Authorization Request
  // PISP Backend will ask the User to sign AuthorizationRequest
  // and in response delivers the cryptographic proof of signing in AuthenticationValue.pinValue
  async signAuthorization (
    inRequest: tpAPI.Schemas.AuthorizationsPostRequest
  ): Promise<fspiopAPI.Schemas.AuthenticationValue | void> {
    return this.post<tpAPI.Schemas.AuthorizationsPostRequest, fspiopAPI.Schemas.AuthenticationValue>(
      this.signAuthorizationPath, inRequest
    )
  }
}


export interface PISPBackendConfig extends HttpRequestsConfig {
  // the path for signAuthorizationRequest
  signAuthorizationPath: string
}


// used by handler in POST /authorizations @ Ingoing
// requests signing of Authorization Request
// PISP Backend will ask the User to sign AuthorizationRequest
// and in response delivers the cryptographic proof of signing in AuthenticationValue.pinValue
async signAuthorization (
    inRequest: tpAPI.Schemas.AuthorizationsPostRequest
  ): Promise<fspiopAPI.Schemas.AuthenticationValue | void> {
    return this.post<tpAPI.Schemas.AuthorizationsPostRequest, fspiopAPI.Schemas.AuthenticationValue>(
      this.signAuthorizationPath, inRequest
    )
  }


export default function AddressClaimModal({ isOpen, onDismiss }: { isOpen: boolean; onDismiss: () => void }) {
  const { chainId } = useActiveWeb3React()

  // state for smart contract input
  const [typed, setTyped] = useState('')
  function handleRecipientType(val: string) {
    setTyped(val)
  }

  // monitor for third party recipient of claim
  const { address: parsedAddress } = useENS(typed)

  // used for UI loading states
  const [attempting, setAttempting] = useState<boolean>(false)

  // monitor the status of the claim from contracts and txns
  const { claimCallback } = useClaimCallback(parsedAddress)
  const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(parsedAddress)

  // check if the user has something available
  const hasAvailableClaim = useUserHasAvailableClaim(parsedAddress)

  const [hash, setHash] = useState<string | undefined>()

  // monitor the status of the claim from contracts and txns
  const claimPending = useIsTransactionPending(hash ?? '')
  const claimConfirmed = hash && !claimPending

  // use the hash to monitor this txn

  function onClaim() {
    setAttempting(true)
    claimCallback()
      .then((hash) => {
        setHash(hash)
      })
      // reset modal and log error
      .catch((error) => {
        setAttempting(false)
        console.log(error)
      })
  }

  function wrappedOnDismiss() {
    setAttempting(false)
    setHash(undefined)
    setTyped('')
    onDismiss()
  }

  return (
    <Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
      <Confetti start={Boolean(isOpen && claimConfirmed && attempting)} />
      {!attempting && (
        <ContentWrapper gap="lg">
          <ModalUpper>
            <CardBGImage />
            <CardNoise />
            <CardSection gap="md">
              <RowBetween>
                <TYPE.white fontWeight={500}>
                  <Trans>Claim UNI Token</Trans>
                </TYPE.white>
                <CloseIcon onClick={wrappedOnDismiss} style={{ zIndex: 99 }} stroke="white" />
              </RowBetween>
              <TYPE.white fontWeight={700} fontSize={36}>
                <Trans>{unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')}


function handleRecipientType(val: string) {
    setTyped(val)
  }


// use the hash to monitor this txn
function onClaim() {
    setAttempting(true)
    claimCallback()
      .then((hash) => {
        setHash(hash)
      })
      // reset modal and log error
      .catch((error) => {
        setAttempting(false)
        console.log(error)
      })
  }


function wrappedOnDismiss() {
    setAttempting(false)
    setHash(undefined)
    setTyped('')
    onDismiss()
  }


({ activeBG }) =>
    activeBG &&
    'radial-gradient(76.02% 75.41% at 1.84% 0%, rgba(255, 0, 122, 0.2) 0%, rgba(33, 114, 229, 0.2) 100%), #FFFFFF;'


(hash) => {
        setHash(hash)
      }


(error) => {
        setAttempting(false)
        console.log(error)
      }


toFixed(0, { groupSeparator: ',' }


claimConfirmed ? <Trans>


Claimed<


shortenAddress(parsedAddress)


(): void => {
  nock
    .default('https://api.github.com')
    .get('/repos/michaeljolley/test-repo/stats/commit_activity')
    .reply(500)
}


(): void => {
  nock
    .default('https://api.github.com')
    .get('/repos/michaeljolley/test-repo/stats/commit_activity')
    .reply(200, [
      {
        total: 0,
        week: 1555804800,
        days: [1, 2, 3, 4, 5, 6, 7]
      },
      {
        total: 0,
        week: 1556409600,
        days: [8, 9, 10, 11, 12, 13, 14]
      }
    ])
}


(): void => {
  nock
    .default('https://api.github.com')
    .get('/repos/michaeljolley/test-repo')
    .reply(500)
}


(): void => {
  nock
    .default('https://api.github.com')
    .get('/repos/michaeljolley/test-repo')
    .reply(200, {
      id: 1111111,
      name: 'test-repo'
    })
}


(): void => {
  nock
    .default('https://api.github.com')
    .post('/graphql')
    .reply(500)
}


(): void => {
  nock
    .default('https://api.github.com')
    .post('/graphql')
    .reply(200, {
      data: {
        repository: {
          openIssues: {
            totalCount: 1
          },
          closedIssues: {
            totalCount: 2
          },
          openPRs: {
            totalCount: 3
          },
          closedPRs: {
            totalCount: 4
          },
          mergedPRs: {
            totalCount: 5
          },
          contributors: {
            totalCount: 6
          }
        }
      }
    })
}


/**
 * A common rendering engine utility. This abstracts the logic
 * for handling the platformServer compiler, the module cache, and
 * the document loader
 */
export declare class ɵCommonEngine {
    private moduleOrFactory?;
    private providers;
    /** Return an instance of the platformServer compiler */
    getCompiler(): Compiler;
    private factoryCacheMap;
    private templateCache;
    constructor(moduleOrFactory?: Type<{}> | NgModuleFactory<{}> | undefined, providers?: StaticProvider[]);
    /**
     * Render an HTML document for a specific URL with specified
     * render options
     */
    render(opts: ɵRenderOptions): Promise<string>;
    /** Return the factory for a given engine instance */
    getFactory(moduleOrFactory: Type<{}> | NgModuleFactory<{}>): Promise<NgModuleFactory<{}>>;
    /** Retrieve the document from the cache or the filesystem */
    getDocument(filePath: string): Promise<string>;
}


/** ResourceLoader implementation for loading files */
export declare class ɵFileLoader implements ResourceLoader {
    get(url: string): Promise<string>;
}


/** These are the allowed options for the render */
export declare interface ɵRenderOptions {
    bootstrap: Type<{}> | NgModuleFactory<{}>;
    providers?: StaticProvider[];
    url?: string;
    document?: string;
    documentFilePath?: string;
}


/** Return an instance of the platformServer compiler */
getCompiler(): Compiler;


/**
     * Render an HTML document for a specific URL with specified
     * render options
     */
render(opts: ɵRenderOptions): Promise<string>;


/** Return the factory for a given engine instance */
getFactory(moduleOrFactory: Type<{}> | NgModuleFactory<{}>): Promise<NgModuleFactory<{}>>;


/** Retrieve the document from the cache or the filesystem */
getDocument(filePath: string): Promise<string>;


get(url: string): Promise<string>;


export interface IOrderCreateRequest {
    id: string;
    tradein: IVehicleTrade | undefined;
    insurance: IOrderInsuranceRequest | undefined;
    payment: IOrderPaymentRequest;
    deliveryType: DeliveryType;
    customer: ICustomer;
}


export interface IOrderCreateResponse {
    getId(): string;
}


export interface IOrderCreateResponseData {
    id: string;
}


export interface IOrderOptionsRequest {
    id: string;
    branchId?: string;
}


export interface IOrderOptionsResponse {
    requiresDealerSelection(): boolean;
    getDealerSites(): IDealerOption[];
    getPaymentOptions(): IPaymentOption[];
    getDeliveryOptions(): IDeliveryOption[];
    getInsuranceOption(): IAvailableInsuranceOption | undefined;
    getOrderConditions(): string | undefined;
    getOrderReturnConditions(): string | undefined;
    getConditionsPdfUri(): string | null | undefined;
    getContactInformation(): IContactInformation | undefined;
    allowsTradeIn(): boolean;
    isUnavailable(): boolean;
}


export interface IOrderInsuranceRequest {
    personalNumber?: string;
    drivingDistance: DrivingDistance;
    addons: string[] | undefined;
}


export interface IOrderCreditAssessment {
    scoreId: string;
    financialProductCode: string;
    recommendation: string;
    decision: string;
}


export interface IOrderPaymentRequest {
    type: PaymentType;
    months: number;
    downPayment: number;
    residualValue: number | undefined;
    externalId?: string;
    creditAssessment?: IOrderCreditAssessment;
}


export interface IOrderOptionsResponseData {
    dealers: IDealerOption[];
    conditions: string | undefined;
    returnConditions: string | undefined;
    conditionsPdfUri: string | null | undefined;
    contactInformation: IContactInformation | undefined;
    delivery: IDeliveryOption[];
    insurance: IAvailableInsuranceOption | undefined;
    payment: IPaymentOptionResponseData[];
    tradeIn: boolean;
    unavailable: boolean;
}


export interface IDealerOption {
    id: string;
    name: string;
    location: ILocation | undefined;
}


export interface ILocation {
    address: string | undefined;
    city: string | undefined;
    latitude: number | undefined;
    longitude: number | undefined;
}


export interface IOrderCustomer {
    personalNumber: string;
    email: string;
    phoneNumber: string;
    name: string;
    address: IAddress;
}


export interface IDeliveryOption {
    type: DeliveryType;
    deliveryTime: string;
    startupCost: number;
    unitPrice?: number;
    unit?: string;
    minQuantity?: number;
    maxQuantity?: number;
}


export interface IAvailableInsuranceOption {
    description: string | undefined;
    logo: string | undefined;
    title: string;
    url: string | undefined;
    ecomInsuranceText: string | undefined;
}


export interface IPaymentOptionResponseData {
    loanDetails: IPaymentLookupResponseData | undefined;
    logo: string | undefined;
    name: string | undefined;
    price: number | undefined;
    type: PaymentType;
    unit: string | undefined;
    externalId: string | undefined;
}


export interface IPaymentOption {
    loanDetails: PaymentLookupResponse | undefined;
    logo: string | undefined;
    name: string | undefined;
    price: number | undefined;
    type: PaymentType;
    unit: string | undefined;
    externalId: string | undefined;
}


export interface IContactInformation {
    address: string | undefined;
    city: string | undefined;
    email: string | undefined;
    homePage: string | undefined;
    name: string | undefined;
    phone: string | undefined;
    zip: string | undefined;
}


export enum DeliveryType {
    None = "None",
    Pickup = "Pickup",
    Delivery = "Delivery",
}


export enum PaymentType {
    Cash = "Cash",
    Lease = "Lease",
    Loan = "Loan",
}


export default class Dropdown extends SvelteComponentTyped<
  DropdownProps,
  {
    select: CustomEvent<{
      selectedId: DropdownItemId;
      selectedItem: DropdownItem;
    }>;
  },
  {}
> {}


export interface DropdownItem {
  id: DropdownItemId;
  text: DropdownItemText;
}


export interface DropdownProps
  extends svelte.JSX.HTMLAttributes<HTMLElementTagNameMap["div"]> {
  /**
   * Set the dropdown items
   * @default []
   */
  items?: DropdownItem[];

  /**
   * Override the display of a dropdown item
   * @default (item) => item.text || item.id
   */
  itemToString?: (item: DropdownItem) => string;

  /**
   * Specify the selected item id
   * @default undefined
   */
  selectedId?: DropdownItemId;

  /**
   * Specify the type of dropdown
   * @default "default"
   */
  type?: "default" | "inline";

  /**
   * Specify the direction of the dropdown menu
   * @default "bottom"
   */
  direction?: "bottom" | "top";

  /**
   * Specify the size of the dropdown field
   * @default undefined
   */
  size?: "sm" | "lg" | "xl";

  /**
   * Set to `true` to open the dropdown
   * @default false
   */
  open?: boolean;

  /**
   * Set to `true` to use the inline variant
   * @default false
   */
  inline?: boolean;

  /**
   * Set to `true` to enable the light variant
   * @default false
   */
  light?: boolean;

  /**
   * Set to `true` to disable the dropdown
   * @default false
   */
  disabled?: boolean;

  /**
   * Specify the title text
   * @default ""
   */
  titleText?: string;

  /**
   * Set to `true` to indicate an invalid state
   * @default false
   */
  invalid?: boolean;

  /**
   * Specify the invalid state text
   * @default ""
   */
  invalidText?: string;

  /**
   * Set to `true` to indicate an warning state
   * @default false
   */
  warn?: boolean;

  /**
   * Specify the warning state text
   * @default ""
   */
  warnText?: string;

  /**
   * Specify the helper text
   * @default ""
   */
  helperText?: string;

  /**
   * Specify the list box label
   * @default undefined
   */
  label?: string;

  /**
   * Set to `true` to visually hide the label text
   * @default false
   */
  hideLabel?: boolean;

  /**
   * Override the default translation ids
   * @default undefined
   */
  translateWithId?: (id: any) => string;

  /**
   * Set an id for the list box component
   * @default "ccs-" + Math.random().toString(36)
   */
  id?: string;

  /**
   * Specify a name attribute for the list box
   * @default undefined
   */
  name?: string;

  /**
   * Obtain a reference to the button HTML element
   * @default null
   */
  ref?: null | HTMLButtonElement;
}


export type DropdownItemId = any;


export type DropdownItemText = string;


/**
 * MockObservable is used when we need to pass observable somewhere
 * and than set what should be returned and how.
 * Source can even be rxjs marble
 */
export class MockObservable<T> extends Observable<T> {
  public source: Observable<any> = EMPTY;

  /**
   * Overriding lift function mocks .pipe() function
   */
  public lift<R>(operator: Operator<T, R>): Observable<R> {
    const observable = new MockObservable<R>();
    observable.source = this;
    observable.operator = operator;
    return observable;
  }

  /**
   * Set's source for mock observable
   */
  public setSource(source: Observable<T>): void {
    this.source = source;
  }
}


/**
 * A mock implementation of {@link NgZone}.
 */
@Injectable()
export class MockNgZone extends NgZone {
  public onStable: EventEmitter<any> = new EventEmitter(false);

  constructor() {
    super({ enableLongStackTrace: false });
  }

  public run(fn: Function): any {
    return fn();
  }

  public runOutsideAngular(fn: Function): any {
    return fn();
  }

  public simulateZoneExit(): void {
    this.onStable.emit(null);
  }
}


export class MockHttp {
  public getSource: MockObservable<any> = new MockObservable();
  public get: jest.Mock<any> = jest.fn().mockReturnValue(this.getSource);

  public postSource: MockObservable<any> = new MockObservable();
  public post: jest.Mock<any> = jest.fn().mockReturnValue(this.postSource);

  public requestSource: MockObservable<any> = new MockObservable();
  public request: jest.Mock<any> = jest.fn().mockReturnValue(this.requestSource);
}


/**
   * Overriding lift function mocks .pipe() function
   */
public lift<R>(operator: Operator<T, R>): Observable<R> {
    const observable = new MockObservable<R>();
    observable.source = this;
    observable.operator = operator;
    return observable;
  }


/**
   * Set's source for mock observable
   */
public setSource(source: Observable<T>): void {
    this.source = source;
  }


public run(fn: Function): any {
    return fn();
  }


public runOutsideAngular(fn: Function): any {
    return fn();
  }


public simulateZoneExit(): void {
    this.onStable.emit(null);
  }


function bindTask(baseUri: string) {
    if ('Notification' in window) {
        Notification.requestPermission();
    }
    refreshPanel(baseUri);
    $('.panel').on('dragover', function(ev) {
        ev.stopPropagation();
        ev.preventDefault();
    }).on('drop', function(ev) {
        ev.stopPropagation();
		ev.preventDefault();
        const id = parseInt(ev.originalEvent.dataTransfer.getData('task'), 10);
        if (id < 1) {
            return;
        }
        box.trigger(TASK_BATCH_ADD, id);
    }).on(TASK_START, '.task-item', function() {
        playTask(baseUri, $(this));
    }).on(TASK_PAUSE, '.task-item', function() {
        pauseTask(baseUri, $(this));
    }).on(TASK_STOP, '.task-item', function() {
        stopTask(baseUri, $(this));
    }).on(TASK_REFRESH_TIME, '.task-item', function(_, time: string) {
        $(this).find('.time').text(time);
    }).on(TASK_CHECK, '.task-item', function() {
        checkTask(baseUri, $(this));
    }).on(TASK_SHOW_TIMER, '.task-item', function() {
        let row = $(this);
        if (!row.hasClass('active')) {
            return;
        }
        timerBox.trigger(TASK_SHOW_TIMER, $(this).find('.name').text());
    }).on('click', '.task-item .fa-pause-circle', function() {
        $(this).closest('.task-item').trigger(TASK_PAUSE);
    }).on('click', '.task-item .fa-play-circle', function() {
        $(this).closest('.task-item').trigger(TASK_START);
    }).on('click', '.task-item .fa-stop-circle', function() {
        $(this).closest('.task-item').trigger(TASK_STOP);
    }).on('click', '.task-item .name', function() {
        $(this).closest('.task-item').trigger(TASK_SHOW_TIMER);
    }).on('click', '.last-task-time .time', function() {
        let $this = $(this);
        const time = $this.data('time');
        if ($this.text().indexOf('前') >= 0) {
            $this.text(time);
            return;
        }
        $this.text(Timer.format(new Date().getTime() - new Date(time).getTime(), 'i分s秒') + '前');
    });
    let box = $('.dialog-panel');
    $('[data-action=add]').click(function(e) {
        e.preventDefault();
        box.show();
    });
    box.on('click', '.task-item', function() {
        $(this).toggleClass('active');
    }).on('click', '.btn', function() {
        let items = [];
        box.find('.task-item.active').each(function() {
            items.push($(this).removeClass('active').data('id'));
        });
        box.hide();
        if (items.length < 1) {
            return;
        }
        box.trigger(TASK_BATCH_ADD, items);
    }).on(TASK_BATCH_ADD, function(_, ...items) {
        if (!items) {
            return;
        }
        postJson(baseUri + 'task/batch_add', {
            id: items
        }, res => {
            if (res.code !== 200) {
                parseAjax(res);
                return;
            }
            refreshPanel(baseUri);
        });
    }).on('mouseenter', '.task-item', function() {
        $(this).attr('draggable', 'true');
    }).on('dragstart', '.task-item', function(ev) {
        ev.originalEvent.dataTransfer.setData('task', $(this).data('id'));
    });
    let timerBox = $('.dialog-timer');
    let startPoint = null;
    timerBox.on(TASK_HIDE_TIMER, function(_, i = 0) {
        animation(i, -$(window).height(), i => {
            timerBox.css('transform', 'translateY('+ i +'px)');
        }, () => {
            timerBox.removeClass('closing');
            timerBox.hide();
            exitFullscreen();
        });
    }).on(TASK_SHOW_TIMER, function(_, name: string) {
        timerBox.find('.timer-tip').text(name);
        timerBox.show();
        timer.dialog = timerBox;
        fullScreen();
        animation(-$(window).height(), 0, i => {
            timerBox.css('transform', 'translateY('+ i +'px)');
        }, () => {
            timerBox.removeClass('closing');
        });
    }).on(TASK_TICK_TIMER, function(_, time: string) {
        timerBox.find('.timer-box').text(time);
    }).on(TASK_PAUSE, function() {
        timer.element.trigger(TASK_PAUSE);
        timerBox.trigger(TASK_HIDE_TIMER);
    }).on(TASK_STOP, function(_, i) {
        timer.element.trigger(TASK_STOP);
        timerBox.trigger(TASK_HIDE_TIMER, i);
    }).on('click', '.timer-close', function(e) {
        e.preventDefault();
        timerBox.trigger(TASK_STOP);
    }).on('click', '.timer-pause', function(e) {
        e.preventDefault();
        timerBox.trigger(TASK_PAUSE);
    }).on('touchstart', function(e) {
        startPoint = {x: e.touches[0].clientX, y: e.touches[0].clientY};
    }).on('touchmove', function(e) {
        const diff = Math.min(e.changedTouches[0].clientY - startPoint.y, 0);
        timerBox.css('transform', 'translateY('+ diff +'px)');
        timerBox.addClass('closing');
    }).on('touchend', function(e) {
        const diff = startPoint.y - e.changedTouches[0].clientY;
        const h = $(window).height();
        if (diff < h / 3) {
            animation(-diff, 0, i => {
                timerBox.css('transform', 'translateY('+ i +'px)');
            }, () => {
                timerBox.removeClass('closing');
            });
            return;
        }
        timerBox.trigger(TASK_STOP, -diff);
    });
}


function animation(
    start: number, end: number,
    callHandle: (i: number) => void, endHandle?: () => void) {
    const diff = start > end ? -1 : 1;
    let step = 1;
    const handle = setInterval(() => {
        start += (step ++) * diff;
        if ((diff > 0 && start >= end) || (diff < 0 && start <= end)) {
            clearInterval(handle);
            callHandle(end);
            endHandle && endHandle();
            return;
        }
        callHandle(start);
    }, 16);
}


function refreshPanel(baseUri: string) {
    timer.stop();
    $.get(baseUri + 'home/panel', html => {
        $('.panel .panel-body').html(html);
        $('.panel .task-item').each(function() {
            let item = $(this),
                time = item.find('.time');
            if (item.hasClass('active')) {
                timer.start(item, parseInt(time.data('start'), 10));
                return;
            }
            item.trigger(TASK_REFRESH_TIME, Timer.format(parseInt(time.text()) * 1000))
        });
    });
}


function checkTask(baseUri: string, element: JQuery) {
    if (isLoading) {
        return;
    }
    isLoading = true;
    postJson(baseUri + 'task/check', {
        id: element.data('id')
    }, function(data) {
        isLoading = false;
        if (data.code == 200 && data.data) {
            timer.dialog && timer.dialog.trigger(TASK_HIDE_TIMER);
            refreshPanel(baseUri);
            Dialog.notify('提示', data.message as string);
            alert(data.message);
        }
    });
}


function pauseTask(baseUri: string, element: JQuery) {
    timer.stop();
    postJson(baseUri + 'task/pause', {
        id: element.data('id')
    }, function(data) {
        if (data.code == 200) {
            refreshPanel(baseUri);
        }
    });
}


function playTask(baseUri: string, element: JQuery) {
    postJson(baseUri + 'task/play', {
        id: element.data('id')
    }, function(data) {
        if (data.code == 200) {
            refreshPanel(baseUri);
        }
    });
}


function stopTask(baseUri: string, element: JQuery) {
    if (element.hasClass('active')) {
        timer.stop();
        postJson(baseUri + 'task/stop', {
            id: element.data('id')
        }, _ => {
            refreshPanel(baseUri);
        });
        return;
    }
    if (!confirm('是否确定结束此任务？')) {
        return;
    }
    postJson(baseUri + 'task/stop_task', {
        id: element.data('id')
    }, _ => {
        refreshPanel(baseUri);
    });
}


function bindRecord() {
    $('#today').datetimer({format: 'yyyy-mm-dd'});
    $('.search-box form').submit(function() {
        let $this = $(this);
        $.get($this.attr('action'), $this.serialize(), html => {
            $('.chart-box').html(html);
        });
        return false;
    }).trigger('submit');
}


function bindReview() {
    $('#today').datetimer({format: 'yyyy-mm-dd'});
    $('.search-box form').submit(function() {
        let $this = $(this);
        $.get($this.attr('action'), $this.serialize(), html => {
            $('.review-box').html(html);
        });
        return false;
    }).trigger('submit');
}


function fullScreen() {
    const element = document.documentElement;
    if (element.requestFullscreen) {
        element.requestFullscreen();
    }
}


function exitFullscreen() {
    if (document.exitFullscreen) {
        document.exitFullscreen();
    }
}


res => {
            if (res.code !== 200) {
                parseAjax(res);
                return;
            }
            refreshPanel(baseUri);
        }


i => {
            timerBox.css('transform', 'translateY('+ i +'px)');
        }


() => {
            timerBox.removeClass('closing');
            timerBox.hide();
            exitFullscreen();
        }


() => {
            timerBox.removeClass('closing');
        }


i => {
                timerBox.css('transform', 'translateY('+ i +'px)');
            }


() => {
                timerBox.removeClass('closing');
            }


() => {
        start += (step ++) * diff;
        if ((diff > 0 && start >= end) || (diff < 0 && start <= end)) {
            clearInterval(handle);
            callHandle(end);
            endHandle && endHandle();
            return;
        }
        callHandle(start);
    }


html => {
        $('.panel .panel-body').html(html);
        $('.panel .task-item').each(function() {
            let item = $(this),
                time = item.find('.time');
            if (item.hasClass('active')) {
                timer.start(item, parseInt(time.data('start'), 10));
                return;
            }
            item.trigger(TASK_REFRESH_TIME, Timer.format(parseInt(time.text()) * 1000))
        });
    }


_ => {
            refreshPanel(baseUri);
        }


_ => {
        refreshPanel(baseUri);
    }


html => {
            $('.chart-box').html(html);
        }


html => {
            $('.review-box').html(html);
        }


class Timer {

    public element: JQuery;

    public startAt: Date;

    private _handle: number;

    public dialog: JQuery;

    private _timeSpace = 300;

    private _timeDown = 0;

    private _maxTime = 1; // 1分钟

    public start(element: JQuery, start_at: string | number) {
        this.stop();
        this.element = element;
        this.dialog = undefined;
        this.startAt = new Date(typeof start_at == 'number' ? start_at * 1000 : start_at);
        let that = this;
        this._handle = setInterval(function() {
            that._showTime();
        }, this._timeSpace);
    }

    private _showTime() {
        const diff = new Date().getTime() - this.startAt.getTime();
        this.element.trigger(TASK_REFRESH_TIME, Timer.format(diff));
        this._timeDown -= this._timeSpace;
        if (this._timeDown <= 0) {
            this.element.trigger(TASK_CHECK);
            this._timeDown = this._maxTime * 60000;
        }
        if (!this.dialog) {
            return;
        }
        const total = this.element.data('time');
        if (total < 1) {
            return;
        }
        const time = total * 60000 - diff;
        this.dialog.trigger(TASK_TICK_TIMER, Timer.format(Math.max(0, time)));
    }

    public stop() {
        if (this.element) {
            this.element.text('统计中。。。');
            clearInterval(this._handle);
        }
        this.element = undefined;
        this._handle = 0;
        this.dialog = undefined;
    }

    public static format(time: number, format?: string): string {
        if (isNaN(time)) {
            time = 0;
        }
        time = Math.floor(time / 1000);
        let s = time % 60,
            m = format && format.indexOf('h') < 0 ? Math.floor(time / 60) : (Math.floor(time / 60) % 60),
            h = Math.floor(time / 3600),
            b = function(t) {
                return t < 10 && t >= 0 ? '0' + t : t;
            };
        if (!format) {
            return (h !== 0 ? b(h) + ':' : '') + b(m) + ':' + b(s);
        }
        return format.replace(/h+/, b(h)).replace(/i+/, b(m)).replace(/s+/, b(s));
    }
}


public start(element: JQuery, start_at: string | number) {
        this.stop();
        this.element = element;
        this.dialog = undefined;
        this.startAt = new Date(typeof start_at == 'number' ? start_at * 1000 : start_at);
        let that = this;
        this._handle = setInterval(function() {
            that._showTime();
        }, this._timeSpace);
    }


private _showTime() {
        const diff = new Date().getTime() - this.startAt.getTime();
        this.element.trigger(TASK_REFRESH_TIME, Timer.format(diff));
        this._timeDown -= this._timeSpace;
        if (this._timeDown <= 0) {
            this.element.trigger(TASK_CHECK);
            this._timeDown = this._maxTime * 60000;
        }
        if (!this.dialog) {
            return;
        }
        const total = this.element.data('time');
        if (total < 1) {
            return;
        }
        const time = total * 60000 - diff;
        this.dialog.trigger(TASK_TICK_TIMER, Timer.format(Math.max(0, time)));
    }


public stop() {
        if (this.element) {
            this.element.text('统计中。。。');
            clearInterval(this._handle);
        }
        this.element = undefined;
        this._handle = 0;
        this.dialog = undefined;
    }


public static format(time: number, format?: string): string {
        if (isNaN(time)) {
            time = 0;
        }
        time = Math.floor(time / 1000);
        let s = time % 60,
            m = format && format.indexOf('h') < 0 ? Math.floor(time / 60) : (Math.floor(time / 60) % 60),
            h = Math.floor(time / 3600),
            b = function(t) {
                return t < 10 && t >= 0 ? '0' + t : t;
            };
        if (!format) {
            return (h !== 0 ? b(h) + ':' : '') + b(m) + ':' + b(s);
        }
        return format.replace(/h+/, b(h)).replace(/i+/, b(m)).replace(/s+/, b(s));
    }


export function createIcon<C extends React.SFC<{}>, W extends number, H extends number>(
  component: C,
  size: [W, H]
): Icon<W, H>


export interface OcticonProps {
  ariaLabel?: string
  children?: React.ReactElement<any>
  height?: number
  icon: Icon
  size?: number | Size
  verticalAlign?: 'middle' | 'text-bottom' | 'text-top' | 'top'
  width?: number
}


type Size = 'small' | 'medium' | 'large'


export class DeleteJobCommand extends $Command<
  DeleteJobCommandInput,
  DeleteJobCommandOutput,
  GlueClientResolvedConfig
> {
  // Start section: command_properties
  // End section: command_properties

  constructor(readonly input: DeleteJobCommandInput) {
    // Start section: command_constructor
    super();
    // End section: command_constructor
  }

  resolveMiddleware(
    clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
    configuration: GlueClientResolvedConfig,
    options?: __HttpHandlerOptions
  ): Handler<DeleteJobCommandInput, DeleteJobCommandOutput> {
    this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));

    const stack = clientStack.concat(this.middlewareStack);

    const { logger } = configuration;
    const handlerExecutionContext: HandlerExecutionContext = {
      logger,
      inputFilterSensitiveLog: DeleteJobRequest.filterSensitiveLog,
      outputFilterSensitiveLog: DeleteJobResponse.filterSensitiveLog,
    };
    const { requestHandler } = configuration;
    return stack.resolve(
      (request: FinalizeHandlerArguments<any>) =>
        requestHandler.handle(request.request as __HttpRequest, options || {}),
      handlerExecutionContext
    );
  }

  private serialize(input: DeleteJobCommandInput, context: __SerdeContext): Promise<__HttpRequest> {
    return serializeAws_json1_1DeleteJobCommand(input, context);
  }

  private deserialize(output: __HttpResponse, context: __SerdeContext): Promise<DeleteJobCommandOutput> {
    return deserializeAws_json1_1DeleteJobCommand(output, context);
  }

  // Start section: command_body_extra
  // End section: command_body_extra
}


export type DeleteJobCommandInput = DeleteJobRequest;


export type DeleteJobCommandOutput = DeleteJobResponse & __MetadataBearer;


resolveMiddleware(
    clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
    configuration: GlueClientResolvedConfig,
    options?: __HttpHandlerOptions
  ): Handler<DeleteJobCommandInput, DeleteJobCommandOutput> {
    this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));

    const stack = clientStack.concat(this.middlewareStack);

    const { logger } = configuration;
    const handlerExecutionContext: HandlerExecutionContext = {
      logger,
      inputFilterSensitiveLog: DeleteJobRequest.filterSensitiveLog,
      outputFilterSensitiveLog: DeleteJobResponse.filterSensitiveLog,
    };
    const { requestHandler } = configuration;
    return stack.resolve(
      (request: FinalizeHandlerArguments<any>) =>
        requestHandler.handle(request.request as __HttpRequest, options || {}),
      handlerExecutionContext
    );
  }


private serialize(input: DeleteJobCommandInput, context: __SerdeContext): Promise<__HttpRequest> {
    return serializeAws_json1_1DeleteJobCommand(input, context);
  }


private deserialize(output: __HttpResponse, context: __SerdeContext): Promise<DeleteJobCommandOutput> {
    return deserializeAws_json1_1DeleteJobCommand(output, context);
  }


() => {
  const [isLoading, setIsLoading] = useState(false);
  const { createAccount, createAccountLink } = useManageAccount();

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  const onSubmit = async (e: any) => {
    setIsLoading(true);
    const params: Stripe.AccountCreateParams = { ...e };
    const { id } = await createAccount(params);
    await createAccountLink(id);
    setIsLoading(false);
  };

  return (

チケットオーナーアカウント作成

        <Attention />

          <form onSubmit={handleSubmit(onSubmit)} className="text-center">
            {inputItems.map((item) => {
              return (
                <InputLayout key={item.id} item={item} errorMessage={errors}>
                  <InputType item={item} register={register} />
                </InputLayout>
              );
            })}


async (e: any) => {
    setIsLoading(true);
    const params: Stripe.AccountCreateParams = { ...e };
    const { id } = await createAccount(params);
    await createAccountLink(id);
    setIsLoading(false);
  }


(item) => {
              return (
                <InputLayout key={item.id} item={item} errorMessage={errors}>
                  <InputType item={item} register={register} />
                </InputLayout>


() => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [Service416Service]
    });
  });

  it('should ...', inject([Service416Service], (service: Service416Service) => {
    expect(service).toBeTruthy();
  }));
}


() => {
    TestBed.configureTestingModule({
      providers: [Service416Service]
    });
  }


(service: Service416Service) => {
    expect(service).toBeTruthy();
  }


({ size, rem }: {
  size: number | string,
  rem?: boolean
}) => {
  const width = (typeof size === 'string') ? size : rem ? `${size}rem` : `${size}px`

  return (
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" style={{ width: width }}


export function xmlToFieldGroup(
  xml: XMLObject,
  idPrefix: string,
  allMappings: IAtlasmapMapping[],
) {
  const fromElement = (jf: XmlField): IAtlasmapField => {
    const id = `${idPrefix}-${jf.path}`;
    const mappings = allMappings.filter(
      (m) =>
        m.sourceFields.find((s) => s.id === id) ||
        m.targetFields.find((t) => t.id === id),
    );

    return {
      id,
      name: jf.name,
      type: jf.fieldType,
      previewValue: "",
      path: jf.path,
      scope: "current",
      mappings,
      hasTransformations:
        mappings.length > 0 &&
        (jf.name.startsWith("a") ||
          jf.name.startsWith("b") ||
          jf.name.startsWith("c")),
      isAttribute: false,
      isCollection: jf.collectionType === "LIST",
      isInCollection: false,
      isConnected: false,
      isDisabled: jf.collectionType === "COMPLEX",
      amField: {} as IAtlasmapField["amField"],
    };
  };
  const fromGroup = (f: Field): IAtlasmapGroup => ({
    name: f.name,
    type: f.fieldType,
    id: `${idPrefix}-${f.path}`,
    fields: f.xmlFields.xmlField.map((f) =>
      f.xmlFields ? fromGroup(f as Field) : fromElement(f),
    ),
    isCollection: false,
    isInCollection: false,
    amField: {} as IAtlasmapField["amField"],
  });

  return xml.XmlInspectionResponse.xmlDocument.fields.field.map((f) =>
    f.xmlFields ? fromGroup(f as Field) : fromElement(f),
  );
}


(jf: XmlField): IAtlasmapField => {
    const id = `${idPrefix}-${jf.path}`;
    const mappings = allMappings.filter(
      (m) =>
        m.sourceFields.find((s) => s.id === id) ||
        m.targetFields.find((t) => t.id === id),
    );

    return {
      id,
      name: jf.name,
      type: jf.fieldType,
      previewValue: "",
      path: jf.path,
      scope: "current",
      mappings,
      hasTransformations:
        mappings.length > 0 &&
        (jf.name.startsWith("a") ||
          jf.name.startsWith("b") ||
          jf.name.startsWith("c")),
      isAttribute: false,
      isCollection: jf.collectionType === "LIST",
      isInCollection: false,
      isConnected: false,
      isDisabled: jf.collectionType === "COMPLEX",
      amField: {} as IAtlasmapField["amField"],
    };
  }


(m) =>
        m.sourceFields.find((s) => s.id === id) ||
        m.targetFields.find((t) => t.id === id)


(s) => s.id === id


(t) => t.id === id


(f: Field): IAtlasmapGroup => ({
    name: f.name,
    type: f.fieldType,
    id: `${idPrefix}-${f.path}`,
    fields: f.xmlFields.xmlField.map((f) =>
      f.xmlFields ? fromGroup(f as Field) : fromElement(f),
    ),
    isCollection: false,
    isInCollection: false,
    amField: {} as IAtlasmapField["amField"],
  })


(f) =>
      f.xmlFields ? fromGroup(f as Field) : fromElement(f)


(f) =>
    f.xmlFields ? fromGroup(f as Field) : fromElement(f)


interface Restrictions {
  restriction: any[];
}


interface XmlField {
  jsonType: string;
  path: string;
  fieldType: string;
  name: string;
  value?: any;
  status?: any;
  xmlFields?: XmlFields;
  restrictions?: Restrictions;
  collectionType?: string;
}


interface XmlFields {
  xmlField: XmlField[];
}


interface Field {
  jsonType: string;
  path: string;
  fieldType: string;
  name: string;
  xmlFields: XmlFields;
  typeName?: any;
}


interface Fields {
  field: Field[];
}


interface XmlNamespace {
  alias: string;
  uri: string;
}


interface XmlNamespaces {
  xmlNamespace: XmlNamespace[];
}


interface XmlDocument {
  jsonType: string;
  fields: Fields;
  xmlNamespaces: XmlNamespaces;
}


interface XmlInspectionResponse {
  jsonType: string;
  xmlDocument: XmlDocument;
  executionTime: number;
}


export interface XMLObject {
  XmlInspectionResponse: XmlInspectionResponse;
}


(event?: React.FormEvent<HTMLFormElement>) => {
		if (event) {
			event.preventDefault();
		}

		const { name, scope } = this.state;

		if (!name) {
			return;
		}

		this.props.saveView(name, scope);

		this.setState({
			name: '',
			showForm: false,
			id: '',
		});
	}


x => x.slug === this.state.scope


(filter, index) => {
						return (
							<FilterDescription
								key={filter.$id || index}
								filter={filter}
								onClick={() => this.props.edit(filter)}


class FilterSummary extends React.Component<
	FilterSummaryProps,
	FilterSummaryState
> {
	constructor(props: FilterSummaryProps) {
		super(props);
		this.state = {
			name: '',
			showForm: false,
			id: '',
			scope: this.props.scopes ? this.props.scopes[0].slug : null,
		};
	}

	save = (event?: React.FormEvent<HTMLFormElement>) => {
		if (event) {
			event.preventDefault();
		}

		const { name, scope } = this.state;

		if (!name) {
			return;
		}

		this.props.saveView(name, scope);

		this.setState({
			name: '',
			showForm: false,
			id: '',
		});
	};

	handleChange(e: React.ChangeEvent<HTMLInputElement>) {
		const name = e.target.value;
		this.setState({ name });
	}

	render() {
		const { scopes } = this.props;
		return (
			<Box p={3} mt={3} width="100%" bg="quartenary.light">
				{this.state.showForm && (
					<Modal
						title="Save current view"
						cancel={() => this.setState({ showForm: false })}
						done={this.save}
						action="Save"
					>
						<form onSubmit={this.save}>
							{!!scopes && scopes.length > 1 && (
								<Flex mb={4} alignItems="center">
									<Txt width={90}>Visible to:</Txt>
									<Select<ViewScope>
										ml={2}
										width="auto"
										options={scopes}
										valueKey="slug"
										labelKey="name"
										// TODO: Remove this logic and pass the primitive value when this is fixed: https://github.com/grommet/grommet/issues/3154
										value={scopes.find(x => x.slug === this.state.scope)}
										onChange={({ option }) =>
											this.setState({
												scope: option.slug,
											})
										}


export interface FilterSummaryProps {
	edit: (rule: JSONSchema6) => void;
	delete: (rule: JSONSchema6) => void;
	saveView: (name: string, scope: string | null) => void;
	clearAllFilters: () => void;
	filters: JSONSchema6[];
	views: FiltersView[];
	scopes?: ViewScope[];
	schema: JSONSchema6;
}


export interface FilterSummaryState {
	name: string;
	showForm: boolean;
	id: string;
	scope: string | null;
}


handleChange(e: React.ChangeEvent<HTMLInputElement>) {
		const name = e.target.value;
		this.setState({ name });
	}


render() {
		const { scopes } = this.props;
		return (
			<Box p={3} mt={3} width="100%" bg="quartenary.light">
				{this.state.showForm && (
					<Modal
						title="Save current view"
						cancel={() => this.setState({ showForm: false })}
						done={this.save}
						action="Save"
					>
						<form onSubmit={this.save}>
							{!!scopes && scopes.length > 1 && (
								<Flex mb={4} alignItems="center">
									<Txt width={90}>Visible to:</Txt>
									<Select<ViewScope>
										ml={2}
										width="auto"
										options={scopes}
										valueKey="slug"
										labelKey="name"
										// TODO: Remove this logic and pass the primitive value when this is fixed: https://github.com/grommet/grommet/issues/3154
										value={scopes.find(x => x.slug === this.state.scope)}
										onChange={({ option }) =>
											this.setState({
												scope: option.slug,
											})
										}


@NgModule({
  imports: [
    HttpClientModule,
    BrowserModule,
    FormsModule,
    // Mock HTTP backend
    HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, {
      apiBase: environment.prefix,
      dataEncapsulation: false
    }),
    // Router config
    ResourceRouterModule.configure({
      prefix: environment.prefix,
      useHash: environment.useHash
    }),
    // Routes
    AppRoutingModule
  ],
  declarations: [
    AppComponent,
    MessagesComponent,
    DashboardComponent,
    HeroesComponent,
    HeroDetailComponent,
  ],
  providers: [
    MessageService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: MessageHttpInterceptor,
      multi: true
    },
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}


(entry: ResizeObserverEntry[]) => {
            this.zone.run(() => {
                if (!this.hasOverflow) {
                    this.checkHeadingsView();
                }
                this.onContainerResize(entry[0]);
                this.checkResponsiveMode();
                this.cdRef.markForCheck();
            });
        }


() => {
                if (!this.hasOverflow) {
                    this.checkHeadingsView();
                }
                this.onContainerResize(entry[0]);
                this.checkResponsiveMode();
                this.cdRef.markForCheck();
            }


() => {
            this.checkDynamicSteps();

            if (this.dynamicSteps.length) {
                this.checkHeadingsView();
            }

            this.dynamicStepWidthAdjustment = this.stepsArray.includes(this.dynamicSteps[0]) ? this.stepHeaderWidth : 0;

            // We keep the cached array intact to properly tack when dynamic steps are added. Multiple dynamic steps may be added,
            // but we're only interested in the latest one, because the rest will be hidden under the overflow elements, if there is
            // no room for them among the visible items
            this.stepsCachedArray = [...this.stepsArray];
        }


(event: StepperSelectionEvent) => {

                // This describes the navigation between the headers in a visible and hidden areas in the responsive mode.
                if (this.hasOverflow) {
                    if (event.previouslySelectedIndex < event.selectedIndex) {
                        if (this.overflownStepsEnd.length) {
                            const selectedIndex = this.overflownStepsEnd.findIndex((item) => item === event.selectedStep as WizardStepV2Component);
                            this.visibleSteps = this.visibleSteps.concat(this.overflownStepsEnd.splice(0, selectedIndex + 1));
                            this.overflownStepsStart = this.overflownStepsStart.concat(this.visibleSteps.splice(0, selectedIndex + 1));
                        }
                    } else {
                        if (this.overflownStepsStart.length) {
                            const startPiece = this.overflownStepsStart.splice(event.selectedIndex, this.overflownStepsStart.length - event.selectedIndex);
                            this.visibleSteps = startPiece.concat(this.visibleSteps);
                            this.overflownStepsEnd.splice(0, 0, ...this.visibleSteps.splice(this.visibleSteps.length - startPiece.length, startPiece.length));
                        }
                    }
                }
            }


(item) => item === event.selectedStep as WizardStepV2Component


ngOnInit(): void {
        // Checking the validity of previous steps by default.
        this.linear = true;
    }


ngAfterViewInit(): void {
        super.ngAfterViewInit();

        this.stepsCachedArray = [...this.stepsArray];

        // Initial checking if there is an overflow and determining some values we'll use in calculations later
        this.checkHeadingsView();
        this.cdRef.detectChanges();

        this.overflowComponentWidth = this.overflowComponents?.first?.el?.nativeElement?.getBoundingClientRect()?.width || 0;

        // This handles how headers are processed during the resize process
        this.headerResizeObserver = new ResizeObserver((entry: ResizeObserverEntry[]) => {
            this.zone.run(() => {
                if (!this.hasOverflow) {
                    this.checkHeadingsView();
                }
                this.onContainerResize(entry[0]);
                this.checkResponsiveMode();
                this.cdRef.markForCheck();
            });
        });

        // Steps change event is triggered every time we navigate through the steps AND when dynamic steps are being added/removed (or anything else
        // happens in the scope of the current step's view). We only need to rebuild the headers view if there are any changes connected with dynamic steps.
        // Otherwise, we don't do anything. We also have to track whether any of the dynamic steps were added or not on every event run, because we don't
        // know when exactly user will attempt to add a dynamic step. If this happens, we need to also adjust the width of all the header items with respect
        // to the added step to properly track the overflow, as it can happen when the dynamic step is added.
        this.steps.changes.pipe(takeUntil(this._destroyed)).subscribe(() => {
            this.checkDynamicSteps();

            if (this.dynamicSteps.length) {
                this.checkHeadingsView();
            }

            this.dynamicStepWidthAdjustment = this.stepsArray.includes(this.dynamicSteps[0]) ? this.stepHeaderWidth : 0;

            // We keep the cached array intact to properly tack when dynamic steps are added. Multiple dynamic steps may be added,
            // but we're only interested in the latest one, because the rest will be hidden under the overflow elements, if there is
            // no room for them among the visible items
            this.stepsCachedArray = [...this.stepsArray];
        });

        this.selectionChange
            .pipe(takeUntil(this._destroyed))
            .subscribe((event: StepperSelectionEvent) => {

                // This describes the navigation between the headers in a visible and hidden areas in the responsive mode.
                if (this.hasOverflow) {
                    if (event.previouslySelectedIndex < event.selectedIndex) {
                        if (this.overflownStepsEnd.length) {
                            const selectedIndex = this.overflownStepsEnd.findIndex((item) => item === event.selectedStep as WizardStepV2Component);
                            this.visibleSteps = this.visibleSteps.concat(this.overflownStepsEnd.splice(0, selectedIndex + 1));
                            this.overflownStepsStart = this.overflownStepsStart.concat(this.visibleSteps.splice(0, selectedIndex + 1));
                        }
                    } else {
                        if (this.overflownStepsStart.length) {
                            const startPiece = this.overflownStepsStart.splice(event.selectedIndex, this.overflownStepsStart.length - event.selectedIndex);
                            this.visibleSteps = startPiece.concat(this.visibleSteps);
                            this.overflownStepsEnd.splice(0, 0, ...this.visibleSteps.splice(this.visibleSteps.length - startPiece.length, startPiece.length));
                        }
                    }
                }
            });

        this.headerResizeObserver.observe(this.headerContainer.nativeElement);
    }


public ngOnDestroy(): void {
        super.ngOnDestroy();

        this.headerResizeObserver.disconnect();
    }


private handleOverflow(): void {
        this.checkOverflow();

        if (this.hasOverflow || this.isInResponsiveMode) {
            const numberOfVisibleItems = this.numberOfVisibleItems;

            if (!this.isInResponsiveMode) {
                if (this.state?.finished) {
                    this.visibleSteps = this.stepsArray.slice(-numberOfVisibleItems);
                    this.overflownStepsStart = this.stepsArray.slice(0, -numberOfVisibleItems);

                    // This covers an edge case with the saved wizard state. It can happen that the active element
                    // appears inside the hidden list of items, when the dynamnic steps are re-added.
                    // We make it visible again by retrieving it back to the visible items array
                    if (this.overflownStepsStart.length) {
                        while(!this.isCurrentStepVisible) {
                            this.takeLastAddFirst(this.overflownStepsStart, this.visibleSteps);
                            this.takeLastAddFirst(this.visibleSteps, this.overflownStepsEnd);
                        }
                    }
                } else {
                    this.visibleSteps = this.stepsArray.slice(0, numberOfVisibleItems);
                    this.overflownStepsEnd = this.stepsArray.slice(numberOfVisibleItems);
                }
            } else {
                this.handleDynamicHeaderChanges(numberOfVisibleItems);
            }
        }

        this.checkResponsiveMode();
    }


private handleDynamicHeaderChanges(visibleItemsNUmber: number): void {
        const arr: WizardStepV2Component[] = [...this.stepsArray];

        if (this.overflownStepsStart.length) {
            arr.splice(0, this.overflownStepsStart.length)
        }
        this.visibleSteps = arr.slice(0, visibleItemsNUmber);
        this.overflownStepsEnd = arr.slice(visibleItemsNUmber);

        this.checkOverflow();
        if (!this.hasOverflow && this.overflownStepsStart.length) {
            this.visibleSteps.unshift(this.overflownStepsStart.splice(this.overflownStepsStart.length - 1, 1)[0]);
        }
    }


private checkDynamicSteps(): void {
        if (this.stepsArray.length > this.stepsCachedArray.length) {
            this.dynamicSteps = without(this.stepsArray, ...this.stepsCachedArray);
        }

        if (this.stepsArray.length < this.stepsCachedArray.length) {
            this.dynamicSteps = without(this.stepsCachedArray, ...this.stepsArray);

            // If there are no dynamic items left in the main array we need to clean up the visual items array
            if (!this.stepsArray.includes(this.dynamicSteps[0])) {
                pull(this.visibleSteps, ...this.dynamicSteps);
            }
        }

        if (this.stepsArray.length === this.stepsCachedArray.length) {
            this.dynamicSteps = [];
        }
    }


private checkHeadingsView(): void {
        this.checkHeaderWidth();
        this.checkHeaderPaddings();
        this.getWidthsForCalculations();
        this.handleOverflow();
    }


private checkHeaderPaddings(): void {
        if (!this.headerPaddings) {
            this.headerPaddings = +getComputedStyle(this.headerContainer.nativeElement).paddingLeft.slice(0, -2);
        }
    }


private checkHeaderWidth(): void {
        if (!this.stepHeaderWidth) {
            this.stepHeaderWidth = this.stepHeaders.first?._elementRef?.nativeElement?.getBoundingClientRect().width || 0;
        }
    }


private getWidthsForCalculations(): void {
        this.headerContainerWidth = this.headerContainer.nativeElement?.getBoundingClientRect().width;
        this.allHeadersWidth = this.stepHeaderWidth * this.steps.length;
    }


private checkOverflow(): void {
        this.hasOverflow = this.allHeadersWidth > this.headerContainerWidth;
    }


private checkResponsiveMode(): void {
        this.isInResponsiveMode = !!this.overflownStepsStart.length || !!this.overflownStepsEnd.length;
    }


private takeLastAddFirst(source: Array<any>, target: Array<any>): void {
        target.unshift(source.splice(-1, 1)[0]);
    }


private takeFirstAddLast(source: Array<any>, target: Array<any>): void {
        target.push(source.splice(0, 1)[0]);
    }


private onContainerResize(entry: ResizeObserverEntry): void {
        this.checkOverflow();

        const newWidth = entry.contentRect.width;
        const visibleStepsWidth = this.visibleSteps.length * this.stepHeaderWidth;

        if (newWidth < (visibleStepsWidth + this.overflowComponentWidth * 2 + this.dynamicStepWidthAdjustment)) {
            if (this.visibleSteps[0] !== this.stepsArray[this.selectedIndex]) {
                return this.takeFirstAddLast(this.visibleSteps, this.overflownStepsStart);
            }

            if (last(this.visibleSteps) !== this.stepsArray[this.selectedIndex]) {
                return  this.takeLastAddFirst(this.visibleSteps, this.overflownStepsEnd);
            }
        }

        if (newWidth > (visibleStepsWidth + this.stepHeaderWidth + this.overflowComponentWidth * 2 + this.dynamicStepWidthAdjustment)) {
            if (this.overflownStepsEnd.length) {
                return this.takeFirstAddLast(this.overflownStepsEnd, this.visibleSteps);
            }

            if (this.overflownStepsStart.length) {
                return  this.takeLastAddFirst(this.overflownStepsStart, this.visibleSteps);
            }
        }
    }


export declare class Util {
    /**
     * Console log helper
     *
     * Prints the file and line number where it has been called.
     * @param {number} pathDepth
     */
    static consoleLog(pathDepth?: number): void;
    /**
     * Creates random string of length 7
     */
    static randomStr(length?: number): string;
    /**
     * Async delay helper
     * @param delayInMilliseconds
     */
    static delay(delayInMilliseconds: number): Promise<unknown>;
    /**
     * Returns random in a given range.
     * @param min
     * @param max
     */
    static randomNumber(min: number, max: number): number;
    /**
     * Post https
     * @param options
     * @param payload
     */
    static httpsPost<T>(options: RequestOptions, payload: object): Promise<T>;
    static httpsPost<T>(url: string, payload: object): Promise<T>;
    /**
     * Post http
     * @param options
     * @param payload
     */
    static httpPost<T>(options: RequestOptions, payload: object): Promise<T>;
    static httpPost<T>(url: string, payload: object): Promise<T>;
}


/**
     * Console log helper
     *
     * Prints the file and line number where it has been called.
     * @param {number} pathDepth
     */
static consoleLog(pathDepth?: number): void;


/**
     * Creates random string of length 7
     */
static randomStr(length?: number): string;


/**
     * Async delay helper
     * @param delayInMilliseconds
     */
static delay(delayInMilliseconds: number): Promise<unknown>;


/**
     * Returns random in a given range.
     * @param min
     * @param max
     */
static randomNumber(min: number, max: number): number;


/**
     * Post https
     * @param options
     * @param payload
     */
static httpsPost<T>(options: RequestOptions, payload: object): Promise<T>;


static httpsPost<T>(url: string, payload: object): Promise<T>;


/**
     * Post http
     * @param options
     * @param payload
     */
static httpPost<T>(options: RequestOptions, payload: object): Promise<T>;


static httpPost<T>(url: string, payload: object): Promise<T>;


({
  generator,
  name,
  env,
  prefix,
  statements,
  api,
  vars,
  type,
  bucket,
  version,
}: RouterOps) => {
  const shortName = `${prefix}-${name}`;
  const fullName = `${shortName}-${env}`;

  const role = awsUtils.lambdaRole({
    generator,
    fullName,
    name,
    statements,
  });
  const lambda = awsUtils.lambda({
    generator,
    name,
    fullName,
    shortName,
    role,
    type,
    api,
    vars,
    bucket,
    version,
  });
  const permission = awsUtils.lambdaApiGPerm({
    generator,
    name,
    lambda,
    api,
  });

  return {
    role,
    lambda,
    permission,
    shortName,
    fullName,
  };
}


export type RouterOps = {
  generator: TerraformGenerator;
  prefix: string;
  name: string;
  env: string;
  api: Resource;
  statements: Statement[];
  vars: Record<string, string>;
  type: awsUtils.LambdaOps['type'];
  bucket: string;
  version: string;
};


/**
 * A Twitch stream.
 */
@rtfm<HelixStream>('twitch', 'HelixStream', 'id')
export class HelixStream {
	@Enumerable(false) private readonly _data: HelixStreamData;
	@Enumerable(false) private readonly _client: ApiClient;

	/** @private */
	constructor(data: HelixStreamData, client: ApiClient) {
		this._data = data;
		this._client = client;
	}

	/**
	 * The stream ID.
	 */
	get id(): string {
		return this._data.id;
	}

	/**
	 * The user ID.
	 */
	get userId(): string {
		return this._data.user_id;
	}

	/**
	 * The user's name.
	 */
	get userName(): string {
		return this._data.user_login;
	}

	/**
	 * The user's display name.
	 */
	get userDisplayName(): string {
		return this._data.user_name;
	}

	/**
	 * Retrieves information about the user broadcasting the stream.
	 */
	async getUser(): Promise<HelixUser | null> {
		return this._client.helix.users.getUserById(this._data.user_id);
	}

	/**
	 * The game ID.
	 */
	get gameId(): string {
		return this._data.game_id;
	}

	/**
	 * Retrieves information about the game that is being played on the stream.
	 */
	async getGame(): Promise<HelixGame | null> {
		return this._client.helix.games.getGameById(this._data.game_id);
	}

	/**
	 * The type of the stream.
	 */
	get type(): HelixStreamType {
		return this._data.type;
	}

	/**
	 * The title of the stream.
	 */
	get title(): string {
		return this._data.title;
	}

	/**
	 * The number of viewers the stream currently has.
	 */
	get viewers(): number {
		return this._data.viewer_count;
	}

	/**
	 * The time when the stream started.
	 */
	get startDate(): Date {
		return new Date(this._data.started_at);
	}

	/**
	 * The language of the stream.
	 */
	get language(): string {
		return this._data.language;
	}

	/**
	 * The URL of the thumbnail of the stream.
	 */
	get thumbnailUrl(): string {
		return this._data.thumbnail_url;
	}

	/**
	 * The IDs of the tags of the stream.
	 */
	get tagIds(): string[] {
		return this._data.tag_ids ?? [];
	}

	/**
	 * Retrieves the tags of the stream.
	 */
	async getTags(): Promise<HelixTag[]> {
		return this._client.helix.tags.getStreamTagsByIds(this._data.tag_ids ?? []);
	}
}


/** @private */
export interface HelixStreamData {
	id: string;
	user_id: string;
	user_login: string;
	user_name: string;
	game_id: string;
	community_ids: string[];
	type: HelixStreamType;
	title: string;
	viewer_count: number;
	started_at: string;
	language: string;
	thumbnail_url: string;
	tag_ids: string[] | null;
}


/**
 * The type of a stream.
 */
export enum HelixStreamType {
	/**
	 * Returned by Twitch in case of an error.
	 */
	None = '',

	/**
	 * A live stream.
	 */
	Live = 'live',

	/**
	 * A vodcast.
	 *
	 * Currently not supported by Twitch - but one can only hope and leave it in here.
	 */
	Vodcast = 'vodcast'
}


/**
	 * Retrieves information about the user broadcasting the stream.
	 */
async getUser(): Promise<HelixUser | null> {
		return this._client.helix.users.getUserById(this._data.user_id);
	}


/**
	 * Retrieves information about the game that is being played on the stream.
	 */
async getGame(): Promise<HelixGame | null> {
		return this._client.helix.games.getGameById(this._data.game_id);
	}


/**
	 * Retrieves the tags of the stream.
	 */
async getTags(): Promise<HelixTag[]> {
		return this._client.helix.tags.getStreamTagsByIds(this._data.tag_ids ?? []);
	}


@NgModule({
  declarations: [
    ChatPage,
  ],
  imports: [
    IonicPageModule.forChild(ChatPage),
    TranslateModule.forChild(),
    MomentModule
  ],
})
export class ChatPageModule {
}


(params) => {
            this.courseId = Number(params['courseId']);
            this.exerciseId = Number(params['exerciseId']);
            this.examId = Number(params['examId']);
        }


(queryParams) => {
            this.tutorId = Number(queryParams['tutorId']);
            this.correctionRound = Number(queryParams['correctionRound']);
        }


(data) => (this.complaintType = data.complaintType)


(res) => {
                this.complaints = res.body!;

                if (!this.showAddressedComplaints) {
                    this.complaintsToShow = this.complaints.filter((complaint) => complaint.accepted === undefined);
                } else {
                    this.complaintsToShow = this.complaints;
                }

                if (this.complaints.some((complaint) => complaint.student)) {
                    this.hasStudentInformation = true;
                }
            }


(complaint) => complaint.accepted === undefined


(complaint) => complaint.student


(error: HttpErrorResponse) => onError(this.alertService, error)


() => (this.loading = false)


(complaint) => this.complaintService.getResponseTimeInSeconds(complaint)


(complaint) => this.calculateComplaintLockStatus(complaint)


@Component({
    selector: 'jhi-complaint-list',
    templateUrl: './list-of-complaints.component.html',
    providers: [],
})
export class ListOfComplaintsComponent implements OnInit {
    public complaints: Complaint[] = [];
    public hasStudentInformation = false;
    public complaintType: ComplaintType;
    ComplaintType = ComplaintType;

    private courseId: number;
    private exerciseId: number;
    private tutorId: number;
    private examId?: number;
    correctionRound?: number;
    complaintsSortingPredicate = 'id';
    complaintsReverseOrder = false;
    complaintsToShow: Complaint[] = [];
    showAddressedComplaints = false;
    allComplaintsForTutorLoaded = false;
    isLoadingAllComplaints = false;

    loading = true;
    // Icons
    faSort = faSort;
    faFolderOpen = faFolderOpen;
    faExclamationTriangle = faExclamationTriangle;

    constructor(
        public complaintService: ComplaintService,
        private alertService: AlertService,
        private route: ActivatedRoute,
        private router: Router,
        private modalService: NgbModal,
        private sortService: SortService,
        private translateService: TranslateService,
        private artemisDatePipe: ArtemisDatePipe,
    ) {}

    ngOnInit(): void {
        this.route.params.subscribe((params) => {
            this.courseId = Number(params['courseId']);
            this.exerciseId = Number(params['exerciseId']);
            this.examId = Number(params['examId']);
        });
        this.route.queryParams.subscribe((queryParams) => {
            this.tutorId = Number(queryParams['tutorId']);
            this.correctionRound = Number(queryParams['correctionRound']);
        });
        this.route.data.subscribe((data) => (this.complaintType = data.complaintType));
        this.loadComplaints();
    }

    loadComplaints() {
        let complaintResponse: Observable<HttpResponse<Complaint[]>>;

        if (this.tutorId) {
            if (this.exerciseId) {
                complaintResponse = this.complaintService.findAllByTutorIdForExerciseId(this.tutorId, this.exerciseId, this.complaintType);
            } else if (this.examId) {
                // TODO make exam complaints visible for tutors too
                complaintResponse = this.complaintService.findAllByTutorIdForCourseId(this.tutorId, this.courseId, this.complaintType);
            } else {
                complaintResponse = this.complaintService.findAllByTutorIdForCourseId(this.tutorId, this.courseId, this.complaintType);
            }
        } else {
            if (this.exerciseId) {
                complaintResponse = this.complaintService.findAllByExerciseId(this.exerciseId, this.complaintType);
            } else if (this.examId) {
                complaintResponse = this.complaintService.findAllByCourseIdAndExamId(this.courseId, this.examId);
            } else {
                complaintResponse = this.complaintService.findAllByCourseId(this.courseId, this.complaintType);
            }
        }
        this.subscribeToComplaintResponse(complaintResponse);
    }

    subscribeToComplaintResponse(complaintResponse: Observable<HttpResponse<Complaint[]>>) {
        complaintResponse.subscribe({
            next: (res) => {
                this.complaints = res.body!;

                if (!this.showAddressedComplaints) {
                    this.complaintsToShow = this.complaints.filter((complaint) => complaint.accepted === undefined);
                } else {
                    this.complaintsToShow = this.complaints;
                }

                if (this.complaints.some((complaint) => complaint.student)) {
                    this.hasStudentInformation = true;
                }
            },
            error: (error: HttpErrorResponse) => onError(this.alertService, error),
            complete: () => (this.loading = false),
        });
    }

    openAssessmentEditor(complaint: Complaint) {
        if (!complaint || !complaint.result || !complaint.result.participation || !complaint.result.submission) {
            return;
        }

        const studentParticipation = complaint.result.participation as StudentParticipation;
        const exercise = studentParticipation.exercise;
        const submissionId = complaint.result.submission.id;
        if (!exercise || !exercise.type || !submissionId) {
            return;
        }
        if (!this.correctionRound) {
            this.correctionRound = 0;
        }
        if (complaint.accepted != undefined) {
            this.correctionRound += 1;
        }
        const url = getLinkToSubmissionAssessment(
            exercise.type,
            this.courseId,
            exercise.id!,
            studentParticipation.id,
            submissionId,
            0, // even if the list of complaints are part of an exam, the assessment of non-exam exercises gets executed
            0,
            complaint.result.id,
        );
        this.router.navigate(url, { queryParams: { 'correction-round': this.correctionRound } });
    }

    sortRows() {
        switch (this.complaintsSortingPredicate) {
            case 'responseTime':
                this.sortService.sortByFunction(this.complaintsToShow, (complaint) => this.complaintService.getResponseTimeInSeconds(complaint), this.complaintsReverseOrder);
                break;
            case 'lockStatus':
                this.sortService.sortByFunction(this.complaintsToShow, (complaint) => this.calculateComplaintLockStatus(complaint), this.complaintsReverseOrder);
                break;
            default:
                this.sortService.sortByProperty(this.complaintsToShow, this.complaintsSortingPredicate, this.complaintsReverseOrder);
        }
    }

    triggerAddressedComplaints() {
        this.showAddressedComplaints = !this.showAddressedComplaints;

        if (this.showAddressedComplaints) {
            this.complaintsToShow = this.complaints;
        } else {
            this.complaintsToShow = this.complaints.filter((complaint) => complaint.accepted === undefined);
        }
    }

    /**
     * Used to lazy-load all complaints from the server for a tutor or editor.
     */
    triggerShowAllComplaints() {
        this.isLoadingAllComplaints = true;
        let complaintResponse: Observable<HttpResponse<Complaint[]>>;
        complaintResponse = this.complaintService.findAllWithoutStudentInformationForCourseId(this.courseId, this.complaintType);
        this.subscribeToComplaintResponse(complaintResponse);
        this.isLoadingAllComplaints = false;
        this.allComplaintsForTutorLoaded = true;
    }

    calculateComplaintLockStatus(complaint: Complaint) {
        if (complaint.complaintResponse && this.complaintService.isComplaintLocked(complaint)) {
            if (this.complaintService.isComplaintLockedByLoggedInUser(complaint)) {
                const endDate = this.artemisDatePipe.transform(complaint.complaintResponse?.lockEndDate);
                return this.translateService.instant('artemisApp.locks.lockInformationYou', {
                    endDate,
                });
            } else {
                const endDate = this.artemisDatePipe.transform(complaint.complaintResponse?.lockEndDate);
                const user = complaint.complaintResponse?.reviewer?.login;

                return this.translateService.instant('artemisApp.locks.lockInformation', {
                    endDate,
                    user,
                });
            }
        } else {
            return this.translateService.instant('artemisApp.locks.notUnlocked');
        }
    }
}


ngOnInit(): void {
        this.route.params.subscribe((params) => {
            this.courseId = Number(params['courseId']);
            this.exerciseId = Number(params['exerciseId']);
            this.examId = Number(params['examId']);
        });
        this.route.queryParams.subscribe((queryParams) => {
            this.tutorId = Number(queryParams['tutorId']);
            this.correctionRound = Number(queryParams['correctionRound']);
        });
        this.route.data.subscribe((data) => (this.complaintType = data.complaintType));
        this.loadComplaints();
    }


loadComplaints() {
        let complaintResponse: Observable<HttpResponse<Complaint[]>>;

        if (this.tutorId) {
            if (this.exerciseId) {
                complaintResponse = this.complaintService.findAllByTutorIdForExerciseId(this.tutorId, this.exerciseId, this.complaintType);
            } else if (this.examId) {
                // TODO make exam complaints visible for tutors too
                complaintResponse = this.complaintService.findAllByTutorIdForCourseId(this.tutorId, this.courseId, this.complaintType);
            } else {
                complaintResponse = this.complaintService.findAllByTutorIdForCourseId(this.tutorId, this.courseId, this.complaintType);
            }
        } else {
            if (this.exerciseId) {
                complaintResponse = this.complaintService.findAllByExerciseId(this.exerciseId, this.complaintType);
            } else if (this.examId) {
                complaintResponse = this.complaintService.findAllByCourseIdAndExamId(this.courseId, this.examId);
            } else {
                complaintResponse = this.complaintService.findAllByCourseId(this.courseId, this.complaintType);
            }
        }
        this.subscribeToComplaintResponse(complaintResponse);
    }


subscribeToComplaintResponse(complaintResponse: Observable<HttpResponse<Complaint[]>>) {
        complaintResponse.subscribe({
            next: (res) => {
                this.complaints = res.body!;

                if (!this.showAddressedComplaints) {
                    this.complaintsToShow = this.complaints.filter((complaint) => complaint.accepted === undefined);
                } else {
                    this.complaintsToShow = this.complaints;
                }

                if (this.complaints.some((complaint) => complaint.student)) {
                    this.hasStudentInformation = true;
                }
            },
            error: (error: HttpErrorResponse) => onError(this.alertService, error),
            complete: () => (this.loading = false),
        });
    }


openAssessmentEditor(complaint: Complaint) {
        if (!complaint || !complaint.result || !complaint.result.participation || !complaint.result.submission) {
            return;
        }

        const studentParticipation = complaint.result.participation as StudentParticipation;
        const exercise = studentParticipation.exercise;
        const submissionId = complaint.result.submission.id;
        if (!exercise || !exercise.type || !submissionId) {
            return;
        }
        if (!this.correctionRound) {
            this.correctionRound = 0;
        }
        if (complaint.accepted != undefined) {
            this.correctionRound += 1;
        }
        const url = getLinkToSubmissionAssessment(
            exercise.type,
            this.courseId,
            exercise.id!,
            studentParticipation.id,
            submissionId,
            0, // even if the list of complaints are part of an exam, the assessment of non-exam exercises gets executed
            0,
            complaint.result.id,
        );
        this.router.navigate(url, { queryParams: { 'correction-round': this.correctionRound } });
    }


sortRows() {
        switch (this.complaintsSortingPredicate) {
            case 'responseTime':
                this.sortService.sortByFunction(this.complaintsToShow, (complaint) => this.complaintService.getResponseTimeInSeconds(complaint), this.complaintsReverseOrder);
                break;
            case 'lockStatus':
                this.sortService.sortByFunction(this.complaintsToShow, (complaint) => this.calculateComplaintLockStatus(complaint), this.complaintsReverseOrder);
                break;
            default:
                this.sortService.sortByProperty(this.complaintsToShow, this.complaintsSortingPredicate, this.complaintsReverseOrder);
        }
    }


triggerAddressedComplaints() {
        this.showAddressedComplaints = !this.showAddressedComplaints;

        if (this.showAddressedComplaints) {
            this.complaintsToShow = this.complaints;
        } else {
            this.complaintsToShow = this.complaints.filter((complaint) => complaint.accepted === undefined);
        }
    }


/**
     * Used to lazy-load all complaints from the server for a tutor or editor.
     */
triggerShowAllComplaints() {
        this.isLoadingAllComplaints = true;
        let complaintResponse: Observable<HttpResponse<Complaint[]>>;
        complaintResponse = this.complaintService.findAllWithoutStudentInformationForCourseId(this.courseId, this.complaintType);
        this.subscribeToComplaintResponse(complaintResponse);
        this.isLoadingAllComplaints = false;
        this.allComplaintsForTutorLoaded = true;
    }


calculateComplaintLockStatus(complaint: Complaint) {
        if (complaint.complaintResponse && this.complaintService.isComplaintLocked(complaint)) {
            if (this.complaintService.isComplaintLockedByLoggedInUser(complaint)) {
                const endDate = this.artemisDatePipe.transform(complaint.complaintResponse?.lockEndDate);
                return this.translateService.instant('artemisApp.locks.lockInformationYou', {
                    endDate,
                });
            } else {
                const endDate = this.artemisDatePipe.transform(complaint.complaintResponse?.lockEndDate);
                const user = complaint.complaintResponse?.reviewer?.login;

                return this.translateService.instant('artemisApp.locks.lockInformation', {
                    endDate,
                    user,
                });
            }
        } else {
            return this.translateService.instant('artemisApp.locks.notUnlocked');
        }
    }


() => {
  const { reset } = useNavigation();
  const { params } = useRoute();

  const routeParams = params as RouteParams;

  const formattedDate = useMemo(() => {
    const formatDateStr = "EEEE', dia' dd 'de' MMMM 'de' yyyy 'às' HH:mm'h'";
    const formatConfig = { locale: ptBR };

    return format(routeParams.date, formatDateStr, formatConfig);
  }, [routeParams.date]);

  const handleOkPressed = useCallback(() => {
    reset({
      routes: [{ name: 'Dashboard' }],
      index: 0,
    });
  }, [reset]);

  return (
    <Container>
      <Icon name="check" size={80} color="#04d361" />

      <Title>Agendamento concluído</Title>
      <Description>{formattedDate}</Description>
      <OkButton onPress={handleOkPressed}>
        <OkButtonText>Ok</OkButtonText>
      </OkButton>
    </Container>


() => {
    const formatDateStr = "EEEE', dia' dd 'de' MMMM 'de' yyyy 'às' HH:mm'h'";
    const formatConfig = { locale: ptBR };

    return format(routeParams.date, formatDateStr, formatConfig);
  }


() => {
    reset({
      routes: [{ name: 'Dashboard' }],
      index: 0,
    });
  }


interface RouteParams {
  date: number;
}


class Comment {
  @Column()
  id: ObjectID;

  @Column('uuid')
  user_id: string;

  @Column()
  content: string;

  @CreateDateColumn()
  created_at: Date;

  @UpdateDateColumn()
  updated_at: Date;
}


() => {
  console.log(
      'App is running at http://localhost:%d in %s mode', PORT, app.get('env'));
  console.log('  Press CTRL-C to stop\n');
}


(itemId: string, isFavorite: boolean) => {}


() => {
  it('does not mutate the original object', () => {
    const object = {
      key1: 'some value',
      key2: 'another value',
    }
    const cloneOfObject = clone(object);
    deepReplaceInObject('some value', 'a new value', object);
    expect(object).to.deep.equal(cloneOfObject);
  });

  it('can replace a string value', () => {
    const object = {
      key1: 'some value',
      key2: 'another value',
    }
    const replacedObject = deepReplaceInObject('some value', 'a new value', object);
    const expected = {
      key1: 'a new value',
      key2: 'another value',
    }
    expect(replacedObject).to.deep.equal(expected);
  });

  it('can replace a string value with null', () => {
    const object = {
      key1: 'some value',
      key2: 'another value',
    }
    const replacedObject = deepReplaceInObject('some value', null, object);
    const expected = {
      key1: <null>null,
      key2: 'another value',
    }
    expect(replacedObject).to.deep.equal(expected);
  });

  it('can replace a number value', () => {
    const object = {
      key1: 4,
      key2: 5,
    }
    const replacedObject = deepReplaceInObject(4, 10, object);
    const expected = {
      key1: 10,
      key2: 5,
    }
    expect(replacedObject).to.deep.equal(expected);
  });

  it('can replace a number value with null', () => {
    const object = {
      key1: 4,
      key2: 5,
    }
    const replacedObject = deepReplaceInObject(4, null, object);
    const expected = {
      key1: <null>null,
      key2: 5,
    }
    expect(replacedObject).to.deep.equal(expected);
  })

  it('can replace multiple string values', () => {
    const object = {
      key1: 'some value',
      key2: 5,
      key3: 'some value',
      key4: 'another value',
    }
    const replacedObject = deepReplaceInObject('some value', 'a new value', object);
    const expected = {
      key1: 'a new value',
      key2: 5,
      key3: 'a new value',
      key4: 'another value',
    }
    expect(replacedObject).to.deep.equal(expected);
  });

  it('can replace multiple number values', () => {
    const object = {
      key1: 4,
      key2: 5,
      key3: 4,
      key4: 'another value',
    }
    const replacedObject = deepReplaceInObject(4, 'a new value', object);
    const expected = {
      key1: 'a new value',
      key2: 5,
      key3: 'a new value',
      key4: 'another value',
    }
    expect(replacedObject).to.deep.equal(expected);
  });

  it('returns a copy of the object if there are no matches', () => {
    const object = {
      key1: 'some value',
      key2: 'another value',
    }
    const replacedObject = deepReplaceInObject('no match', 'a new value', object);
    expect(replacedObject).to.deep.equal(object);
  });
}


() => {
    const object = {
      key1: 'some value',
      key2: 'another value',
    }
    const cloneOfObject = clone(object);
    deepReplaceInObject('some value', 'a new value', object);
    expect(object).to.deep.equal(cloneOfObject);
  }


() => {
    const object = {
      key1: 'some value',
      key2: 'another value',
    }
    const replacedObject = deepReplaceInObject('some value', 'a new value', object);
    const expected = {
      key1: 'a new value',
      key2: 'another value',
    }
    expect(replacedObject).to.deep.equal(expected);
  }


() => {
    const object = {
      key1: 'some value',
      key2: 'another value',
    }
    const replacedObject = deepReplaceInObject('some value', null, object);
    const expected = {
      key1: <null>null,
      key2: 'another value',
    }
    expect(replacedObject).to.deep.equal(expected);
  }


() => {
    const object = {
      key1: 4,
      key2: 5,
    }
    const replacedObject = deepReplaceInObject(4, 10, object);
    const expected = {
      key1: 10,
      key2: 5,
    }
    expect(replacedObject).to.deep.equal(expected);
  }


() => {
    const object = {
      key1: 4,
      key2: 5,
    }
    const replacedObject = deepReplaceInObject(4, null, object);
    const expected = {
      key1: <null>null,
      key2: 5,
    }
    expect(replacedObject).to.deep.equal(expected);
  }


() => {
    const object = {
      key1: 'some value',
      key2: 5,
      key3: 'some value',
      key4: 'another value',
    }
    const replacedObject = deepReplaceInObject('some value', 'a new value', object);
    const expected = {
      key1: 'a new value',
      key2: 5,
      key3: 'a new value',
      key4: 'another value',
    }
    expect(replacedObject).to.deep.equal(expected);
  }


() => {
    const object = {
      key1: 4,
      key2: 5,
      key3: 4,
      key4: 'another value',
    }
    const replacedObject = deepReplaceInObject(4, 'a new value', object);
    const expected = {
      key1: 'a new value',
      key2: 5,
      key3: 'a new value',
      key4: 'another value',
    }
    expect(replacedObject).to.deep.equal(expected);
  }


() => {
    const object = {
      key1: 'some value',
      key2: 'another value',
    }
    const replacedObject = deepReplaceInObject('no match', 'a new value', object);
    expect(replacedObject).to.deep.equal(object);
  }


() => {
  it('does not mutate the original array', () => {
    const array = ['some value', 'another value']
    const clonedArray = clone(array);
    deepReplaceInObject('some value', 'a new value', array);
    expect(array).to.deep.equal(clonedArray);
  });

  it('can replace a string value', () => {
    const array = ['some value', 'another value']
    const replacedArray = deepReplaceInObject('some value', 'a new value', array);
    const expected = ['a new value', 'another value'];
    expect(replacedArray).to.deep.equal(expected);
  });

  it('can replace a string value with null', () => {
    const array = ['some value', 'another value']
    const replacedArray = deepReplaceInObject('some value', null, array);
    const expected = [null, 'another value'];
    expect(replacedArray).to.deep.equal(expected);
  })

  it('can replace a number value', () => {
    const array = [4, 5]
    const replacedArray = deepReplaceInObject(4, 10, array);
    const expected = [10, 5];
    expect(replacedArray).to.deep.equal(expected);
  });

  it('can replace a number value with null', () => {
    const array = [4, 5]
    const replacedArray = deepReplaceInObject(4, null, array);
    const expected = [null, 5];
    expect(replacedArray).to.deep.equal(expected);
  });

  it('can replace multiple string values', () => {
    const array = ['some value', 'another value', 'some value', 'yet another value'];
    const replacedArray = deepReplaceInObject('some value', 'a new value', array);
    const expected = ['a new value', 'another value', 'a new value', 'yet another value'];
    expect(replacedArray).to.deep.equal(expected);
  });

  it('can replace multiple number values', () => {
    const array = [4, 5, 4, 6];
    const replacedArray = deepReplaceInObject(4, 10, array);
    const expected = [10, 5, 10, 6];
    expect(replacedArray).to.deep.equal(expected);
  });

  it('returns a copy of the array if there are no matches', () => {
    const array = [4, 5, 4, 6];
    const replacedArray = deepReplaceInObject(9, 10, array);
    const expected = [4, 5, 4, 6];
    expect(replacedArray).to.deep.equal(expected);
  });
}


() => {
    const array = ['some value', 'another value']
    const clonedArray = clone(array);
    deepReplaceInObject('some value', 'a new value', array);
    expect(array).to.deep.equal(clonedArray);
  }


() => {
    const array = ['some value', 'another value']
    const replacedArray = deepReplaceInObject('some value', 'a new value', array);
    const expected = ['a new value', 'another value'];
    expect(replacedArray).to.deep.equal(expected);
  }


() => {
    const array = ['some value', 'another value']
    const replacedArray = deepReplaceInObject('some value', null, array);
    const expected = [null, 'another value'];
    expect(replacedArray).to.deep.equal(expected);
  }


() => {
    const array = [4, 5]
    const replacedArray = deepReplaceInObject(4, 10, array);
    const expected = [10, 5];
    expect(replacedArray).to.deep.equal(expected);
  }


() => {
    const array = [4, 5]
    const replacedArray = deepReplaceInObject(4, null, array);
    const expected = [null, 5];
    expect(replacedArray).to.deep.equal(expected);
  }


() => {
    const array = ['some value', 'another value', 'some value', 'yet another value'];
    const replacedArray = deepReplaceInObject('some value', 'a new value', array);
    const expected = ['a new value', 'another value', 'a new value', 'yet another value'];
    expect(replacedArray).to.deep.equal(expected);
  }


() => {
    const array = [4, 5, 4, 6];
    const replacedArray = deepReplaceInObject(4, 10, array);
    const expected = [10, 5, 10, 6];
    expect(replacedArray).to.deep.equal(expected);
  }


() => {
    const array = [4, 5, 4, 6];
    const replacedArray = deepReplaceInObject(9, 10, array);
    const expected = [4, 5, 4, 6];
    expect(replacedArray).to.deep.equal(expected);
  }