class Point implements L.LatLngLiteral {
  lat: number = 0.00;
  lng: number = 0.00;
}

enum OverlayType {
  POINT = 0,
  CIRCLE = 1,
  POLYGON = 2,
}

interface Overlay {
  name: string;
  desc: string;
  points: Point[];
  options: any;
}

class OverlayData implements Overlay {
  name: string;
  desc: string;
  points: Point[];
  options: any;
  type: OverlayType;
  
  constructor(type: OverlayType, name: string, desc: string, points: Point[], options: any) {
    this.type = type;
    this.name = name;
    this.desc = desc;
    this.points = points;
    this.options = options;
  }
}

abstract class OverlayBase implements Overlay {
  name: string;
  desc: string;
  points: Point[];
  options: any;
  protected self: any;
  
  constructor(name: string, desc: string, points: Point[], options: any) {
    this.name = name;
    this.desc = desc;
    this.points = points;
    this.options = options;
  }
  
  add(map: L.Map): void {
    this.self.addTo(map);
  }
  
  remove(map: L.Map): void {
    this.self.removeFrom(map);
  }
}


class Marker extends OverlayBase {
  
  constructor(name: string, desc: string, point: Point, options: any) {
    super(name, desc, [ point ], options);
    this.self = L.marker(point);
    this.self.bindPopup(`<h3>${name}</h3><p>${desc}</p>`);
  }
}

class Circle extends OverlayBase {

  constructor(name: string, desc: string, point: Point, options: any) {
    super(name, desc, [ point ], options);
    this.self = L.circle(point, options);
  }
}

class Polygon extends OverlayBase {

  constructor(name: string, desc: string, points: Point[], options: any) {
    super(name, desc, points, options);    
    this.self = L.polygon(points, options);
  }
}

class OverlayState {
  markers: Marker[];
  circles: Circle[];
  polygons: Polygon[];
  
  constructor() {
    this.markers = [];
    this.circles = [];
    this.polygons = [];
  }
  
  static load(): OverlayState {
    const store = localStorage.getItem("overlay_state");
    if (store) {  
      const model = JSON.parse(store);
      return {
        markers: model.markers.map((m: OverlayData) => OverlayState.fromData(m)),
        circles: model.circles.map((c: OverlayData) => OverlayState.fromData(c)),
        polygons: model.polygons.map((p: OverlayData) => OverlayState.fromData(p)),
      } as OverlayState
    } else {
      return new OverlayState();
    }
  }
  
  static save(overlayState: OverlayState): void {
    localStorage.setItem("overlay_state", JSON.stringify({
      markers: overlayState.markers.map((m: OverlayBase) => OverlayState.toData(m)),
      circles: overlayState.circles.map((c: OverlayBase) => OverlayState.toData(c)),
      polygons: overlayState.polygons.map((p: OverlayBase) => OverlayState.toData(p)),
    }));
  }
  
  static clear(overlayState: OverlayState, map: L.Map): OverlayState {
    overlayState.markers.forEach((m: Marker) => m.remove(map));
    overlayState.circles.forEach((c: Circle) => c.remove(map));
    overlayState.polygons.forEach((p: Polygon) => p.remove(map));
    
    return new OverlayState();
  }
  
  private static toData(source: OverlayBase): OverlayData {
    let type = OverlayType.POINT;
    if (source.points.length > 1) {
      type = OverlayType.POLYGON;
    } else if (source.options.radius) {
      type = OverlayType.CIRCLE;
    }
    return new OverlayData(type, source.name, source.desc, source.points, source.options);
  }
  
  private static fromData(data: OverlayData): OverlayBase {
    switch(data.type) {
      case OverlayType.POINT:
        return new Marker(data.name, data.desc, data.points[0], data.options);
      case OverlayType.CIRCLE:
        return new Circle(data.name, data.desc, data.points[0], data.options);
      case OverlayType.POLYGON:
        return new Polygon(data.name, data.desc, data.points, data.options);
    }
  }


}