// 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 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

enum LerpTarget {
  circle,
  roundedRect,
  rect,
  stadium,
  polygon,
  star,
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
  final OptionModel _model = OptionModel();
  final TextEditingController textController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _model.addListener(_modelChanged);
  }

  @override
  void dispose() {
    super.dispose();
    _model.removeListener(_modelChanged);
  }

  void _modelChanged() {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        key: scaffoldKey,
        appBar: AppBar(
          title: const Text('Star Border'),
          backgroundColor: const Color(0xff323232),
        ),
        body: Column(
          children: <Widget>[
            Container(color: Colors.grey.shade200, child: Options(_model)),
            Expanded(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  Container(
                    key: UniqueKey(),
                    alignment: Alignment.center,
                    width: 300,
                    height: 200,
                    decoration: ShapeDecoration(
                      color: Colors.blue.shade100,
                      shape: lerpBorder(
                        StarBorder.polygon(
                          side: const BorderSide(strokeAlign: StrokeAlign.center, width: 2),
                          sides: _model.points,
                          pointRounding: _model.pointRounding,
                          rotation: _model.rotation,
                          squash: _model.squash,
                        ),
                        _model._lerpTarget,
                        _model._lerpAmount,
                        to: _model.lerpTo,
                      )!,
                    ),
                    child: const Text('Polygon'),
                  ),
                  Container(
                    key: UniqueKey(),
                    alignment: Alignment.center,
                    width: 300,
                    height: 200,
                    decoration: ShapeDecoration(
                      color: Colors.blue.shade100,
                      shape: lerpBorder(
                        StarBorder(
                          side: const BorderSide(strokeAlign: StrokeAlign.center, width: 2),
                          points: _model.points,
                          innerRadiusRatio: _model.innerRadiusRatio,
                          pointRounding: _model.pointRounding,
                          valleyRounding: _model.valleyRounding,
                          rotation: _model.rotation,
                          squash: _model.squash,
                        ),
                        _model._lerpTarget,
                        _model._lerpAmount,
                        to: _model.lerpTo,
                      )!,
                    ),
                    child: const Text('Star'),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class OptionModel extends ChangeNotifier {
  double get pointRounding => _pointRounding;
  double _pointRounding = 0.0;
  set pointRounding(double value) {
    if (value != _pointRounding) {
      _pointRounding = value;
      if (_valleyRounding + _pointRounding > 1) {
        _valleyRounding = 1.0 - _pointRounding;
      }
      notifyListeners();
    }
  }

  double get valleyRounding => _valleyRounding;
  double _valleyRounding = 0.0;
  set valleyRounding(double value) {
    if (value != _valleyRounding) {
      _valleyRounding = value;
      if (_valleyRounding + _pointRounding > 1) {
        _pointRounding = 1.0 - _valleyRounding;
      }
      notifyListeners();
    }
  }

  double get squash => _squash;
  double _squash = 0.0;
  set squash(double value) {
    if (value != _squash) {
      _squash = value;
      notifyListeners();
    }
  }

  double get rotation => _rotation;
  double _rotation = 0.0;
  set rotation(double value) {
    if (value != _rotation) {
      _rotation = value;
      notifyListeners();
    }
  }

  double get innerRadiusRatio => _innerRadiusRatio;
  double _innerRadiusRatio = 0.4;
  set innerRadiusRatio(double value) {
    if (value != _innerRadiusRatio) {
      _innerRadiusRatio = clampDouble(value, 0.0001, double.infinity);
      notifyListeners();
    }
  }

  double get points => _points;
  double _points = 5;
  set points(double value) {
    if (value != _points) {
      _points = value;
      notifyListeners();
    }
  }

  double get lerpAmount => _lerpAmount;
  double _lerpAmount = 0.0;
  set lerpAmount(double value) {
    if (value != _lerpAmount) {
      _lerpAmount = value;
      notifyListeners();
    }
  }

  bool get lerpTo => _lerpTo;
  bool _lerpTo = true;
  set lerpTo(bool value) {
    if (_lerpTo != value) {
      _lerpTo = value;
      notifyListeners();
    }
  }

  LerpTarget get lerpTarget => _lerpTarget;
  LerpTarget _lerpTarget = LerpTarget.circle;
  set lerpTarget(LerpTarget value) {
    if (value != _lerpTarget) {
      _lerpTarget = value;
      notifyListeners();
    }
  }

  void reset() {
    final OptionModel defaultModel = OptionModel();
    _pointRounding = defaultModel.pointRounding;
    _valleyRounding = defaultModel.valleyRounding;
    _rotation = defaultModel.rotation;
    _squash = defaultModel.squash;
    _lerpAmount = defaultModel.lerpAmount;
    _lerpTo = defaultModel.lerpTo;
    _lerpTarget = defaultModel.lerpTarget;
    _innerRadiusRatio = defaultModel._innerRadiusRatio;
    _points = defaultModel.points;
    notifyListeners();
  }
}

class LabeledCheckbox extends StatelessWidget {
  const LabeledCheckbox({super.key, required this.label, this.onChanged, this.value});

  final String label;
  final ValueChanged<bool?>? onChanged;
  final bool? value;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Checkbox(
          onChanged: onChanged,
          value: value,
        ),
        Text(label),
      ],
    );
  }
}

class Options extends StatefulWidget {
  const Options(this.model, {super.key});

  final OptionModel model;

  @override
  State<Options> createState() => _OptionsState();
}

class _OptionsState extends State<Options> {
  @override
  void initState() {
    super.initState();
    widget.model.addListener(_modelChanged);
  }

  @override
  void didUpdateWidget(Options oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.model != oldWidget.model) {
      oldWidget.model.removeListener(_modelChanged);
      widget.model.addListener(_modelChanged);
    }
  }

  @override
  void dispose() {
    super.dispose();
    widget.model.removeListener(_modelChanged);
  }

  void _modelChanged() {
    setState(() {});
  }

  double sliderValue = 0.0;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(5.0, 0.0, 5.0, 10.0),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Row(
            children: <Widget>[
              Expanded(
                child: ControlSlider(
                  label: 'Point Rounding',
                  value: widget.model.pointRounding,
                  onChanged: (double value) {
                    widget.model.pointRounding = value;
                  },
                ),
              ),
              Expanded(
                child: ControlSlider(
                  label: 'Valley Rounding',
                  value: widget.model.valleyRounding,
                  onChanged: (double value) {
                    widget.model.valleyRounding = value;
                  },
                ),
              ),
            ],
          ),
          Row(
            children: <Widget>[
              Expanded(
                child: ControlSlider(
                  label: 'Squash',
                  value: widget.model.squash,
                  onChanged: (double value) {
                    widget.model.squash = value;
                  },
                ),
              ),
              Expanded(
                child: ControlSlider(
                  label: 'Rotation',
                  value: widget.model.rotation,
                  max: 360,
                  onChanged: (double value) {
                    widget.model.rotation = value;
                  },
                ),
              ),
            ],
          ),
          Row(
            children: <Widget>[
              Expanded(
                child: Row(
                  children: <Widget>[
                    Expanded(
                      child: ControlSlider(
                        label: 'Points',
                        value: widget.model.points,
                        min: 2,
                        max: 20,
                        onChanged: (double value) {
                          widget.model.points = value;
                        },
                      ),
                    ),
                    OutlinedButton(
                        child: const Text('Nearest'),
                        onPressed: () {
                          widget.model.points = widget.model.points.roundToDouble();
                        }),
                  ],
                ),
              ),
              Expanded(
                child: ControlSlider(
                  label: 'Inner Radius',
                  value: widget.model.innerRadiusRatio,
                  onChanged: (double value) {
                    widget.model.innerRadiusRatio = value;
                  },
                ),
              ),
            ],
          ),
          Row(
            children: <Widget>[
              Expanded(
                flex: 2,
                child: Padding(
                  padding: const EdgeInsetsDirectional.only(end: 8.0),
                  child: ControlSlider(
                    label: 'Lerp',
                    value: widget.model.lerpAmount,
                    onChanged: (double value) {
                      widget.model.lerpAmount = value;
                    },
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsetsDirectional.only(start: 8.0, end: 20.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Row(children: <Widget>[
                      Radio<bool>(
                          value: true,
                          groupValue: widget.model.lerpTo,
                          onChanged: (bool? value) {
                            widget.model.lerpTo = value!;
                          }),
                      const Text('To'),
                    ]),
                    Row(children: <Widget>[
                      Radio<bool>(
                          value: false,
                          groupValue: widget.model.lerpTo,
                          onChanged: (bool? value) {
                            widget.model.lerpTo = value!;
                          }),
                      const Text('From'),
                    ])
                  ],
                ),
              ),
              Expanded(
                child: Row(
                  children: <Widget>[
                    Expanded(
                      child: DropdownButton<LerpTarget>(
                        items: LerpTarget.values.map<DropdownMenuItem<LerpTarget>>((LerpTarget target) {
                          return DropdownMenuItem<LerpTarget>(value: target, child: Text(target.name));
                        }).toList(),
                        value: widget.model.lerpTarget,
                        onChanged: (LerpTarget? value) {
                          if (value == null) {
                            return;
                          }
                          widget.model.lerpTarget = value;
                        },
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
          ElevatedButton(
            onPressed: () {
              widget.model.reset();
              sliderValue = 0.0;
            },
            child: const Text('Reset'),
          ),
        ],
      ),
    );
  }
}

class ControlSlider extends StatelessWidget {
  const ControlSlider({
    super.key,
    required this.label,
    required this.value,
    required this.onChanged,
    this.min = 0.0,
    this.max = 1.0,
  });

  final String label;
  final double value;
  final void Function(double value) onChanged;
  final double min;
  final double max;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(4.0),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Text(label),
          Expanded(
            child: Slider(
              label: value.toStringAsFixed(1),
              onChanged: onChanged,
              min: min,
              max: max,
              value: value,
            ),
          ),
          Text(
            value.toStringAsFixed(3),
          ),
        ],
      ),
    );
  }
}

const Color lerpToColor = Colors.red;
const BorderSide lerpToBorder = BorderSide(width: 5, color: lerpToColor);

ShapeBorder? lerpBorder(StarBorder border, LerpTarget target, double t, {bool to = true}) {
  switch (target) {
    case LerpTarget.circle:
      if (to) {
        return border.lerpTo(const CircleBorder(side: lerpToBorder), t);
      } else {
        return border.lerpFrom(const CircleBorder(side: lerpToBorder), t);
      }
    case LerpTarget.roundedRect:
      if (to) {
        return border.lerpTo(
          const RoundedRectangleBorder(
            side: lerpToBorder,
            borderRadius: BorderRadius.all(
              Radius.circular(10),
            ),
          ),
          t,
        );
      } else {
        return border.lerpFrom(
          const RoundedRectangleBorder(
            side: lerpToBorder,
            borderRadius: BorderRadius.all(
              Radius.circular(10),
            ),
          ),
          t,
        );
      }
    case LerpTarget.rect:
      if (to) {
        return border.lerpTo(const RoundedRectangleBorder(side: lerpToBorder), t);
      } else {
        return border.lerpFrom(const RoundedRectangleBorder(side: lerpToBorder), t);
      }
    case LerpTarget.stadium:
      if (to) {
        return border.lerpTo(const StadiumBorder(side: lerpToBorder), t);
      } else {
        return border.lerpFrom(const StadiumBorder(side: lerpToBorder), t);
      }
    case LerpTarget.polygon:
      if (to) {
        return border.lerpTo(const StarBorder.polygon(side: lerpToBorder, sides: 4), t);
      } else {
        return border.lerpFrom(const StarBorder.polygon(side: lerpToBorder, sides: 4), t);
      }
    case LerpTarget.star:
      if (to) {
        return border.lerpTo(const StarBorder(side: lerpToBorder, innerRadiusRatio: .5), t);
      } else {
        return border.lerpFrom(const StarBorder(side: lerpToBorder, innerRadiusRatio: .5), t);
      }
  }
}
