library petitparser.example.lispshell;

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:petitparser/petitparser.dart';

import '../lisp/lisp.dart';

/// Read, evaluate, print loop.
void evalInteractive(Parser parser, Environment env, Stream<String> input,
    IOSink output, IOSink error) {
  output.write('>> ');
  input.listen((String line) {
    try {
      output.writeln('=> ${evalString(parser, env, line)}');
    } on ParserError catch (exception) {
      error.writeln('Parser error: ${exception.toString()}');
    } on Error catch (exception) {
      error.writeln(exception.toString());
    }
    output.write('>> ');
  });
}

/// Entry point for the command line interpreter.
void main(List<String> arguments) {
  // default options
  var standardLibrary = true;
  var interactiveMode = false;
  var files = <File>[];

  // parse arguments
  for (var option in arguments) {
    if (option.startsWith('-') && files.isEmpty) {
      if (option == '-n') {
        standardLibrary = false;
      } else if (option == '-i') {
        interactiveMode = true;
      } else if (option == '-?') {
        print('${Platform.executable} lisp.dart -n -i [files]');
        print(' -i enforces the interactive mode');
        print(' -n does not load the standard library');
        exit(0);
      } else {
        print('Unknown option: $option');
        exit(1);
      }
    } else {
      var file = new File(option);
      if (file.existsSync()) {
        files.add(file);
      } else {
        print('File not found: $option');
        exit(2);
      }
    }
  }

  // evaluation context
  Environment environment = new NativeEnvironment();

  // add additional primitives
  environment.define(new Name('exit'), (Environment env, Cons args) {
    exit(args == null ? 0 : args.head);
  });
  environment.define(new Name('sleep'), (Environment env, Cons args) {
    sleep(new Duration(milliseconds: args.head));
  });

  // process standard library
  if (standardLibrary) {
    environment = new StandardEnvironment(environment);
  }

  // create empty context
  environment = environment.create();

  // process files given as argument
  for (var file in files) {
    evalString(lispParser, environment, file.readAsStringSync());
  }

  // process console input
  if (interactiveMode || files.isEmpty) {
    var input = stdin
        .transform(SYSTEM_ENCODING.decoder)
        .transform(const LineSplitter());
    evalInteractive(lispParser, environment, input, stdout, stderr);
  }
}
