import GrainDescriber from "grain/GrainDescriber";
import { cloneDeep } from "lodash";
import { MarkerData } from "maps/mapMarkers/Markers";
import { pond } from "protobuf-ts/pond";
import { stringToMaterialColour } from "utils/strings";
import { or } from "utils/types";

export class Bin {
  public settings: pond.BinSettings = pond.BinSettings.create();
  public status: pond.BinStatus = pond.BinStatus.create();
  public permissions: pond.Permission[] = [];

  public static create(pb?: pond.Bin): Bin {
    let my = new Bin();
    if (pb) {
      my.settings = pond.BinSettings.fromObject(cloneDeep(or(pb.settings, {})));
      my.status = pond.BinStatus.fromObject(cloneDeep(or(pb.status, {})));
      my.permissions = pb.binPermissions;
    }
    return my;
  }

  public static clone(other?: Bin): Bin {
    if (other) {
      return Bin.create(
        pond.Bin.fromObject({
          settings: cloneDeep(other.settings),
          status: cloneDeep(other.status),
          binPermissions: cloneDeep(other.permissions)
        })
      );
    }
    return Bin.create();
  }

  public static any(data: any): Bin {
    return Bin.create(pond.Bin.fromObject(cloneDeep(data)));
  }

  public setLocation(long: number, lat: number): void {
    this.settings.location = pond.Location.create();
    this.settings.location.longitude = long;
    this.settings.location.latitude = lat;
  }

  public removeLocation(): void {
    this.settings.location = null;
  }

  public binShape(): pond.BinShape | undefined {
    return this.settings.specs?.shape;
  }

  public key(): string {
    return this.settings.key;
  }

  public name(): string {
    return this.settings.name !== "" ? this.settings.name : "Bin " + this.key();
  }

  public grain(): pond.Grain {
    let g: pond.Grain = pond.Grain.GRAIN_NONE;
    if (this.settings.inventory) {
      g = this.settings.inventory.grainType;
    }
    return g;
  }

  public getLocation(): pond.Location | null | undefined {
    return this.settings.location;
  }

  public binMapped(): boolean {
    let location = this.getLocation();
    return (
      location !== undefined &&
      location !== null &&
      location.latitude !== 0 &&
      location.longitude !== 0
    );
  }

  public storage(): pond.BinStorage {
    return !this.settings.storage
      ? pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN
      : this.settings.storage;
  }

  public customType(): string {
    let c = "";
    if (this.settings.inventory) {
      c = this.settings.inventory.customTypeName;
    }
    return c;
  }

  public empty(): boolean {
    return this.settings.inventory?.empty || (this.settings.inventory !== undefined && this.settings.inventory !== null && this.settings.inventory.grainBushels < 5);
  }

  public objectType(): pond.ObjectType {
    return pond.ObjectType.OBJECT_TYPE_BIN;
  }

  public objectTypeString(): string {
    return "Bin";
  }

  public subtype(): string {
    return this.settings.inventory?.grainSubtype ?? "";
  }

  public grainColour(): string {
    let colour = "";
    if (this.settings.inventory) {
      if (this.grain() === pond.Grain.GRAIN_CUSTOM) {
        if (this.customType() !== "") {
          colour = stringToMaterialColour(this.customType());
        }
      } else {
        colour = GrainDescriber(this.grain()).colour;
      }
    }
    return colour;
  }

  public bushels(): number {
    let control = this.settings.inventory?.inventoryControl;
    let bushels = this.settings.inventory?.grainBushels || 0
    if (control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
        control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART){
      bushels = this.status.grainBushels
    }
    return bushels
  }

  public fillPercent(): number {
    let fill = 0;
    if (this.settings.inventory && this.settings.specs) {
      let bushels = this.settings.inventory.grainBushels
      if (this.settings.inventory.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
          this.settings.inventory.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART){
        bushels = this.status.grainBushels
      }
      fill = Math.round(
        (bushels / this.settings.specs.bushelCapacity) * 100
      );
    }
    return fill;
  }

  public grainName(): string {
    if (this.grain() !== pond.Grain.GRAIN_INVALID) {
      if (this.grain() === pond.Grain.GRAIN_CUSTOM) {
        return this.customType();
      } else {
        return GrainDescriber(this.grain()).name;
      }
    } else {
      return "None";
    }
  }

  public binFillCap(): string {
    let fillCap = "";
    if (this.settings.specs && this.settings.inventory) {
      fillCap = this.settings.inventory.grainBushels + "/" + this.settings.specs.bushelCapacity;
    }
    return fillCap;
  }

  public location(): pond.Location | undefined | null {
    let loc = this.settings.location;
    return loc;
  }

  public fanID(): number {
    return this.settings.fanId;
  }

  public supportedGrain(): boolean {
    let s = false;
    if (
      this.grain() !== pond.Grain.GRAIN_NONE &&
      this.grain() !== pond.Grain.GRAIN_CUSTOM &&
      this.grain() !== pond.Grain.GRAIN_INVALID
    ) {
      s = true;
    }
    return s;
  }

  public getMarkerData(
    clickFunc?: (event: React.PointerEvent<HTMLElement>, index: number, isMobile: boolean) => void,
    updateFunc?: (location: pond.Location) => void
  ): MarkerData {
    let m: MarkerData = {
      centered: true,
      longitude: this.location()?.longitude ?? 0,
      latitude: this.location()?.latitude ?? 0,
      title: this.name(),
      colour: this.grainColour(),
      visibleLevels: { min: 17 },
      graphPercent: this.fillPercent(),
      customSize: this.settings.theme?.height,
      details: [this.name() + ", " + this.binFillCap()],
      clickFunc: clickFunc,
      updateFunc: updateFunc
    };
    return m;
  }

  /**
   * returns the bushels per tonne set in the bins settings, if not set will return 1
   * @returns 1 or bushels per tonne
   */
  public bushelsPerTonne(): number {
    //trying to avoid a divide by 0 error by only returning a value greater than 0
    //since to get the weight you divide the current bushels by the bushels per tonne
    let bpt = 1;
    if (this.settings.inventory) {
      if (this.settings.inventory.bushelsPerTonne > 0) {
        bpt = this.settings.inventory.bushelsPerTonne;
      }
    }
    return bpt;
  }

  /**
   * gets the weight in tonnes for the grain inventory provided the bushels per tonne is set
   * if it is not set it will divide by 1 effectively returning the bushels
   * @returns grain weight
   */
  public grainTonnes(): number {
    let weight = 0;
    if (this.settings.inventory) {
      weight = this.settings.inventory.grainBushels / this.bushelsPerTonne();
    }
    return Math.round(weight * 100) / 100;
  }

  /**
   * gets the enum value for the inventory control in the bins inventory
   */
  public inventoryControl(): pond.BinInventoryControl {
    let c = pond.BinInventoryControl.BIN_INVENTORY_CONTROL_UNKNOWN;
    if (this.settings.inventory) {
      return this.settings.inventory.inventoryControl;
    }
    return c;
  }
}
