class MapHandler {
  map: L.Map;
  overlays: OverlayState;
  layers: TileLayerWrapper[];
  modals: ModalCollection;
  
  static instance: MapHandler | null = null;
  
  private constructor(map: L.Map, overlays: OverlayState, layers: TileLayerWrapper[], modals: ModalCollection) {
    this.map = map;
    this.overlays = overlays;
    this.layers = layers;
    this.modals = modals;
  }
  
  static init(map: L.Map, overlays: OverlayState, layers: TileLayerWrapper[], modals: ModalCollection): void {
    if (!MapHandler.instance) {
      MapHandler.instance = new MapHandler(map, overlays, layers, modals);
    }
  }

  static setButtonClick(btnId: string, handler: any): void {
      const button = document.getElementById(btnId);
      if (button) {
        button.onclick = handler;
      }
  }
  
  static resetMapClick(): void {
    const self = MapHandler.instance;
    if (self) {
      try {
        const addPointBtn = document.getElementById("addPoint-btn");
        if (addPointBtn) {
          addPointBtn.classList.remove("activeBtn");
        }
        self.map.off("click", MapHandler.addMarker);
      } catch {}
      try {
        const addCircleBtn = document.getElementById("addCircle-btn");
        if (addCircleBtn) {
          addCircleBtn.classList.remove("activeBtn");
        }
        self.map.off("click", MapHandler.addCircle);
      } catch {}
      try {
        const addPolygonBtn = document.getElementById("addPolygon-btn");
        if (addPolygonBtn) {
          addPolygonBtn.classList.remove("activeBtn");
        }
        self.map.off("click", MapHandler.polygonAddPoint);
      } catch {}
      try {
        const saveBtn = document.getElementById("save-btn");
        if (saveBtn) {
          saveBtn.classList.remove("activeBtn");
        }
      } catch {}
      try {
        const clearBtn = document.getElementById("clear-btn");
        if (clearBtn) {
          clearBtn.classList.remove("activeBtn");
        }
      } catch {}
      try {
        const resetBtn = document.getElementById("restore-btn");
        if (resetBtn) {
          resetBtn.classList.remove("activeBtn");
        }
      } catch {}
      try {
        const menuBtn = document.getElementById("menu-btn");
        if (menuBtn) {
          menuBtn.classList.remove("activeBtn");
        }
      } catch {}
      self.overlays.polyline.clearPoints();
    }
  }
  
  static addMarker(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.cancel.setVisible(false);
      self.modals.createOverlay.setState(OverlayType.POINT, {
        latlng: e.latlng,
        map: self.map,
        overlays: self.overlays,
      });    
      MapHandler.resetMapClick();
      self.modals.createOverlay.setVisible(true);
    }
  }
  
  static editOverlay(overlay: OverlayBase, type: OverlayType): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      self.modals.createOverlay.setState(type, {
        self: overlay,
        map: self.map,
        overlays: self.overlays,
      });
      MapHandler.resetMapClick();
      self.modals.createOverlay.setVisible(true);
    }
  }
  
  static addCircle(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.cancel.setVisible(false);
      self.modals.createOverlay.setState(OverlayType.CIRCLE, {
        latlng: e.latlng,
        map: self.map,
        overlays: self.overlays,
      });    
      MapHandler.resetMapClick();
      self.modals.createOverlay.setVisible(true);
    }
  }
  
  static addPolygon(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.okCancel.setVisible(false);
      self.modals.createOverlay.setState(OverlayType.POLYGON, {
        points: self.overlays.polyline.points,
        map: self.map,
        overlays: self.overlays,
      });
      MapHandler.resetMapClick();
      self.overlays.polyline.clearPoints();
      self.modals.createOverlay.setVisible(true);
    }
  }
  
  static circleCollect(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      MapHandler.resetMapClick();
      (e.target as HTMLElement).classList.add("activeBtn");
      self.map.on("click", MapHandler.addCircle);
      
      const cancelBtn = self.modals.cancel.cancelBtn();
      if (cancelBtn) {
        cancelBtn.onclick = ()=> {
          self.modals.closeAll();
          MapHandler.resetMapClick();
        };
      }
      self.modals.cancel.setMsg("Placing circle");
      self.modals.cancel.setVisible(true);
    }
  }
  
  static markerCollect(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      MapHandler.resetMapClick();
      (e.target as HTMLElement).classList.add("activeBtn");
      self.map.on("click", MapHandler.addMarker);
      
      const cancelBtn = self.modals.cancel.cancelBtn();
      if (cancelBtn) {
        cancelBtn.onclick = ()=> {
          self.modals.closeAll();
          MapHandler.resetMapClick();
        };
      }
      self.modals.cancel.setMsg("Placing marker");
      self.modals.cancel.setVisible(true);
    }
  }
  
  static polygonCollect(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      MapHandler.resetMapClick();
      (e.target as HTMLElement).classList.add("activeBtn");
      
      // show cancel -- on cancel, clear polyline and reset map handling
      const cancelBtn = self.modals.cancel.cancelBtn();
      if (cancelBtn) {
        cancelBtn.onclick = MapHandler.polygonClearPoints;
      }
      self.modals.cancel.setMsg("Creating polygon");
      self.modals.cancel.setVisible(true);
      
      self.map.on("click", MapHandler.polygonAddPoint);
    }
  }
  
  static polygonClearPoints(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      self.overlays.polyline.clearPoints();
      MapHandler.resetMapClick();
    }
  }
  
  static polygonAddPoint(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      self.overlays.polyline.insertPoint(e.latlng);
      if (self.overlays.polyline.numPoints() >= 3) {
        self.modals.cancel.setVisible(false);
        
        const okBtn = self.modals.okCancel.okBtn();
        if (okBtn) {
          okBtn.onclick = MapHandler.addPolygon;
        }
        const cancelBtn = self.modals.okCancel.cancelBtn();
        if (cancelBtn) {
          cancelBtn.onclick = MapHandler.polygonClearPoints;
        }
        self.modals.okCancel.setMsg("Creating polygon");
        self.modals.okCancel.setVisible(true);
      }
    }
  }
  
  static overlaySave(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      MapHandler.resetMapClick();
      (e.target as HTMLElement).classList.add("activeBtn");
      self.modals.okCancel.setMsg("Save current map overlays?");

      const okBtn = self.modals.okCancel.okBtn();
      if (okBtn) {
        okBtn.onclick = ()=> {
          OverlayState.save(self.overlays);
          self.modals.okCancel.setVisible(false);
          MapHandler.resetMapClick();
          self.modals.info.setMsg("Save complete");
          self.modals.info.setVisible(true);
        }
      }
      const cancelBtn = self.modals.okCancel.cancelBtn();
      if (cancelBtn) {
        cancelBtn.onclick = () => {
          self.modals.okCancel.setVisible(false);
          MapHandler.resetMapClick();
        }
      }
      self.modals.okCancel.setVisible(true);
    }
  }
  
  static overlayReset(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      MapHandler.resetMapClick();
      (e.target as HTMLElement).classList.add("activeBtn");
      self.modals.okCancel.setMsg("Restore overlays from saved data?");

      const okBtn = self.modals.okCancel.okBtn();
      if (okBtn) {
        okBtn.onclick = ()=> {
          self.overlays = OverlayState.clear(self.overlays, self.map);
          self.overlays = OverlayState.load();
          self.overlays.markers.forEach(m=>m.add(self.map));
          self.overlays.circles.forEach(m=>m.add(self.map));
          self.overlays.polygons.forEach(m=>m.add(self.map));
          self.overlays.polyline.add(self.map);
          self.modals.okCancel.setVisible(false);
          MapHandler.resetMapClick();
          self.modals.info.setMsg("Restored");
          self.modals.info.setVisible(true);
        }
      }
      const cancelBtn = self.modals.okCancel.cancelBtn();
      if (cancelBtn) {
        cancelBtn.onclick = () => {
          self.modals.okCancel.setVisible(false);
          MapHandler.resetMapClick();
        }
      }
      self.modals.okCancel.setVisible(true);
    }
  }
  
  static overlayClear(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      MapHandler.resetMapClick();
      (e.target as HTMLElement).classList.add("activeBtn");
      self.modals.okCancel.setMsg("Clear all map overlays (will not affect saved data)?");

      const okBtn = self.modals.okCancel.okBtn();
      if (okBtn) {
        okBtn.onclick = ()=> {
          self.overlays = OverlayState.clear(self.overlays, self.map);
          MapHandler.resetMapClick();
          self.modals.okCancel.setVisible(false);
        }
      }
      const cancelBtn = self.modals.okCancel.cancelBtn();
      if (cancelBtn) {
        cancelBtn.onclick = () => {
          MapHandler.resetMapClick();
          self.modals.okCancel.setVisible(false);
        }
      }
      self.modals.okCancel.setVisible(true);
    }
  }
  
  static swapTiles(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      if (TileLayerWrapper.getActiveLayer() == "satelliteLayer") {
        TileLayerWrapper.enableOnly("streetLayer", self.map);
      } else {
        TileLayerWrapper.enableOnly("satelliteLayer", self.map);
      }
    }
  }
  
  static setHome(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      const okCancel = self.modals.okCancel;
      okCancel.setMsg("Set Home to current coordinates?");
      const okBtn = okCancel.okBtn();
      if (okBtn) {
        okBtn.onclick = (e: any) => {
          okCancel.setVisible(false);
          localStorage.setItem("home", JSON.stringify(self.map.getCenter() as Point));
          const info = self.modals.info;
          info.setMsg("Home coordinates set");
          info.setVisible(true);
        }
      }
      const cancelBtn = okCancel.cancelBtn();
      if (cancelBtn) {
        cancelBtn.onclick = () => {
          okCancel.setVisible(false);
        }
      }
      okCancel.setVisible(true);
    }
  }
  
  static goHome(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      const homeData = localStorage.getItem("home");
      if (homeData) {
        const home = <Point>JSON.parse(homeData);
        if (home) {
          self.map.setView(home, 13);
        }
      }
    }
  }
  
  static toggleMenu(e: any): void {
    const self = MapHandler.instance;
    if (self) {
      const visible = self.modals.overlayMgr.visible();
      self.modals.closeAll();
      MapHandler.resetMapClick();
      self.modals.overlayMgr.setVisible(!visible);
      if (!visible) {
        (e.target as HTMLElement).classList.add("activeBtn");
      }
    }
  }

  static confirmDelete(overlay: OverlayBase): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      self.modals.okCancel.setMsg(`Delete "${overlay.name}"?`);
      const okBtn = self.modals.okCancel.okBtn();
      if (okBtn) {
        okBtn.onclick = () => {
          self.modals.closeAll();
          overlay.remove(self.map);
          self.modals.info.setMsg(`"${overlay.name}" deleted`);
          self.modals.info.setVisible(true);
        }
      }
      const cancelBtn = self.modals.okCancel.cancelBtn();
      if (cancelBtn) {
        cancelBtn.onclick = () => {
          self.modals.closeAll();
        }
      }
      self.modals.okCancel.setVisible(true);
    }
  }
  
  static exportSingle(overlay: OverlayBase): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      self.modals.importExport.setTitle("Export Overlay");
      self.modals.importExport.setErrMsg("", false);
      
      const okBtn = self.modals.importExport.okBtn();
      if (okBtn) {
        okBtn.innerText = "Copy to clipboard";
        okBtn.onclick = () => {
          self.modals.importExport.copyTextArea();
          self.modals.closeAll();
          self.modals.info.setMsg("Copied the data to the clipboard");
          self.modals.info.setVisible(true);
        }
      }
      
      self.modals.importExport.setTextArea(OverlayState.exportSingle(overlay), true);
      self.modals.importExport.setVisible(true);
    }
  }
  
  static exportAll(): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      self.modals.importExport.setTitle("Export All Overlays");
      self.modals.importExport.setErrMsg("", false);

      const okBtn = self.modals.importExport.okBtn();
      if (okBtn) {
        okBtn.innerText = "Copy to clipboard";
        okBtn.onclick = () => {
          self.modals.importExport.copyTextArea();
          self.modals.closeAll();
          self.modals.info.setMsg("Copied the data to the clipboard");
          self.modals.info.setVisible(true);
        }
      }
      
      self.modals.importExport.setTextArea(OverlayState.export(self.overlays), true);
      self.modals.importExport.setVisible(true);
    }
  }
  
  static import(): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
      self.modals.importExport.setTitle("Import Overlay Data");
      self.modals.importExport.setErrMsg("", false);
      self.modals.importExport.setTextArea("", false);
      
      const okBtn = self.modals.importExport.okBtn();
      if (okBtn) {
        okBtn.innerText = "Import";
        okBtn.onclick = () => {
          MapHandler.doImport(self.modals.importExport.getText());
        }
      }
      self.modals.importExport.setVisible(true);
    }
  }
  
  static doImport(data: string): void {
    const self = MapHandler.instance;
    if (self) {
      if (OverlayState.importWrapper(data, self.overlays, self.map)) {
        self.modals.closeAll();
        self.modals.info.setMsg("Import successful");
        self.modals.info.setVisible(true);
      } else {
        self.modals.importExport.setErrMsg("The data was malformed &mdash; please check that it is valid JSON exported from ONYX/scry", true);
      }
    }
  }
  
  static closeImportExport(): void {
    const self = MapHandler.instance;
    if (self) {
      self.modals.closeAll();
    }
  }
}