/*
 * 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 { Linking } from 'react-native';
import {
  ClubInformationScreenParams,
  MainRoutes,
  PlanningInformationScreenParams,
} from '../navigation/MainNavigator';

export type ParsedUrlDataType = {
  route: MainRoutes.ClubInformation | MainRoutes.PlanningInformation;
  data: ClubInformationScreenParams | PlanningInformationScreenParams;
};

export type ParsedUrlCallbackType = (parsedData: ParsedUrlDataType) => void;

type RawParsedUrlDataType = {
  path: string;
  queryParams: { [key: string]: string };
};

/**
 * Class use to handle depp links scanned or clicked.
 */
export default class URLHandler {
  static SCHEME = 'campus-insat://'; // Urls beginning with this string will be opened in the app

  static CLUB_INFO_URL_PATH = 'club';

  static EVENT_INFO_URL_PATH = 'event';

  onInitialURLParsed: ParsedUrlCallbackType;

  onDetectURL: ParsedUrlCallbackType;

  constructor(
    onInitialURLParsed: ParsedUrlCallbackType,
    onDetectURL: ParsedUrlCallbackType
  ) {
    this.onInitialURLParsed = onInitialURLParsed;
    this.onDetectURL = onDetectURL;
  }

  /**
   * Parses the given url to retrieve the corresponding app path and associated arguments.
   *
   * @param url The url to parse
   * @returns {{path: string, queryParams: {}}}
   */
  static parseUrl(url: string): RawParsedUrlDataType | null {
    let parsedData: RawParsedUrlDataType | null = null;
    const urlNoScheme = url.replace(URLHandler.SCHEME, '');
    if (urlNoScheme != null) {
      const params: { [key: string]: string } = {};
      const [path, fullParamsString] = urlNoScheme.split('?');
      if (fullParamsString != null) {
        const paramsStringArray = fullParamsString.split('&');
        paramsStringArray.forEach((paramString: string) => {
          const [key, value] = paramString.split('=');
          if (value != null) {
            params[key] = value;
          }
        });
      }
      if (path != null) {
        parsedData = { path, queryParams: params };
      }
    }
    return parsedData;
  }

  /**
   * Gets routing data corresponding to the given url.
   * If the url does not match any existing route, null will be returned.
   *
   * @param rawParsedUrlData The data just parsed
   * @returns {null}
   */
  static getUrlData(
    rawParsedUrlData: RawParsedUrlDataType | null
  ): ParsedUrlDataType | null {
    let parsedData: null | ParsedUrlDataType = null;
    if (rawParsedUrlData != null) {
      const { path } = rawParsedUrlData;
      const { queryParams } = rawParsedUrlData;
      if (URLHandler.isClubInformationLink(path)) {
        parsedData = URLHandler.generateClubInformationData(queryParams);
      } else if (URLHandler.isPlanningInformationLink(path)) {
        parsedData = URLHandler.generatePlanningInformationData(queryParams);
      }
    }

    return parsedData;
  }

  /**
   * Checks if the given url is in a valid format
   *
   * @param url The url to check
   * @returns {boolean}
   */
  static isUrlValid(url: string): boolean {
    return this.getUrlData(URLHandler.parseUrl(url)) !== null;
  }

  /**
   * Check if the given path links to the club information screen
   *
   * @param path The url to check
   * @returns {boolean}
   */
  static isClubInformationLink(path: string): boolean {
    return path === URLHandler.CLUB_INFO_URL_PATH;
  }

  /**
   * Check if the given path links to the planning information screen
   *
   * @param path The url to check
   * @returns {boolean}
   */
  static isPlanningInformationLink(path: string): boolean {
    return path === URLHandler.EVENT_INFO_URL_PATH;
  }

  /**
   * Generates data formatted for the club information screen from the url parameters.
   *
   * @param params Url parameters to convert
   * @returns {null|{route: string, data: {clubId: number}}}
   */
  static generateClubInformationData(params: {
    [key: string]: string;
  }): ParsedUrlDataType | null {
    if (params.id != null) {
      const id = parseInt(params.id, 10);
      if (!Number.isNaN(id)) {
        return {
          route: MainRoutes.ClubInformation,
          data: {
            type: 'id',
            clubId: id,
          },
        };
      }
    }
    return null;
  }

  /**
   * Generates data formatted for the planning information screen from the url parameters.
   *
   * @param params Url parameters to convert
   * @returns {null|{route: string, data: {clubId: number}}}
   */
  static generatePlanningInformationData(params: {
    [key: string]: string;
  }): ParsedUrlDataType | null {
    if (params.id != null) {
      const id = parseInt(params.id, 10);
      if (!Number.isNaN(id)) {
        return {
          route: MainRoutes.PlanningInformation,
          data: {
            type: 'id',
            eventId: id,
          },
        };
      }
    }
    return null;
  }

  /**
   * Starts listening to events.
   *
   * There are 2 types of event.
   *
   * A classic event, triggered while the app is active.
   * An initial event, called when the app was opened by clicking on a link
   *
   */
  listen() {
    Linking.addEventListener('url', this.onUrl);
    Linking.getInitialURL().then(this.onInitialUrl);
  }

  /**
   * Gets data from the given url and calls the classic callback with it.
   *
   * @param url The url detected
   */
  onUrl = ({ url }: { url: string }) => {
    if (url != null) {
      const data = URLHandler.getUrlData(URLHandler.parseUrl(url));
      if (data != null) {
        this.onDetectURL(data);
      }
    }
  };

  /**
   * Gets data from the given url and calls the initial callback with it.
   *
   * @param url The url detected
   */
  onInitialUrl = (url: string | null) => {
    if (url != null) {
      const data = URLHandler.getUrlData(URLHandler.parseUrl(url));
      if (data != null) {
        this.onInitialURLParsed(data);
      }
    }
  };
}
