import { Command } from 'commander';
import fs from 'fs';
import log from 'loglevel';
import { DateTime } from 'luxon';

import {
  extendedGtfsProfile,
  GtfsParser,
  GtfsProfile,
  standardGtfsProfile,
  StraightLineTransferGenerator,
} from '../parser.js';
import { Router, StopsIndex, Timetable } from '../router.js';
import {
  loadQueriesFromJson,
  loadRangeQueriesFromJson,
  prettyPrintPerformanceResults,
  testBestRoutePerformance,
  testRangeResultPerformance,
  testRangeRouterPerformance,
  testRouterPerformance,
} from './perf.js';
import { startRepl } from './repl.js';

const program = new Command();

export type GtfsVariant = string;

export type Config = {
  [config: GtfsVariant]: GtfsProfile;
};

const profiles: Config = {
  extended: extendedGtfsProfile,
  standard: standardGtfsProfile,
};

program
  .name('minotor')
  .description('CLI for minotor route planner.')
  .version('0.0.1');

program
  .command('parse-gtfs')
  .description('Parse a GTFS feed and output a timetable and stops file.')
  .argument('<gtfsPath>', 'Path to GTFS data')
  .option(
    '-d, --date <date>',
    'Date of the day to parse in yyyy-MM-dd format',
    DateTime.now().toFormat('yyyy-MM-dd'),
  )
  .option(
    '-t, --timetableOutputPath <path>',
    'Path to output timetable file',
    '/tmp/timetable',
  )
  .option(
    '-s, --stopsOutputPath <path>',
    'Path to output stops file',
    '/tmp/stops',
  )
  .option(
    '-p, --profileName <name>',
    'Profile name for GTFS config',
    'extended',
  )
  .option(
    '--virtual-transfers-radius <meters>',
    'Generate virtual walking transfers between stops within this radius (in meters). Disabled when omitted.',
  )
  .option('-v, --verbose', 'Verbose mode', false)
  .action(
    async (
      gtfsPath: string,
      options: {
        date: string;
        timetableOutputPath: string;
        stopsOutputPath: string;
        profileName: string;
        virtualTransfersRadius?: string;
        verbose: boolean;
      },
    ) => {
      if (options.verbose) {
        log.setDefaultLevel(log.levels.INFO);
      } else {
        log.setDefaultLevel(log.levels.ERROR);
      }
      const radiusMeters =
        options.virtualTransfersRadius !== undefined
          ? parseInt(options.virtualTransfersRadius, 10)
          : undefined;
      const transferGenerator =
        radiusMeters !== undefined && Number.isFinite(radiusMeters)
          ? new StraightLineTransferGenerator({
              maxDistanceMeters: radiusMeters,
            })
          : undefined;
      const parser = new GtfsParser(
        gtfsPath,
        profiles[options.profileName],
        transferGenerator,
      );
      const stopsIndex = await parser.parseStops();
      fs.writeFileSync(options.stopsOutputPath, stopsIndex.serialize());
      const timetable = await parser.parseTimetable(new Date(options.date));
      fs.writeFileSync(options.timetableOutputPath, timetable.serialize());
    },
  );

program
  .command('parse-stops')
  .description('Parse a GTFS feed and output a stops file.')
  .argument('<gtfsPath>', 'Path to GTFS data')
  .option('-s, --outputPath <path>', 'Path to output stops file', '/tmp/stops')
  .option('-p, --profileName <name>', 'Profile name for GTFS config', 'CH')
  .option('-v, --verbose', 'Verbose mode', false)
  .action(
    async (
      gtfsPath: string,
      options: {
        stopsOutputPath: string;
        profileName: string;
        verbose: boolean;
      },
    ) => {
      if (options.verbose) {
        log.setDefaultLevel(log.levels.INFO);
      } else {
        log.setDefaultLevel(log.levels.ERROR);
      }
      const parser = new GtfsParser(gtfsPath, profiles[options.profileName]);
      const stopsIndex = await parser.parseStops();
      fs.writeFileSync(options.stopsOutputPath, stopsIndex.serialize());
    },
  );

program
  .command('repl')
  .description('Find stops matching a textual query')
  .option('-s, --stopsPath <path>', 'Path to the stops file', '/tmp/stops')
  .option(
    '-t, --timetablePath <path>',
    'Path to the timetable file',
    '/tmp/timetable',
  )
  .action((options: { stopsPath: string; timetablePath: string }) => {
    startRepl(options.stopsPath, options.timetablePath);
  });

program
  .command('perf')
  .description('Evaluate the performance of the router on a set of routes.')
  .argument('<routesPath>', 'Path to the JSON file containing the routes')
  .option('-s, --stopsPath <path>', 'Path to the stops file', '/tmp/stops')
  .option(
    '-t, --timetablePath <path>',
    'Path to the timetable file',
    '/tmp/timetable',
  )
  .option(
    '-i, --iterations <number>',
    'Number of iterations for performance tests',
    '20',
  )
  .action(
    (
      routesPath: string,
      options: {
        stopsPath: string;
        timetablePath: string;
        iterations: string;
      },
    ) => {
      const stopsIndex = StopsIndex.fromData(
        fs.readFileSync(options.stopsPath),
      );
      const timetable = Timetable.fromData(
        fs.readFileSync(options.timetablePath),
      );
      const router = new Router(timetable, stopsIndex);

      const queries = loadQueriesFromJson(routesPath, stopsIndex);
      const iterations = parseInt(options.iterations, 10);

      const routerResults = testRouterPerformance(
        router,
        queries,
        iterations,
        stopsIndex,
      );
      prettyPrintPerformanceResults(
        routerResults,
        'Point queries — router.route()',
      );

      const bestRouteResults = testBestRoutePerformance(
        router,
        queries,
        iterations,
        stopsIndex,
      );
      prettyPrintPerformanceResults(
        bestRouteResults,
        'Point queries — result.bestRoute() (reconstruction only)',
      );

      const rangeQueries = loadRangeQueriesFromJson(routesPath, stopsIndex);

      const rangeRouterResults = testRangeRouterPerformance(
        router,
        rangeQueries,
        iterations,
        stopsIndex,
      );
      prettyPrintPerformanceResults(
        rangeRouterResults,
        'Range queries — router.rangeRoute()',
      );

      const rangeResultResults = testRangeResultPerformance(
        router,
        rangeQueries,
        iterations,
        stopsIndex,
      );
      prettyPrintPerformanceResults(
        rangeResultResults,
        'Range queries — rangeResult.getRoutes() (reconstruction only)',
      );
    },
  );

program.parse(process.argv);
