import {
  PermissionInfo,
  PermissionMap,
  PermissionStatus,
  PermissionType,
} from './Permissions.types';

/*
 * TODO: Bacon: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Permissions
 * Add messages to manifest like we do with iOS info.plist
 */

// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Using_the_new_API_in_older_browsers
// Older browsers might not implement mediaDevices at all, so we set an empty object first
function _getUserMedia(constraints: MediaStreamConstraints): Promise<MediaStream> {
  if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
    return navigator.mediaDevices.getUserMedia(constraints);
  }

  // Some browsers partially implement mediaDevices. We can't just assign an object
  // with getUserMedia as it would overwrite existing properties.
  // Here, we will just add the getUserMedia property if it's missing.

  // First get ahold of the legacy getUserMedia, if present
  const getUserMedia =
    navigator.getUserMedia ||
    (navigator as any).webkitGetUserMedia ||
    (navigator as any).mozGetUserMedia ||
    function() {
      const error: any = new Error('Permission unimplemented');
      error.code = 0;
      error.name = 'NotAllowedError';
      throw error;
    };

  return new Promise((resolve, reject) => {
    getUserMedia.call(navigator, constraints, resolve, reject);
  });
}

async function askForMediaPermissionAsync(
  options: MediaStreamConstraints
): Promise<PermissionInfo> {
  try {
    await _getUserMedia(options);
    return { status: PermissionStatus.GRANTED, expires: 'never' };
  } catch ({ message }) {
    // name: NotAllowedError
    // code: 0
    if (message === 'Permission dismissed') {
      // message: Permission dismissed
      return { status: PermissionStatus.UNDETERMINED, expires: 'never' };
    } else {
      // TODO: Bacon: [OSX] The system could deny access to chrome.
      // TODO: Bacon: add: { status: 'unimplemented' }
      // message: Permission denied
      return { status: PermissionStatus.DENIED, expires: 'never' };
    }
  }
}

async function askForMicrophonePermissionAsync(): Promise<PermissionInfo> {
  return await askForMediaPermissionAsync({ audio: true });
}

async function askForCameraPermissionAsync(): Promise<PermissionInfo> {
  return await askForMediaPermissionAsync({ video: true });
}

async function askForLocationPermissionAsync(): Promise<PermissionInfo> {
  return new Promise(resolve => {
    navigator.geolocation.getCurrentPosition(
      () => resolve({ status: PermissionStatus.GRANTED, expires: 'never' }),
      ({ code }: PositionError) => {
        // https://developer.mozilla.org/en-US/docs/Web/API/PositionError/code
        if (code === 1) {
          resolve({ status: PermissionStatus.DENIED, expires: 'never' });
        } else {
          resolve({ status: PermissionStatus.UNDETERMINED, expires: 'never' });
        }
      }
    );
  });
}

async function getPermissionAsync(
  permission: PermissionType,
  shouldAsk: boolean
): Promise<PermissionInfo> {
  switch (permission) {
    case 'userFacingNotifications':
    case 'notifications':
      {
        const { Notification = {} } = window as any;
        if (Notification.requestPermission) {
          let status = Notification.permission;
          if (shouldAsk) {
            status = await Notification.requestPermission();
          }
          if (!status || status === 'default') {
            return { status: PermissionStatus.UNDETERMINED, expires: 'never' };
          }
          return { status, expires: 'never' };
        }
      }
      break;
    case 'location':
      {
        const { navigator = {} } = window as any;
        if (navigator.permissions) {
          const { state } = await navigator.permissions.query({ name: 'geolocation' });
          if (state !== PermissionStatus.GRANTED && state !== PermissionStatus.DENIED) {
            if (shouldAsk) {
              return await askForLocationPermissionAsync();
            }
            return { status: PermissionStatus.UNDETERMINED, expires: 'never' };
          }

          return { status: state, expires: 'never' };
        } else if (shouldAsk) {
          // TODO: Bacon: should this function as ask async when not in chrome?
          return await askForLocationPermissionAsync();
        }
      }
      break;
    case 'audioRecording':
      if (shouldAsk) {
        return await askForMicrophonePermissionAsync();
      } else {
        //TODO: Bacon: Is it possible to get this permission?
      }
      break;
    case 'camera':
      if (shouldAsk) {
        return await askForCameraPermissionAsync();
      } else {
        //TODO: Bacon: Is it possible to get this permission?
      }
      break;
    default:
      break;
  }
  return { status: PermissionStatus.UNDETERMINED, expires: 'never' };
}

export default {
  get name(): string {
    return 'ExpoPermissions';
  },

  async getAsync(permissionTypes: PermissionType[]): Promise<PermissionMap> {
    const results = {};
    for (const permissionType of new Set(permissionTypes)) {
      results[permissionType] = await getPermissionAsync(permissionType, /* shouldAsk */ false);
    }
    return results;
  },

  async askAsync(permissionTypes: PermissionType[]): Promise<PermissionMap> {
    const results = {};
    for (const permissionType of new Set(permissionTypes)) {
      results[permissionType] = await getPermissionAsync(permissionType, /* shouldAsk */ true);
    }
    return results;
  },
};