import 'dart:typed_data';

import 'package:pointycastle/asn1/asn1_encoding_rule.dart';
import 'package:pointycastle/asn1/asn1_object.dart';
import 'package:pointycastle/asn1/asn1_parser.dart';
import 'package:pointycastle/asn1/asn1_tags.dart';
import 'package:pointycastle/asn1/asn1_utils.dart';
import 'package:pointycastle/asn1/unsupported_asn1_encoding_rule_exception.dart';

///
/// An ASN1 Bit String object
///
class ASN1BitString extends ASN1Object {
  ///
  /// The decoded string value
  ///
  List<int>? stringValues;

  ///
  /// The unused bits
  ///
  int? unusedbits;

  ///
  /// A list of elements. Only set if this ASN1IA5String is constructed, otherwhise null.
  ///
  ///
  List<ASN1Object>? elements;

  ///
  /// Create an [ASN1BitString] entity with the given [stringValues].
  ///
  ASN1BitString(
      {this.stringValues, this.elements, int tag = ASN1Tags.BIT_STRING})
      : super(tag: tag);

  ///
  /// Creates an [ASN1BitString] entity from the given [encodedBytes].
  ///
  ASN1BitString.fromBytes(Uint8List super.bytes) : super.fromBytes() {
    if (ASN1Utils.isConstructed(encodedBytes!.elementAt(0))) {
      elements = [];
      var parser = ASN1Parser(valueBytes);
      stringValues = [];
      while (parser.hasNext()) {
        var bitString = parser.nextObject() as ASN1BitString;
        stringValues!.addAll(bitString.stringValues!);
        elements!.add(bitString);
      }
    } else {
      unusedbits = valueBytes![0];
      stringValues = valueBytes!.sublist(1);
    }
  }

  ///
  /// Encodes this ASN1Object depending on the given [encodingRule]
  ///
  /// If no [ASN1EncodingRule] is given, ENCODING_DER will be used.
  ///
  /// Supported encoding rules are :
  /// * [ASN1EncodingRule.ENCODING_DER]
  /// * [ASN1EncodingRule.ENCODING_BER_LONG_LENGTH_FORM]
  /// * [ASN1EncodingRule.ENCODING_BER_CONSTRUCTED]
  /// * [ASN1EncodingRule.ENCODING_BER_CONSTRUCTED_INDEFINITE_LENGTH]
  /// * [ASN1EncodingRule.ENCODING_BER_PADDED]
  ///
  /// Throws an [UnsupportedAsn1EncodingRuleException] if the given [encodingRule] is not supported.
  ///
  @override
  Uint8List encode(
      {ASN1EncodingRule encodingRule = ASN1EncodingRule.ENCODING_DER}) {
    switch (encodingRule) {
      case ASN1EncodingRule.ENCODING_BER_PADDED:
      case ASN1EncodingRule.ENCODING_DER:
      case ASN1EncodingRule.ENCODING_BER_LONG_LENGTH_FORM:
        var b = <int>[];
        if (unusedbits != null) {
          b.add(unusedbits!);
        } else {
          b.add(0);
        }
        b.addAll(stringValues!);
        valueBytes = Uint8List.fromList(b);
        break;
      case ASN1EncodingRule.ENCODING_BER_CONSTRUCTED_INDEFINITE_LENGTH:
      case ASN1EncodingRule.ENCODING_BER_CONSTRUCTED:
        valueByteLength = 0;
        if (elements == null) {
          elements = <ASN1Object>[];
          elements!.add(ASN1BitString(stringValues: stringValues));
        }
        valueByteLength = _childLength(
            isIndefinite: encodingRule ==
                ASN1EncodingRule.ENCODING_BER_CONSTRUCTED_INDEFINITE_LENGTH);
        valueBytes = Uint8List(valueByteLength!);
        var i = 0;
        for (var obj in elements!) {
          var b = obj.encode();
          valueBytes!.setRange(i, i + b.length, b);
          i += b.length;
        }
        break;
    }

    return super.encode(encodingRule: encodingRule);
  }

  ///
  /// Calculate encoded length of all children
  ///
  int _childLength({bool isIndefinite = false}) {
    var l = 0;
    for (var obj in elements!) {
      l += obj.encode().length;
    }
    if (isIndefinite) {
      return l + 2;
    }
    return l;
  }

  @override
  String dump({int spaces = 0}) {
    var sb = StringBuffer();
    for (var i = 0; i < spaces; i++) {
      sb.write(' ');
    }
    if (isConstructed!) {
      sb.write('BIT STRING (${elements!.length} elem)');
      for (var e in elements!) {
        var dump = e.dump(spaces: spaces + dumpIndent);
        sb.write('\n$dump');
      }
    } else {
      if (ASN1Utils.isASN1Tag(stringValues!.elementAt(0))) {
        var sb2 = StringBuffer();
        for (var v in stringValues!) {
          var s = v.toRadixString(2);
          sb2.write(s);
        }
        var bits = sb2.toString();
        if (unusedbits != null) {
          bits = bits.substring(0, bits.length - unusedbits!);
        }
        var parser = ASN1Parser(stringValues as Uint8List?);
        var next = parser.nextObject();
        var dump = next.dump(spaces: spaces + dumpIndent);
        sb.write('BIT STRING (${bits.length} bit)\n$dump');
      } else {
        var sb2 = StringBuffer();
        for (var v in stringValues!) {
          var s = v.toRadixString(2);
          sb2.write(s);
        }
        var bits = sb2.toString();
        if (unusedbits != null) {
          bits = bits.substring(0, bits.length - unusedbits!);
        }
        sb.write('BIT STRING (${bits.length} bit) ');
        sb.write(bits);
      }
    }
    return sb.toString();
  }
}
