// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import 'package:wyatt_http_client/src/middleware.dart';
import 'package:wyatt_http_client/src/models/middleware_context.dart';
import 'package:wyatt_http_client/src/models/middleware_request.dart';
import 'package:wyatt_http_client/src/models/middleware_response.dart';

/// {@template pipeline}
/// A [Pipeline] is a list of [Middleware]s that are executed in order.
/// {@endtemplate}
class Pipeline {
  /// {@macro pipeline}
  Pipeline() : _middlewares = <Middleware>[];

  /// {@macro pipeline}
  Pipeline.fromIterable(Iterable<Middleware> middlewares)
      : _middlewares = middlewares.toList();
  
  final List<Middleware> _middlewares;

  /// The length of the [Pipeline].
  ///
  /// This is the number of [Middleware]s in the [Pipeline].
  int get length => _middlewares.length;

  /// Add a [Middleware] to this [Pipeline]
  void addMiddleware(Middleware middleware) {
    _middlewares.add(middleware);
  }

  /// Create new [Pipeline] from the start or end to a specified [Middleware].
  Pipeline sub(
    Middleware middleware, {
    bool include = false,
    bool fromEnd = false,
  }) {
    final nodes = <Middleware>[];
    final list = fromEnd ? _middlewares.reversed : _middlewares;
    for (final m in list) {
      if (m != middleware) {
        nodes.add(m);
      }
      if (m == middleware) {
        if (include) {
          nodes.add(m);
        }
        break;
      }
    }
    return Pipeline.fromIterable(fromEnd ? nodes.reversed : nodes);
  }

  /// Call the [onRequest] method of all [OnRequestMiddleware]s in the
  /// [Pipeline].
  ///
  /// The [MiddlewareRequest] returned by the last [OnRequestMiddleware] is
  /// returned.
  Future<MiddlewareRequest> onRequest(
    MiddlewareContext context,
    MiddlewareRequest request,
  ) async {
    MiddlewareRequest req = request..apply();
    MiddlewareContext ctx = context.copyWith(lastRequest: req);
    for (final middleware in _middlewares) {
      if (middleware is OnRequestMiddleware) {
        req = await (middleware as OnRequestMiddleware).onRequest(ctx, request);
        ctx = context.copyWith(lastRequest: req);
      }
    }
    return req;
  }

  /// Call the [onResponse] method of all [OnResponseMiddleware]s in the
  /// [Pipeline].
  ///
  /// The [MiddlewareResponse] returned by the last [OnResponseMiddleware] is
  /// returned.
  Future<MiddlewareResponse> onResponse(
    MiddlewareContext context,
    MiddlewareResponse response,
  ) async {
    MiddlewareResponse res = response;
    MiddlewareContext ctx = context.copyWith(lastResponse: res);
    for (final middleware in _middlewares.reversed) {
      if (middleware is OnResponseMiddleware) {
        res = await (middleware as OnResponseMiddleware)
            .onResponse(ctx, response);
        ctx = context.copyWith(lastResponse: res);
      }
    }
    return res;
  }

  @override
  String toString() {
    final req = <String>[];
    final res = <String>[];
    for (final middleware in _middlewares) {
      if (middleware is OnRequestMiddleware) {
        req.add(middleware.getName());
      }
      if (middleware is OnResponseMiddleware) {
        res.insert(0, middleware.getName());
      }
    }
    return '[Req] -> ${req.join(' -> ')}\n[Res] -> ${res.join(' -> ')}';
  }
}
