@startuml MVC

namespace Model {


    Feature --> Position
    Feature --> Scale

    MapPin --> Position
    Viewport --> Position

    Observation --> Feature
    Observation --> Position
    Observation --> ObservationStation

    ObservationStation --> Feature
    ObservationStation --> Position

    Scale --> Color

    Polygon --> ObservationStation

    class FrostResult<T> {
            constructor(res : T, success : boolean, err? : string)
            result : T
            success : boolean
            errorMessage : string
        }

    class Polygon {
        + constructor(color : Color, stations : ObservationStation[])
        ..
        + getColor() : Color
        + setColor(color : Color)
        + getStations() : ObservationStation[]
    }

    class MapPin {
        +constructor(id: string, position: Position, vaule: number,color: Color)
        ..
        + getPosition() : Position
        + setPosition(position : Position) : void

        + getValue() : number

        + getStationId() : string

        + getColor() : Color
    }

    class Viewport {
        + constructor(center: Position, zoom: number)
        ..
        + getCenter() : Position
        + getZoom() : number
        + getRadius() : number
        ..
        + setCenter(position : Position) : void
        + setZoom(zoom : number) : void
    }

    class ObservationStation {
        - id : string
        - name : string
        - description : string
        - position : Position
        - availableFeatures : Feature[]

        + constructor(id: string, name: string, description: string, position: Position)
        + setFeatures(features : Features[])
        + getId() : string
        + getName() : string
        + getDescription() : string
        + getPosition() : Position
        + getFeatures() : Feature[]
        + <<async>> getDiagrams() : Promise<DetailPage.Diagram[]>
        + hasFeature(feature : Feature) : Boolean
    }

    class Observation {
        - observationStation : ObservationStation
        - feature : Feature
        - value : number
        - timestamp : Date

        + constructor(observationStation: ObservationStation, feature: Feature, date: Date, value: number)
        + getObservationStation() : ObservationStation
        + getFeature() : Feature
        + getValue() : number
        + getTimeStamp() : Date
    }

    enum Timespan {
        LAST_YEAR,
        LAST_3_MONTHS,
        LAST_MONTH,
        LAST_WEEK,
        LAST_3_DAYS,
        TODAY,
    }

    class Position {
        - latitude : number
        - logitude : number

        + constructor(latitude: number, longitude: number)
        + getCoordinates(): [lat : number, lng: number]

        + getLatitude(): number
        + getLongitude(): number
        + toString(): string
    }

    class Feature {
        - id : number
        - nameId : string
        - descriptionId : string
        - scale : Scale
        - relatedWeblinkId : string
        - limit: number
        - unitOfMeasurement: string
        - drawableDiagramIds: string[]

        + constructor(id : string, nameId : string, descriptionId : string, relatedWeblinkId : string, scale : Scale, drawableDiagramIds: string[], limit: number, unitOfMeasurement: string)
        + getID(): number
        + getName(): string
        + getDescription(): string
        + getRelatedScale(): Scale
        + getRelatedWeblink(): string
        + <<async>> getDrawableDiagrams(observationStation: ObservationStation): Promise<DetailPage.Diagram[]>
        + getLimit(): number
        + getUnitOfMeasurement(): string
        + isLimitExeeded(observation: Observation): boolean
    }

    class Color {
        {static} + readonly GREEN = "#50EE70"
        {static} + readonly LIGHTGREEN = "#AAFF55"
        {static} + readonly YELLOW = "#FFFF00"
        {static} + readonly ORANGE = "#FFA500"
        {static} + readonly RED = "#FF0000"
        {static} + readonly WHITE = "#FFFFFF"
        {static} + readonly BLACK = "#000000"
        {static} + readonly PRIMARY
        {static} + readonly SECONDARY

        + constructor(r: number, g: number, b: number)
        + constructor(hex: string)
        + getRGB() : [number, number, number]
        + getHex() : string
    }

    class Scale {
        + constructor(linearTransition: boolean, colors : {number, string}[])
        + getColor(value : number) : Color
    }

}

namespace View {
    namespace Components {
        namespace MapPage {

            'View.Components.Layout.MapPage *-- View.Components.MapPage.MapView
            
            MapView *-- Search
            
            MapView *-- Map
            MapView *-- FeatureSelect
            MapView *-- FeatureInfo

            MapView *-- Legend

            class FeatureSelect {
                + getSelectedFeature() : Feature
                + onConfigurationChanged(conf : MapConfiguration)
            }
            class Search {
                + State.searchTerm : string
                + onSearch(string searchTerm) : void
                + onLocationSearch() : void
                + reset() : void
            }

            class MapView {
                + selectStation(station : Messstation)
                + getValueAt(position: Position, feature : Feature) : number
                + onViewportChange(viewport : Viewport)
                + onStationSelected(pin : MapPin)
            }
            class Map {
                + Props.pins : MapPin[]
                + Props.polygons : Polygon[]
                + Props.onViewportChange()
                + Props.handlePopup(pin : MapPin)
            }
            class Legend {
                + Props.scale : Scale
            }

            Map *-- StationInfo

            class StationInfo {
                + Props.station : ObservationStation
            }

            class FeatureInfo {
                + Props.feature : Feature
            }
        }
        namespace DetailPage {
            class ObservationStationProfile {
                + Props.observationStation: ObservationStation
            }
            class LocationMap {
                + Props.position: Position
            }
            abstract Diagram {
                + props.feature: Feature
                + props.observationStation: ObservationStation
                + getId() : string
            }
            class FeatureHistoryLineChart extends Diagram
            class YearComparisonLineChart extends Diagram
            class ComparisonToLastYearPieChart extends Diagram
        }


        namespace Layout {
            Layout *-- PageMenu
            PageMenu *-- LanguageMenu

            class MapPage
            class DetailPage {
                Props.id: string
            }
            class ErrorPage {
                props.errorCode : number
                props.errorMessage : string
            }
            class PrivacyPolicyPage
            class AboutPage
            class PageMenu
            class LanguageMenu
            class Layout
        }
    }
}


namespace Controller {
    namespace Frost {
        class DataProvider {
            + {static} getObservationStations(middle: Position, radius : number) : ObservationStation[]
            + {static} getLatestObservation(station : ObservationStation, feature : Feature) : Observation
            + {static} getObservations(station : ObservationStation, start : Date, end : Date, 
              {static}  feature : Feature, freuqency? : Frequency) : Observation[]
            + {static} getObservationStations(middle : Position, radius : number) : ObservationStation[]
            + {static} getStation(id : string) : ObservationStation
        }

        note as N1
            Fassade, nur diese Klasse ist nach außen sichtbar.
        end note

        DataProvider .. N1

        DataProvider --> FrostFactory
        DataProvider --> FrostServer

        abstract QueryBuilder {
            getQuery(options : any) : string
        }

        FrostFactory --> ResultModelConverter

        namespace GetObservationStation {
            class GetObservationStationsBuilder implements Controller.Frost.QueryBuilder {
                getQuery(options: GetObservationStationsOptions) : string
            }

            class GetObservationStationFactory implements Controller.Frost.FrostFactory{
                GetQueryBuilder() : GetObservationStationsBuilder
                GetConverter() : GetObservationStationsConverter
            }

            GetObservationStationsBuilder --> GetObservationStationsOptions
            GetObservationStationFactory --> GetObservationStationsBuilder
            GetObservationStationFactory --> GetObservationStationsConverter

            interface GetObservationStationsOptions {
                middle : Position
                radius : number
            }
            
            class GetObservationStationsConverter<ObservationStation[]> implements Controller.Frost.ResultModelConverter {
                convert(json : string) : FrostResult<ObservationStation[]>
            }

            note as N3
            GetObservationStation ist beispielhaft für alle Factorys
            die für die Übersicht weggelassen wurden.
            end note

            GetObservationStationFactory .. N3
        }


        abstract ResultModelConverter<T> {
            convert(json : string): FrostResult<T>
        }


        abstract FrostFactory {
            GetConverter() : ResultModelConverter<T>
            GetQueryBuilder() : QueryBuilder
        }



        FrostFactory --> QueryBuilder

        class FrostServer {
            getUrl() : string
            setUrl(url : string)

            request(ff : FrostFactory, options : any) : FrostResult
            <<async>> asyncRequest(ff : FrostFactory, options : any) : Promise<FrostResult>
        }
        note as N2
        request(new GetObservationStationsFactory(), options : GetObservationStationsOptions) {
            var query = ff.getQueryBuilder().getQuery(options)
            var json = this.send(query)
            var obs = ff.getConverter().convert(json)
            return obs
            }
        end note
        FrostServer .. N2
    }
    namespace Storage {
        class Language {
            {static} + getText(id: string) : string
            {static} + changeLanguage(languageId : string): void
            {static} + getSelectedLanguageId() : string
        }

        class MapConfigurationMemory {
            + save(MapConfiguration conf, viewport : Viewport)
            + load() : (MapConfiguration, Viewport)
        }
    }

    MapController --> Controller.Storage.MapConfigurationMemory

    class MapController {
        + handlePopup(pin : MapPin) : [Station, Observation]
        + handleViewportChange(viewport : Viewport)
        --
        + getPins() : MapPin[]
        + getPolygons() : Polygon[]

        + changeFeature(feature : Feature) : void
        + onConfigurationChange(mapConf : MapConfiguration) : void
        + search(searchTerm : string) : void
        + updateCurrentPosition(position : Position) : void
    }

    abstract MapConfiguration {
        + getPins(port : Viewport) : MapPin[]
        + getPolygons(port: Viewport) : Polygon[]
        + getScale() : Scale
        + getFeatures() : Feature[]
    }

    class StationConfiguration extends MapConfiguration {
        + setFeature(feature : Feature)
    }

    class PolygonConfiguration extends MapConfiguration {
        + setFeature(feature : Feature)
    }

    class NearConfiguration extends MapConfiguration {
        + radius : number
        --
        + setFeature(feature : Feature)
    }

}

@enduml
