import 'package:android_intent/android_intent.dart';
import 'package:exifdart/exifdart.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/double_extension.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file/data_source.dart';
import 'package:nc_photos/exception_util.dart' as exception_util;
import 'package:nc_photos/iterable_extension.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/platform.dart'
    if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
import 'package:nc_photos/platform/features.dart' as features;
import 'package:nc_photos/platform/k.dart' as platform_k;
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/use_case/remove.dart';
import 'package:nc_photos/use_case/update_album.dart';
import 'package:nc_photos/use_case/update_property.dart';
import 'package:nc_photos/widget/album_picker_dialog.dart';
import 'package:nc_photos/widget/photo_date_time_edit_dialog.dart';
import 'package:path/path.dart';
import 'package:tuple/tuple.dart';

class ViewerDetailPane extends StatefulWidget {
  const ViewerDetailPane({
    Key key,
    @required this.account,
    @required this.file,
  }) : super(key: key);

  @override
  createState() => _ViewerDetailPaneState();

  final Account account;
  final File file;
}

class _ViewerDetailPaneState extends State<ViewerDetailPane> {
  @override
  initState() {
    super.initState();

    _dateTime = widget.file.bestDateTime.toLocal();
    if (widget.file.metadata == null) {
      _log.info("[initState] Metadata missing in File");
    } else {
      _log.info("[initState] Metadata exists in File");
      if (widget.file.metadata.exif != null) {
        _initMetadata();
      }
    }
  }

  @override
  build(BuildContext context) {
    final dateStr = DateFormat(DateFormat.YEAR_ABBR_MONTH_DAY,
            Localizations.localeOf(context).languageCode)
        .format(_dateTime);
    final timeStr = DateFormat(DateFormat.HOUR_MINUTE,
            Localizations.localeOf(context).languageCode)
        .format(_dateTime);

    String sizeSubStr = "";
    const space = "    ";
    if (widget.file.metadata?.imageWidth != null &&
        widget.file.metadata?.imageHeight != null) {
      final pixelCount =
          widget.file.metadata.imageWidth * widget.file.metadata.imageHeight;
      if (pixelCount >= 500000) {
        final mpCount = pixelCount / 1000000.0;
        sizeSubStr += AppLocalizations.of(context)
            .megapixelCount(mpCount.toStringAsFixed(1));
        sizeSubStr += space;
      }
      sizeSubStr += _byteSizeToString(widget.file.contentLength);
    }

    String cameraSubStr = "";
    if (_fNumber != null) {
      cameraSubStr += "f/${_fNumber.toStringAsFixed(1)}$space";
    }
    if (_exposureTime != null) {
      cameraSubStr +=
          AppLocalizations.of(context).secondCountSymbol(_exposureTime);
      cameraSubStr += space;
    }
    if (_focalLength != null) {
      cameraSubStr += AppLocalizations.of(context)
          .millimeterCountSymbol(_focalLength.toStringAsFixedTruncated(2));
      cameraSubStr += space;
    }
    if (_isoSpeedRatings != null) {
      cameraSubStr += "ISO$_isoSpeedRatings$space";
    }
    cameraSubStr = cameraSubStr.trim();

    return Material(
      type: MaterialType.transparency,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              _DetailPaneButton(
                icon: Icons.playlist_add_outlined,
                label: AppLocalizations.of(context).addToAlbumTooltip,
                onPressed: () => _onAddToAlbumPressed(context),
              ),
              _DetailPaneButton(
                icon: Icons.delete_outline,
                label: AppLocalizations.of(context).deleteTooltip,
                onPressed: () => _onDeletePressed(context),
              ),
            ],
          ),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 32),
            child: const Divider(),
          ),
          ListTile(
            leading: Icon(
              Icons.image_outlined,
              color: AppTheme.getSecondaryTextColor(context),
            ),
            title: Text(basenameWithoutExtension(widget.file.path)),
            subtitle: Text(widget.file.strippedPath),
          ),
          ListTile(
            leading: Icon(
              Icons.calendar_today_outlined,
              color: AppTheme.getSecondaryTextColor(context),
            ),
            title: Text("$dateStr $timeStr"),
            trailing: Icon(
              Icons.edit_outlined,
              color: AppTheme.getSecondaryTextColor(context),
            ),
            onTap: () => _onDateTimeTap(context),
          ),
          if (widget.file.metadata?.imageWidth != null &&
              widget.file.metadata?.imageHeight != null)
            ListTile(
              leading: Icon(
                Icons.aspect_ratio,
                color: AppTheme.getSecondaryTextColor(context),
              ),
              title: Text(
                  "${widget.file.metadata.imageWidth} x ${widget.file.metadata.imageHeight}"),
              subtitle: Text(sizeSubStr),
            )
          else
            ListTile(
              leading: Icon(
                Icons.aspect_ratio,
                color: AppTheme.getSecondaryTextColor(context),
              ),
              title: Text(_byteSizeToString(widget.file.contentLength)),
            ),
          if (_model != null)
            ListTile(
              leading: Icon(
                Icons.camera_outlined,
                color: AppTheme.getSecondaryTextColor(context),
              ),
              title: Text(_model),
              subtitle: cameraSubStr.isNotEmpty ? Text(cameraSubStr) : null,
            ),
          if (features.isSupportMapView && _gps != null)
            SizedBox(
              height: 256,
              child: platform.Map(
                center: _gps,
                zoom: 16,
                onTap: _onMapTap,
              ),
            ),
        ],
      ),
    );
  }

  /// Convert EXIF data to readable format
  void _initMetadata() {
    final exif = widget.file.metadata.exif;
    _log.info("[_initMetadata] $exif");

    if (exif.make != null && exif.model != null) {
      _model = "${exif.make} ${exif.model}";
    }
    if (exif.fNumber != null) {
      _fNumber = exif.fNumber.toDouble();
    }
    if (exif.exposureTime != null) {
      if (exif.exposureTime.denominator == 1) {
        _exposureTime = exif.exposureTime.numerator.toString();
      } else {
        _exposureTime = exif.exposureTime.toString();
      }
    }
    if (exif.focalLength != null) {
      _focalLength = exif.focalLength.toDouble();
    }
    if (exif.isoSpeedRatings != null) {
      _isoSpeedRatings = exif.isoSpeedRatings;
    }
    if (exif.gpsLatitudeRef != null &&
        exif.gpsLatitude != null &&
        exif.gpsLongitudeRef != null &&
        exif.gpsLongitude != null) {
      final lat = _gpsDmsToDouble(exif.gpsLatitude) *
          (exif.gpsLatitudeRef == "S" ? -1 : 1);
      final lng = _gpsDmsToDouble(exif.gpsLongitude) *
          (exif.gpsLongitudeRef == "W" ? -1 : 1);
      _log.fine("GPS: ($lat, $lng)");
      _gps = Tuple2(lat, lng);
    }
  }

  void _onAddToAlbumPressed(BuildContext context) {
    showDialog(
      context: context,
      builder: (_) => AlbumPickerDialog(
        account: widget.account,
      ),
    ).then((value) {
      if (value == null) {
        // user cancelled the dialog
      } else if (value is Album) {
        _log.info("[_onAddToAlbumPressed] Album picked: ${value.name}");
        _addToAlbum(context, value).then((_) {
          SnackBarManager().showSnackBar(SnackBar(
            content: Text(AppLocalizations.of(context)
                .addToAlbumSuccessNotification(value.name)),
            duration: k.snackBarDurationNormal,
          ));
        }).catchError((_) {});
      } else {
        SnackBarManager().showSnackBar(SnackBar(
          content:
              Text(AppLocalizations.of(context).addToAlbumFailureNotification),
          duration: k.snackBarDurationNormal,
        ));
      }
    }).catchError((e, stacktrace) {
      _log.severe(
          "[_onAddToAlbumPressed] Failed while showDialog", e, stacktrace);
      SnackBarManager().showSnackBar(SnackBar(
        content: Text(
            "${AppLocalizations.of(context).addToAlbumFailureNotification}: "
            "${exception_util.toUserString(e, context)}"),
        duration: k.snackBarDurationNormal,
      ));
    });
  }

  void _onDeletePressed(BuildContext context) async {
    _log.info("[_onDeletePressed] Removing file: ${widget.file.path}");
    var controller = SnackBarManager().showSnackBar(SnackBar(
      content: Text(AppLocalizations.of(context).deleteProcessingNotification),
      duration: k.snackBarDurationShort,
    ));
    controller?.closed?.whenComplete(() {
      controller = null;
    });
    try {
      await Remove(FileRepo(FileCachedDataSource()),
          AlbumRepo(AlbumCachedDataSource()))(widget.account, widget.file);
      controller?.close();
      SnackBarManager().showSnackBar(SnackBar(
        content: Text(AppLocalizations.of(context).deleteSuccessNotification),
        duration: k.snackBarDurationNormal,
      ));
      Navigator.of(context).pop();
    } catch (e, stacktrace) {
      _log.shout(
          "[_onDeletePressed] Failed while remove" +
              (kDebugMode ? ": ${widget.file.path}" : ""),
          e,
          stacktrace);
      controller?.close();
      SnackBarManager().showSnackBar(SnackBar(
        content:
            Text("${AppLocalizations.of(context).deleteFailureNotification}: "
                "${exception_util.toUserString(e, context)}"),
        duration: k.snackBarDurationNormal,
      ));
    }
  }

  void _onMapTap() {
    if (platform_k.isAndroid) {
      final intent = AndroidIntent(
        action: "action_view",
        data: Uri.encodeFull("geo:${_gps.item1},${_gps.item2}?z=16"),
      );
      intent.launch();
    }
  }

  void _onDateTimeTap(BuildContext context) {
    showDialog(
      context: context,
      builder: (context) => PhotoDateTimeEditDialog(initialDateTime: _dateTime),
    ).then((value) async {
      if (value == null || value is! DateTime) {
        return;
      }
      final fileRepo = FileRepo(FileCachedDataSource());
      try {
        await UpdateProperty(fileRepo)
            .updateOverrideDateTime(widget.account, widget.file, value);
        setState(() {
          _dateTime = value;
        });
      } catch (e, stacktrace) {
        _log.shout(
            "[_onDateTimeTap] Failed while updateOverrideDateTime" +
                (kDebugMode ? ": ${widget.file.path}" : ""),
            e,
            stacktrace);
        SnackBarManager().showSnackBar(SnackBar(
          content: Text(
              AppLocalizations.of(context).updateDateTimeFailureNotification),
          duration: k.snackBarDurationNormal,
        ));
      }
    }).catchError((e, stacktrace) {
      _log.shout("[_onDateTimeTap] Failed while showDialog", e, stacktrace);
    });
  }

  static double _gpsDmsToDouble(List<Rational> dms) {
    double product = dms[0].toDouble();
    if (dms.length > 1) {
      product += dms[1].toDouble() / 60;
    }
    if (dms.length > 2) {
      product += dms[2].toDouble() / 3600;
    }
    return product;
  }

  Future<void> _addToAlbum(BuildContext context, Album album) async {
    assert(album.provider is AlbumStaticProvider);
    try {
      final albumRepo = AlbumRepo(AlbumCachedDataSource());
      final newItem = AlbumFileItem(file: widget.file);
      if (AlbumStaticProvider.of(album)
          .items
          .whereType<AlbumFileItem>()
          .containsIf(newItem, (a, b) => a.file.path == b.file.path)) {
        // already added, do nothing
        _log.info("[_addToAlbum] File already in album: ${widget.file.path}");
        SnackBarManager().showSnackBar(SnackBar(
          content: Text(
              "${AppLocalizations.of(context).addToAlbumAlreadyAddedNotification}"),
          duration: k.snackBarDurationNormal,
        ));
        return Future.error(ArgumentError("File already in album"));
      }
      await UpdateAlbum(albumRepo)(
          widget.account,
          album.copyWith(
            provider: AlbumStaticProvider(
              items: [
                ...AlbumStaticProvider.of(album).items,
                AlbumFileItem(file: widget.file),
              ],
            ),
          ));
    } catch (e, stacktrace) {
      _log.shout("[_addToAlbum] Failed while updating album", e, stacktrace);
      SnackBarManager().showSnackBar(SnackBar(
        content: Text(
            "${AppLocalizations.of(context).addToAlbumFailureNotification}: "
            "${exception_util.toUserString(e, context)}"),
        duration: k.snackBarDurationNormal,
      ));
      rethrow;
    }
  }

  DateTime _dateTime;
  // EXIF data
  String _model;
  double _fNumber;
  String _exposureTime;
  double _focalLength;
  int _isoSpeedRatings;
  Tuple2<double, double> _gps;

  static final _log =
      Logger("widget.viewer_detail_pane._ViewerDetailPaneState");
}

class _DetailPaneButton extends StatelessWidget {
  const _DetailPaneButton({Key key, this.icon, this.label, this.onPressed})
      : super(key: key);

  @override
  build(BuildContext context) {
    return TextButton(
      onPressed: onPressed,
      child: SizedBox(
        width: 96,
        height: 96,
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              Icon(
                icon,
                color: AppTheme.getPrimaryTextColor(context),
              ),
              const SizedBox(height: 4),
              Text(
                label,
                textAlign: TextAlign.center,
                style: TextStyle(
                  fontSize: 12,
                  color: AppTheme.getSecondaryTextColor(context),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  final IconData icon;
  final String label;
  final VoidCallback onPressed;
}

String _byteSizeToString(int byteSize) {
  const units = ["B", "KB", "MB", "GB"];
  var remain = byteSize.toDouble();
  int i = 0;
  while (i < units.length) {
    final next = remain / 1024;
    if (next < 1) {
      break;
    }
    remain = next;
    ++i;
  }
  return "${remain.toStringAsFixed(2)}${units[i]}";
}