import React, { ChangeEvent, useEffect, useState } from 'react'
import { CircleMarker, LayerGroup, LayersControl, MapContainer, Popup, TileLayer, ZoomControl } from 'react-leaflet'
import { useHistory } from 'react-router-dom'
import chroma from 'chroma-js'
import { InputWithLabel } from '../../components/inputWithLabel/InputWithLabel'
import DatePicker from 'react-datepicker'
import Input from '../../components/input/Input'
import styled from 'styled-components'
import { distance } from '../../style/sizes'
import Select from '../../components/select/Select'
import Button from '../../components/button/Button'
import { ClipLoader } from 'react-spinners'
import { BikePointProperty } from '../../../../backend/src/entities/BikePoint'

interface IBikePoint {
    id: string
    commonName: string
    lat: number
    lon: number
    additionalProperties: BikePointProperty[]
}

interface IBikePointActivity {
    rentals: number
    returns: number
    rentalsReturnsImbalance: number
}

interface IBikePointsActivity {
    [bikePointId: string]: IBikePointActivity
}

interface INumberRange {
    min: number
    max: number
}

interface IBikePointsActivityResponse {
    bikePointsActivity: IBikePointsActivity
    rentalsRange: INumberRange
    returnsRange: INumberRange
    rentalsReturnsImbalanceRange: INumberRange
}

interface IColorRange {
    min: string
    max: string
}

type ActivityType = 'rentals' | 'returns' | 'rentalsReturnsImbalance'

/**
 * This page contains a map of London. The user can select from different interactive visualizations.
 */
export const BikeSharingMap = () => {
    const history = useHistory()

    const [startTimeStamp, setStartTimestamp] = useState(Date.UTC(2015, 0, 4, 0, 0))
    const [endTimeStamp, setEndTimeStamp] = useState(Date.UTC(2015, 0, 19, 0, 0))
    const [activityType, setActivityType] = useState<ActivityType>('rentals')
    const [colorRange, setColorRange] = useState<IColorRange>({min: '#fcf2ae', max: '#a90e00'})

    const [bikePoints, setBikePoints] = useState<IBikePoint[] | undefined>(undefined)
    const [data, setData] = useState<IBikePointsActivityResponse | undefined>(undefined)

    const [loadingState, setLoadingState] = useState(false)

    // first, fetch bike-points with their coordinates to show on map fast
    useEffect(() => {
        (async () => {
            const response = await fetch(`http://localhost:8081/api/bike-points/all`)
            const jsonResponse = await response.json()
            setBikePoints(jsonResponse.bikePoints)
        })()
    }, [])

    const fetchData = async () => {
        setLoadingState(true)
        const response = await fetch(`http://localhost:8081/api/bike-points-activity?from=${startTimeStamp / 1000}&to=${endTimeStamp / 1000}`)
        const jsonResponse = await response.json()
        setData(jsonResponse)
        setLoadingState(false)
    }

    // fetch data for bike stations from our server once at beginning, later only on button click
    useEffect(() => {fetchData()}, [])

    return (
        <>
            <StyledControl>
                <div>
                    <StyledRow>
                        <Button onClick={() => history.push("/")}>Back home</Button>
                    </StyledRow>
                    <StyledRow>
                        <h2>Map view</h2>
                    </StyledRow>
                    <StyledRow>
                        Visualize the number of rented or returned bikes at bike points in the chosen timeframe. Or the imbalance between the two.
                        <br/><br/>
                        An intense color indicates a high number, relative to the other ones. The color range always adjusts to the current max and min value.
                        <br/><br/>
                        Black dots represent bike points, for which no data is available in the selected timeframe.
                        <br/><br/>
                    </StyledRow>
                    <StyledRow>
                        <Select onChange={(event: ChangeEvent<HTMLSelectElement>) => setActivityType(event.target.value as ActivityType)}>
                            <option value='rentals'>Number of rentals</option>
                            <option value='returns'>Number of returns</option>
                            <option value='rentalsReturnsImbalance'>Returns/Rentals imbalance</option>
                        </Select>
                    </StyledRow>
                    <StyledRow>
                        <InputWithLabel label={'From'}>
                            <DatePicker
                                selected={new Date(startTimeStamp)}
                                onChange={(date: Date) => setStartTimestamp(date.getTime())}
                                dateFormat='yyyy/MM/dd, HH:mm'
                                showTimeSelect
                                timeFormat='HH:mm'
                                timeIntervals={5}
                                customInput={<Input/>}
                            />
                        </InputWithLabel>
                        <InputWithLabel label={'To'}>
                            <DatePicker
                                selected={new Date(endTimeStamp)}
                                onChange={(date: Date) => setEndTimeStamp(date.getTime())}
                                dateFormat='yyyy/MM/dd, HH:mm'
                                showTimeSelect
                                timeFormat='HH:mm'
                                timeIntervals={5}
                                customInput={<Input/>}
                            />
                        </InputWithLabel>
                    </StyledRow>
                    <StyledRow>
                        <Button onClick={fetchData}>Apply Options</Button>&nbsp;&nbsp;&nbsp;{loadingState && <ClipLoader color={'white'} loading={loadingState}/>}
                    </StyledRow>
                </div>
                <div>
                    <StyledRow>
                        <StyledLegendText>
                            <div>{data ? getValuesRange(activityType, data).min : '-'}</div>
                            <div>{data ? getValuesRange(activityType, data).max : '-'}</div>
                        </StyledLegendText>
                    </StyledRow>
                    <StyledRow>
                        <StyledGradient {...colorRange}/>
                    </StyledRow>
                </div>
            </StyledControl>
            <MapContainer center={[51.5, -0.17]} zoom={12} zoomControl={false} style={{zIndex: 1}}>
                <LayersControl position='bottomright'>
                    <LayersControl.Overlay checked name='Bike points'>
                        <LayerGroup>
                            {
                                bikePoints?.map(bikePoint => {
                                    const activities = data?.bikePointsActivity[bikePoint.id]
                                    const numberToVisualize = activities && activities[activityType]
                                    const color = numberToVisualize && data ? getColor(numberToVisualize, activityType, data, colorRange) : 'black'

                                    return (
                                        <CircleMarker center={[bikePoint.lat, bikePoint.lon]} pathOptions={{color: color, fillColor: color, fillOpacity: 1}} radius={6}>
                                            <Popup>
                                                <h3><Black>{bikePoint.commonName}</Black></h3>

                                                <br/><b>Number of docks:</b> {Number(bikePoint.additionalProperties.find(additionalProperty => additionalProperty.key==='NbDocks')?.value ?? 'unknown')}
                                                <br/>
                                                <br/><b>Rentals:</b> {activities?.rentals ?? '-'}
                                                <br/><b>Returns:</b> {activities?.returns ?? '-'}
                                                <br/><b>Returns/Rentals imbalance:</b> {activities?.rentalsReturnsImbalance ?? '-'}
                                                <br/><br/>
                                                <Button onClick={() => history.push(`/bike-point-details/${bikePoint.id}`)}>To bike point details</Button>
                                            </Popup>
                                        </CircleMarker>
                                    )
                                })
                            }
                        </LayerGroup>
                    </LayersControl.Overlay>
                    <LayersControl.BaseLayer checked name='Stadia.AlidadeSmoothDark'>
                        <TileLayer
                            url='https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png'
                            attribution='&copy; <a href="https://stadiamaps.com/">Stadia Maps</a>, &copy; <a href="https://openmaptiles.org/">OpenMapTiles</a> &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors'
                        />
                    </LayersControl.BaseLayer>
                    <LayersControl.BaseLayer name='Stadia.AlidadeSmooth'>
                        <TileLayer
                            url='https://tiles.stadiamaps.com/tiles/alidade_smooth/{z}/{x}/{y}{r}.png'
                            attribution='&copy; <a href="https://stadiamaps.com/">Stadia Maps</a>, &copy; <a href="https://openmaptiles.org/">OpenMapTiles</a> &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors'
                        />
                    </LayersControl.BaseLayer>
                    <LayersControl.BaseLayer name="OpenStreetMap.BlackAndWhite">
                        <TileLayer
                            attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
                            url="https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png"
                        />
                    </LayersControl.BaseLayer>
                    <LayersControl.BaseLayer name="OpenStreetMap.Mapnik">
                        <TileLayer
                            attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
                            url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
                        />
                    </LayersControl.BaseLayer>
                </LayersControl>
                <ZoomControl position={'topright'}/>
            </MapContainer>
        </>
    )
}

// returns interpolated color according to provided value, using current selected range
const getColor = (value: number, activityType: ActivityType, data: IBikePointsActivityResponse, colorRange: IColorRange) => {
    const valuesRange = getValuesRange(activityType, data)
    return chroma.scale([colorRange.min, colorRange.max]).domain([valuesRange.min, valuesRange.max])(value).hex()
}

// returns value range for selected data
const getValuesRange = (activityType: ActivityType, data: IBikePointsActivityResponse) => {
    let rangeValues: INumberRange
    switch (activityType) {
        case 'rentals':
            rangeValues = data.rentalsRange
            break
        case 'returns':
            rangeValues = data.returnsRange
            break
        case 'rentalsReturnsImbalance':
            rangeValues = data.rentalsReturnsImbalanceRange
            break
    }
    return rangeValues
}

const StyledGradient = styled.div<IColorRange>`
    height: 2rem;
    width: 100%;
    background: linear-gradient(to right, ${props => props.min} 0%, ${props => props.max} 100%);
`
const StyledLegendText = styled.div`
    width: 100%;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
`
const StyledControl = styled.div`
    padding: ${distance.large};
    position: absolute;
    height: 100vh;
    max-width: 30vw;
    min-width: 24vw;
    z-index: 2;
    background: ${({theme}) => theme.colors.backgroundSecondary};
    
    display: flex;
    flex-direction: column;
    justify-content: space-between;
`
const Black = styled.div`
    color: black;
`
const StyledRow = styled.div`
      padding: ${distance.verySmall};
      display: flex;
      flex-direction: row;
`
