import { FilesChangeNotifierService, IHashesCache } from "./files_change_notifier_service";
import { IPipeline, Trigger } from "../models/process_model";
import { ExecutorProgramService } from "./executor_program_service";
import { EXEC_EVENT, ExecError, SpawnError } from "../models/exec_error_model";
import { TypedEvent } from "../helpers/typed_event";
import { Result } from "../helpers/result";
import { ExecutorResult } from "../models/executor_result";
import { delay } from "../helpers/delay";
import { TriggerService } from "./trigger_service";
 
export interface Iteration {
  hashes: IHashesCache | null;
  pipeline: IPipeline;
  result?: ExecError | SpawnError | ExecutorResult;
}

export abstract class IStackService {
  abstract callStack: Iteration[];
  abstract path: string;
  abstract init(processed: IPipeline[], path: string): void;
}

export class StackService extends TypedEvent<Iteration[]> implements IStackService {
  callStack: Iteration[];
  path: string;
  constructor(processed: IPipeline[], path: string) {
    super();
    this.path = path;
    this.callStack = [];
    this.init(processed);
  }

  public init(processed: IPipeline[]) {
    for (let el of processed) {
      el = this.commandHandler(el);
      this.callStack.push({
        hashes: null,
        pipeline: el,
      });
    }
  }
  private commandHandler(processMetaData: IPipeline) {
    processMetaData.process.command = processMetaData.process.command.replace("$PATH", this.path).pathNormalize();
    return processMetaData;
  }
  public async call() {
    this.callStack.map(async (el, index) => {
      await this.execStack(index, el);
      this.emit(this.callStack);
    });
  }
  async execStack(stackNumber: number, stackLayer: Iteration): Promise<void | boolean> {
    const executorService = new ExecutorProgramService(this.path);
    executorService.call(stackLayer.pipeline.process.type, stackLayer.pipeline.process.command);
    const filesChangeNotifierService = new FilesChangeNotifierService(this.path);

    filesChangeNotifierService.call();

    const result = await executorService.waitedEvent((event: Result<ExecError | SpawnError, ExecutorResult>) => {
      event.map((value) => {
        if (value.event === EXEC_EVENT.END) {
          return true;
        }
      });
      return false;
    });

    await delay(100);
    result.map(async (el) => {
      this.callStack.at(stackNumber).result = el;
      this.callStack.at(stackNumber).hashes = filesChangeNotifierService.hashes;
      (await this.triggerExec(stackLayer.pipeline.trigger, stackNumber)).map(() => {
        filesChangeNotifierService.cancel();
      });
    });

    return;
  }

  private async triggerExec(trigger: Trigger | null, stackNumber: number): Promise<Result<Error, void>> {
    try {
      if (trigger !== null) {
        const hashes = this.callStack[stackNumber].hashes;

        if (hashes != null) {
          await new TriggerService(trigger, hashes, this.path).call();
        }
        throw new Error("Hashes is null");
      }
      return Result.ok();
    } catch (error) {
      return Result.error(error);
    }
  }
}
