content: // Copyright 2019 Google Inc. Use of this source code is governed by an
  // MIT-style license that can be found in the LICENSE file or at
  // https://opensource.org/licenses/MIT.
  
  import 'dart:io';
  import 'dart:typed_data';
  
  import 'package:protobuf/protobuf.dart';
  import 'package:source_span/source_span.dart';
  import 'package:stack_trace/stack_trace.dart';
  import 'package:term_glyph/term_glyph.dart' as term_glyph;
  
  import '../syntax.dart';
  import 'embedded_sass.pb.dart' as proto;
  import 'embedded_sass.pb.dart' hide SourceSpan, Syntax;
  import 'util/varint_builder.dart';
  
  /// The special ID that indicates an error that's not associated with a specific
  /// inbound request ID.
  const errorId = 0xffffffff;
  
  /// Returns a [ProtocolError] indicating that a mandatory field with the given
  /// [fieldName] was missing.
  ProtocolError mandatoryError(String fieldName) =>
      paramsError("Missing mandatory field $fieldName");
  
  /// Returns a [ProtocolError] indicating that the parameters for an inbound
  /// message were invalid.
  ProtocolError paramsError(String message) => ProtocolError()
    // Set the ID to [errorId] by default. This will be overwritten by the
    // dispatcher if a request ID is available.
    ..id = errorId
    ..type = ProtocolErrorType.PARAMS
    ..message = message;
  
  /// Returns a [ProtocolError] with type `PARSE` and the given [message].
  ProtocolError parseError(String message) => ProtocolError()
    ..type = ProtocolErrorType.PARSE
    ..message = message;
  
  /// Converts a Dart source span to a protocol buffer source span.
  proto.SourceSpan protofySpan(SourceSpan span) {
    var protoSpan = proto.SourceSpan()
      ..text = span.text
      ..start = _protofyLocation(span.start)
      ..end = _protofyLocation(span.end)
      ..url = span.sourceUrl?.toString() ?? "";
    if (span is SourceSpanWithContext) protoSpan.context = span.context;
    return protoSpan;
  }
  
  /// Converts a Dart source location to a protocol buffer source location.
  SourceSpan_SourceLocation _protofyLocation(SourceLocation location) =>
      SourceSpan_SourceLocation()
        ..offset = location.offset
        ..line = location.line
        ..column = location.column;
  
  /// Converts a protocol buffer syntax enum into a Sass API syntax enum.
  Syntax syntaxToSyntax(proto.Syntax syntax) => switch (syntax) {
        proto.Syntax.SCSS => Syntax.scss,
        proto.Syntax.INDENTED => Syntax.sass,
        proto.Syntax.CSS => Syntax.css,
        _ => throw "Unknown syntax $syntax.",
      };
  
  /// Returns the result of running [callback] with the global ASCII config set
  /// to [ascii].
  T withGlyphs<T>(T callback(), {required bool ascii}) {
    var currentConfig = term_glyph.ascii;
    term_glyph.ascii = ascii;
    var result = callback();
    term_glyph.ascii = currentConfig;
    return result;
  }
  
  /// Serializes [value] to an unsigned varint.
  Uint8List serializeVarint(int value) {
    if (value == 0) return Uint8List.fromList([0]);
    RangeError.checkNotNegative(value);
  
    // Essentially `(value.bitLength / 7).ceil()`, but without getting floats
    // involved.
    var lengthInBytes = (value.bitLength + 6) ~/ 7;
    var list = Uint8List(lengthInBytes);
    for (var i = 0; i < lengthInBytes; i++) {
      // The highest-order bit indicates whether more bytes are necessary to fully
      // express the number. The lower 7 bits indicate the number's value.
      list[i] = (value > 0x7f ? 0x80 : 0) | (value & 0x7f);
      value >>= 7;
    }
    return list;
  }
  
  /// Serializes a compilation ID and protobuf message into a packet buffer as
  /// specified in the embedded protocol.
  Uint8List serializePacket(int compilationId, GeneratedMessage message) {
    var varint = serializeVarint(compilationId);
    var protobufWriter = CodedBufferWriter();
    message.writeToCodedBufferWriter(protobufWriter);
  
    var packet = Uint8List(varint.length + protobufWriter.lengthInBytes);
    packet.setAll(0, varint);
    protobufWriter.writeTo(packet, varint.length);
    return packet;
  }
  
  /// A [VarintBuilder] that's shared across invocations of [parsePacket] to avoid
  /// unnecessary allocations.
  final _compilationIdBuilder = VarintBuilder(32, 'compilation ID');
  
  /// Parses a compilation ID and encoded protobuf message from a packet buffer as
  /// specified in the embedded protocol.
  (int, Uint8List) parsePacket(Uint8List packet) {
    try {
      var i = 0;
      while (true) {
        if (i == packet.length) {
          throw parseError(
            "Invalid compilation ID: continuation bit always set.",
          );
        }
  
        var compilationId = _compilationIdBuilder.add(packet[i]);
        i++;
        if (compilationId != null) {
          return (compilationId, Uint8List.sublistView(packet, i));
        }
      }
    } finally {
      _compilationIdBuilder.reset();
    }
  }
  
  /// Wraps error object into ProtocolError, writes error to stderr, and returns the ProtocolError.
  ProtocolError handleError(
    Object error,
    StackTrace stackTrace, {
    int? messageId,
  }) {
    if (error is ProtocolError) {
      error.id = messageId ?? errorId;
      stderr.write("Host caused ${error.type.name.toLowerCase()} error");
      if (error.id != errorId) stderr.write(" with request ${error.id}");
      stderr.writeln(": ${error.message}");
      // PROTOCOL error from https://bit.ly/2poTt90
      exitCode = 76; // EX_PROTOCOL
      return error;
    } else {
      var errorMessage = "$error\n${Chain.forTrace(stackTrace)}";
      stderr.write("Internal compiler error: $errorMessage");
      exitCode = 70; // EX_SOFTWARE
      return ProtocolError()
        ..type = ProtocolErrorType.INTERNAL
        ..id = messageId ?? errorId
        ..message = errorMessage;
    }
  }
  