/*
 * Copyright (c) 2019 - 2020 Arnaud Vergnet.
 *
 * This file is part of Campus INSAT.
 *
 * Campus INSAT is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Campus INSAT is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
 */

import * as React from 'react';
import {
  Button,
  Caption,
  Card,
  Headline,
  Subheading,
  withTheme,
} from 'react-native-paper';
import {StackNavigationProp, StackScreenProps} from '@react-navigation/stack';
import {BackHandler, View} from 'react-native';
import * as Animatable from 'react-native-animatable';
import i18n from 'i18n-js';
import {CalendarList, PeriodMarking} from 'react-native-calendars';
import type {DeviceType} from './EquipmentListScreen';
import LoadingConfirmDialog from '../../../components/Dialogs/LoadingConfirmDialog';
import ErrorDialog from '../../../components/Dialogs/ErrorDialog';
import {
  generateMarkedDates,
  getFirstEquipmentAvailability,
  getISODate,
  getRelativeDateString,
  getValidRange,
  isEquipmentAvailable,
} from '../../../utils/EquipmentBooking';
import ConnectionManager from '../../../managers/ConnectionManager';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
import {MainStackParamsList} from '../../../navigation/MainNavigator';

type EquipmentRentScreenNavigationProp = StackScreenProps<
  MainStackParamsList,
  'equipment-rent'
>;

type Props = EquipmentRentScreenNavigationProp & {
  navigation: StackNavigationProp<any>;
  theme: ReactNativePaper.Theme;
};

export type MarkedDatesObjectType = {
  [key: string]: PeriodMarking;
};

type StateType = {
  dialogVisible: boolean;
  errorDialogVisible: boolean;
  markedDates: MarkedDatesObjectType;
  currentError: number;
};

class EquipmentRentScreen extends React.Component<Props, StateType> {
  item: DeviceType | null;

  bookedDates: Array<string>;

  bookRef: {current: null | (Animatable.View & View)};

  canBookEquipment: boolean;

  lockedDates: {
    [key: string]: PeriodMarking;
  };

  constructor(props: Props) {
    super(props);
    this.item = null;
    this.lockedDates = {};
    this.state = {
      dialogVisible: false,
      errorDialogVisible: false,
      markedDates: {},
      currentError: 0,
    };
    this.resetSelection();
    this.bookRef = React.createRef();
    this.canBookEquipment = false;
    this.bookedDates = [];
    if (props.route.params != null) {
      if (props.route.params.item != null) {
        this.item = props.route.params.item;
      } else {
        this.item = null;
      }
    }
    const {item} = this;
    if (item != null) {
      this.lockedDates = {};
      item.booked_at.forEach((date: {begin: string; end: string}) => {
        const range = getValidRange(
          new Date(date.begin),
          new Date(date.end),
          null,
        );
        this.lockedDates = {
          ...this.lockedDates,
          ...generateMarkedDates(false, props.theme, range),
        };
      });
    }
  }

  /**
   * Captures focus and blur events to hook on android back button
   */
  componentDidMount() {
    const {navigation} = this.props;
    navigation.addListener('focus', () => {
      BackHandler.addEventListener(
        'hardwareBackPress',
        this.onBackButtonPressAndroid,
      );
    });
    navigation.addListener('blur', () => {
      BackHandler.removeEventListener(
        'hardwareBackPress',
        this.onBackButtonPressAndroid,
      );
    });
  }

  /**
   * Overrides default android back button behaviour to deselect date if any is selected.
   *
   * @return {boolean}
   */
  onBackButtonPressAndroid = (): boolean => {
    if (this.bookedDates.length > 0) {
      this.resetSelection();
      this.updateMarkedSelection();
      return true;
    }
    return false;
  };

  onDialogDismiss = () => {
    this.setState({dialogVisible: false});
  };

  onErrorDialogDismiss = () => {
    this.setState({errorDialogVisible: false});
  };

  /**
   * Sends the selected data to the server and waits for a response.
   * If the request is a success, navigate to the recap screen.
   * If it is an error, display the error to the user.
   *
   * @returns {Promise<void>}
   */
  onDialogAccept = (): Promise<void> => {
    return new Promise((resolve: () => void) => {
      const {item, props} = this;
      const start = this.getBookStartDate();
      const end = this.getBookEndDate();
      if (item != null && start != null && end != null) {
        ConnectionManager.getInstance()
          .authenticatedRequest('location/booking', {
            device: item.id,
            begin: getISODate(start),
            end: getISODate(end),
          })
          .then(() => {
            this.onDialogDismiss();
            props.navigation.replace('equipment-confirm', {
              item: this.item,
              dates: [getISODate(start), getISODate(end)],
            });
            resolve();
          })
          .catch((error: number) => {
            this.onDialogDismiss();
            this.showErrorDialog(error);
            resolve();
          });
      } else {
        this.onDialogDismiss();
        resolve();
      }
    });
  };

  getBookStartDate(): Date | null {
    return this.bookedDates.length > 0 ? new Date(this.bookedDates[0]) : null;
  }

  getBookEndDate(): Date | null {
    const {length} = this.bookedDates;
    return length > 0 ? new Date(this.bookedDates[length - 1]) : null;
  }

  /**
   * Selects a new date on the calendar.
   * If both start and end dates are already selected, unselect all.
   *
   * @param day The day selected
   */
  selectNewDate = (day: {
    dateString: string;
    day: number;
    month: number;
    timestamp: number;
    year: number;
  }) => {
    const selected = new Date(day.dateString);
    const start = this.getBookStartDate();

    if (!this.lockedDates[day.dateString] != null) {
      if (start === null) {
        this.updateSelectionRange(selected, selected);
        this.enableBooking();
      } else if (start.getTime() === selected.getTime()) {
        this.resetSelection();
      } else if (this.bookedDates.length === 1) {
        this.updateSelectionRange(start, selected);
        this.enableBooking();
      } else {
        this.resetSelection();
      }
      this.updateMarkedSelection();
    }
  };

  showErrorDialog = (error: number) => {
    this.setState({
      errorDialogVisible: true,
      currentError: error,
    });
  };

  showDialog = () => {
    this.setState({dialogVisible: true});
  };

  /**
   * Shows the book button by plying a fade animation
   */
  showBookButton() {
    if (this.bookRef.current && this.bookRef.current.fadeInUp) {
      this.bookRef.current.fadeInUp(500);
    }
  }

  /**
   * Hides the book button by plying a fade animation
   */
  hideBookButton() {
    if (this.bookRef.current && this.bookRef.current.fadeOutDown) {
      this.bookRef.current.fadeOutDown(500);
    }
  }

  enableBooking() {
    if (!this.canBookEquipment) {
      this.showBookButton();
      this.canBookEquipment = true;
    }
  }

  resetSelection() {
    if (this.canBookEquipment) {
      this.hideBookButton();
    }
    this.canBookEquipment = false;
    this.bookedDates = [];
  }

  updateSelectionRange(start: Date, end: Date) {
    this.bookedDates = getValidRange(start, end, this.item);
  }

  updateMarkedSelection() {
    const {theme} = this.props;
    this.setState({
      markedDates: generateMarkedDates(true, theme, this.bookedDates),
    });
  }

  render() {
    const {item, props, state} = this;
    const start = this.getBookStartDate();
    const end = this.getBookEndDate();
    let subHeadingText;
    if (start == null) {
      subHeadingText = i18n.t('screens.equipment.booking');
    } else if (end != null && start.getTime() !== end.getTime()) {
      subHeadingText = i18n.t('screens.equipment.bookingPeriod', {
        begin: getRelativeDateString(start),
        end: getRelativeDateString(end),
      });
    } else {
      subHeadingText = i18n.t('screens.equipment.bookingDay', {
        date: getRelativeDateString(start),
      });
    }
    if (item != null) {
      const isAvailable = isEquipmentAvailable(item);
      const firstAvailability = getFirstEquipmentAvailability(item);
      return (
        <View style={{flex: 1}}>
          <CollapsibleScrollView>
            <Card style={{margin: 5}}>
              <Card.Content>
                <View style={{flex: 1}}>
                  <View
                    style={{
                      marginLeft: 'auto',
                      marginRight: 'auto',
                      flexDirection: 'row',
                      flexWrap: 'wrap',
                    }}>
                    <Headline style={{textAlign: 'center'}}>
                      {item.name}
                    </Headline>
                    <Caption
                      style={{
                        textAlign: 'center',
                        lineHeight: 35,
                        marginLeft: 10,
                      }}>
                      ({i18n.t('screens.equipment.bail', {cost: item.caution})})
                    </Caption>
                  </View>
                </View>

                <Button
                  icon={isAvailable ? 'check-circle-outline' : 'update'}
                  color={
                    isAvailable
                      ? props.theme.colors.success
                      : props.theme.colors.primary
                  }
                  mode="text">
                  {i18n.t('screens.equipment.available', {
                    date: getRelativeDateString(firstAvailability),
                  })}
                </Button>
                <Subheading
                  style={{
                    textAlign: 'center',
                    marginBottom: 10,
                    minHeight: 50,
                  }}>
                  {subHeadingText}
                </Subheading>
              </Card.Content>
            </Card>
            <CalendarList
              // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
              minDate={new Date()}
              // Max amount of months allowed to scroll to the past. Default = 50
              pastScrollRange={0}
              // Max amount of months allowed to scroll to the future. Default = 50
              futureScrollRange={3}
              // Enable horizontal scrolling, default = false
              horizontal
              // Enable paging on horizontal, default = false
              pagingEnabled
              // Handler which gets executed on day press. Default = undefined
              onDayPress={this.selectNewDate}
              // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
              firstDay={1}
              // Hide month navigation arrows.
              hideArrows={false}
              // Date marking style [simple/period/multi-dot/custom]. Default = 'simple'
              markingType={'period'}
              markedDates={{...this.lockedDates, ...state.markedDates}}
              theme={{
                backgroundColor: props.theme.colors.agendaBackgroundColor,
                calendarBackground: props.theme.colors.background,
                textSectionTitleColor: props.theme.colors.agendaDayTextColor,
                selectedDayBackgroundColor: props.theme.colors.primary,
                selectedDayTextColor: '#ffffff',
                todayTextColor: props.theme.colors.text,
                dayTextColor: props.theme.colors.text,
                textDisabledColor: props.theme.colors.agendaDayTextColor,
                dotColor: props.theme.colors.primary,
                selectedDotColor: '#ffffff',
                arrowColor: props.theme.colors.primary,
                monthTextColor: props.theme.colors.text,
                indicatorColor: props.theme.colors.primary,
                textDayFontFamily: 'monospace',
                textMonthFontFamily: 'monospace',
                textDayHeaderFontFamily: 'monospace',
                textDayFontWeight: '300',
                textMonthFontWeight: 'bold',
                textDayHeaderFontWeight: '300',
                textDayFontSize: 16,
                textMonthFontSize: 16,
                textDayHeaderFontSize: 16,
                'stylesheet.day.period': {
                  base: {
                    overflow: 'hidden',
                    height: 34,
                    width: 34,
                    alignItems: 'center',
                  },
                },
              }}
              style={{marginBottom: 50}}
            />
          </CollapsibleScrollView>
          <LoadingConfirmDialog
            visible={state.dialogVisible}
            onDismiss={this.onDialogDismiss}
            onAccept={this.onDialogAccept}
            title={i18n.t('screens.equipment.dialogTitle')}
            titleLoading={i18n.t('screens.equipment.dialogTitleLoading')}
            message={i18n.t('screens.equipment.dialogMessage')}
          />

          <ErrorDialog
            visible={state.errorDialogVisible}
            onDismiss={this.onErrorDialogDismiss}
            errorCode={state.currentError}
          />
          <Animatable.View
            ref={this.bookRef}
            useNativeDriver
            style={{
              position: 'absolute',
              bottom: 0,
              left: 0,
              width: '100%',
              flex: 1,
              transform: [{translateY: 100}],
            }}>
            <Button
              icon="bookmark-check"
              mode="contained"
              onPress={this.showDialog}
              style={{
                width: '80%',
                flex: 1,
                marginLeft: 'auto',
                marginRight: 'auto',
                marginBottom: 20,
                borderRadius: 10,
              }}>
              {i18n.t('screens.equipment.bookButton')}
            </Button>
          </Animatable.View>
        </View>
      );
    }
    return null;
  }
}

export default withTheme(EquipmentRentScreen);
