// Copyright 2015 The Chromium 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:async';
import 'dart:ui' as ui show Image;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_sprites/flutter_sprites.dart';

ImageMap _images;
SpriteSheet _sprites;

enum WeatherType {
  sun,
  rain,
  snow
}

class WeatherDemo extends StatefulWidget {
  WeatherDemo({ Key key }) : super(key: key);

  @override
  _WeatherDemoState createState() => new _WeatherDemoState();
}

class _WeatherDemoState extends State<WeatherDemo> {

  Future<Null> _loadAssets(AssetBundle bundle) async {
    _images = new ImageMap(bundle);
    await _images.load(<String>[
      'packages/flutter_gallery_assets/clouds-0.png',
      'packages/flutter_gallery_assets/clouds-1.png',
      'packages/flutter_gallery_assets/ray.png',
      'packages/flutter_gallery_assets/sun.png',
      'packages/flutter_gallery_assets/weathersprites.png',
      'packages/flutter_gallery_assets/icon-sun.png',
      'packages/flutter_gallery_assets/icon-rain.png',
      'packages/flutter_gallery_assets/icon-snow.png'
    ]);

    String json = await DefaultAssetBundle.of(context).loadString('packages/flutter_gallery_assets/weathersprites.json');
    _sprites = new SpriteSheet(_images['packages/flutter_gallery_assets/weathersprites.png'], json);
  }

  @override
  void initState() {
    super.initState();

    AssetBundle bundle = DefaultAssetBundle.of(context);
    _loadAssets(bundle).then((_) {
      setState(() {
        assetsLoaded = true;
        weatherWorld = new WeatherWorld();
      });
    });
  }

  bool assetsLoaded = false;

  WeatherWorld weatherWorld;

  @override
  Widget build(BuildContext context) {
    if (!assetsLoaded) {
      return new Scaffold(
        appBar: new AppBar(
          title: new Text("Weather")
        ),
        body: new Container(
          decoration: new BoxDecoration(
            backgroundColor: const Color(0xff4aaafb)
          )
        )
      );
    }

    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Weather")
      ),
      body: new Material(
        child: new Stack(
          children: <Widget>[
            new SpriteWidget(weatherWorld),
            new Align(
              alignment: new FractionalOffset(0.5, 0.8),
              child: new Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  new WeatherButton(
                    onPressed: () {
                      setState(() {
                        weatherWorld.weatherType = WeatherType.sun;
                      });
                    },
                    selected: weatherWorld.weatherType == WeatherType.sun,
                    icon: "packages/flutter_gallery_assets/icon-sun.png"
                  ),
                  new WeatherButton(
                    onPressed: () {
                      setState(() {
                        weatherWorld.weatherType = WeatherType.rain;
                      });
                    },
                    selected: weatherWorld.weatherType == WeatherType.rain,
                    icon: "packages/flutter_gallery_assets/icon-rain.png"
                  ),
                  new WeatherButton(
                    onPressed: () {
                      setState(() {
                        weatherWorld.weatherType = WeatherType.snow;
                      });
                    },
                    selected: weatherWorld.weatherType == WeatherType.snow,
                    icon: "packages/flutter_gallery_assets/icon-snow.png"
                  )
                ]
              )
            )
          ]
        )
      )
    );
  }
}

const double _kWeatherButtonSize = 56.0;
const double _kWeatherIconSize = 36.0;

class WeatherButton extends StatelessWidget {
  WeatherButton({ this.icon, this.selected, this.onPressed, Key key }) : super(key: key);

  final String icon;
  final bool selected;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    Color color;
    if (selected)
      color = Theme.of(context).primaryColor;
    else
      color = const Color(0x33000000);

    return new Padding(
      padding: const EdgeInsets.all(15.0),
      child: new Material(
        color: color,
        type: MaterialType.circle,
        elevation: 0,
        child: new Container(
          width: _kWeatherButtonSize,
          height: _kWeatherButtonSize,
          child: new InkWell(
            onTap: onPressed,
            child: new Center(
              child: new AssetImage(
                name: icon,
                width: _kWeatherIconSize,
                height: _kWeatherIconSize
              )
            )
          )
        )
      )
    );
  }
}

const List<Color> _kBackgroundColorsTop = const <Color>[
  const Color(0xff5ebbd5),
  const Color(0xff0b2734),
  const Color(0xffcbced7)
];

const List<Color> _kBackgroundColorsBottom = const <Color>[
  const Color(0xff4aaafb),
  const Color(0xff4c5471),
  const Color(0xffe0e3ec)
];

class WeatherWorld extends NodeWithSize {
  WeatherWorld() : super(const Size(2048.0, 2048.0)) {
    _background = new GradientNode(
      this.size,
      _kBackgroundColorsTop[0],
      _kBackgroundColorsBottom[0]
    );
    addChild(_background);

    _cloudsSharp = new CloudLayer(
      image: _images['packages/flutter_gallery_assets/clouds-0.png'],
      rotated: false,
      dark: false,
      loopTime: 20.0
    );
    addChild(_cloudsSharp);

    _cloudsDark = new CloudLayer(
      image: _images['packages/flutter_gallery_assets/clouds-1.png'],
      rotated: true,
      dark: true,
      loopTime: 40.0
    );
    addChild(_cloudsDark);

    _cloudsSoft = new CloudLayer(
      image: _images['packages/flutter_gallery_assets/clouds-1.png'],
      rotated: false,
      dark: false,
      loopTime: 60.0
    );
    addChild(_cloudsSoft);

    _sun = new Sun();
    _sun.position = const Point(1024.0, 1024.0);
    _sun.scale = 1.5;
    addChild(_sun);

    _rain = new Rain();
    addChild(_rain);

    _snow = new Snow();
    addChild(_snow);
  }

  GradientNode _background;
  CloudLayer _cloudsSharp;
  CloudLayer _cloudsSoft;
  CloudLayer _cloudsDark;
  Sun _sun;
  Rain _rain;
  Snow _snow;

  WeatherType get weatherType => _weatherType;

  WeatherType _weatherType = WeatherType.sun;

  void set weatherType(WeatherType weatherType) {
    if (weatherType == _weatherType)
      return;

    _weatherType = weatherType;

    // Fade the background
    _background.actions.stopAll();

    _background.actions.run(new ActionTween(
      (Color a) => _background.colorTop = a,
      _background.colorTop,
      _kBackgroundColorsTop[weatherType.index],
      1.0
    ));

    _background.actions.run(new ActionTween(
      (Color a) => _background.colorBottom = a,
      _background.colorBottom,
      _kBackgroundColorsBottom[weatherType.index],
      1.0
    ));

    _cloudsDark.active = weatherType != WeatherType.sun;
    _sun.active = weatherType == WeatherType.sun;
    _rain.active = weatherType == WeatherType.rain;
    _snow.active = weatherType == WeatherType.snow;
  }

  @override
  void spriteBoxPerformedLayout() {
    _sun.position = spriteBox.visibleArea.topLeft + const Offset(350.0, 180.0);
  }
}

class GradientNode extends NodeWithSize {
  GradientNode(Size size, this.colorTop, this.colorBottom) : super(size);

  Color colorTop;
  Color colorBottom;

  @override
  void paint(Canvas canvas) {
    applyTransformForPivot(canvas);

    Rect rect = Point.origin & size;
    Paint gradientPaint = new Paint()..shader = new LinearGradient(
      begin: FractionalOffset.topLeft,
      end: FractionalOffset.bottomLeft,
      colors: <Color>[colorTop, colorBottom],
      stops: <double>[0.0, 1.0]
    ).createShader(rect);

    canvas.drawRect(rect, gradientPaint);
  }
}

class CloudLayer extends Node {
  CloudLayer({ ui.Image image, bool dark, bool rotated, double loopTime }) {
    _sprites.add(_createSprite(image, dark, rotated));
    _sprites[0].position = const Point(1024.0, 1024.0);
    addChild(_sprites[0]);

    _sprites.add(_createSprite(image, dark, rotated));
    _sprites[1].position = const Point(3072.0, 1024.0);
    addChild(_sprites[1]);

    actions.run(new ActionRepeatForever(
      new ActionTween(
        (Point a) => position = a,
        Point.origin,
        const Point(-2048.0, 0.0),
        loopTime)
    ));
  }

  List<Sprite> _sprites = <Sprite>[];

  Sprite _createSprite(ui.Image image, bool dark, bool rotated) {
    Sprite sprite = new Sprite.fromImage(image);

    if (rotated)
      sprite.scaleX = -1.0;

    if (dark) {
      sprite.colorOverlay = const Color(0xff000000);
      sprite.opacity = 0.0;
    }

    return sprite;
  }

  void set active(bool active) {
    double opacity;
    if (active) opacity = 1.0;
    else opacity = 0.0;

    for (Sprite sprite in _sprites) {
      sprite.actions.stopAll();
      sprite.actions.run(new ActionTween(
        (double a) => sprite.opacity = a,
        sprite.opacity,
        opacity,
        1.0
      ));
    }
  }
}

const double _kNumSunRays = 50.0;

class Sun extends Node {
  Sun() {
    _sun = new Sprite.fromImage(_images['packages/flutter_gallery_assets/sun.png']);
    _sun.scale = 4.0;
    _sun.transferMode = TransferMode.plus;
    addChild(_sun);

    _rays = <Ray>[];
    for (int i = 0; i < _kNumSunRays; i += 1) {
      Ray ray = new Ray();
      addChild(ray);
      _rays.add(ray);
    }
  }

  Sprite _sun;
  List<Ray> _rays;

  void set active(bool active) {
    actions.stopAll();

    double targetOpacity;
    if (!active) targetOpacity = 0.0;
    else targetOpacity = 1.0;

    actions.run(
      new ActionTween(
        (double a) => _sun.opacity = a,
        _sun.opacity,
        targetOpacity,
        2.0
      )
    );

    if (active) {
      for (Ray ray in _rays) {
        actions.run(new ActionSequence([
          new ActionDelay(1.5),
          new ActionTween(
            (double a) => ray.opacity = a,
            ray.opacity,
            ray.maxOpacity,
            1.5
          )
        ]));
      }
    } else {
      for (Ray ray in _rays) {
        actions.run(new ActionTween(
          (double a) => ray.opacity = a,
          ray.opacity,
          0.0,
          0.2
        ));
      }
    }
  }
}

class Ray extends Sprite {
  double _rotationSpeed;
  double maxOpacity;

  Ray() : super.fromImage(_images['packages/flutter_gallery_assets/ray.png']) {
    pivot = const Point(0.0, 0.5);
    transferMode = TransferMode.plus;
    rotation = randomDouble() * 360.0;
    maxOpacity = randomDouble() * 0.2;
    opacity = maxOpacity;
    scaleX = 2.5 + randomDouble();
    scaleY = 0.3;
    _rotationSpeed = randomSignedDouble() * 2.0;

    // Scale animation
    double scaleTime = randomSignedDouble() * 2.0 + 4.0;

    actions.run(new ActionRepeatForever(
      new ActionSequence([
        new ActionTween((double a) => scaleX = a, scaleX, scaleX * 0.5, scaleTime),
        new ActionTween((double a) => scaleX = a, scaleX * 0.5, scaleX, scaleTime)
      ])
    ));
  }

  @override
  void update(double dt) {
    rotation += dt * _rotationSpeed;
  }
}

class Rain extends Node {
  Rain() {
    _addParticles(1.0);
    _addParticles(1.5);
    _addParticles(2.0);
  }

  List<ParticleSystem> _particles = <ParticleSystem>[];

  void _addParticles(double distance) {
    ParticleSystem particles = new ParticleSystem(
      _sprites['raindrop.png'],
      transferMode: TransferMode.srcATop,
      posVar: const Point(1300.0, 0.0),
      direction: 90.0,
      directionVar: 0.0,
      speed: 1000.0 / distance,
      speedVar: 100.0 / distance,
      startSize: 1.2 / distance,
      startSizeVar: 0.2 / distance,
      endSize: 1.2 / distance,
      endSizeVar: 0.2 / distance,
      life: 1.5 * distance,
      lifeVar: 1.0 * distance
    );
    particles.position = const Point(1024.0, -200.0);
    particles.rotation = 10.0;
    particles.opacity = 0.0;

    _particles.add(particles);
    addChild(particles);
  }

  void set active(bool active) {
    actions.stopAll();
    for (ParticleSystem system in _particles) {
      if (active) {
        actions.run(
          new ActionTween(
            (double a) => system.opacity = a,
            system.opacity,
            1.0,
            2.0
        ));
      } else {
        actions.run(
          new ActionTween(
            (double a) => system.opacity = a,
            system.opacity,
            0.0,
            0.5
        ));
      }
    }
  }
}

class Snow extends Node {
  Snow() {
    _addParticles(_sprites['flake-0.png'], 1.0);
    _addParticles(_sprites['flake-1.png'], 1.0);
    _addParticles(_sprites['flake-2.png'], 1.0);

    _addParticles(_sprites['flake-3.png'], 1.5);
    _addParticles(_sprites['flake-4.png'], 1.5);
    _addParticles(_sprites['flake-5.png'], 1.5);

    _addParticles(_sprites['flake-6.png'], 2.0);
    _addParticles(_sprites['flake-7.png'], 2.0);
    _addParticles(_sprites['flake-8.png'], 2.0);
  }

  List<ParticleSystem> _particles = <ParticleSystem>[];

  void _addParticles(Texture texture, double distance) {
    ParticleSystem particles = new ParticleSystem(
      texture,
      transferMode: TransferMode.srcATop,
      posVar: const Point(1300.0, 0.0),
      direction: 90.0,
      directionVar: 0.0,
      speed: 150.0 / distance,
      speedVar: 50.0 / distance,
      startSize: 1.0 / distance,
      startSizeVar: 0.3 / distance,
      endSize: 1.2 / distance,
      endSizeVar: 0.2 / distance,
      life: 20.0 * distance,
      lifeVar: 10.0 * distance,
      emissionRate: 2.0,
      startRotationVar: 360.0,
      endRotationVar: 360.0,
      radialAccelerationVar: 10.0 / distance,
      tangentialAccelerationVar: 10.0 / distance
    );
    particles.position = const Point(1024.0, -50.0);
    particles.opacity = 0.0;

    _particles.add(particles);
    addChild(particles);
  }

  void set active(bool active) {
    actions.stopAll();
    for (ParticleSystem system in _particles) {
      if (active) {
        actions.run(
          new ActionTween((double a) => system.opacity = a, system.opacity, 1.0, 2.0
        ));
      } else {
        actions.run(
          new ActionTween((double a) => system.opacity = a, system.opacity, 0.0, 0.5
        ));
      }
    }
  }
}