import React, {
  useImperativeHandle,
  useEffect,
  useRef,
  forwardRef,
  useState,
} from 'react';
import {
  SafeAreaView,
  findNodeHandle,
  DeviceEventEmitter,
  NativeEventEmitter,
  Platform,
  PixelRatio,
  StyleSheet,
  DimensionValue,
} from 'react-native';
import type {
  COORDINATE_TYPE,
  MAP_EVENT_LISTENER,
  MAP_LOCALE,
  MAP_SIMULATION_REFERENCE,
  MAP_VIEW_REFERENCE,
  SIMULATION_COMMAND,
} from './CommandTypes';
import type {
  INSTRUCTION,
  JIBESTREAM_DESTINATION,
  JIBESTREAM_PATH,
  JIBESTREAM_STYLE,
  JIBESTREAM_WAYPOINT,
  MAP_CREDENTIALS,
  MAP_OPTIONS,
  MAP_TAP_EVENT,
  SCREEN_ROTATED_EVENT,
  JIBESTREAM_GEOFENCE,
  MAP_IMAGE,
  GEOJSON_PATH,
  SIMULATION_STATUS,
  SIMULATION_PATH,
  SIMULATION_OPTIONS,
} from './JibestreamTypes';
import { MapViewManager, CTPEventEmitter } from './MapViewManager';
import {
  centerMap,
  createFragment,
  drawNavigationPath,
  setFollowBlueDot,
  setMapVenue,
  setUserPosition,
  clearWayfindingPath,
  wayfindToWaypoint,
  wayfindBetweenWaypoints,
  styleUnitsForDestination,
  addMapIcon,
} from './SendCommand';

import Compass from './Compass';
import { PathSimulator } from './PathSimulator';

export interface MapViewProps {
  credentials: MAP_CREDENTIALS;
  mapOptions?: MAP_OPTIONS;
  style: { height: number; width: number; visible: boolean };
  compassStyle?: {
    size: number;
    right: DimensionValue;
    top: DimensionValue;
  };
  onMapMessage: MAP_EVENT_LISTENER;
  locale?: MAP_LOCALE;
}

const updateGeojsonTimestamps = (geojson: GEOJSON_PATH) => {
  if (Platform.OS == 'android') {
    geojson.startTime = geojson.startTime * 1000;
    geojson.endTime = geojson.endTime * 1000;
  }
  return geojson;
};

const MapView: React.ForwardRefRenderFunction<
  MAP_VIEW_REFERENCE & MAP_SIMULATION_REFERENCE,
  MapViewProps
> = (
  props: MapViewProps,
  parentRef:
    | React.Ref<MAP_VIEW_REFERENCE & MAP_SIMULATION_REFERENCE>
    | undefined
) => {
  const nodeRef = useRef(null);

  const compassStyle = StyleSheet.create({
    compass: {
      height: props.style.visible ? props?.compassStyle?.size ?? 0 : 0,
      width: props.style.visible ? props?.compassStyle?.size ?? 0 : 0,
      borderRadius: 5,
      alignItems: 'center',
      justifyContent: 'center',
      zIndex: 1,
      position: 'absolute',
      right: props?.compassStyle?.right,
      top: props?.compassStyle?.top,
      flex: 1,
      resizeMode: 'contain',
    },
  });

  const [compassTransform, setCompassTransform] = useState({
    rotate: '0deg',
  });

  // CALLBACK DATA
  const [onNavigationPathGeneratedValue, setOnNavigationPathGeneratedValue] =
    useState<{
      pathId: string;
      instructions: INSTRUCTION[];
      start: JIBESTREAM_WAYPOINT;
      end: JIBESTREAM_WAYPOINT;
      jibestreamPath: JIBESTREAM_PATH[];
      path: GEOJSON_PATH;
    } | null>(null);
  const [
    onRerouteNavigationPathGeneratedValue,
    setOnRerouteNavigationPathGeneratedValue,
  ] = useState<{
    pathId: string;
    instructions: INSTRUCTION[];
    start: JIBESTREAM_WAYPOINT;
    end: JIBESTREAM_WAYPOINT;
    jibestreamPath: JIBESTREAM_PATH[];
    path: GEOJSON_PATH;
  } | null>(null);
  const [onDestinationSelectedValue, setOnDestinationSelectedValue] =
    useState<JIBESTREAM_DESTINATION | null>(null);
  const [onUserPositionUpdatedValue, setOnUserPositionUpdatedValue] = useState<
    number[] | null
  >(null);
  const [
    onSearchDestinationsGeneratedValue,
    setOnSearchDestinationsGeneratedValue,
  ] = useState<JIBESTREAM_DESTINATION[] | null>(null);
  const [onMapTappedGenericValue, setOnMapTappedGenericValue] =
    useState<MAP_TAP_EVENT | null>(null);
  const [onGeofencesChangedValue, setOnGeofencesChangedValue] = useState<{
    entered: boolean;
    geofences: JIBESTREAM_GEOFENCE[];
  } | null>(null);
  const [onErrorValue, setOnErrorValue] = useState<string | null>(null);
  const [onVenueLoadedValue, setOnVenueLoadedValue] = useState<number | null>(
    null
  );

  // STATE VARIABLES
  // const [pathSimulator, _] = useState<PathSimulator>(new PathSimulator((loc: number[]) => {setUserPosition(findNodeHandle(nodeRef.current), loc[0]!, loc[1]!, 0, 5, null);}));

  const simRef = useRef<PathSimulator>(null);

  useEffect(() => {
    if (Platform.OS == 'android') {
      createFragment(findNodeHandle(nodeRef.current));
    }
    setTimeout(function () {
      setMapVenue({
        viewId: findNodeHandle(nodeRef.current),
        credentials: props.credentials,
        mapOptions: props.mapOptions,
      });
    }, 500);
  }, []);

  useEffect(() => {
    if (props.style.visible) {
      centerMap(findNodeHandle(nodeRef.current), 0, 5, 100);
    }
  }, [props.style.visible]);

  useEffect(() => {
    if (onDestinationSelectedValue != null)
      props.onMapMessage.onDestinationSelected?.(onDestinationSelectedValue);
  }, [onDestinationSelectedValue]);
  useEffect(() => {
    if (onNavigationPathGeneratedValue != null) {
      let pathId, instructions, start, end, jibestreamPath, path;
      ({ pathId, instructions, start, end, jibestreamPath, path } =
        onNavigationPathGeneratedValue);
      props.onMapMessage.onNavigationPathGenerated?.(
        pathId,
        instructions,
        start,
        end,
        jibestreamPath,
        updateGeojsonTimestamps(path)
      );
    }
  }, [onNavigationPathGeneratedValue]);

  useEffect(() => {
    if (onRerouteNavigationPathGeneratedValue != null) {
      let pathId, instructions, start, end, jibestreamPath, path;
      ({ pathId, instructions, start, end, jibestreamPath, path } =
        onRerouteNavigationPathGeneratedValue);
      props.onMapMessage.onRerouteNavigationPathGenerated?.(
        pathId,
        instructions,
        start,
        end,
        jibestreamPath,
        updateGeojsonTimestamps(path)
      );
    }
  }, [onRerouteNavigationPathGeneratedValue]);
  useEffect(() => {
    if (onUserPositionUpdatedValue != null)
      props.onMapMessage.onUserPositionUpdated?.(onUserPositionUpdatedValue);
  }, [onUserPositionUpdatedValue]);
  useEffect(() => {
    if (onSearchDestinationsGeneratedValue != null)
      props.onMapMessage.onSearchDestinationsGenerated?.(
        onSearchDestinationsGeneratedValue
      );
  }, [onSearchDestinationsGeneratedValue]);
  useEffect(() => {
    if (onMapTappedGenericValue != null)
      props.onMapMessage.onMapTappedGeneric?.(onMapTappedGenericValue);
  }, [onMapTappedGenericValue]);
  useEffect(() => {
    if (onErrorValue != null) props.onMapMessage.onError?.(onErrorValue);
  }, [onErrorValue]);
  useEffect(() => {
    if (onVenueLoadedValue != null)
      props.onMapMessage.onVenueLoaded?.(onVenueLoadedValue);
  }, [onVenueLoadedValue]);

  useEffect(() => {
    if (onGeofencesChangedValue != null) {
      let entered, geofences;
      ({ entered, geofences } = onGeofencesChangedValue);
      console.log('Geofences Changed: ', entered, geofences);
      props.onMapMessage.onGeofencesChanged?.(entered, geofences);
    }
  }, [onGeofencesChangedValue]);

  useImperativeHandle(parentRef, () => {
    const viewId = findNodeHandle(nodeRef.current);
    const listeners: MAP_VIEW_REFERENCE & MAP_SIMULATION_REFERENCE = {
      setMapVenue: (credentials: MAP_CREDENTIALS, mapOptions?: MAP_OPTIONS) => {
        setTimeout(function () {
          setMapVenue({
            viewId,
            credentials,
            mapOptions,
          });
        }, 500);
      },
      centerMapOnUser: (
        orientation: number,
        zoom: number = 5,
        animationDuration: number = 1000
      ) => {
        centerMap(viewId, orientation, zoom, animationDuration);
      },
      setFollowBlueDot: (
        enabled: boolean,
        rotate: boolean = false,
        interval: number = 500
      ) => {
        setFollowBlueDot(viewId, enabled, rotate, interval);
      },
      setUserPosition: (
        lat: number,
        long: number,
        heading: number,
        confidence: number
      ) => {
        setUserPosition(viewId, lat, long, heading, confidence, null);
      },
      setUserPositionXY: (
        x: number,
        y: number,
        heading: number,
        confidence: number
      ) => {
        setUserPosition(viewId, x, y, heading, confidence, 'xy');
      },
      clearWayfindingPath: () => {
        clearWayfindingPath(viewId);
      },
      wayfindToWaypoint: (waypointId: number) => {
        wayfindToWaypoint(viewId, waypointId);
      },
      wayfindBetweenWaypoints: (originId: number, destinationId: number) => {
        wayfindBetweenWaypoints(viewId, originId, destinationId);
      },
      drawNavigationPath: (
        pathId: string,
        {
          shouldFocus,
          style,
          originIcon,
          destinationIcon,
        }: {
          shouldFocus?: boolean;
          style?: JIBESTREAM_STYLE;
          originIcon?: MAP_IMAGE;
          destinationIcon?: MAP_IMAGE;
        }
      ) => {
        drawNavigationPath(
          viewId,
          pathId,
          shouldFocus ?? true,
          style ?? {},
          originIcon,
          destinationIcon
        );
      },
      styleUnitsForDestination: (
        destinationId: number,
        style?: JIBESTREAM_STYLE
      ) => {
        styleUnitsForDestination(viewId, destinationId, style);
      },
      addMapIcon: (
        coordinates: number[],
        coordType: COORDINATE_TYPE,
        icon: MAP_IMAGE
      ) => {
        addMapIcon(viewId, coordinates, coordType, icon);
      },

      // Path Simulator
      startPathSimulation: (
        path: GEOJSON_PATH,
        options: SIMULATION_OPTIONS
      ) => {
        simRef.current!.startPathSimulation(path, options);
      },
      controlSimulation: (command: SIMULATION_COMMAND): SIMULATION_STATUS => {
        return simRef.current!.controlSimulation(command);
      },
      getSimulationStatus: (): SIMULATION_STATUS => {
        return simRef.current!.getSimulationStatus();
      },
      getSimulationsPaths: (): SIMULATION_PATH[] => {
        return simRef.current!.getSimulationsPaths();
      },
    };
    return listeners;
  });

  useEffect(() => {
    console.log('Initializing Event Emitter');
    let eventEmitter: typeof DeviceEventEmitter | NativeEventEmitter | null =
      null;
    if (Platform.OS == 'android') {
      eventEmitter = DeviceEventEmitter;
    } else if (Platform.OS == 'ios') {
      console.log(CTPEventEmitter);
      eventEmitter = new NativeEventEmitter(CTPEventEmitter);
    }
    eventEmitter?.addListener(
      'DESTINATION_SELECTED',
      (data: JIBESTREAM_DESTINATION) => setOnDestinationSelectedValue(data)
    );
    eventEmitter?.addListener(
      'NAVIGATION_PATH_GENERATED',
      (data: {
        pathId: string;
        instructions: INSTRUCTION[];
        start: JIBESTREAM_WAYPOINT;
        end: JIBESTREAM_WAYPOINT;
        jibestreamPath: JIBESTREAM_PATH[];
        path: GEOJSON_PATH;
      }) => setOnNavigationPathGeneratedValue(data)
    );

    eventEmitter?.addListener(
      'REROUTE_NAVIGATION_PATH_GENERATED',
      (data: {
        pathId: string;
        instructions: INSTRUCTION[];
        start: JIBESTREAM_WAYPOINT;
        end: JIBESTREAM_WAYPOINT;
        jibestreamPath: JIBESTREAM_PATH[];
        path: GEOJSON_PATH;
      }) => setOnRerouteNavigationPathGeneratedValue(data)
    );
    eventEmitter?.addListener(
      'USER_POSITION_UPDATED',
      (userPosition: number[]) => setOnUserPositionUpdatedValue(userPosition)
    );

    eventEmitter?.addListener(
      'GENERATED_SEARCH_DESTINATIONS',
      (destinations: JIBESTREAM_DESTINATION[]) =>
        setOnSearchDestinationsGeneratedValue(destinations)
    );

    eventEmitter?.addListener('MAP_TAPPED_GENERIC', (event: MAP_TAP_EVENT) =>
      setOnMapTappedGenericValue(event)
    );

    eventEmitter?.addListener(
      'SCREEN_ROTATED',
      (event: SCREEN_ROTATED_EVENT) => {
        const newTransform = {
          rotate: Math.round(event.degrees).toString() + 'deg',
        };
        setCompassTransform(newTransform);
      }
    );

    eventEmitter?.addListener('ERROR', (msg: string) => setOnErrorValue(msg));
    eventEmitter?.addListener('VENUE_LOADED', (venueId: number) =>
      setOnVenueLoadedValue(venueId)
    );
    eventEmitter?.addListener(
      'GEOFENCES_CHANGED',
      (data: { entered: boolean; geofences: JIBESTREAM_GEOFENCE[] }) =>
        setOnGeofencesChangedValue(data)
    );
    return () => {
      console.log('Removing Event Emitters');
      eventEmitter?.removeAllListeners('DESTINATION_SELECTED');
      eventEmitter?.removeAllListeners('NAVIGATION_PATH_GENERATED');
      eventEmitter?.removeAllListeners('USER_POSITION_UPDATED');
      eventEmitter?.removeAllListeners('GENERATED_SEARCH_DESTINATIONS');
      eventEmitter?.removeAllListeners('MAP_TAPPED_GENERIC');
      eventEmitter?.removeAllListeners('ERROR');
      eventEmitter?.removeAllListeners('VENUE_LOADED');
      eventEmitter?.removeAllListeners('SCREEN_ROTATED');
      eventEmitter?.removeAllListeners('GEOFENCES_CHANGED');
      eventEmitter?.removeAllListeners('REROUTE_NAVIGATION_PATH_GENERATED');
    };
  }, []);

  return (
    <SafeAreaView
      style={{
        flex: 1,
        width: props.style.visible ? props.style.width : 0,
        height: props.style.visible ? props.style.height : 0,
      }}
    >
      <PathSimulator
        ref={simRef}
        updateUserLocation={(pos, heading) => {
          setUserPosition(
            findNodeHandle(nodeRef.current),
            pos[0]!,
            pos[1]!,
            heading,
            100,
            null
          );
        }}
        onSimStatusChanged={props.onMapMessage?.onSimulationStatusChanged}
      />
      <Compass
        style={[
          compassStyle.compass,
          {
            transform: [compassTransform],
          },
        ]}
      ></Compass>
      {MapViewManager ? (
        <MapViewManager
          style={
            Platform.OS == 'android'
              ? {
                  height: props.style.visible
                    ? PixelRatio.getPixelSizeForLayoutSize(props.style.height)
                    : 0,
                  width: props.style.visible
                    ? PixelRatio.getPixelSizeForLayoutSize(props.style.width)
                    : 0,
                }
              : {
                  height: props.style.visible ? props.style.height / 1 : 0,
                  width: props.style.visible ? props.style.width / 1 : 0,
                }
          }
          ref={nodeRef}
        />
      ) : null}
    </SafeAreaView>
  );
};

const mapViewFwRef = forwardRef(MapView);
export default mapViewFwRef;
