/**
 * Launch this script to re-generate the files containing the list of extensions
 * being used by each example.
 */
const initializeGDevelopJs = require('../public/libGD.js');
const { mapFor, mapVector } = require('./lib/MapFor');
const makeExtensionsLoader = require('./lib/LocalJsExtensionsLoader');
const { getExampleNames } = require('./lib/ExamplesLoader');
const {
  readProjectFile,
  loadSerializedProject,
} = require('./lib/LocalProjectOpener');
const fs = require('fs');
const _ = require('lodash');
var shell = require('shelljs');

shell.exec('node import-GDJS-Runtime.js');

const outputFile = '../src/ProjectCreation/ExamplesInformation.js';

const writeFile = object => {
  return new Promise((resolve, reject) => {
    const content = [
      `// This file is generated by update-examples-information-from-resources-examples.js script`,
      `// prettier-ignore`,
      `module.exports = ${JSON.stringify(object, null, 2)};`,
      ``,
    ].join('\n');
    fs.writeFile(outputFile, content, err => {
      if (err) return reject(err);

      resolve();
    });
  });
};

const readFileContent = filename => {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, 'utf8', (err, content) => {
      if (err) return reject(err);

      resolve(content);
    });
  });
};

initializeGDevelopJs().then(gd => {
  const getObjectTypes = projectOrLayout => {
    return _.uniq(
      mapFor(0, projectOrLayout.getObjectsCount(), i =>
        projectOrLayout.getObjectAt(i).getType()
      )
    );
  };

  const getBehaviorTypes = projectOrLayout => {
    return _.uniq(
      _.flatten(
        mapFor(0, projectOrLayout.getObjectsCount(), i => {
          const object = projectOrLayout.getObjectAt(i);
          const allBehaviorNames = object.getAllBehaviorNames();
          return mapVector(allBehaviorNames, behaviorName => {
            return object.getBehavior(behaviorName).getTypeName();
          });
        })
      )
    );
  };

  const getEventsAndInstructionsTypes = (project, events) => {
    const eventsTypesLister = new gd.EventsTypesLister(project);
    eventsTypesLister.launch(events);
    const types = {
      events: _.uniq(eventsTypesLister.getAllEventsTypes().toJSArray()),
      conditions: _.uniq(eventsTypesLister.getAllConditionsTypes().toJSArray()),
      actions: _.uniq(eventsTypesLister.getAllActionsTypes().toJSArray()),
    };
    eventsTypesLister.delete();
    return types;
  };

  const computeUsedExtensions = project => {
    // TODO: gd.WholeProjectRefactorer.exposeProjectEvents should be used to browse events
    // so that events functions are also browsed.
    const layoutExtensions = _.flatten(
      mapFor(0, project.getLayoutsCount(), i => {
        const layout = project.getLayoutAt(i);
        const extensionsFromObjects = getObjectTypes(layout).map(objectType => {
          return gd.MetadataProvider.getExtensionAndObjectMetadata(
            project.getCurrentPlatform(),
            objectType
          ).getExtension();
        });
        const extensionsFromBehaviors = getBehaviorTypes(layout).map(
          behaviorType => {
            return gd.MetadataProvider.getExtensionAndBehaviorMetadata(
              project.getCurrentPlatform(),
              behaviorType
            ).getExtension();
          }
        );
        const events = layout.getEvents();
        const eventsAndInstructionsTypes = getEventsAndInstructionsTypes(
          project,
          events
        );
        const extensionsFromConditions = eventsAndInstructionsTypes.conditions.map(
          conditionType => {
            return gd.MetadataProvider.getExtensionAndConditionMetadata(
              project.getCurrentPlatform(),
              conditionType
            ).getExtension();
          }
        );
        const extensionsFromActions = eventsAndInstructionsTypes.actions.map(
          actionType => {
            return gd.MetadataProvider.getExtensionAndActionMetadata(
              project.getCurrentPlatform(),
              actionType
            ).getExtension();
          }
        );

        return _.uniq([
          ...extensionsFromObjects,
          ...extensionsFromBehaviors,
          ...extensionsFromConditions,
          ...extensionsFromActions,
        ]);
      })
    );
    const externalEventsExtensions = _.flatten(
      mapFor(0, project.getExternalEventsCount(), i => {
        const externalEvents = project.getExternalEventsAt(i);

        const events = externalEvents.getEvents();
        const eventsAndInstructionsTypes = getEventsAndInstructionsTypes(
          project,
          events
        );
        const extensionsFromConditions = eventsAndInstructionsTypes.conditions.map(
          conditionType => {
            return gd.MetadataProvider.getExtensionAndConditionMetadata(
              project.getCurrentPlatform(),
              conditionType
            ).getExtension();
          }
        );
        const extensionsFromActions = eventsAndInstructionsTypes.actions.map(
          actionType => {
            return gd.MetadataProvider.getExtensionAndActionMetadata(
              project.getCurrentPlatform(),
              actionType
            ).getExtension();
          }
        );

        return _.uniq([...extensionsFromConditions, ...extensionsFromActions]);
      })
    );

    return _.uniq([...layoutExtensions, ...externalEventsExtensions]);
  };

  const noopTranslationFunction = str => str;

  const examplesInformation = {};

  makeExtensionsLoader({ gd, filterExamples: false })
    .loadAllExtensions(noopTranslationFunction)
    .then(loadingResults => {
      console.info('Loaded extensions', loadingResults);

      return getExampleNames();
    })
    .then(
      exampleNames => {
        return Promise.all(
          exampleNames.map(exampleName => {
            const exampleInformation = (examplesInformation[exampleName] = {
              description: '',
              usedExtensions: [],
            });

            return Promise.all([
              readProjectFile(
                `../resources/examples/${exampleName}/${exampleName}.json`
              )
                .then(projectObject => {
                  console.log(`Example "${exampleName}" loaded.`);

                  const project = loadSerializedProject(gd, projectObject);
                  const usedExtensions = computeUsedExtensions(project);
                  exampleInformation.usedExtensions = usedExtensions.map(
                    extension => ({
                      fullName: extension.getFullName(),
                      name: extension.getName(),
                    })
                  );
                })
                .catch(error => {
                  console.error('Error caught while analyzing game:', error);
                }),
              readFileContent(
                `../resources/examples/${exampleName}/README.md`
              ).then(
                readmeContent => {
                  exampleInformation.description = readmeContent;
                },
                error => {
                  console.error(
                    `⚠️ No/invalid README found for ${exampleName}:`,
                    error
                  );
                }
              ),
            ]);
          })
        );
      },
      err => console.error('Error while loading extensions', err)
    )
    .then(() => {
      return writeFile(examplesInformation);
    })
    .then(
      () => console.info('Done.'),
      err => console.error('Error while writing output', err)
    );
});
