/*
 * Copyright 2023 Hongen Wang All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import 'dart:math';
import 'dart:typed_data';

import 'package:proxypin/network/http/constants.dart';
import 'package:proxypin/network/http/http.dart';

import '../../../utils/num.dart';
import '../codec.dart';

class Result {
  final bool isDone;
  final bool supportedParse;

  Uint8List? body;

  Result(this.isDone, {this.body, this.supportedParse = true});
}

class BodyReader {
  final HttpMessage message;

  // BytesBuilder msgBytes = BytesBuilder();
  int _offset = 0;
  ReaderState _state;

  final BytesBuilder _bodyBuffer = BytesBuilder();

  ///chunked编码 剩余未读取的chunk大小
  int _chunkReadableSize = 0;

  BodyReader(this.message)
      : _state = message.headers.isChunked ? ReaderState.readChunkSize : ReaderState.readFixedLengthContent;

  Result readBody(Uint8List data) {
    if (_bodyBuffer.length > Codec.maxBodyLength) {
      _bodyBuffer.clear();
      throw ParserException('Body length exceeds ${Codec.maxBodyLength}');
    }

    _offset = 0;

    if (message.headers.contentType == 'video/x-flv' || message.headers.contentType.startsWith("text/event-stream")) {
      //Directly forward without processing for now
      return Result(false, supportedParse: false, body: data);
    }

    //chunked编码
    if (message.headers.isChunked) {
      _readChunked(data);
    } else {
      _readFixedLengthContent(data);
    }

    if (_state == ReaderState.done) {
      var body = _bodyBuffer.toBytes();
      _bodyBuffer.clear();
      return Result(true, body: body);
    }

    return Result(false);
  }

  void _readFixedLengthContent(Uint8List data) {
    if (message.contentLength > 0) {
      _bodyBuffer.add(data.sublist(_offset));
    }

    if (message.contentLength == -1 || _bodyBuffer.length >= message.contentLength) {
      _state = ReaderState.done;
    }
  }

  void _readChunked(Uint8List data) {
    while (_offset < data.length) {
      //读取chunk length
      if (_state == ReaderState.readChunkSize) {
        _chunkReadableSize = _readChunkSize(data);

        if (_chunkReadableSize == 0) {
          //chunked编码结束
          _state = ReaderState.done;
          break;
        }

        if (_chunkReadableSize == -1) {
          continue;
        }
        _state = ReaderState.readChunkedContent;
      }

      //读取chunk内容
      if (_state == ReaderState.readChunkedContent) {
        int end = min(data.length, _offset + _chunkReadableSize);
        _bodyBuffer.add(data.sublist(_offset, end));

        //可读大小
        _chunkReadableSize -= (end - _offset);
        _offset = end;
        if (_chunkReadableSize == 0) {
          _state = ReaderState.readChunkSize;
          _offset += 2; //内容结尾\r\n
        }
      }
    }
  }

  int _readChunkSize(Uint8List data) {
    if (_offset >= data.length) {
      return -1;
    }

    for (int i = _offset; i < data.length; i++) {
      /// chunked编码内容结尾\r\n
      if (data[i] == HttpConstants.lf) {
        if (i > 0 && data[i - 1] == HttpConstants.cr) {
          var line = data.sublist(_offset, i - 1);
          _offset = i + 1;
          if (line.isEmpty) {
            return -1;
          }
          return hexToInt(String.fromCharCodes(line));
        }

        //可能上个包是结尾\r 最好做法是缓存上个不完整的包，先临时处理下
        if (data.length == 1) {
          _offset = i + 1;
          return -1;
        }
      }
    }

    throw Exception('Invalid chunked encoding line: ${String.fromCharCodes(data)}');
  }
}

enum ReaderState { readFixedLengthContent, readChunked, readChunkSize, readChunkedContent, done }
