// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:math';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';

// Adapted from test case submitted in
// https://github.com/flutter/flutter/issues/92366
// Converted to use fixed data rather than reading a waveform file
class VeryLongPictureScrollingPerf extends StatefulWidget {
  const VeryLongPictureScrollingPerf({super.key});

  @override
  State createState() => VeryLongPictureScrollingPerfState();
}

class VeryLongPictureScrollingPerfState extends State<VeryLongPictureScrollingPerf> {
  bool consolidate = false;
  bool useList = false;
  Int16List waveData = loadGraph();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: <Widget>[
          Row(
            children: <Widget>[
              const Text('list:'),
              Checkbox(value: useList, onChanged: (bool? value) => setState(() {
                useList = value!;
              }),),
            ],
          ),
          Row(
            children: <Widget>[
              const Text('consolidate:'),
              Checkbox(value: consolidate, onChanged: (bool? value) => setState(() {
                consolidate = value!;
              }),),
            ],
          ),
        ],
      ),
      backgroundColor: Colors.transparent,
      body: SizedBox(
        width: MediaQuery.of(context).size.width,
        height: MediaQuery.of(context).size.height,
        child: useList
            ? ListView.builder(
          key: const ValueKey<String>('vlp_list_view_scrollable'),
          scrollDirection: Axis.horizontal,
          clipBehavior: Clip.none,
          itemCount: (waveData.length / 200).ceil(),
          itemExtent: 100,
          itemBuilder: (BuildContext context, int index) => CustomPaint(
              painter: PaintSomeTest(
                waveData: waveData,
                from: index * 200,
                to: min((index + 1) * 200, waveData.length - 1),
              )
          ),
        )
            : SingleChildScrollView(
          key: const ValueKey<String>('vlp_single_child_scrollable'),
          scrollDirection: Axis.horizontal,
          child: SizedBox(
            width: MediaQuery.of(context).size.width * 20,
            height: MediaQuery.of(context).size.height,
            child: RepaintBoundary(
              child: CustomPaint(
                isComplex: true,
                painter: PaintTest(
                  consolidate: consolidate,
                  waveData: waveData,
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class PaintTest extends CustomPainter {
  const PaintTest({
    required this.consolidate,
    required this.waveData,
  });

  final bool consolidate;
  final Int16List waveData;

  @override
  void paint(Canvas canvas, Size size) {
    final double height = size.height;
    double x = 0;
    const double strokeSize = .5;
    const double zoomFactor = .5;

    final Paint paintPos = Paint()
      ..color = Colors.pink
      ..strokeWidth = strokeSize
      ..isAntiAlias = false
      ..style = PaintingStyle.stroke;

    final Paint paintNeg = Paint()
      ..color = Colors.pink
      ..strokeWidth = strokeSize
      ..isAntiAlias = false
      ..style = PaintingStyle.stroke;

    final Paint paintZero = Paint()
      ..color = Colors.green
      ..strokeWidth = strokeSize
      ..isAntiAlias = false
      ..style = PaintingStyle.stroke;

    int index = 0;
    Paint? listPaint;
    final Float32List offsets = Float32List(consolidate ? waveData.length * 4 : 4);
    int used = 0;
    for (index = 0; index < waveData.length; index++) {
      Paint curPaint;
      Offset p1;
      if (waveData[index].isNegative) {
        curPaint = paintPos;
        p1 = Offset(x, height * 1 / 2 - waveData[index] / 32768 * (height / 2));
      } else if (waveData[index] == 0) {
        curPaint = paintZero;
        p1 = Offset(x, height * 1 / 2 + 1);
      } else {
        curPaint = (waveData[index] == 0) ? paintZero : paintNeg;
        p1 = Offset(x, height * 1 / 2 - waveData[index] / 32767 * (height / 2));
      }
      final Offset p0 = Offset(x, height * 1 / 2);
      if (consolidate) {
        if (listPaint != null && listPaint != curPaint) {
          canvas.drawRawPoints(PointMode.lines, offsets.sublist(0, used), listPaint);
          used = 0;
        }
        listPaint = curPaint;
        offsets[used++] = p0.dx;
        offsets[used++] = p0.dy;
        offsets[used++] = p1.dx;
        offsets[used++] = p1.dy;
      } else {
        canvas.drawLine(p0, p1, curPaint);
      }
      x += zoomFactor;
    }
    if (consolidate && used > 0) {
      canvas.drawRawPoints(PointMode.lines, offsets.sublist(0, used), listPaint!);
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return oldDelegate is! PaintTest ||
        oldDelegate.consolidate != consolidate ||
        oldDelegate.waveData != waveData;
  }
}

class PaintSomeTest extends CustomPainter {
  const PaintSomeTest({
    required this.waveData,
    int? from,
    int? to,
  }) : from = from ?? 0, to = to?? waveData.length;

  final Int16List waveData;
  final int from;
  final int to;

  @override
  void paint(Canvas canvas, Size size) {
    final double height = size.height;
    double x = 0;
    const double strokeSize = .5;
    const double zoomFactor = .5;

    final Paint paintPos = Paint()
      ..color = Colors.pink
      ..strokeWidth = strokeSize
      ..isAntiAlias = false
      ..style = PaintingStyle.stroke;

    final Paint paintNeg = Paint()
      ..color = Colors.pink
      ..strokeWidth = strokeSize
      ..isAntiAlias = false
      ..style = PaintingStyle.stroke;

    final Paint paintZero = Paint()
      ..color = Colors.green
      ..strokeWidth = strokeSize
      ..isAntiAlias = false
      ..style = PaintingStyle.stroke;

    for (int index = from; index <= to; index++) {
      Paint curPaint;
      Offset p1;
      if (waveData[index].isNegative) {
        curPaint = paintPos;
        p1 = Offset(x, height * 1 / 2 - waveData[index] / 32768 * (height / 2));
      } else if (waveData[index] == 0) {
        curPaint = paintZero;
        p1 = Offset(x, height * 1 / 2 + 1);
      } else {
        curPaint = (waveData[index] == 0) ? paintZero : paintNeg;
        p1 = Offset(x, height * 1 / 2 - waveData[index] / 32767 * (height / 2));
      }
      final Offset p0 = Offset(x, height * 1 / 2);
      canvas.drawLine(p0, p1, curPaint);
      x += zoomFactor;
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return oldDelegate is! PaintSomeTest ||
        oldDelegate.waveData != waveData ||
        oldDelegate.from != from ||
        oldDelegate.to != to;
  }
}

Int16List loadGraph() {
  final Int16List waveData = Int16List(350000);
  final Random r = Random(0x42);
  for (int i = 0; i < waveData.length; i++) {
    waveData[i] = r.nextInt(32768) - 16384;
  }
  return waveData;
}
