responseIntercept(observable: Observable<Response>): Observable<Response> {
        return observable.map((response: Response) => {
            const headers = [];
            response.headers.forEach((value, name) => {
                if (name.toLowerCase().endsWith('app-alert') || name.toLowerCase().endsWith('app-params')) {
                    headers.push(name);
                }
            });
            if (headers.length > 1) {
                headers.sort();
                const alertKey = response.headers.get(headers[ 0 ]);
                if (typeof alertKey === 'string') {
                    if (this.alertService) {
                        this.alertService.success(alertKey, null, null);
                    }
                }
            }
            return response;
        }).catch((error) => {
            return Observable.throw(error); // here, response is an error
        });
    }


export function getPackageOptions() {
    const devices = core.getInput("types", { required: true });
    const openwrt_version = core.getInput("openwrt-version");
    const kernel_version = core.getInput("kernel-version");
    const whoami = core.getInput("whoami");
    const out = core.getInput("out");
    const openwrt_path = core.getInput("openwrt-path");
    const openwrt_url = core.getInput("openwrt-url");
    const sub_name = core.getInput("sub-name");
    const githubrepository = core.getInput("githubrepository");

    if (util.isNull(openwrt_path) && util.isNull(openwrt_url)) {
        core.setFailed(`Both [openwrt-path] and [.openwrt-url] cannot be empty.`);
    }
    return {
        devices,
        openwrt_version,
        kernel_version,
        whoami,
        out,
        openwrt_path,
        openwrt_url,
        sub_name,
        githubrepository
    }
}


export interface PackageOptions {
    devices: string;
    openwrt_version: string;
    kernel_version: string;
    whoami: string;
    out: string;
    openwrt_path: string;
    openwrt_url: string;
    sub_name: string;
}


export interface Kernels {
    Item: Array<string>
    Latest: string;
    Latest_O: string;
}


({ submitSearch, searchValue, onChangeSearchValue, searchFilter, changeFilter }) => {
  return (
      <div className="input-field">
        <input
        type="search"
        placeholder="Enter movie/series name"
        value={searchValue}
        onChange={onChangeSearchValue}
        />
        <button className="btn search-btn" type="submit" onClick={submitSearch}>Search</button>
        <label htmlFor="radio-all">
          <input
            type="radio"
            id="radio-all"
            data-type="all"
            name="type"
            onChange={changeFilter}
            checked={searchFilter === 'all'}


interface SearchProps {
  submitSearch: (e: MouseEvent<HTMLButtonElement>) => void
  onChangeSearchValue: (e: ChangeEvent<HTMLInputElement>) => void
  searchValue: string
  searchFilter: SearchType
  changeFilter: (e: ChangeEvent<HTMLInputElement>) => void
}


() => {
  let service: PlanesService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(PlanesService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
}


() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(PlanesService);
  }


_type => SortOrder


@TypeGraphQL.InputType({
  isAbstract: true
})
export class PostMinOrderByAggregateInput {
  @TypeGraphQL.Field(_type => SortOrder, {
    nullable: true
  })
  id?: "asc" | "desc" | undefined;

  @TypeGraphQL.Field(_type => SortOrder, {
    nullable: true
  })
  createdAt?: "asc" | "desc" | undefined;

  @TypeGraphQL.Field(_type => SortOrder, {
    nullable: true
  })
  updatedAt?: "asc" | "desc" | undefined;

  @TypeGraphQL.Field(_type => SortOrder, {
    nullable: true
  })
  published?: "asc" | "desc" | undefined;

  @TypeGraphQL.Field(_type => SortOrder, {
    nullable: true
  })
  title?: "asc" | "desc" | undefined;

  @TypeGraphQL.Field(_type => SortOrder, {
    nullable: true
  })
  content?: "asc" | "desc" | undefined;

  @TypeGraphQL.Field(_type => SortOrder, {
    nullable: true
  })
  authorId?: "asc" | "desc" | undefined;
}


interface Container {
    inspect(options: {}, callback: Callback<ContainerInspectInfo>): void;
    inspect(callback: Callback<ContainerInspectInfo>): void;
    inspect(options?: {}): Promise<ContainerInspectInfo>;

    rename(options: {}, callback: Callback<any>): void;
    rename(options: {}): Promise<any>;

    update(options: {}, callback: Callback<any>): void;
    update(options: {}): Promise<any>;

    top(options: {}, callback: Callback<any>): void;
    top(callback: Callback<any>): void;
    top(options?: {}): Promise<any>;

    changes(callback: Callback<any>): void;
    changes(): Promise<any>;

    export(callback: Callback<NodeJS.ReadableStream>): void;
    export(): Promise<NodeJS.ReadableStream>;

    start(options: {}, callback: Callback<any>): void;
    start(callback: Callback<any>): void;
    start(options?: {}): Promise<any>;

    pause(options: {}, callback: Callback<any>): void;
    pause(callback: Callback<any>): void;
    pause(options?: {}): Promise<any>;

    unpause(options: {}, callback: Callback<any>): void;
    unpause(callback: Callback<any>): void;
    unpause(options?: {}): Promise<any>;

    exec(options: {}, callback: Callback<any>): void;
    exec(options: {}): Promise<any>;

    commit(options: {}, callback: Callback<any>): void;
    commit(callback: Callback<any>): void;
    commit(options?: {}): Promise<any>;

    stop(options: {}, callback: Callback<any>): void;
    stop(callback: Callback<any>): void;
    stop(options?: {}): Promise<any>;

    restart(options: {}, callback: Callback<any>): void;
    restart(callback: Callback<any>): void;
    restart(options?: {}): Promise<any>;

    kill(options: {}, callback: Callback<any>): void;
    kill(callback: Callback<any>): void;
    kill(options?: {}): Promise<any>;

    resize(options: {}, callback: Callback<any>): void;
    resize(callback: Callback<any>): void;
    resize(options?: {}): Promise<any>;

    wait(callback: Callback<any>): void;
    wait(): Promise<any>;

    remove(options: {}, callback: Callback<any>): void;
    remove(callback: Callback<any>): void;
    remove(options?: {}): Promise<any>;

    /** Deprecated since RAPI v1.20 */
    copy(options: {}, callback: Callback<any>): void;
    /** Deprecated since RAPI v1.20 */
    copy(callback: Callback<any>): void;
    /** Deprecated since RAPI v1.20 */
    copy(options?: {}): Promise<any>;

    getArchive(options: {}, callback: Callback<NodeJS.ReadableStream>): void;
    getArchive(options: {}): Promise<NodeJS.ReadableStream>;

    infoArchive(options: {}, callback: Callback<any>): void;
    infoArchive(options: {}): Promise<any>;

    /** @param file Filename (will read synchronously), Buffer or stream */
    putArchive(file: string | Buffer | NodeJS.ReadableStream, options: {}, callback: Callback<NodeJS.WritableStream>): void;
    putArchive(file: string | Buffer | NodeJS.ReadableStream, options: {}): Promise<NodeJS.ReadWriteStream>;

    logs(options: ContainerLogsOptions, callback: Callback<NodeJS.ReadableStream>): void;
    logs(callback: Callback<NodeJS.ReadableStream>): void;
    logs(options?: ContainerLogsOptions): Promise<NodeJS.ReadableStream>;

    stats(options: {}, callback: Callback<any>): void;
    stats(callback: Callback<any>): void;
    stats(options?: {}): Promise<any>;

    attach(options: {}, callback: Callback<NodeJS.ReadableStream>): void;
    attach(options: {}): Promise<NodeJS.ReadableStream>;

    modem: any;
    id?: string;
  }


interface Image {
    inspect(callback: Callback<ImageInspectInfo>): void;
    inspect(): Promise<ImageInspectInfo>;

    history(callback: Callback<any>): void;
    history(): Promise<any>;

    get(callback: Callback<NodeJS.ReadableStream>): void;
    get(): Promise<NodeJS.ReadableStream>;

    push(options: {}, callback: Callback<NodeJS.ReadableStream>): void;
    push(callback: Callback<NodeJS.ReadableStream>): void;
    push(options?: {}): Promise<NodeJS.ReadableStream>;

    tag(options: {}, callback: Callback<any>): void;
    tag(callback: Callback<any>): void;
    tag(options?: {}): Promise<any>;

    remove(options: {}, callback: Callback<ImageRemoveInfo>): void;
    remove(callback: Callback<ImageRemoveInfo>): void;
    remove(options?: {}): Promise<any>;

    modem: any;
    id?: string;
  }


interface Volume {
    inspect(callback: Callback<any>): void;
    inspect(): Promise<any>;

    remove(options: {}, callback: Callback<any>): void;
    remove(callback: Callback<any>): void;
    remove(options?: {}): Promise<any>;

    modem: any;
    name?: string;
  }


interface Service {
    inspect(callback: Callback<any>): void;
    inspect(): Promise<any>;

    remove(options: {}, callback: Callback<any>): void;
    remove(callback: Callback<any>): void;
    remove(options?: {}): Promise<any>;

    update(options: {}, callback: Callback<any>): void;
    update(options: {}): Promise<any>;

    modem: any;
    id?: string;
  }


interface Task {
    inspect(callback: Callback<any>): void;
    inspect(): Promise<any>;

    modem: any;
    id?: string;
  }


interface Node {
    inspect(callback: Callback<any>): void;
    inspect(): Promise<any>;

    update(options: {}, callback: Callback<any>): void;
    update(callback: Callback<any>): void;
    update(options?: {}): Promise<any>;

    remove(options: {}, callback: Callback<any>): void;
    remove(callback: Callback<any>): void;
    remove(options?: {}): Promise<any>;

    modem: any;
    id?: string;
  }


interface Plugin {
    modem: any;
    name: string;
    remote: any;

    inspect(callback: Callback<PluginInspectInfo>): void;
    inspect(): Promise<PluginInspectInfo>;

    remove(options: {}, callback: Callback<any>): void;
    remove(callback: Callback<any>): void;
    remove(options?: {}): Promise<any>;

    privileges(callback: Callback<any>): void;
    privileges(): Promise<any>;

    pull(options: {}, callback: Callback<any>): void;
    pull(options: {}): Promise<any>;

    enable(options: {}, callback: Callback<any>): void;
    enable(callback: Callback<any>): void;
    enable(options?: {}): Promise<any>;

    disable(options: {}, callback: Callback<any>): void;
    disable(callback: Callback<any>): void;
    disable(options?: {}): Promise<any>;

    push(options: {}, callback: Callback<any>): void;
    push(callback: Callback<any>): void;
    push(options?: {}): Promise<any>;

    configure(options: {}, callback: Callback<any>): void;
    configure(callback: Callback<any>): void;
    configure(options?: {}): Promise<any>;

    upgrade(auth: any, options: {}, callback: Callback<any>): void;
    upgrade(auth: any, callback: Callback<any>): void;
    upgrade(auth: any, options?: {}): Promise<any>;
  }


interface Secret {
    inspect(callback: Callback<SecretInfo>): void;
    inspect(): Promise<SecretInfo>;

    update(options: {}, callback: Callback<any>): void;
    update(callback: Callback<any>): void;
    update(options?: {}): Promise<any>;

    remove(options: {}, callback: Callback<any>): void;
    remove(callback: Callback<any>): void;
    remove(options?: {}): Promise<any>;

    modem: any;
    id?: string;
  }


interface Network {
    inspect(callback: Callback<any>): void;
    inspect(): Promise<any>;

    remove(options: {}, callback: Callback<any>): void;
    remove(callback: Callback<any>): void;
    remove(options?: {}): Promise<any>;

    connect(options: {}, callback: Callback<any>): void;
    connect(callback: Callback<any>): void;
    connect(options?: {}): Promise<any>;

    disconnect(options: {}, callback: Callback<any>): void;
    disconnect(callback: Callback<any>): void;
    disconnect(options?: {}): Promise<any>;

    modem: any;
    id?: string;
  }


interface Exec {
    inspect(callback: Callback<any>): void;
    inspect(): Promise<any>;

    start(options: {}, callback: Callback<any>): void;
    start(options: {}): Promise<any>;

    resize(options: {}, callback: Callback<any>): void;
    resize(options: {}): Promise<any>;

    modem: any;
    id?: string;
  }


interface ImageInfo {
    Id: string;
    ParentId: string;
    RepoTags: string[];
    RepoDigests?: string[];
    Created: number;
    Size: number;
    VirtualSize: number;
    Labels: { [label: string]: string };
  }


interface ContainerInfo {
    Id: string;
    Names: string[];
    Image: string;
    ImageID: string;
    Command: string;
    Created: number;
    Ports: Port[];
    Labels: { [label: string]: string };
    State: string;
    Status: string;
    HostConfig: {
      NetworkMode: string;
    };
    NetworkSettings: {
      Networks: { [networkType: string]: NetworkInfo }
    };
  }


interface Port {
    IP: string;
    PrivatePort: number;
    PublicPort: number;
    Type: string;
  }


interface NetworkInfo {
    IPAMConfig?: any;
    Links?: any;
    Aliases?: any;
    NetworkID: string;
    EndpointID: string;
    Gateway: string;
    IPAddress: string;
    IPPrefixLen: number;
    IPv6Gateway: string;
    GlobalIPv6Address: string;
    GlobalIPv6PrefixLen: number;
    MacAddress: string;
  }


interface ContainerInspectInfo {
    Id: string;
    Created: string;
    Path: string;
    Args: string[];
    State: {
      Status: string;
      Running: boolean;
      Paused: boolean;
      Restarting: boolean;
      OOMKilled: boolean;
      Dead: boolean;
      Pid: number;
      ExitCode: number;
      Error: string;
      StartedAt: string;
      FinishedAt: string;
    };
    Image: string;
    ResolvConfPath: string;
    HostnamePath: string;
    HostsPath: string;
    LogPath: string;
    Name: string;
    RestartCount: number;
    Driver: string;
    MountLabel: string;
    ProcessLabel: string;
    AppArmorProfile: string;
    ExecIDs?: any;
    HostConfig: HostConfig;
    GraphDriver: {
      Name: string;
      Data: {
        DeviceId: string;
        DeviceName: string;
        DeviceSize: string;
      }
    };
    Mounts: Array<{
      Source: string;
      Destination: string;
      Mode: string;
      RW: boolean;
      Propagation: string;
    }>;
    Config: {
      Hostname: string;
      Domainname: string;
      User: string;
      AttachStdin: boolean;
      AttachStdout: boolean;
      AttachStderr: boolean;
      ExposedPorts: { [portAndProtocol: string]: {} };
      Tty: boolean;
      OpenStdin: boolean;
      StdinOnce: boolean;
      Env: string[];
      Cmd: string[];
      Image: string;
      Volumes: { [volume: string]: {} };
      WorkingDir: string;
      Entrypoint?: any;
      OnBuild?: any;
      Labels: { [label: string]: string }
    };
    NetworkSettings: {
      Bridge: string;
      SandboxID: string;
      HairpinMode: boolean;
      LinkLocalIPv6Address: string;
      LinkLocalIPv6PrefixLen: number;
      Ports: {
        [portAndProtocol: string]: {
          HostIp: string;
          HostPort: string;
        }
      };
      SandboxKey: string;
      SecondaryIPAddresses?: any;
      SecondaryIPv6Addresses?: any;
      EndpointID: string;
      Gateway: string;
      GlobalIPv6Address: string;
      GlobalIPv6PrefixLen: number;
      IPAddress: string;
      IPPrefixLen: number;
      IPv6Gateway: string;
      MacAddress: string;
      Networks: {
        [type: string]: {
          IPAMConfig?: any;
          Links?: any;
          Aliases?: any;
          NetworkID: string;
          EndpointID: string;
          Gateway: string;
          IPAddress: string;
          IPPrefixLen: number;
          IPv6Gateway: string;
          GlobalIPv6Address: string;
          GlobalIPv6PrefixLen: number;
          MacAddress: string;
        }
      };
      Node?: {
          ID: string;
          IP: string;
          Addr: string;
          Name: string;
          Cpus: number;
          Memory: number;
          Labels: any;
      }
    };
  }


interface HostConfig {
    AutoRemove: boolean;
    Binds: string[];
    ContainerIDFile: string;
    LogConfig: {
      Type: string;
      Config: any;
    };
    NetworkMode: string;
    PortBindings?: any;
    RestartPolicy: {
      Name: string;
      MaximumRetryCount: number;
    };
    VolumeDriver: string;
    VolumesFrom?: any;
    CapAdd?: any;
    CapDrop?: any;
    Dns: any[];
    DnsOptions: any[];
    DnsSearch: any[];
    ExtraHosts?: any;
    IpcMode: string;
    Links?: any;
    OomScoreAdj: number;
    PidMode: string;
    Privileged: boolean;
    PublishAllPorts: boolean;
    ReadonlyRootfs: boolean;
    SecurityOpt?: any;
    UTSMode: string;
    ShmSize: number;
    ConsoleSize: number[];
    Isolation: string;
    CpuShares: number;
    CgroupParent: string;
    BlkioWeight: number;
    BlkioWeightDevice?: any;
    BlkioDeviceReadBps?: any;
    BlkioDeviceWriteBps?: any;
    BlkioDeviceReadIOps?: any;
    BlkioDeviceWriteIOps?: any;
    CpuPeriod: number;
    CpuQuota: number;
    CpusetCpus: string;
    CpusetMems: string;
    Devices?: any;
    DiskQuota: number;
    KernelMemory: number;
    Memory: number;
    MemoryReservation: number;
    MemorySwap: number;
    MemorySwappiness: number;
    OomKillDisable: boolean;
    PidsLimit: number;
    Ulimits?: any;
  }


interface ImageInspectInfo {
    Id: string;
    RepoTags: string[];
    RepoDigests: string[];
    Parent: string;
    Comment: string;
    Created: string;
    Container: string;
    ContainerConfig:
    {
      Hostname: string;
      Domainname: string;
      User: string;
      AttachStdin: boolean;
      AttachStdout: boolean;
      AttachStderr: boolean;
      ExposedPorts: { [portAndProtocol: string]: {} };
      Tty: boolean;
      OpenStdin: boolean;
      StdinOnce: boolean;
      Env: string[];
      Cmd: string[];
      ArgsEscaped: boolean;
      Image: string;
      Volumes: { [path: string]: {} },
      WorkingDir: string;
      Entrypoint?: any;
      OnBuild?: any[];
      Labels: { [label: string]: string }
    };
    DockerVersion: string;
    Author: string;
    Config:
    {
      Hostname: string;
      Domainname: string;
      User: string;
      AttachStdin: boolean;
      AttachStdout: boolean;
      AttachStderr: boolean;
      ExposedPorts: { [portAndProtocol: string]: {} }
      Tty: boolean;
      OpenStdin: boolean;
      StdinOnce: boolean;
      Env: string[];
      Cmd: string[];
      ArgsEscaped: boolean;
      Image: string;
      Volumes: { [path: string]: {} },
      WorkingDir: string;
      Entrypoint?: any;
      OnBuild: any[];
      Labels: { [label: string]: string }
    };
    Architecture: string;
    Os: string;
    Size: number;
    VirtualSize: number;
    GraphDriver:
    {
      Name: string;
      Data:
      {
        DeviceId: string;
        DeviceName: string;
        DeviceSize: string;
      }
    };
  }


interface ContainerCreateOptions {
    name?: string;
    Hostname?: string;
    Domainname?: string;
    User?: string;
    AttachStdin?: boolean;
    AttachStdout?: boolean;
    AttachStderr?: boolean;
    Tty?: boolean;
    OpenStdin?: boolean;
    StdinOnce?: boolean;
    Env?: string[];
    Cmd?: string[];
    Entrypoint?: string;
    Image?: string;
    Labels?: { [label: string]: string };
    Volumes?: { [volume: string]: {} };
    WorkingDir?: string;
    NetworkDisabled?: boolean;
    MacAddress?: boolean;
    ExposedPorts?: { [port: string]: {} };
    StopSignal?: string;
    HostConfig?: {
      AutoRemove?: boolean;
      Binds?: string[];
      Links?: string[];
      Memory?: number;
      MemorySwap?: number;
      MemoryReservation?: number;
      KernelMemory?: number;
      CpuPercent?: number;
      CpuShares?: number;
      CpuPeriod?: number;
      CpuQuota?: number;
      CpusetMems?: string;
      MaximumIOps?: number;
      MaxmimumIOBps?: number;
      BlkioWeightDevice?: Array<{}>;
      BlkioDeviceReadBps?: Array<{}>;
      BlkioDeviceReadIOps?: Array<{}>;
      BlkioDeviceWriteBps?: Array<{}>;
      BlkioDeviceWriteIOps?: Array<{}>;
      MemorySwappiness?: number;
      OomKillDisable?: boolean;
      OomScoreAdj?: number;
      PidMode?: string;
      PidsLimit?: number;
      PortBindings?: { [portAndProtocol: string]: Array<{ [index: string]: string }> };
      PublishAllPorts?: boolean;
      Privileged?: boolean;
      ReadonlyRootfs?: boolean;
      Dns?: string[];
      DnsOptions?: string[];
      DnsSearch?: string[];
      ExtraHosts?: any;
      VolumesFrom?: string[];
      CapAdd?: string[];
      CapDrop?: string[];
      GroupAdd?: string[];
      RestartPolicy?: { [index: string]: number | string };
      NetworkMode?: string;
      Devices?: any[];
      Sysctls?: { [index: string]: string };
      Ulimits?: Array<{}>;
      LogConfig?: { [index: string]: string | {} };
      SecurityOpt?: { [index: string]: any };
      CgroupParent?: string;
      VolumeDriver?: string;
      ShmSize?: number;
    };
    NetworkingConfig?: {
      EndpointsConfig?: {
        [index: string]: any;
        isolated_nw?: {
          [index: string]: any;
          IPAMConfig?: {
            IPv4Address?: string;
            IPv6Adress?: string;
            LinkLocalIPs?: string[];
          }
          Links?: string[];
          Aliases?: string[];
        }
      }
    };
  }


interface KeyObject {
    pem: string | Buffer;
    passphrase?: string;
  }


interface DockerOptions {
    socketPath?: string;
    host?: string;
    port?: number | string;
    ca?: string | string[] | Buffer | Buffer[];
    cert?: string | string[] | Buffer | Buffer[];
    key?: string | string[] | Buffer | Buffer[] | KeyObject[];
    protocol?: "https" | "http";
    timeout?: number;
    version?: string;
    Promise?: typeof Promise;
  }


interface SecretVersion {
    Index: number;
  }


interface ServiceSpec {
    Name: string;
  }


interface SecretInfo {
    ID: string;
    Version: SecretVersion;
    CreatedAt: string;
    UpdatedAt?: string;
    Spec?: ServiceSpec;
  }


interface PluginInfo {
    Id?: string;
    Name: string;
    Enabled: boolean;
    Settings: PluginSettings;
    PluginReference?: string;
    Config: PluginConfig;
  }


interface PluginSettings {
    Mounts: PluginMount[];
    Env: string[];
    Args: string[];
    Devices: PluginDevice[];
  }


interface PluginConfig {
    Description: string;
    Documentation: string;
    Interface: any;
    Entrypoint: string[];
    WorkDir: string;
    User?: User;
    Network: Network;
    Linux: Linux;
    PropagatedMount: string;
    Mounts: PluginMount[];
    Env: PluginEnv[];
    Args: Args;
    rootfs: any;
  }


interface Interface {
    Types: PluginInterfaceType[];
    Socket: string;
  }


interface PluginInterfaceType {
    Prefix: string;
    Capability: string;
    Version: string;
  }


interface PluginMount {
    Name: string;
    Description: string;
    Settable: string[];
    Source: string;
    Destination: string;
    Type: string;
    Options: string[];
  }


interface Linux {
    Capabilities: string[];
    AllowAllDevices: boolean;
    Devices: PluginDevice[];
  }


interface PluginDevice {
    Name: string;
    Description: string;
    Settable: string[];
    Path: string;
  }


interface Network {
    Type: string;
  }


interface PluginEnv {
    Name: string;
    Description: string;
    Settable: string[];
    Value: string;
  }


interface Args {
    Name: string;
    Description: string;
    Settable: string[];
    Value: string;
  }


interface User {
    UID: number;
    GID: number;
  }


interface ImageRemoveInfo {
    Untagged: string;
    Deleted: string;
  }


interface PruneImagesInfo {
    ImagesDeleted: ImageRemoveInfo[];
    SpaceReclaimed: number;
  }


interface PruneVolumesInfo {
    VolumesDeleted: string[];
    SpaceReclaimed: number;
  }


interface PruneContainersInfo {
    ContainersDeleted: string[];
    SpaceReclaimed: number;
  }


interface PruneNetworksInfo {
    NetworksDeleted: string[];
  }


interface ContainerLogsOptions {
    stdout?: boolean;
    stderr?: boolean;
    follow?: boolean;
    since?: number;
    details?: boolean;
    tail?: number;
    timestamps?: boolean;
  }


interface ImageBuildContext {
  	context: string;
  	src: string[];
  }


type PluginInspectInfo = PluginInfo;


type Callback<T> = (error?: any, result?: T) => void;


createContainer(options: Dockerode.ContainerCreateOptions, callback: Callback<Dockerode.Container>): void;


createContainer(options: Dockerode.ContainerCreateOptions): Promise<Dockerode.Container>;


createImage(options: {}, callback: Callback<Dockerode.Image>): void;


createImage(auth: any, options: {}, callback: Callback<Dockerode.Image>): void;


createImage(options: {}): Promise<Dockerode.Image>;


createImage(auth: any, options: {}): Promise<Dockerode.Image>;


loadImage(file: string, options: {}, callback: Callback<any>): void;


loadImage(file: string, callback: Callback<any>): void;


loadImage(file: string, options?: {}): Promise<any>;


importImage(file: string, options: {}, callback: Callback<any>): void;


importImage(file: string, callback: Callback<any>): void;


importImage(file: string, options?: {}): Promise<any>;


checkAuth(options: any, callback: Callback<any>): void;


checkAuth(options: any): Promise<any>;


buildImage(file: string | NodeJS.ReadableStream | Dockerode.ImageBuildContext, options: {}, callback: Callback<any>): void;


buildImage(file: string | NodeJS.ReadableStream | Dockerode.ImageBuildContext, callback: Callback<any>): void;


buildImage(file: string | NodeJS.ReadableStream | Dockerode.ImageBuildContext, options?: {}): Promise<any>;


getContainer(id: string): Dockerode.Container;


getImage(name: string): Dockerode.Image;


getVolume(name: string): Dockerode.Volume;


getPlugin(name: string, remote: any): Dockerode.Plugin;


getService(id: string): Dockerode.Service;


getTask(id: string): Dockerode.Task;


getNode(id: string): Dockerode.Node;


getNetwork(id: string): Dockerode.Network;


getSecret(id: string): Dockerode.Secret;


getExec(id: string): Dockerode.Exec;


listContainers(options: {}, callback: Callback<Dockerode.ContainerInfo[]>): void;


listContainers(callback: Callback<Dockerode.ContainerInfo[]>): void;


listContainers(options?: {}): Promise<Dockerode.ContainerInfo[]>;


listImages(options: {}, callback: Callback<Dockerode.ImageInfo[]>): void;


listImages(callback: Callback<Dockerode.ImageInfo[]>): void;


listImages(options?: {}): Promise<Dockerode.ImageInfo[]>;


listServices(options: {}, callback: Callback<any[]>): void;


listServices(callback: Callback<any[]>): void;


listServices(options?: {}): Promise<any[]>;


listNodes(options: {}, callback: Callback<any[]>): void;


listNodes(callback: Callback<any[]>): void;


listNodes(options?: {}): Promise<any[]>;


listTasks(options: {}, callback: Callback<any[]>): void;


listTasks(callback: Callback<any[]>): void;


listTasks(options?: {}): Promise<any[]>;


listSecrets(options: {}, callback: Callback<Dockerode.SecretInfo[]>): void;


listSecrets(callback: Callback<Dockerode.SecretInfo[]>): void;


listSecrets(options?: {}): Promise<Dockerode.SecretInfo[]>;


listPlugins(options: {}, callback: Callback<Dockerode.PluginInfo[]>): void;


listPlugins(callback: Callback<Dockerode.PluginInfo[]>): void;


listPlugins(options?: {}): Promise<Dockerode.PluginInfo[]>;


listVolumes(options: {}, callback: Callback<any[]>): void;


listVolumes(callback: Callback<any[]>): void;


listVolumes(options?: {}): Promise<any[]>;


listNetworks(options: {}, callback: Callback<any[]>): void;


listNetworks(callback: Callback<any[]>): void;


listNetworks(options?: {}): Promise<any[]>;


createSecret(options: {}, callback: Callback<any>): void;


createSecret(options: {}): Promise<any>;


createPlugin(options: {}, callback: Callback<any>): void;


createPlugin(options: {}): Promise<any>;


createVolume(options: {}, callback: Callback<any>): void;


createVolume(options: {}): Promise<any>;


createService(options: {}, callback: Callback<any>): void;


createService(options: {}): Promise<any>;


createNetwork(options: {}, callback: Callback<any>): void;


createNetwork(options: {}): Promise<any>;


searchImages(options: {}, callback: Callback<any>): void;


searchImages(options: {}): Promise<any>;


pruneImages(options: {}, callback: Callback<Dockerode.PruneImagesInfo>): void;


pruneImages(callback: Callback<Dockerode.PruneImagesInfo>): void;


pruneImages(options?: {}): Promise<Dockerode.PruneImagesInfo>;


pruneContainers(options: {}, callback: Callback<Dockerode.PruneContainersInfo>): void;


pruneContainers(callback: Callback<Dockerode.PruneContainersInfo>): void;


pruneContainers(options?: {}): Promise<Dockerode.PruneContainersInfo>;


pruneVolumes(options: {}, callback: Callback<Dockerode.PruneVolumesInfo>): void;


pruneVolumes(callback: Callback<Dockerode.PruneVolumesInfo>): void;


pruneVolumes(options?: {}): Promise<Dockerode.PruneVolumesInfo>;


pruneNetworks(options: {}, callback: Callback<Dockerode.PruneNetworksInfo>): void;


pruneNetworks(callback: Callback<Dockerode.PruneNetworksInfo>): void;


pruneNetworks(options?: {}): Promise<Dockerode.PruneNetworksInfo>;


info(callback: Callback<any>): void;


info(): Promise<any>;


df(callback: Callback<any>): void;


df(): Promise<any>;


version(callback: Callback<any>): void;


version(): Promise<any>;


ping(callback: Callback<any>): void;


ping(): Promise<any>;


getEvents(options: {}, callback: Callback<NodeJS.ReadableStream>): void;


getEvents(callback: Callback<NodeJS.ReadableStream>): void;


getEvents(options?: {}): Promise<NodeJS.ReadableStream>;


pull(repoTag: string, options: {}, callback: Callback<any>, auth?: {}): Dockerode.Image;


pull(repoTag: string, options: {}, auth?: {}): Promise<any>;


run(image: string, cmd: string[], outputStream: NodeJS.WritableStream, createOptions: {}, startOptions: {}, callback: Callback<any>): events.EventEmitter;


run(image: string, cmd: string[], outputStream: NodeJS.WritableStream, startOptions: {}, callback: Callback<any>): events.EventEmitter;


run(image: string, cmd: string[], outputStream: NodeJS.WritableStream, callback: Callback<any>): events.EventEmitter;


run(image: string, cmd: string[], outputStream: NodeJS.WritableStream, createOptions: {}, callback: Callback<any>): events.EventEmitter;


run(image: string, cmd: string[], outputStream: NodeJS.WritableStream, createOptions?: {}, startOptions?: {}): Promise<any>;


swarmInit(options: {}, callback: Callback<any>): void;


swarmInit(options: {}): Promise<any>;


swarmJoin(options: {}, callback: Callback<any>): void;


swarmJoin(options: {}): Promise<any>;


swarmLeave(options: {}, callback: Callback<any>): void;


swarmLeave(options: {}): Promise<any>;


swarmUpdate(options: {}, callback: Callback<any>): void;


swarmUpdate(options: {}): Promise<any>;


swarmInspect(callback: Callback<any>): void;


swarmInspect(): Promise<any>;


@Controller('movies')
export class MoviesController {
  constructor(private readonly prismaService: PrismaService) {}

  @Post()
  create(
    @Body() { director, movieName, yearReleased }: MovieModel,
  ): Promise<MovieModel> {
    return this.prismaService.movie.create({
      data: { director, movieName, yearReleased },
    });
  }

  @Get()
  findAll(): Promise<MovieModel[]> {
    return this.prismaService.movie.findMany();
  }
}


@Post()
  create(
    @Body() { director, movieName, yearReleased }: MovieModel,
  ): Promise<MovieModel> {
    return this.prismaService.movie.create({
      data: { director, movieName, yearReleased },
    });
  }


@Get()
  findAll(): Promise<MovieModel[]> {
    return this.prismaService.movie.findMany();
  }


function prepareDimensions(
    config: AggregateTransformOption['config'],
    upstream: ExternalSource,
    groupByDimInfo: ExternalDimensionDefinition
): {
    finalResultDimInfoList: ResultDimInfoInternal[];
    collectionDimInfoList: ResultDimInfoInternal[];
} {
    const resultDimensionsConfig = config.resultDimensions;
    const finalResultDimInfoList: ResultDimInfoInternal[] = [];
    const collectionDimInfoList: ResultDimInfoInternal[] = [];
    let gIndexInLine = 0;

    for (let i = 0; i < resultDimensionsConfig.length; i++) {
        const resultDimInfoConfig = resultDimensionsConfig[i];

        const dimInfoInUpstream = upstream.getDimensionInfo(resultDimInfoConfig.from);
        assert(dimInfoInUpstream, 'Can not find dimension by `from`: ' + resultDimInfoConfig.from);

        const rawMethod = resultDimInfoConfig.method;

        assert(
            groupByDimInfo.index !== dimInfoInUpstream.index || rawMethod == null,
            `Dimension ${dimInfoInUpstream.name} is the "groupBy" dimension, must not have any "method".`
        );

        const method = normalizeMethod(rawMethod);
        assert(method, 'method is required');

        const name = resultDimInfoConfig.name != null ? resultDimInfoConfig.name : dimInfoInUpstream.name;

        const finalResultDimInfo = new ResultDimInfoInternal(
            finalResultDimInfoList.length,
            dimInfoInUpstream.index,
            method,
            name,
            hasOwn(METHOD_NEEDS_GATHER_VALUES, method)
        );
        finalResultDimInfoList.push(finalResultDimInfo);

        // For collection.
        let needCollect = false;
        if (hasOwn(METHOD_NEEDS_COLLECT, method)) {
            needCollect = true;
            const collectionTargetMethods = METHOD_NEEDS_COLLECT[method as keyof typeof METHOD_NEEDS_COLLECT];
            for (let j = 0; j < collectionTargetMethods.length; j++) {
                finalResultDimInfo.addCollectionInfo({
                    method: collectionTargetMethods[j],
                    indexInLine: gIndexInLine++
                });
            }
        }
        if (hasOwn(METHOD_NEEDS_GATHER_VALUES, method)) {
            needCollect = true;
        }
        if (needCollect) {
            collectionDimInfoList.push(finalResultDimInfo);
        }
    }

    return { collectionDimInfoList, finalResultDimInfoList };
}


function prepareGroupByDimInfo(
    config: AggregateTransformOption['config'],
    upstream: ExternalSource
): ExternalDimensionDefinition {
    const groupByConfig = config.groupBy;
    let groupByDimInfo;
    if (groupByConfig != null) {
        groupByDimInfo = upstream.getDimensionInfo(groupByConfig);
        assert(groupByDimInfo, 'Can not find dimension by `groupBy`: ' + groupByConfig);
    }
    return groupByDimInfo;
}


function travel<LINE>(
    groupByDimInfo: ExternalDimensionDefinition,
    upstream: ExternalSource,
    resultDimInfoList: ResultDimInfoInternal[],
    doCreate: CreateInTravel<LINE>,
    doUpdate: UpdateInTravel<LINE>
): TravelResult<LINE> {
    const outList: TravelResult<LINE>['outList'] = [];
    let mapByGroup: TravelResult<LINE>['mapByGroup'];

    if (groupByDimInfo) {
        mapByGroup = {};

        for (let dataIndex = 0, len = upstream.count(); dataIndex < len; dataIndex++) {
            const groupByVal = upstream.retrieveValue(dataIndex, groupByDimInfo.index);

            // PENDING: when value is null/undefined
            if (groupByVal == null) {
                continue;
            }

            const groupByValStr = groupByVal + '';

            if (!hasOwn(mapByGroup, groupByValStr)) {
                const newLine = doCreate(upstream, dataIndex, resultDimInfoList, groupByDimInfo, groupByVal);
                outList.push(newLine);
                mapByGroup[groupByValStr] = newLine;
            }
            else {
                const targetLine = mapByGroup[groupByValStr];
                doUpdate(upstream, dataIndex, targetLine, resultDimInfoList, groupByDimInfo, groupByVal);
            }
        }
    }
    else {
        const targetLine = doCreate(upstream, 0, resultDimInfoList);
        outList.push(targetLine);
        for (let dataIndex = 1, len = upstream.count(); dataIndex < len; dataIndex++) {
            doUpdate(upstream, dataIndex, targetLine, resultDimInfoList);
        }
    }

    return { mapByGroup, outList };
}


function normalizeMethod(method: AggregateMethodLoose): AggregateMethodInternal {
    if (method == null) {
        return 'FIRST';
    }
    let methodInternal = method.toUpperCase() as AggregateMethodInternal;
    methodInternal = hasOwn(METHOD_ALIAS, methodInternal)
        ? METHOD_ALIAS[methodInternal as keyof typeof METHOD_ALIAS]
        : methodInternal;
    assert(hasOwn(METHOD_INTERNAL, methodInternal), `Illegal method ${method}.`);
    return methodInternal;
}


function isGroupByDimension(
    groupByDimInfo: ExternalDimensionDefinition,
    targetDimInfo: ResultDimInfoInternal
): boolean {
    return groupByDimInfo && targetDimInfo.indexInUpstream === groupByDimInfo.index;
}


function asc(list: number[]) {
    list.sort((a, b) => {
        return a - b;
    });
}


function lineCreatorForQ(
    percent: number,
    dimInfo: ResultDimInfoInternal,
    groupByDimInfo: ExternalDimensionDefinition,
    groupByVal: OptionDataValue
) {
    const gatheredValues = groupByDimInfo
        ? dimInfo.gatheredValuesByGroup[groupByVal + '']
        : dimInfo.gatheredValuesNoGroup;
    return quantile(gatheredValues, percent);
}


(
    upstream, dataIndex, collectionDimInfoList, groupByDimInfo, groupByVal
) => {
    const newLine = [] as number[];
    for (let i = 0; i < collectionDimInfoList.length; i++) {
        const dimInfo = collectionDimInfoList[i];
        const collectionInfoList = dimInfo.collectionInfoList;
        for (let j = 0; j < collectionInfoList.length; j++) {
            const collectionInfo = collectionInfoList[j];
            // FIXME: convert to number compulsorily temporarily.
            newLine[collectionInfo.indexInLine] = +lineCreator[collectionInfo.method](
                upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal
            );
        }
        // FIXME: refactor
        if (dimInfo.needGatherValues) {
            const val = upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream);
            dimInfo.gatherValue(groupByDimInfo, groupByVal, val);
        }
    }
    return newLine;
}


(
    upstream, dataIndex, targetLine: number[], collectionDimInfoList, groupByDimInfo, groupByVal
) => {
    for (let i = 0; i < collectionDimInfoList.length; i++) {
        const dimInfo = collectionDimInfoList[i];
        const collectionInfoList = dimInfo.collectionInfoList;
        for (let j = 0; j < collectionInfoList.length; j++) {
            const collectionInfo = collectionInfoList[j];
            const indexInLine = collectionInfo.indexInLine;
            // FIXME: convert to number compulsorily temporarily.
            targetLine[indexInLine] = +lineUpdater[collectionInfo.method](
                targetLine[indexInLine], upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal
            );
        }
        // FIXME: refactor
        if (dimInfo.needGatherValues) {
            const val = upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream);
            dimInfo.gatherValue(groupByDimInfo, groupByVal, val);
        }
    }
}


(
    upstream, dataIndex, finalResultDimInfoList, groupByDimInfo, groupByVal
) => {
    const newLine = [];
    for (let i = 0; i < finalResultDimInfoList.length; i++) {
        const dimInfo = finalResultDimInfoList[i];
        const method = dimInfo.method;
        newLine[i] = isGroupByDimension(groupByDimInfo, dimInfo)
            ? groupByVal
            : lineCreator[method](
                upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal
            );
    }
    return newLine;
}


(
    upstream, dataIndex, targetLine, finalResultDimInfoList, groupByDimInfo, groupByVal
) => {
    for (let i = 0; i < finalResultDimInfoList.length; i++) {
        const dimInfo = finalResultDimInfoList[i];
        if (isGroupByDimension(groupByDimInfo, dimInfo)) {
            continue;
        }
        const method = dimInfo.method;
        targetLine[i] = lineUpdater[method](
            targetLine[i], upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal
        );
    }
}


(a, b) => {
        return a - b;
    }


class ResultDimInfoInternal {

    readonly method: AggregateMethodInternal;
    readonly name: DimensionName;
    readonly index: number;
    readonly indexInUpstream: number;

    readonly collectionInfoList = [] as {
        method: AggregateMethodInternal;
        indexInLine: number;
    }[];

    // FIXME: refactor
    readonly gatheredValuesByGroup: { [groupVal: string]: number[] } = {};
    readonly gatheredValuesNoGroup = [] as number[];
    readonly needGatherValues: boolean = false;

    __collectionResult: TravelResult<CollectionResultLine>;

    private _collectionInfoMap = {} as {
        // number is the index of `list`
        [method in AggregateMethodInternal]: number
    };

    constructor(
        index: number,
        indexInUpstream: number,
        method: AggregateMethodInternal,
        name: DimensionName,
        needGatherValues: boolean
    ) {
        this.method = method;
        this.name = name;
        this.index = index;
        this.indexInUpstream = indexInUpstream;
        this.needGatherValues = needGatherValues;
    }

    addCollectionInfo(item: ResultDimInfoInternal['collectionInfoList'][number]) {
        this._collectionInfoMap[item.method] = this.collectionInfoList.length;
        this.collectionInfoList.push(item);
    }

    getCollectionInfo(method: AggregateMethodInternal) {
        return this.collectionInfoList[this._collectionInfoMap[method]];
    }

    // FIXME: temp implementation. Need refactor.
    gatherValue(groupByDimInfo: ExternalDimensionDefinition, groupVal: OptionDataValue, value: OptionDataValue) {
        // FIXME: convert to number compulsorily temporarily.
        value = +value;
        if (groupByDimInfo) {
            if (groupVal != null) {
                const groupValStr = groupVal + '';
                const values = this.gatheredValuesByGroup[groupValStr]
                    || (this.gatheredValuesByGroup[groupValStr] = []);
                values.push(value);
            }
        }
        else {
            this.gatheredValuesNoGroup.push(value);
        }
    }
}


/**
 * @usage
 *
 * ```js
 * dataset: [{
 *     source: [
 *         ['aa', 'bb', 'cc', 'tag'],
 *         [12, 0.33, 5200, 'AA'],
 *         [21, 0.65, 7100, 'AA'],
 *         [51, 0.15, 1100, 'BB'],
 *         [71, 0.75, 9100, 'BB'],
 *         ...
 *     ]
 * }, {
 *     transform: {
 *         type: 'ecSimpleTransform:aggregate',
 *         config: {
 *             resultDimensions: [
 *                 // by default, use the same name with `from`.
 *                 { from: 'aa', method: 'sum' },
 *                 { from: 'bb', method: 'count' },
 *                 { from: 'cc' }, // method by default: use the first value.
 *                 { from: 'dd', method: 'Q1' },
 *                 { from: 'tag' }
 *             ],
 *             groupBy: 'tag'
 *         }
 *     }
 *     // Then the result data will be:
 *     // [
 *     //     ['aa', 'bb', 'cc', 'tag'],
 *     //     [12, 0.33, 5200, 'AA'],
 *     //     [21, 0.65, 8100, 'BB'],
 *     //     ...
 *     // ]
 * }]
 * ```
 */
export interface AggregateTransformOption extends DataTransformOption {
    type: 'ecSimpleTransform:aggregate';
    config: {
        // Mandatory
        resultDimensions: {
            // Optional. The name of the result dimensions.
            // If not provided, inherit the name from `from`.
            name: DimensionName;
            // Mandatory. `from` is used to reference dimension from `source`.
            from: DimensionLoose;
            // Optional. Aggregate method. Currently only these method supported.
            // If not provided, use `'first'`.
            method: AggregateMethodLoose;
        }[];
        // Optional
        groupBy: DimensionLoose;
    };
}


interface TravelResult<LINE> {
    mapByGroup: { [groupVal: string]: LINE };
    outList: LINE[];
}


type AggregateMethodLoose =
    AggregateMethodInternal
    | 'sum' | 'count' | 'first' | 'average' | 'Q1' | 'Q2' | 'Q3' | 'median' | 'min' | 'max';


type AggregateMethodInternal = keyof typeof METHOD_INTERNAL;


type CreateInTravel<LINE> = (
    upstream: ExternalSource,
    dataIndex: number,
    dimInfoList: ResultDimInfoInternal[],
    groupByDimInfo?: ExternalDimensionDefinition,
    groupByVal?: OptionDataValue
) => LINE;


type UpdateInTravel<LINE> = (
    upstream: ExternalSource,
    dataIndex: number,
    targetLine: LINE,
    dimInfoList: ResultDimInfoInternal[],
    groupByDimInfo?: ExternalDimensionDefinition,
    groupByVal?: OptionDataValue
) => void;


type CollectionResultLine = number[];


type FinalResultLine = OptionDataValue[];


addCollectionInfo(item: ResultDimInfoInternal['collectionInfoList'][number]) {
        this._collectionInfoMap[item.method] = this.collectionInfoList.length;
        this.collectionInfoList.push(item);
    }


getCollectionInfo(method: AggregateMethodInternal) {
        return this.collectionInfoList[this._collectionInfoMap[method]];
    }


// FIXME: temp implementation. Need refactor.
gatherValue(groupByDimInfo: ExternalDimensionDefinition, groupVal: OptionDataValue, value: OptionDataValue) {
        // FIXME: convert to number compulsorily temporarily.
        value = +value;
        if (groupByDimInfo) {
            if (groupVal != null) {
                const groupValStr = groupVal + '';
                const values = this.gatheredValuesByGroup[groupValStr]
                    || (this.gatheredValuesByGroup[groupValStr] = []);
                values.push(value);
            }
        }
        else {
            this.gatheredValuesNoGroup.push(value);
        }
    }


'SUM'(upstream, dataIndex, dimInfo) {
        return upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream);
    }


'COUNT'() {
        return 1;
    }


'FIRST'(upstream, dataIndex, dimInfo) {
        return upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream);
    }


'MIN'(upstream, dataIndex, dimInfo) {
        return upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream);
    }


'MAX'(upstream, dataIndex, dimInfo) {
        return upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream);
    }


'AVERAGE'(upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) {
        // FIXME: refactor, bad implementation.
        const collectLine = groupByDimInfo
            ? dimInfo.__collectionResult.mapByGroup[groupByVal + '']
            : dimInfo.__collectionResult.outList[0];
        return (upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number)
            / collectLine[dimInfo.getCollectionInfo('COUNT').indexInLine];
    }


// FIXME: refactor
'Q1'(upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) {
        return lineCreatorForQ(0.25, dimInfo, groupByDimInfo, groupByVal);
    }


'Q2'(upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) {
        return lineCreatorForQ(0.5, dimInfo, groupByDimInfo, groupByVal);
    }


'Q3'(upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) {
        return lineCreatorForQ(0.75, dimInfo, groupByDimInfo, groupByVal);
    }


'SUM'(val, upstream, dataIndex, dimInfo) {
        // FIXME: handle other types
        return (val as number) + (upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number);
    }


'COUNT'(val) {
        return (val as number) + 1;
    }


'FIRST'(val) {
        return val;
    }


'MIN'(val, upstream, dataIndex, dimInfo) {
        return Math.min(val as number, upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number);
    }


'MAX'(val, upstream, dataIndex, dimInfo) {
        return Math.max(val as number, upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number);
    }


'AVERAGE'(val, upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) {
        // FIXME: refactor, bad implementation.
        const collectLine = groupByDimInfo
            ? dimInfo.__collectionResult.mapByGroup[groupByVal + '']
            : dimInfo.__collectionResult.outList[0];
        return (val as number)
            + (upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number)
            / collectLine[dimInfo.getCollectionInfo('COUNT').indexInLine];
    }


'Q1'(val, upstream, dataIndex, dimInfo) {
        return val;
    }


'Q2'(val, upstream, dataIndex, dimInfo) {
        return val;
    }


'Q3'(val, upstream, dataIndex, dimInfo) {
        return val;
    }


/**
 * Interface to handle a forEach loop in dictionaries
 */
export interface IForEachInterface<K, V> {
  /**
   * Callback function
   */
  (callback: { key: K; value: V }): void;
}


status => {
      this.alreadyOnWaitList = !!status;
    }


() => {
      this.disableAddCard = true;
    }


status => {
      this.isFetching = status;
    }


() => {
      this.ready = true;
      this.disableAddCard =
        this.bitpayCardItems &&
        this.bitpayCardItems.find(c => c.provider === 'galileo');
    }


c => c.provider === 'galileo'


() => {
      this.iabCardProvider.sendMessage(
        {
          message
        },
        () => {}
      );
    }


@Component({
  selector: 'bitpay-card-home',
  templateUrl: 'bitpay-card-home.html',
  animations: [
    trigger('fadeUp', [
      transition(':enter', [
        style({
          transform: 'translateY(5px)',
          opacity: 0
        }),
        animate('300ms')
      ])
    ]),
    trigger('fade', [
      transition(':enter', [
        style({
          opacity: 0
        }),
        animate('300ms')
      ])
    ]),
    trigger('tileSlideIn', [
      transition(':enter', [
        style({
          transform: 'translateX(10px)',
          opacity: 0
        }),
        animate('300ms ease')
      ])
    ])
  ]
})
export class BitPayCardHome implements OnInit {
  public appName: string;
  public disableAddCard = true;
  public isFetching: boolean;
  public ready: boolean;
  public alreadyOnWaitList: boolean;

  @Input() showBitpayCardGetStarted: boolean;
  @Input() bitpayCardItems: any;
  @Input() cardExperimentEnabled: boolean;
  @Input() waitList: boolean;
  @Input() hasCards: boolean;
  @Input() network: Network;
  @Input() initialized: boolean;

  constructor(
    private appProvider: AppProvider,
    private navCtrl: NavController,
    private iabCardProvider: IABCardProvider,
    private persistenceProvider: PersistenceProvider,
    private events: Events
  ) {
    this.persistenceProvider.getWaitingListStatus().then(status => {
      this.alreadyOnWaitList = !!status;
    });

    this.events.subscribe('reachedCardLimit', () => {
      this.disableAddCard = true;
    });
    this.events.subscribe('isFetchingDebitCards', status => {
      this.isFetching = status;
    });
  }

  ngOnInit() {
    this.appName = this.appProvider.info.userVisibleName;
    setTimeout(() => {
      this.ready = true;
      this.disableAddCard =
        this.bitpayCardItems &&
        this.bitpayCardItems.find(c => c.provider === 'galileo');
    }, 200);
  }

  public goToBitPayCardIntroPage() {
    this.navCtrl.push(this.waitList ? PhaseOneCardIntro : BitPayCardIntroPage);
  }

  public trackBy(index) {
    return index;
  }

  public async goToCard(cardId) {
    const token = await this.persistenceProvider.getBitPayIdPairingToken(
      this.network
    );
    const email = this.bitpayCardItems[0].email;

    const message = !token
      ? `loadDashboard?${cardId}&${email}`
      : `loadDashboard?${cardId}`;

    this.iabCardProvider.show();
    setTimeout(() => {
      this.iabCardProvider.sendMessage(
        {
          message
        },
        () => {}
      );
    });
  }
}


ngOnInit() {
    this.appName = this.appProvider.info.userVisibleName;
    setTimeout(() => {
      this.ready = true;
      this.disableAddCard =
        this.bitpayCardItems &&
        this.bitpayCardItems.find(c => c.provider === 'galileo');
    }, 200);
  }


public goToBitPayCardIntroPage() {
    this.navCtrl.push(this.waitList ? PhaseOneCardIntro : BitPayCardIntroPage);
  }


public trackBy(index) {
    return index;
  }


public async goToCard(cardId) {
    const token = await this.persistenceProvider.getBitPayIdPairingToken(
      this.network
    );
    const email = this.bitpayCardItems[0].email;

    const message = !token
      ? `loadDashboard?${cardId}&${email}`
      : `loadDashboard?${cardId}`;

    this.iabCardProvider.show();
    setTimeout(() => {
      this.iabCardProvider.sendMessage(
        {
          message
        },
        () => {}
      );
    });
  }


export function GetCodeActions(asmodule : scriptfiles.ASModule, range : Range, diagnostics : Array<Diagnostic>) : Array<CodeAction>
{
    let context = new CodeActionContext();
    context.module = asmodule;
    context.actions = new Array<CodeAction>();
    context.range_start = asmodule.getOffset(Position.create(range.start.line, 0));
    context.range_end = asmodule.getOffset(Position.create(range.end.line, 10000));
    context.scope = asmodule.getScopeAt(context.range_start);
    context.statement = asmodule.getStatementAt(context.range_start);
    context.diagnostics = diagnostics;

    // Actions for adding missing imports
    AddImportActions(context);

    // Actions for autos
    AddAutoActions(context);

    // Actions for members
    AddMemberActions(context);

    // Actions for generating delegate bind functions
    AddGenerateDelegateFunctionActions(context);

    // Actions for method override snippets
    AddMethodOverrideSnippets(context);

    // Actions for adding casts
    AddCastHelpers(context);

    // Actions for adding super calls
    AddSuperCallHelper(context);

    // Actions for promoting to member variables
    AddVariablePromotionHelper(context);

    // Actions for switch blocks
    AddSwitchCaseActions(context);

    return context.actions;
}


export function ResolveCodeAction(asmodule : scriptfiles.ASModule, action : CodeAction, data : any) : CodeAction
{
    if (data.type == "import")
        ResolveImportAction(asmodule, action, data);
    else if (data.type == "delegateBind")
        ResolveGenerateDelegateFunctionAction(asmodule, action, data);
    else if (data.type == "methodOverride")
        ResolveMethodOverrideSnippet(asmodule, action, data);
    else if (data.type == "addCast")
        ResolveCastHelper(asmodule, action, data);
    else if (data.type == "superCall")
        ResolveSuperCallHelper(asmodule, action, data);
    else if (data.type == "materializeAuto")
        ResolveAutoAction(asmodule, action, data);
    else if (data.type == "variablePromotion")
        ResolveVariablePromotionHelper(asmodule, action, data);
    else if (data.type == "insertMacro")
        ResolveInsertMacro(asmodule, action, data);
    else if (data.type == "insertCases")
        ResolveInsertCases(asmodule, action, data);
    return action;
}


function AddImportActions(context : CodeActionContext)
{
    for (let symbol of context.module.symbols)
    {
        if (!symbol.isUnimported)
            continue;
        if (!symbol.overlapsRange(context.range_start, context.range_end))
            continue;

        let appliedTo = new Array<Diagnostic>();
        for (let diag of context.diagnostics)
        {
            if (diag.data)
            {
                let data = diag.data as any;
                if (data.type && data.type == "import")
                {
                    if (data.symbol[0] == symbol.type
                        && data.symbol[1] == symbol.container_type
                        && data.symbol[2] == symbol.symbol_name)
                    {
                        appliedTo.push(diag);
                    }
                }
            }
        }

        let symbolDisplayName = symbol.symbol_name;
        if (symbolDisplayName.startsWith("__"))
            symbolDisplayName = symbolDisplayName.substr(2);

        context.actions.push(<CodeAction> {
            kind: CodeActionKind.QuickFix,
            title: "Import "+symbolDisplayName,
            source: "angelscript",
            diagnostics: appliedTo,
            isPreferred: true,
            data: {
                uri: context.module.uri,
                type: "import",
                symbol: symbol,
            }
        });
    }
}


function ResolveImportAction(asmodule : scriptfiles.ASModule, action : CodeAction, data : any)
{
    let definitions = scriptsymbols.GetSymbolDefinition(asmodule, data.symbol);
    if (!definitions || definitions.length == 0)
        return action;

    let moduleName = definitions[0].module.modulename;
    if (asmodule.isModuleImported(moduleName))
        return action;

    // Find the first line to insert on
    let lastImportLine = 0;

    // Find if the module is already imported, or the position to append the new import
    let lineCount = asmodule.textDocument.lineCount;
    let hasEmptyLine = false;
    let alreadyImported = false;
    let importRegex = /\s*import\s+([A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*);/;

    for(let i = 0; i < lineCount; ++i)
    {
        let line = asmodule.textDocument.getText(
            Range.create(Position.create(i, 0), Position.create(i, 10000))
        );

        let match = importRegex.exec(line);
        if (match)
        {
            if (match[1] == moduleName)
            {
                alreadyImported = true;
                break;
            }

            lastImportLine = i + 1;
            hasEmptyLine = false;
        }
        else if (line.trim().length != 0)
        {
            // Break if we find a line that's not empty, signalling the end of the import-block
            break;
        }
        else
        {
            hasEmptyLine = true;
        }
    }

    action.edit = <WorkspaceEdit> {};
    action.edit.changes = {};
    if (alreadyImported)
        return;

    let insertString = "import "+moduleName+";\n";
    if (!hasEmptyLine)
        insertString += "\n";

    action.edit.changes[asmodule.displayUri] = [
        TextEdit.insert(Position.create(lastImportLine, 0), insertString)
    ];
}


function AddGenerateDelegateFunctionActions(context : CodeActionContext)
{
    for (let diag of context.diagnostics)
    {
        let data = diag.data as any;
        if (data && data.type == "delegateBind")
        {
            context.actions.push(<CodeAction> {
                kind: CodeActionKind.QuickFix,
                title: "Generate Method: "+data.name+"()",
                source: "angelscript",
                diagnostics: [diag],
                isPreferred: true,
                data: {
                    uri: context.module.uri,
                    type: "delegateBind",
                    delegate: data.delegate,
                    name: data.name,
                    position: diag.range.start,
                }
            });
        }
    }
}


function ResolveGenerateDelegateFunctionAction(asmodule : scriptfiles.ASModule, action : CodeAction, data : any)
{
    let delegateType = typedb.GetType(data.delegate);
    if (!delegateType)
        return;

    let [insertPosition, indent, prefix, suffix] = FindInsertPositionForGeneratedMethod(asmodule, data.position);
    let snippet = prefix;
    snippet += indent+"UFUNCTION()\n";
    snippet += GenerateMethodHeaderString("private ", indent, data.name, delegateType.delegateReturn, delegateType.delegateArgs);
    snippet += "\n";
    snippet += indent+"{\n";
    snippet += indent+"}\n";
    snippet += suffix;

    action.edit = <WorkspaceEdit> {};
    action.edit.changes = {};
    action.edit.changes[asmodule.displayUri] = [
        TextEdit.insert(insertPosition, snippet)
    ];
}


function GenerateMethodHeaderString(prefix : string, indent : string, name : string, returnType : string, args : Array<typedb.DBArg>) : string
{
    let snippet = indent+prefix;
    let preambleLength = name.length + 2 + prefix.length;
    if (returnType)
    {
        snippet += returnType;
        preambleLength += returnType.length;
    }
    else
    {
        snippet += "void";
        preambleLength += 4;
    }

    snippet += " ";
    snippet += name;
    snippet += "(";

    let lineLength = preambleLength + indent.length;
    if (args)
    {
        for (let i = 0; i < args.length; ++i)
        {
            let arg = args[i];
            let argLength = arg.typename.length;
            if (arg.name)
                argLength += arg.name.length + 1;

            if (lineLength + argLength > 100)
            {
                if (i != 0)
                {
                    snippet += ",";
                    lineLength += 1;
                }
                snippet += "\n"+indent+" ".repeat(preambleLength);
                lineLength = indent.length + preambleLength;
            }
            else if (i != 0)
            {
                snippet += ", ";
                lineLength += 2;
            }

            snippet += arg.typename;
            if (arg.name)
            {
                snippet += " ";
                snippet += arg.name;
            }

            lineLength += argLength;
        }
    }

    snippet += ")";
    return snippet;
}


function FindInsertPositionForGeneratedMethod(asmodule : scriptfiles.ASModule, afterPosition : Position) : [Position, string, string, string]
{
    let offset = asmodule.getOffset(afterPosition);
    let curScope = asmodule.getScopeAt(offset);

    let classScope = curScope.getParentTypeScope();
    let indent : string = null;
    let prefix : string = "";
    let suffix : string = "";

    // Just insert right here
    if (!classScope)
        return [Position.create(afterPosition.line, 0), "\t", prefix, suffix];

    // Find the first line in the class that has content, and base indentation on that
    for (let statement of classScope.statements)
    {
        let lines = statement.content.split("\n");
        for (let line of lines)
        {
            if (!/^[ \t\r\n]*$/.test(line))
            {
                indent = "";
                for (let i = 0; i < line.length; ++i)
                {
                    let curchar = line[i];
                    if (curchar == ' ' || curchar == '\t')
                    {
                        indent += curchar;
                    }
                    else if (curchar == '\n' || curchar == '\r')
                    {
                        continue;
                    }
                    else if (curchar == '#')
                    {
                        indent = null;
                        break;
                    }
                    else
                    {
                        break;
                    }
                }
                break;
            }
        }
        if (indent)
            break;
    }
    if (!indent)
        indent = "\t";

    // Find the first scope in our parent that starts after the position, and insert before it
    let classStartPos = asmodule.getPosition(classScope.start_offset);

    for (let subscope of classScope.scopes)
    {
        let startOffset = subscope.start_offset;
        while (startOffset < subscope.end_offset)
        {
            let curchar = asmodule.content[startOffset];
            if (curchar == ' ' || curchar == '\t' || curchar == '\r' || curchar == '\n')
                ++startOffset;
            else
                break;
        }

        let scopeStartPos = asmodule.getPosition(startOffset);
        let checkStartPos = scopeStartPos;
        if (subscope.element_head instanceof scriptfiles.ASStatement)
            checkStartPos = asmodule.getPosition(subscope.element_head.start_offset);
        else if (!subscope.element_head)
            checkStartPos = asmodule.getPosition(subscope.end_offset);

        if (checkStartPos.line >= afterPosition.line)
        {
            prefix += "\n";
            return [Position.create(scopeStartPos.line-1, 10000), indent, prefix, suffix];
        }
    }

    let endOfClass = asmodule.getPosition(classScope.end_offset);
    if (!asmodule.isLineEmpty(endOfClass.line-1))
        prefix += "\n";
    return [endOfClass, indent, prefix, suffix];
}


function AddMethodOverrideSnippets(context : CodeActionContext)
{
    if (!context.scope)
        return;

    let typeOfScope = context.scope.getParentType();
    if (!typeOfScope || !typeOfScope.supertype)
        return;

    let validScope = false;
    if (context.scope.scopetype == scriptfiles.ASScopeType.Class)
    {
        validScope = true;
    }
    // If we're inside the actual function declaration that's fine too
    else if (context.scope.scopetype == scriptfiles.ASScopeType.Function)
    {
        if (context.statement && context.statement.ast && context.statement.ast.type == scriptfiles.node_types.FunctionDecl)
        {
            validScope = true;
        }
    }
    if (!validScope)
        return;

    let foundOverrides = new Set<string>();
    for (let checktype of typeOfScope.getInheritanceTypes())
    {
        for (let method of checktype.methods)
        {
            if (checktype.isUnrealType() && !method.isEvent)
                continue;
            if (foundOverrides.has(method.name))
                continue;

            // Ignore methods we've already overridden
            let existingSymbol = typeOfScope.findFirstSymbol(method.name, typedb.DBAllowSymbol.FunctionOnly);
            if (!existingSymbol || !existingSymbol.containingType)
                continue;
            if (existingSymbol.containingType == typeOfScope)
                continue;

            // Ignore private methods
            if (method.isPrivate)
                continue;

            foundOverrides.add(method.name);
            if (method.isFinal)
                continue;

            context.actions.push(<CodeAction> {
                kind: CodeActionKind.RefactorRewrite,
                title: "Override: "+method.name+"()",
                source: "angelscript",
                data: {
                    uri: context.module.uri,
                    type: "methodOverride",
                    inside: method.containingType.typename,
                    name: method.name,
                    position: context.module.getPosition(context.range_start),
                }
            });
        }
    }
}


function ResolveMethodOverrideSnippet(asmodule : scriptfiles.ASModule, action : CodeAction, data : any)
{
    let insideType = typedb.GetType(data.inside);
    if (!insideType)
        return;

    let method = insideType.getMethod(data.name);
    if (!method)
        return;

    let offset = asmodule.getOffset(data.position);
    let scope = asmodule.getScopeAt(offset);
    let scopeType = scope ? scope.getParentType() : null;

    let [insertPosition, indent, prefix, suffix] = FindInsertPositionForGeneratedMethod(asmodule, data.position);
    let snippet = "";
    snippet += prefix;

    if (method.isEvent)
        snippet += indent+"UFUNCTION(BlueprintOverride)\n";

    snippet += GenerateMethodHeaderString("", indent, data.name, method.returnType, method.args);
    if (method.isConst)
        snippet += " const"
    if (!method.isEvent)
        snippet += " override";
    if (!method.isEvent && method.isProperty && method.declaredModule)
        snippet += " property";

    snippet += "\n";
    snippet += indent+"{\n";

    if (scopeType)
    {
        let parentType = typedb.GetType(scopeType.supertype);
        if (parentType)
        {
            let parentMethod = parentType.findFirstSymbol(method.name, typedb.DBAllowSymbol.FunctionOnly);
            if (parentMethod instanceof typedb.DBMethod && parentMethod.declaredModule && !parentMethod.isEmpty)
            {
                if (!method.returnType || method.returnType == "void")
                {
                    snippet += indent+indent+"Super::"+method.name+"(";
                    for (let i = 0; i < method.args.length; ++i)
                    {
                        if (i != 0)
                            snippet += ", ";
                        snippet += method.args[i].name;
                    }
                    snippet += ");\n";
                }
            }
        }
    }

    snippet += indent+"}\n";
    snippet += suffix;

    action.edit = <WorkspaceEdit> {};
    action.edit.changes = {};
    action.edit.changes[asmodule.displayUri] = [
        TextEdit.insert(insertPosition, snippet)
    ];
}


function GetTypeFromExpressionIgnoreNullptr(scope : scriptfiles.ASScope, node : any) : typedb.DBType
{
    if (node && node.type == scriptfiles.node_types.ConstNullptr)
        return null;
    return scriptfiles.ResolveTypeFromExpression(scope, node);
}


function AddCastHelpers(context : CodeActionContext)
{
    if (!context.scope)
        return;
    if (!context.statement)
        return;
    if (!context.statement.ast)
        return;

    let statement = context.statement;
    let scope = context.scope;

    let leftType : typedb.DBType = null;
    let rightType : typedb.DBType = null;

    if (statement.ast.type == scriptfiles.node_types.Assignment)
    {
        let leftNode = statement.ast.children[0];
        let rightNode = statement.ast.children[1];
        if (!leftNode || !rightNode)
            return;

        leftType = scriptfiles.ResolveTypeFromExpression(scope, leftNode);
        rightType = GetTypeFromExpressionIgnoreNullptr(scope, rightNode);
    }
    else if (statement.ast.type == scriptfiles.node_types.VariableDecl)
    {
        if (statement.ast.typename)
            leftType = typedb.GetType(statement.ast.typename.value);

        if (statement.ast.expression)
            rightType = GetTypeFromExpressionIgnoreNullptr(scope, statement.ast.expression);
    }
    else if (statement.ast.type == scriptfiles.node_types.ReturnStatement)
    {
        let dbFunc = scope.getDatabaseFunction();
        if (dbFunc && dbFunc.returnType)
            leftType = typedb.GetType(dbFunc.returnType);

        if (statement.ast.children && statement.ast.children[0])
            rightType = GetTypeFromExpressionIgnoreNullptr(scope, statement.ast.children[0]);
    }

    if (!leftType || !rightType)
        return;

    // Don't care about primitives
    if (leftType.isPrimitive || rightType.isPrimitive)
        return;

    // Don't care about structs
    if (leftType.isStruct || rightType.isStruct)
        return;

    // Maybe we can implicitly convert
    if (rightType.inheritsFrom(leftType.typename))
        return;

    // Cast needs to make sense
    if (!leftType.inheritsFrom(rightType.typename))
        return;

    context.actions.push(<CodeAction> {
        kind: CodeActionKind.QuickFix,
        title: "Cast to "+leftType.typename,
        source: "angelscript",
        data: {
            uri: context.module.uri,
            type: "addCast",
            castTo: leftType.typename,
            position: context.module.getPosition(context.range_start),
        }
    });
}


function ResolveCastHelper(asmodule : scriptfiles.ASModule, action : CodeAction, data : any)
{
    let offset = asmodule.getOffset(data.position);
    let scope = asmodule.getScopeAt(offset);
    if (!scope)
        return;
    let statement = asmodule.getStatementAt(offset);
    if (!statement)
        return;
    if (!statement.ast)
        return;

    let rightNode : any = null;
    if (statement.ast.type == scriptfiles.node_types.Assignment)
    {
        rightNode = statement.ast.children[1];
    }
    else if (statement.ast.type == scriptfiles.node_types.VariableDecl)
    {
        rightNode = statement.ast.expression;
    }
    else if (statement.ast.type == scriptfiles.node_types.ReturnStatement)
    {
        if (statement.ast.children && statement.ast.children[0])
            rightNode = statement.ast.children[0]
    }

    if (!rightNode)
        return;

    action.edit = <WorkspaceEdit> {};
    action.edit.changes = {};
    action.edit.changes[asmodule.displayUri] = [
        TextEdit.insert(
            asmodule.getPosition(statement.start_offset + rightNode.start),
            "Cast<"+data.castTo+">("),
        TextEdit.insert(
            asmodule.getPosition(statement.start_offset + rightNode.end),
            ")"),
    ];
}


function AddSuperCallHelper(context : CodeActionContext)
{
    for (let diag of context.diagnostics)
    {
        let data = diag.data as any;
        if (data && data.type == "superCall")
        {
            context.actions.push(<CodeAction> {
                kind: CodeActionKind.QuickFix,
                title: "Add call to Super::"+data.name+"(...)",
                source: "angelscript",
                diagnostics: [diag],
                isPreferred: true,
                data: {
                    uri: context.module.uri,
                    type: "superCall",
                    name: data.name,
                    inType: data.inType,
                    position: context.module.getPosition(context.range_end),
                }
            });
        }
    }
}


function ResolveSuperCallHelper(asmodule : scriptfiles.ASModule, action : CodeAction, data : any)
{
    let offset = asmodule.getOffset(data.position);
    let scope = asmodule.getScopeAt(offset)
    if (!scope)
        return;

    let scopeFunc = scope.getParentFunction();
    if (!scopeFunc)
        return;

    let superType = typedb.GetType(data.inType);
    if (!superType)
        return;
    let superMethod = superType.findFirstSymbol(data.name, typedb.DBAllowSymbol.FunctionOnly);
    if (!superMethod)
        return;
    if (!(superMethod instanceof typedb.DBMethod))
        return;

    let [insertPosition, indent, prefix, suffix] = FindInsertPositionFunctionStart(scope);

    let callString = prefix+indent+"Super::"+superMethod.name+"(";
    if (scopeFunc.args)
    {
        for (let i = 0; i < scopeFunc.args.length; ++i)
        {
            if (i != 0)
                callString += ", ";
            callString += scopeFunc.args[i].name;
        }
    }

    callString += ");"+suffix;

    action.edit = <WorkspaceEdit> {};
    action.edit.changes = {};
    action.edit.changes[asmodule.displayUri] = [
        TextEdit.insert(insertPosition, callString)
    ];
}


function FindInsertPositionFunctionStart(scope : scriptfiles.ASScope) : [Position, string, string, string]
{
    let indent : string = null;
    let prefix : string = "";
    let suffix : string = "";

    // Find the first line in the class that has content, and base indentation on that
    let endLine = scope.module.getPosition(scope.end_offset).line;
    let curLine = scope.module.getPosition(scope.declaration.end_offset).line + 1;
    while (curLine < endLine)
    {
        let lineText = scope.module.getLineText(curLine);
        if (!/^[\r\n]*$/.test(lineText))
        {
            indent = "";
            for (let i = 0; i < lineText.length; ++i)
            {
                let curchar = lineText[i];
                if (curchar == ' ' || curchar == '\t')
                {
                    indent += curchar;
                }
                else if (curchar == '\n' || curchar == '\r')
                {
                    continue;
                }
                else if (curchar == '#')
                {
                    indent = null;
                    break;
                }
                else
                {
                    break;
                }
            }
            if (indent != null)
                break;
        }
    }

    if(indent == null)
    {
        // Double the class indent
        let [subPos, subIndent, subPrefix, subSuffix] = FindInsertPositionForGeneratedMethod(
            scope.module,
            scope.module.getPosition(scope.declaration.end_offset));
        indent = subIndent + subIndent;
    }

    if (indent == null)
        indent = "\t\t";

    let headPos = scope.module.getPosition(scope.declaration.end_offset);
    prefix += "\n";
    return [Position.create(headPos.line, 100000), indent, prefix, suffix];
}


function AddAutoActions(context : CodeActionContext)
{
    for (let symbol of context.module.symbols)
    {
        if (!symbol.isAuto)
            continue;
        if (!symbol.overlapsRange(context.range_start, context.range_end))
            continue;

        let realTypename = symbol.symbol_name;
        if (realTypename.startsWith("__"))
            realTypename = realTypename.substr(2);

        let dbtype = typedb.GetType(realTypename);
        if (!dbtype)
            continue;

        context.actions.push(<CodeAction> {
            kind: CodeActionKind.RefactorInline,
            title: "Change auto to "+dbtype.getDisplayName(),
            source: "angelscript",
            isPreferred: true,
            data: {
                uri: context.module.uri,
                type: "materializeAuto",
                typename: realTypename,
                symbol: symbol,
            }
        });
    }
}


function ResolveAutoAction(asmodule : scriptfiles.ASModule, action : CodeAction, data : any)
{
    let symbol = data.symbol as scriptfiles.ASSymbol;
    let typename = data.typename;

    action.edit = <WorkspaceEdit> {};
    action.edit.changes = {};

    action.edit.changes[asmodule.displayUri] = [
        TextEdit.replace(
            asmodule.getRange(symbol.start, symbol.end),
            typename,
        )
    ];
}


function AddVariablePromotionHelper(context : CodeActionContext)
{
    if (!context.scope)
        return;
    if (!context.statement)
        return;

    let codeNode = context.statement.ast;
    if (!codeNode)
        return;

    if (codeNode.type == scriptfiles.node_types.Assignment)
    {
        let leftNode = codeNode.children[0];
        if (!leftNode || leftNode.type != scriptfiles.node_types.Identifier)
            return;

        // If the left side is a known variable we can't provide this action
        let lvalueType = scriptfiles.ResolveTypeFromExpression(context.scope, leftNode);
        if (lvalueType)
            return;

        // If we don't know what type is on the right we can't provide this action
        let rvalueType = scriptfiles.ResolveTypeFromExpression(context.scope, codeNode.children[1]);
        if(!rvalueType)
            return;

        let variableName = leftNode.value;

        context.actions.push(<CodeAction> {
            kind: CodeActionKind.RefactorInline,
            title: `Promote ${variableName} to member variable`,
            source: "angelscript",
            isPreferred: true,
            data: {
                uri: context.module.uri,
                type: "variablePromotion",
                variableName: variableName,
                variableType: rvalueType.typename,
                position: context.range_start,
            }
        });
    }
}


function FindInsertPositionForGeneratedMemberVariable(asmodule : scriptfiles.ASModule, classScope : scriptfiles.ASScope, anchor_offset : number = -1) : [Position, string]
{
    let indent : string = null;
    let prefix : string = "";
    let suffix : string = "";

    // Find the first line in the class that has content, and base indentation on that
    for (let statement of classScope.statements)
    {
        let lines = statement.content.split("\n");
        for (let line of lines)
        {
            if (!/^[ \t\r\n]*$/.test(line))
            {
                indent = "";
                for (let i = 0; i < line.length; ++i)
                {
                    let curchar = line[i];
                    if (curchar == ' ' || curchar == '\t')
                    {
                        indent += curchar;
                    }
                    else if (curchar == '\n' || curchar == '\r')
                    {
                        continue;
                    }
                    else if (curchar == '#')
                    {
                        indent = null;
                        break;
                    }
                    else
                    {
                        break;
                    }
                }
                break;
            }
        }
        if (indent)
            break;
    }
    if (!indent)
        indent = "\t";

    // Check if we have a member variable that anchors this one
    let anchoredVariable : string = null;
    if (anchor_offset != -1)
    {
        let subScope = classScope.getScopeAt(anchor_offset);
        if (subScope)
        {
            for (let statement of subScope.statements)
            {
                if (!statement)
                    continue;
                if (!statement.ast)
                    continue;
                if (statement.end_offset >= anchor_offset)
                    continue;

                if (statement.ast.type == scriptfiles.node_types.Assignment)
                {
                    if (statement.ast.children[0] && statement.ast.children[0].type == scriptfiles.node_types.Identifier)
                    {
                        let varName = statement.ast.children[0].value;
                        if (classScope.variablesByName.has(varName))
                            anchoredVariable = varName;
                    }
                }
            }
        }
    }

    // Collect data about where stuff is in the class
    let lastDefaultLineOffset = -1;
    let lastMemberVariableOffset = -1;
    let anchoredMemberVariableOffset = -1;

    for (let statement of classScope.statements)
    {
        if (!statement.ast)
            continue;

        if (statement.ast.type == scriptfiles.node_types.VariableDecl)
        {
            if (anchoredVariable && statement.ast.name && statement.ast.name.value == anchoredVariable)
                anchoredMemberVariableOffset = statement.end_offset;
            if (statement.end_offset > lastMemberVariableOffset)
                lastMemberVariableOffset = statement.end_offset;
        }
        else if (statement.ast.type == scriptfiles.node_types.DefaultStatement)
        {
            if (statement.end_offset > lastDefaultLineOffset)
                lastDefaultLineOffset = statement.end_offset;
        }
    }

    let afterPos : Position = null;
    if (anchoredMemberVariableOffset != -1)
    {
        // Insert after the member variable that was most recently assigned
        afterPos = asmodule.getPosition(anchoredMemberVariableOffset);
    }
    else if (lastMemberVariableOffset != -1)
    {
        // Insert after the last member variable declaration
        afterPos = asmodule.getPosition(lastMemberVariableOffset);
    }
    else if (lastDefaultLineOffset != -1)
    {
        // Insert after the last default statement
        afterPos = asmodule.getPosition(lastDefaultLineOffset);
    }
    else
    {
        // Insert at the top of the class
        afterPos = asmodule.getPosition(classScope.declaration.end_offset);
    }

    afterPos.line += 1;
    afterPos.character = 0;
    return [afterPos, indent];
}


function ResolveVariablePromotionHelper(asmodule : scriptfiles.ASModule, action : CodeAction, data : any)
{
    let variableName : string = data.variableName;
    let variableType : string = data.variableType;
    let position : number = data.position;

    let scope = asmodule.getScopeAt(position);
    if (!scope)
        return;

    let classScope = scope.getParentTypeScope();
    if (!classScope)
        return;

    let [insertPosition, indent] = FindInsertPositionForGeneratedMemberVariable(asmodule, classScope, position);

    action.edit = <WorkspaceEdit> {};
    action.edit.changes = {};

    let declarationString = `${indent}${variableType} ${variableName};\n`;

    action.edit.changes[asmodule.displayUri] = [
        TextEdit.insert(insertPosition, declarationString)
    ];
}


function AddMemberActions(context : CodeActionContext)
{
    if (!context.scope)
        return;
    if (!context.statement)
        return;
    if (!context.statement.ast)
        return;

    let dbType = context.scope.getParentType();
    if (context.statement.ast.type == scriptfiles.node_types.FunctionDecl
        && context.statement.ast.name
        && !context.statement.ast.macro)
    {
        if (!dbType || dbType.isNamespaceOrGlobalScope() || !dbType.isStruct)
        {
            let isOverrideEvent = false;
            if (dbType)
            {
                let superType = typedb.GetType(dbType.supertype);
                if (superType)
                {
                    let superFunc = superType.findFirstSymbol(context.statement.ast.name.value, typedb.DBAllowSymbol.FunctionOnly);
                    if (superFunc && superFunc instanceof typedb.DBMethod)
                    {
                        if (superFunc.isEvent)
                            isOverrideEvent = true;
                    }
                }
            }

            if (isOverrideEvent)
            {
                context.actions.push(<CodeAction> {
                    kind: CodeActionKind.RefactorInline,
                    title: `Add UFUNCTION(BlueprintOverride)`,
                    source: "angelscript",
                    data: {
                        uri: context.module.uri,
                        type: "insertMacro",
                        macro: "UFUNCTION(BlueprintOverride)",
                        position: context.range_start,
                    }
                });
            }
            else
            {
                if (dbType)
                {
                    let scopeFunc = dbType.getMethod(context.statement.ast.name.value);
                    if (scopeFunc
                        && scopeFunc.isConst
                        && scopeFunc.returnType
                        && scopeFunc.returnType != "void")
                    {
                        context.actions.push(<CodeAction> {
                            kind: CodeActionKind.RefactorInline,
                            title: `Add UFUNCTION(BlueprintPure)`,
                            source: "angelscript",
                            data: {
                                uri: context.module.uri,
                                type: "insertMacro",
                                macro: "UFUNCTION(BlueprintPure)",
                                position: context.range_start,
                            }
                        });
                    }
                }

                context.actions.push(<CodeAction> {
                    kind: CodeActionKind.RefactorInline,
                    title: `Add UFUNCTION()`,
                    source: "angelscript",
                    data: {
                        uri: context.module.uri,
                        type: "insertMacro",
                        macro: "UFUNCTION()",
                        position: context.range_start,
                    }
                });
            }
        }
    }
    else if (context.statement.ast.type == scriptfiles.node_types.VariableDecl
        && context.statement.ast.name
        && !context.statement.ast.macro)
    {
        if (dbType && !dbType.isNamespaceOrGlobalScope()
            && context.scope.scopetype == scriptfiles.ASScopeType.Class)
        {
            let variableName = context.statement.ast.name.value;
            let varType = typedb.GetType(context.statement.ast.typename.value);

            if (varType && varType.inheritsFrom("UActorComponent")
                && !dbType.isStruct
                && dbType.inheritsFrom("AActor"))
            {
                context.actions.push(<CodeAction> {
                    kind: CodeActionKind.RefactorInline,
                    title: `Add UPROPERTY(DefaultComponent)`,
                    source: "angelscript",
                    data: {
                        uri: context.module.uri,
                        type: "insertMacro",
                        macro: "UPROPERTY(DefaultComponent)",
                        position: context.range_start,
                    }
                });
            }

            context.actions.push(<CodeAction> {
                kind: CodeActionKind.RefactorInline,
                title: `Add UPROPERTY()`,
                source: "angelscript",
                data: {
                    uri: context.module.uri,
                    type: "insertMacro",
                    macro: "UPROPERTY()",
                    position: context.range_start,
                }
            });
        }
    }
}


function ResolveInsertMacro(asmodule : scriptfiles.ASModule, action : CodeAction, data : any)
{
    let position : number = data.position;
    let macro : string = data.macro;

    let statement = asmodule.getStatementAt(position);
    if (!statement)
        return;
    if (!statement.ast)
        return;

    let scope = asmodule.getScopeAt(position);
    if (!scope)
        return;

    let classScope = scope.getParentTypeScope();
    if (!classScope)
        return;

    let [_, indent] = FindInsertPositionForGeneratedMemberVariable(asmodule, classScope, position);

    action.edit = <WorkspaceEdit> {};
    action.edit.changes = {};

    let macroString = `${indent}${macro}\n`;

    let insertPosition : Position = null;
    if (statement.ast.name)
    {
        insertPosition = asmodule.getPosition(statement.start_offset + statement.ast.name.start);
        insertPosition.character = 0;
    }
    else
    {
        insertPosition = asmodule.getPosition(position);
        insertPosition.character = 0;
    }

    action.edit.changes[asmodule.displayUri] = [
        TextEdit.insert(insertPosition, macroString)
    ];
}


function AddSwitchCaseActions(context : CodeActionContext)
{
    if (!context.scope)
        return;

    // Ensure that this is a switch scope
    let switchBlock : scriptfiles.ASScope = null;
    let switchStatement : scriptfiles.ASStatement = null;

    if (context.statement && context.statement.ast && context.statement.ast.type == scriptfiles.node_types.SwitchStatement)
    {
        if (context.statement.next && context.statement.next instanceof scriptfiles.ASScope)
        {
            switchStatement = context.statement;
            switchBlock = context.statement.next;
        }
    }
    else if (context.scope.previous && context.scope.previous instanceof scriptfiles.ASStatement)
    {
        if (context.scope.previous.ast && context.scope.previous.ast.type == scriptfiles.node_types.SwitchStatement)
        {
            switchStatement = context.scope.previous;
            switchBlock = context.scope;
        }
    }

    if (!switchBlock)
        return;
    if (!switchBlock.parentscope)
        return;

    // Figure out what type we're switching on
    let switchOnType = scriptfiles.ResolveTypeFromExpression(switchBlock.parentscope, switchStatement.ast.children[0]);
    if (!switchOnType)
        return;
    if (!switchOnType.isEnum)
        return;

    // Find all cases that are implemented
    let implementedCases = new Array<string>();
    let defaultStatement : scriptfiles.ASStatement = null;
    for (let caseStatement of switchBlock.statements)
    {
        if (!caseStatement || !caseStatement.ast)
            continue;

        if (caseStatement.ast.type == scriptfiles.node_types.DefaultCaseStatement)
        {
            defaultStatement = caseStatement;
            continue;
        }
        else if (caseStatement.ast.type != scriptfiles.node_types.CaseStatement)
            continue;

        let labelNode = caseStatement.ast.children[0];
        if (!labelNode || labelNode.type != scriptfiles.node_types.NamespaceAccess)
            continue;

        if (!labelNode.children[0] || !labelNode.children[0].value)
            continue;
        if (!labelNode.children[1] || !labelNode.children[1].value)
            continue;

        let label = labelNode.children[0].value + "::" + labelNode.children[1].value;
        implementedCases.push(label);
    }

    // Check if there are any missing cases left
    let missingCases = new Array<string>();
    for (let prop of switchOnType.properties)
    {
        if (prop.name == "MAX")
            continue;
        if (prop.name.endsWith("_MAX"))
            continue;

        let label = switchOnType.getDisplayName()+"::"+prop.name;
        if (!implementedCases.includes(label))
            missingCases.push(label);
    }

    // Add code action for adding all missing cases
    if (missingCases.length >= 2)
    {
        context.actions.push(<CodeAction> {
            kind: CodeActionKind.RefactorInline,
            title: `Add all missing ${switchOnType.getDisplayName()} cases`,
            source: "angelscript",
            data: {
                uri: context.module.uri,
                type: "insertCases",
                cases: missingCases,
                switchPosition: switchStatement.start_offset,
                position: context.scope.start_offset,
                defaultCasePosition: defaultStatement ? defaultStatement.start_offset : -1,
            }
        });
    }

    // Add code action for adding individual missing cases
    for (let label of missingCases)
    {
        context.actions.push(<CodeAction> {
            kind: CodeActionKind.RefactorInline,
            title: `Add case ${label}`,
            source: "angelscript",
            data: {
                uri: context.module.uri,
                type: "insertCases",
                cases: [label],
                switchPosition: switchStatement.start_offset,
                position: context.scope.start_offset,
                defaultCasePosition: defaultStatement ? defaultStatement.start_offset : -1,
            }
        });
    }
}


function ResolveInsertCases(asmodule : scriptfiles.ASModule, action : CodeAction, data : any)
{
    let switchStatement = asmodule.getStatementAt(data.switchPosition);
    if (!switchStatement)
        return;
    if (!switchStatement.next)
        return;
    if (!(switchStatement.next instanceof scriptfiles.ASScope))
        return;

    let switchScope : scriptfiles.ASScope = switchStatement.next;
    if (!switchScope)
        return;

    action.edit = <WorkspaceEdit> {};
    action.edit.changes = {};

    let switchIndent = GetIndentForStatement(switchStatement);
    let indent = GetIndentForBlock(switchScope);
    if (!indent)
        indent = "";

    let insertString = "";
    let cases : Array<string> = data.cases;
    for (let label of cases)
        insertString += `${indent}case ${label}:\n${indent}break;\n`;

    let insertPosition : Position = null;
    if (data.defaultCasePosition != -1)
    {
        insertPosition = asmodule.getPosition(data.defaultCasePosition);
        insertString = "\n" + insertString.substring(0, insertString.length-1);
    }
    else
    {
        let lastElement = switchScope.element_head;
        while (lastElement && lastElement.next)
            lastElement = lastElement.next;

        while (lastElement)
        {
            if (lastElement instanceof scriptfiles.ASScope)
            {
                insertPosition = asmodule.getPosition(lastElement.end_offset);
                insertPosition.line += 1;
                insertPosition.character = 0;
                break;
            }
            else if (lastElement instanceof scriptfiles.ASStatement)
            {
                if (lastElement && lastElement.ast)
                {
                    insertPosition = asmodule.getPosition(lastElement.end_offset);
                    insertPosition.line += 1;
                    insertPosition.character = 0;
                    break;
                }
            }

            lastElement = lastElement.previous;
        }

        if (!insertPosition)
        {
            let scopeStart = asmodule.getPosition(switchScope.start_offset);
            let scopeEnd = asmodule.getPosition(switchScope.end_offset);

            if (scopeEnd.line == scopeStart.line)
            {
                insertPosition = asmodule.getPosition(switchScope.start_offset);
                insertString = "\n" + insertString + switchIndent;
            }
            else if (scopeEnd.line == scopeStart.line + 1)
            {
                insertPosition = asmodule.getPosition(switchScope.start_offset);
                insertString = "\n" + insertString.trimEnd();
            }
            else
            {
                insertPosition = asmodule.getPosition(switchScope.end_offset);
                insertPosition.line -= 1;
                insertPosition.character = 10000;
                insertString = insertString.substring(0, insertString.length-1).trimStart();
            }
        }
    }

    action.edit.changes[asmodule.displayUri] = [
        TextEdit.insert(insertPosition, insertString)
    ];
}


function GetIndentForStatement(statement : scriptfiles.ASStatement) : string
{
    let indent : string = null;
    let lines = statement.content.split("\n");
    for (let line of lines)
    {
        if (!/^[ \t\r\n]*$/.test(line))
        {
            indent = "";
            for (let i = 0; i < line.length; ++i)
            {
                let curchar = line[i];
                if (curchar == ' ' || curchar == '\t')
                {
                    indent += curchar;
                }
                else if (curchar == '\n' || curchar == '\r')
                {
                    continue;
                }
                else if (curchar == '#')
                {
                    indent = null;
                    break;
                }
                else
                {
                    break;
                }
            }
            break;
        }
    }
    return indent;
}


function GetIndentForBlock(scope : scriptfiles.ASScope) : string
{
    if (scope.statements.length != 0 && scope.statements[0])
    {
        for (let statement of scope.statements)
        {
            if (!statement)
                continue;
            let statementIndent = GetIndentForStatement(statement);
            if (statementIndent != null)
                return statementIndent;
        }
    }
    else
    {
        if (scope.previous && scope.previous instanceof scriptfiles.ASStatement)
        {
            let statementIndent = GetIndentForStatement(scope.previous);
            if (statementIndent != null)
            {
                return ExtendIndent(statementIndent);
            }
        }
    }

    return null;
}


function ExtendIndent(indent : string) : string
{
    if (!indent || indent.length == 0)
        return "\t";
    if (indent.includes("\t"))
        return indent + "\t";
    else
        return indent + "    ";
}


class CodeActionContext
{
    range_start : number;
    range_end : number;

    module : scriptfiles.ASModule;
    scope : scriptfiles.ASScope;
    statement : scriptfiles.ASStatement;

    diagnostics : Array<Diagnostic>;
    actions : Array<CodeAction>;
}


export function isLocationLink(thing: any): thing is LocationLink {
	return thing
		&& URI.isUri((thing as LocationLink).uri)
		&& Range.isIRange((thing as LocationLink).range)
		&& (Range.isIRange((thing as LocationLink).originSelectionRange) || Range.isIRange((thing as LocationLink).targetSelectionRange));
}


/**
 * The hover provider interface defines the contract between extensions and
 * the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature.
 */
export interface HoverProvider {
	/**
	 * Provide a hover for the given position and document. Multiple hovers at the same
	 * position will be merged by the editor. A hover can have a range which defaults
	 * to the word range at the position when omitted.
	 */
	provideHover(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<Hover>;
}