import 'dart:async';

import 'package:checker/resources.dart';
import 'package:flutter/material.dart';

import 'package:checker/common.dart';
import 'package:checker/consts.dart';
import 'package:checker/screens/settings.dart';
import 'package:checker/screens/faq.dart';
import 'package:checker/strings.dart';
import 'package:checker/db.dart';
import 'package:flutter/rendering.dart';
import 'package:meta/meta.dart';

abstract class BaseState<T extends StatefulWidget> extends State<T> {

  /// Класс для работы с бд.
  SqliteHelper helper;

  /// Тип сборки. Определяет, какие брать ресурсы (цвета, картинки)
  String app;

  /// Ожидание ответа от сервера.
  bool loading = false;

  /// Текст ошибки, подставляется в подсказку, либо появляется над текстовым полем.
  String error;

  /// Введенное пользователем значение.
  String merchantID = '';

  BaseState(this.helper, this.app);

  Widget getMainWidget() {
    return new Scaffold(appBar: getAppBar(),
        body: new Stack(children: <Widget>[
          getScreenContent(),
          new Center(child: loading ? new CircularProgressIndicator() : null)
        ]));
  }

  Widget getBackground() {
    return new Container(
        decoration: new BoxDecoration(
            image: new DecorationImage(
                image: new ExactAssetImage(Resources.getSplash(app)),
                fit:  BoxFit.cover)));
  }

  /// Возвращает контейнер с всеми виджетами экрана.
  Widget getScreenContent();

  bool isAutomaticallyImplyLeading();

  /// Возвращает заголовок для AppBar
  String getTitle() {
    return null;
  }

  AppBar getAppBar() {
    return new AppBar(title: new Container(
        margin: new EdgeInsets.only(left: 16.0),
        child: new Text(getTitle(), style: new TextStyle(fontSize: 18.0))),
        automaticallyImplyLeading: isAutomaticallyImplyLeading(),
        backgroundColor: Resources.getPrimaryColor(app),  actions: getMenuButtons());
  }

  List<Widget> getMenuButtons() {

    List<Widget> menuItemList = [];
    menuItemList.add(new PopupMenuItem(
        value: 0,
        child: getMenuItem(settings_png, StringsLocalization.settings())
    ));

    menuItemList.add(new PopupMenuItem(
      value: 1,
      child: getMenuItem(help_png, StringsLocalization.help())
    ));

    if (Theme.of(context).platform != TargetPlatform.iOS) {
      menuItemList.add(new PopupMenuItem(
        value: 2,
        child: getMenuItem(exit_png, StringsLocalization.exit())
      ));
    }

    return <Widget>[
      new PopupMenuButton<int>(
          onSelected: onOptionsItemClick,
          itemBuilder: (BuildContext context) {
            return menuItemList;
          }
      )
    ];
  }

  void onOptionsItemClick(int index) {
    switch (index) {
      case 0: {
        new Future.delayed(const Duration(milliseconds: 200), () {
          var route = new MaterialPageRoute<String>(builder: (BuildContext context) => new SettingsScreen(helper, app, false), fullscreenDialog: true);
          Navigator.of(context).push(route).then((token) {
            if (token != null) {
              Navigator.of(context).pop(token);
            }
          });
        });
        break;
      }
      case 1: {
        new Future.delayed(const Duration(milliseconds: 200), () {
          var route = new MaterialPageRoute<String>(builder: (BuildContext context) => new FAQScreen(helper, app), fullscreenDialog: true);
          Navigator.of(context).push(route);
        });
        break;
      }
      case 2: {
        platform.invokeMethod('finish');
        break;
      }
    }
  }

  /// Возвращает пункт меню (Картинка с текстом)
  Widget getMenuItem(String image, String text) {
    return new Row(children: [
      new Image.asset(image, width: 28.0, height: 28.0),
      new Container(padding: new EdgeInsets.only(left: 8.0), child: new Text(text))
    ]);
  }

  /// Возврвщает контейнер, внутри которого Text с подсказкой.
  Widget getHintLabel() {
    double horizontalMargin = 8.0;
    return new Container(margin: new EdgeInsets.only(top: horizontalMargin, bottom: horizontalMargin, left: verticalMargin, right: verticalMargin),
        child: new Row(crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[new Text(getHintOrError(), textAlign: TextAlign.left,
                style: new TextStyle(fontWeight: FontWeight.w300, color: error == null ? greyTextColor : Resources.getPrimaryColor(app), fontSize: 14.0))]));
  }

  /// Возвращает подсказку, либо ошибку, если введенные в поле ввода данные неверны.
  String getHintOrError() {
    if (merchantID.length == 0 && error == null) {
      return ' ';
    } else if (error != null) {
      return error;
    } else {
      return getHintString();
    }
  }

  /// Возвращает текст подсказки для поля ввода.
  /// Должен быть переопределен на экранах, на которых есть поле ввода.
  String getHintString() {
    return null;
  }

  /// Смена состояния экрана при изменении текста в поле ввода.
  void handleUserInput(String text) {
    setState(() {
      merchantID = text;
    });
  }

  /// Метод возвращает контейнер с полем ввода внутри.
  Widget getInputField() {
    return new Container(margin: new EdgeInsets.only(left: verticalMargin, right: verticalMargin),
        padding: getInputFieldContainerPadding(),
        decoration: getInputFieldContainerDecoration(),
        child: getTextWidget()
    );
  }

  getItemToggle(String title, String description) {
    if (title.length == 0) {
      title = description.substring(0,30) + " ...";
    }
    return new Container(
      margin: new EdgeInsets.only(left: 5.0, right: 5.0, top: 5.0),
      child: new Card(
        child: new ExpansionTile(
          title: new Text(
            title,
            style: Theme.of(context).textTheme.button.copyWith(
              fontWeight: FontWeight.bold,
              color: faqTitlesColor
            )
          ),
          children: [
            new Container(
              margin: new EdgeInsets.only(left: 20.0, right: 20.0),
              padding: new EdgeInsets.only(top: 0.0, bottom: 10.0),
              child: new Text(description),
            )
          ]
        )
      )
    );
  }

  // Возвращает контейнер с раскрывающимся элементом и переключателем
  // title - название, description - описание, isSet - состояние переключаиеля
  // handler - обработчик события переключения

  getItemToggleSwitch(String title, String description, bool isSet, handler) {

    if (title.length == 0) {
      title = description.substring(0,25) + " ...";
    }

    return new Container(
      key: new Key(new DateTime.now().millisecondsSinceEpoch.toString()),
      margin: new EdgeInsets.only(left: 5.0, right: 5.0, top: 5.0),
      child: new Card(
        child: new ExpansionTile(
          title: new SwitchListTile(
            title: new Text(
              title,
              style: Theme.of(context).textTheme.button.copyWith(
                fontWeight: FontWeight.bold,
                color: faqTitlesColor
              )
            ),
            value: isSet,
            onChanged: handler
          ),
          children: [
            new Container(
              margin: new EdgeInsets.only(left: 20.0, right: 20.0),
              padding: new EdgeInsets.only(top: 0.0, bottom: 10.0),
              child: new Text(description),
            ),
          ]
        )
      )
    );
  }

  /// Метод возвращает контейнер только с названием элемента
  Widget getItemTitle(String title) {
    return new Container(
      padding: new EdgeInsets.only(
        left: verticalMargin,
        right: verticalMargin,
        top: 18.0
      ),
      child: new Column(
        children: <Widget>[
          new Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: getDescriptionWidget(title)
          ),
        ]
      )
    );
  }

  /// Возвращает поле ввода.
  /// Переопределяется для использования на экранах регистрации и проведения покупки.
  Widget getTextWidget() {
    return null;
  }

  /// Возвращат паддинги для поля ввода.
  EdgeInsets getInputFieldContainerPadding() {
    const double verticalPadding = 12.0;
    const double horizontalPadding = 16.0;
    return new EdgeInsets.only(top: verticalPadding,
        bottom: verticalPadding,
        left: horizontalPadding,
        right: horizontalPadding);
  }

  /// Метод возвращает BoxDecoration для _getDecoratedInputField
  BoxDecoration getInputFieldContainerDecoration() {
    return new BoxDecoration(color: inputFieldBackground,
        border: new Border.all(color: textBorderColor, width: 1.0),
        borderRadius: new BorderRadius.all(new Radius.circular(4.0)));
  }

  /// Возвращает выпуклую залитую фирменным цветом кнопку
  Widget buildRaisedButton(String text, VoidCallback onPressed) {
    return new RaisedButton(child: new Text(text,
        style: new TextStyle(color: Colors.white)),
        onPressed: onPressed,
        color: Resources.getButtonColor(app));
  }

  /// Метод возвращает контейнер с отступами, который содержит картинку с логотипом.
  Widget getLogo() {
    double containerHeight = 92.0;
    double imageWidth = 156.0;
    return new Container(height: containerHeight, child: new Image.asset(Resources.getLogo(app), width: imageWidth));
  }

  /// Возвращает текстовое поле, с однострочным пояснением над ним.
  Widget getValueWithDescription(String title, String value) {
    return new Container(padding: new EdgeInsets.only(left: verticalMargin, right: verticalMargin, top: 18.0),
        child: new Column(children: <Widget>[
          new Row(crossAxisAlignment: CrossAxisAlignment.start, children: getDescriptionWidget(title)),
          new Row(crossAxisAlignment: CrossAxisAlignment.start, children: getValueWidget(value))
        ]));
  }

  /// Возвращает список, единственный элемент которого - Text с заголовком для текстового поля.
  List<Widget> getDescriptionWidget(String title) {
    return <Widget>[new Text(title, textAlign: TextAlign.left, style: new TextStyle(color: greyTextColor, fontSize: 14.0))];
  }

  /// Возвращает список, единственный элемент которого - Text с информацией (размер скидки, сумма проведенной покупки).
  List<Widget> getValueWidget(String value) {
    return <Widget>[new Expanded(child: new Text(value, textAlign: TextAlign.left, style: new TextStyle(color: Colors.black, fontSize: 20.0)))];
  }

  /// Возвращает кнопку, обернутую набором специфичных контейнеров.
  Widget wrapButton(EdgeInsets margin, Widget widget) {
    return new Container(margin: margin, height: buttonHeight, child: new Row(children: <Widget>[new Expanded(child: widget)]));
  }
}

class EnsureVisibleWhenFocused extends StatefulWidget {
  const EnsureVisibleWhenFocused({
    Key key,
    @required this.child,
    @required this.focusNode,
    this.curve: Curves.ease,
    this.duration: const Duration(milliseconds: 100),
  }) : super(key: key);

  /// The node we will monitor to determine if the child is focused
  final FocusNode focusNode;

  /// The child widget that we are wrapping
  final Widget child;

  /// The curve we will use to scroll ourselves into view.
  ///
  /// Defaults to Curves.ease.
  final Curve curve;

  /// The duration we will use to scroll ourselves into view
  ///
  /// Defaults to 100 milliseconds.
  final Duration duration;

  EnsureVisibleWhenFocusedState createState() => new EnsureVisibleWhenFocusedState();
}

class EnsureVisibleWhenFocusedState extends State<EnsureVisibleWhenFocused> {
  @override
  void initState() {
    super.initState();
    widget.focusNode.addListener(_ensureVisible);
  }

  @override
  void dispose() {
    super.dispose();
    widget.focusNode.removeListener(_ensureVisible);
  }

  Future<Null> _ensureVisible() async {
    // Wait for the keyboard to come into view
    // TODO: position doesn't seem to notify listeners when metrics change,
    // perhaps a NotificationListener around the scrollable could avoid
    // the need insert a delay here.
    await new Future.delayed(const Duration(milliseconds: 1000));

    if (!widget.focusNode.hasFocus)
      return;

    final RenderObject object = context.findRenderObject();
    final RenderAbstractViewport viewport = RenderAbstractViewport.of(object);
    assert(viewport != null);

    ScrollableState scrollableState = Scrollable.of(context);
    assert(scrollableState != null);

    ScrollPosition position = scrollableState.position;
    double alignment;
    if (position.pixels > viewport.getOffsetToReveal(object, 0.0)) {
      // Move down to the top of the viewport
      alignment = 0.0;
    } else if (position.pixels < viewport.getOffsetToReveal(object, 1.0)) {
      // Move up to the bottom of the viewport
      alignment = 1.0;
    } else {
      // No scrolling is necessary to reveal the child
      return;
    }
    position.ensureVisible(
      object,
      alignment: alignment,
      duration: widget.duration,
      curve: widget.curve,
    );
  }

  Widget build(BuildContext context) => widget.child;
}
