import {
  AtlasInputNotResolved, ICache, IGeneratorOutput, IImageProcessor, Logger, spriteProcess
} from './index';
import {perPageManifestsSaver} from './perPageManifestsSaver';

export interface IGenerateOptions {
  concurrencyLimit: number,
  cleanCache: boolean,
  cleanOutput: boolean,
  inputPath: string,
  outputPath: string,
  outputDestination: (atlasIndex: number, pageIndex: number, res: IGeneratorOutput) => string,
  atlasPageMetaPathResolver?: (atlasIndex: number, pageIndex: number, res: IGeneratorOutput) => string
  pageManifestBuilder?: (atlasIndex: number,
                         pageIndex: number,
                         res: IGeneratorOutput) => any
}

export interface IEnvironment extends ICache {
  load(): Promise<void>;
  clean(): Promise<void>;
  cleanPath(dir: string): Promise<void>;
  save(): Promise<void>;
  copyTo(targetFilePath: string, toPath: string): Promise<void>;
  joinPaths(...paths: string[]): string;
  ensureDir(dir: string): Promise<void>;
  glob(pattern: string): Promise<string[]>;
  writeFile(outputFilePath: string, raw: string): Promise<void>;
}

export function packer(atlases: AtlasInputNotResolved[],
                       generateOptions: IGenerateOptions,
                       imageProcessor: IImageProcessor,
                       environment: IEnvironment,
                       manifestsSaver?: (generateOptions: IGenerateOptions,
                                         environment: IEnvironment,
                                         result: IGeneratorOutput) => Promise<void>,
                       log?: Logger): Promise<IGeneratorOutput> {

  const logger: Logger = log ? log : {
    assert: () => {
    }, error: () => {
    }, info: () => {
    }, log: () => {
    }, trace: () => {
    }, warn: () => {
    }
  } as any;

  let output: IGeneratorOutput;

  logger.log('START');

  return Promise.resolve()
    .then(() => {
      // createCache
      return environment.load();
    })
    .then(() => {
      // cleanCache
      if (generateOptions.cleanCache) {
        logger.info('Cleaning cache folder');
        return environment.clean();
      }
      return;
    })
    .then(() => {
      // cleanOutput
      if (generateOptions.cleanOutput) {
        logger.info('Cleaning output folder');
        return environment.cleanPath(generateOptions.outputPath);
      }
      return;
    })
    .then(() => {
      // ensurePathsExist
      return Promise.all([
        environment.ensureDir(generateOptions.outputPath)
      ]);
    })
    .then(() => {
      logger.log('spriteProcess start');
      return spriteProcess(atlases, generateOptions.concurrencyLimit, environment, imageProcessor, logger,
        (p) => environment.glob(generateOptions.inputPath ? (generateOptions.inputPath + '/' + p) : p))
        .then(resp => {
          output = resp;
        })
    })
    .then(() => {
      logger.log('spriteProcess end');
      // Save in case error occurs during createAndSaveLoadingManifests step
      return environment.save();
    })
    .then(() => {
      return Promise
        .all(output.atlases.map((atlas, atlasIndex) => {
          return Promise
            .all(atlas.sheets.map((sheet, pageIndex) => {
              const p = generateOptions.outputDestination(atlasIndex, pageIndex, output);
              const outputImagePath = environment.joinPaths(generateOptions.outputPath, p);
              const temp = sheet.path;
              sheet.path = outputImagePath;
              return environment.copyTo(temp, outputImagePath);
            }));
        }));
    })
    .then(() => {
      // process atlases
      if (manifestsSaver) {
        return manifestsSaver(generateOptions, environment, output);
      }
      else {
        if (generateOptions.pageManifestBuilder) {
          return perPageManifestsSaver(generateOptions, environment, output);
        }
        return Promise.resolve();
      }
    })
    .catch(error => {
      logger.error('Error', error);
      throw error;
    })
    .then(() => {
      logger.info('Done');
      return output;
    });
}
