'use strict';

const CSON = require('season');
const deprecatedPackagesMetadata = require('../deprecated-packages');
const fs = require('fs-plus');
const normalizePackageData = require('normalize-package-data');
const path = require('path');
const semver = require('semver');

const CONFIG = require('../config');

let appName = CONFIG.appMetadata.name;
if (process.platform === 'win32') {
  // Use the channel name in the app name on Windows so that the installer will
  // place it in a different folder in AppData\Local
  appName = CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}`;
}

module.exports = function() {
  console.log(
    `Generating metadata for ${path.join(
      CONFIG.intermediateAppPath,
      'package.json'
    )}`
  );
  CONFIG.appMetadata._atomPackages = buildBundledPackagesMetadata();
  CONFIG.appMetadata._atomMenu = buildPlatformMenuMetadata();
  CONFIG.appMetadata._atomKeymaps = buildPlatformKeymapsMetadata();
  CONFIG.appMetadata._deprecatedPackages = deprecatedPackagesMetadata;
  CONFIG.appMetadata.version = CONFIG.computedAppVersion;
  CONFIG.appMetadata.name = appName;
  CONFIG.appMetadata.productName = CONFIG.appName;
  checkDeprecatedPackagesMetadata();
  fs.writeFileSync(
    path.join(CONFIG.intermediateAppPath, 'package.json'),
    JSON.stringify(CONFIG.appMetadata)
  );
};

module.exports = function() {
  console.log(
    `Generating metadata for ${path.join(
      CONFIG.intermediateAppPath,
      'package.json'
    )}`
  );
  CONFIG.appMetadata._atomPackages = buildBundledPackagesMetadata();
  CONFIG.appMetadata._atomMenu = buildPlatformMenuMetadata();
  CONFIG.appMetadata._atomKeymaps = buildPlatformKeymapsMetadata();
  CONFIG.appMetadata._deprecatedPackages = deprecatedPackagesMetadata;
  CONFIG.appMetadata.version = CONFIG.computedAppVersion;
  checkDeprecatedPackagesMetadata();
  fs.writeFileSync(
    path.join(CONFIG.intermediateAppPath, 'package.json'),
    JSON.stringify(CONFIG.appMetadata)
  );
};

function buildBundledPackagesMetadata() {
  const packages = {};
  for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) {
    const packagePath = path.join(
      CONFIG.intermediateAppPath,
      'node_modules',
      packageName
    );
    const packageMetadataPath = path.join(packagePath, 'package.json');
    const packageMetadata = JSON.parse(
      fs.readFileSync(packageMetadataPath, 'utf8')
    );
    normalizePackageData(
      packageMetadata,
      msg => {
        if (!msg.match(/No README data$/)) {
          console.warn(
            `Invalid package metadata. ${packageMetadata.name}: ${msg}`
          );
        }
      },
      true
    );
    if (
      packageMetadata.repository &&
      packageMetadata.repository.url &&
      packageMetadata.repository.type === 'git'
    ) {
      packageMetadata.repository.url = packageMetadata.repository.url.replace(
        /^git\+/,
        ''
      );
    }

    delete packageMetadata['_from'];
    delete packageMetadata['_id'];
    delete packageMetadata['dist'];
    delete packageMetadata['readme'];
    delete packageMetadata['readmeFilename'];

    const packageModuleCache = packageMetadata._atomModuleCache || {};
    if (
      packageModuleCache.extensions &&
      packageModuleCache.extensions['.json']
    ) {
      const index = packageModuleCache.extensions['.json'].indexOf(
        'package.json'
      );
      if (index !== -1) {
        packageModuleCache.extensions['.json'].splice(index, 1);
      }
    }

    const packageNewMetadata = {
      metadata: packageMetadata,
      keymaps: {},
      menus: {},
      grammarPaths: [],
      settings: {}
    };

    packageNewMetadata.rootDirPath = path.relative(
      CONFIG.intermediateAppPath,
      packagePath
    );

    if (packageMetadata.main) {
      const mainPath = require.resolve(
        path.resolve(packagePath, packageMetadata.main)
      );
      packageNewMetadata.main = path.relative(
        path.join(CONFIG.intermediateAppPath, 'static'),
        mainPath
      );
      // Convert backward slashes to forward slashes in order to allow package
      // main modules to be required from the snapshot. This is because we use
      // forward slashes to cache the sources in the snapshot, so we need to use
      // them here as well.
      packageNewMetadata.main = packageNewMetadata.main.replace(/\\/g, '/');
    }

    const packageKeymapsPath = path.join(packagePath, 'keymaps');
    if (fs.existsSync(packageKeymapsPath)) {
      for (let packageKeymapName of fs.readdirSync(packageKeymapsPath)) {
        const packageKeymapPath = path.join(
          packageKeymapsPath,
          packageKeymapName
        );
        if (
          packageKeymapPath.endsWith('.cson') ||
          packageKeymapPath.endsWith('.json')
        ) {
          const relativePath = path.relative(
            CONFIG.intermediateAppPath,
            packageKeymapPath
          );
          packageNewMetadata.keymaps[relativePath] = CSON.readFileSync(
            packageKeymapPath
          );
        }
      }
    }

    const packageMenusPath = path.join(packagePath, 'menus');
    if (fs.existsSync(packageMenusPath)) {
      for (let packageMenuName of fs.readdirSync(packageMenusPath)) {
        const packageMenuPath = path.join(packageMenusPath, packageMenuName);
        if (
          packageMenuPath.endsWith('.cson') ||
          packageMenuPath.endsWith('.json')
        ) {
          const relativePath = path.relative(
            CONFIG.intermediateAppPath,
            packageMenuPath
          );
          packageNewMetadata.menus[relativePath] = CSON.readFileSync(
            packageMenuPath
          );
        }
      }
    }

    const packageGrammarsPath = path.join(packagePath, 'grammars');
    for (let packageGrammarPath of fs.listSync(packageGrammarsPath, [
      'json',
      'cson'
    ])) {
      const relativePath = path.relative(
        CONFIG.intermediateAppPath,
        packageGrammarPath
      );
      packageNewMetadata.grammarPaths.push(relativePath);
    }

    const packageSettingsPath = path.join(packagePath, 'settings');
    for (let packageSettingPath of fs.listSync(packageSettingsPath, [
      'json',
      'cson'
    ])) {
      const relativePath = path.relative(
        CONFIG.intermediateAppPath,
        packageSettingPath
      );
      packageNewMetadata.settings[relativePath] = CSON.readFileSync(
        packageSettingPath
      );
    }

    const packageStyleSheetsPath = path.join(packagePath, 'styles');
    let styleSheets = null;
    if (packageMetadata.mainStyleSheet) {
      styleSheets = [fs.resolve(packagePath, packageMetadata.mainStyleSheet)];
    } else if (packageMetadata.styleSheets) {
      styleSheets = packageMetadata.styleSheets.map(name =>
        fs.resolve(packageStyleSheetsPath, name, ['css', 'less', ''])
      );
    } else {
      const indexStylesheet = fs.resolve(packagePath, 'index', ['css', 'less']);
      if (indexStylesheet) {
        styleSheets = [indexStylesheet];
      } else {
        styleSheets = fs.listSync(packageStyleSheetsPath, ['css', 'less']);
      }
    }

    packageNewMetadata.styleSheetPaths = styleSheets.map(styleSheetPath =>
      path.relative(packagePath, styleSheetPath)
    );

    packages[packageMetadata.name] = packageNewMetadata;
    if (packageModuleCache.extensions) {
      for (let extension of Object.keys(packageModuleCache.extensions)) {
        const paths = packageModuleCache.extensions[extension];
        if (paths.length === 0) {
          delete packageModuleCache.extensions[extension];
        }
      }
    }
  }
  return packages;
}

function buildPlatformMenuMetadata() {
  const menuPath = path.join(
    CONFIG.repositoryRootPath,
    'menus',
    `${process.platform}.cson`
  );
  if (fs.existsSync(menuPath)) {
    return CSON.readFileSync(menuPath);
  } else {
    return null;
  }
}

function buildPlatformKeymapsMetadata() {
  const invalidPlatforms = [
    'darwin',
    'freebsd',
    'linux',
    'sunos',
    'win32'
  ].filter(p => p !== process.platform);
  const keymapsPath = path.join(CONFIG.repositoryRootPath, 'keymaps');
  const keymaps = {};
  for (let keymapName of fs.readdirSync(keymapsPath)) {
    const keymapPath = path.join(keymapsPath, keymapName);
    if (keymapPath.endsWith('.cson') || keymapPath.endsWith('.json')) {
      const keymapPlatform = path.basename(
        keymapPath,
        path.extname(keymapPath)
      );
      if (invalidPlatforms.indexOf(keymapPlatform) === -1) {
        keymaps[path.basename(keymapPath)] = CSON.readFileSync(keymapPath);
      }
    }
  }
  return keymaps;
}

function checkDeprecatedPackagesMetadata() {
  for (let packageName of Object.keys(deprecatedPackagesMetadata)) {
    const packageMetadata = deprecatedPackagesMetadata[packageName];
    if (
      packageMetadata.version &&
      !semver.validRange(packageMetadata.version)
    ) {
      throw new Error(
        `Invalid range: ${packageMetadata.version} (${packageName}).`
      );
    }
  }
}
