import {
  Accordion,
  AccordionDetails,
  AccordionSummary,
  Alert,
  AlertTitle,
  Button,
  Collapse,
  FormControl,
  FormControlLabel,
  Grid2 as Grid,
  IconButton,
  InputAdornment,
  MenuItem,
  Switch,
  TextField,
  Theme,
  Tooltip,
  Typography
} from "@mui/material";
import { 
  ExpandMore,
  Close as CloseIcon
} from "@mui/icons-material";
import PeriodSelect from "common/time/PeriodSelect";
import SearchSelect, { Option } from "common/SearchSelect";
import { cloneDeep } from "lodash";
import { Component, Device } from "models";
import {
  extension,
  getMeasurements,
  hasDimensions,
  hasFan,
  isController,
  isMultiSensor,
  primaryMeasurement
} from "pbHelpers/ComponentType";
import { describeMeasurement, MeasurementDescriber } from "pbHelpers/MeasurementDescriber";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import React, { useEffect, useState } from "react";
import { bestUnit, milliToX } from "common/time/duration";
import { useGlobalState } from "providers";
import { green, red } from "@mui/material/colors";
import { makeStyles } from "@mui/styles";
import { getDistanceUnit } from "utils";
import { GrainOptions } from "grain";
import CompModes from "component/ComponentMode.json";
import FanPicker from "fans/fanPicker";

const useStyles = makeStyles((theme: Theme) => {
  return ({
    stepper: {
      paddingRight: 0,
      paddingLeft: 0
    },
    switchControl: {
      marginLeft: theme.spacing(0.25),
      [theme.breakpoints.up("sm")]: {
        marginLeft: theme.spacing(1)
      }
    },
    selectInput: {
      minHeight: "48px"
    },
    fullWidth: {
      width: "100%"
    },
    heading: {
      fontSize: theme.typography.pxToRem(13),
      flexBasis: "25%",
      flexShrink: 0
    },
    secondaryHeading: {
      fontSize: theme.typography.pxToRem(13),
      color: theme.palette.text.secondary
    },
    greenButton: {
      color: green["600"]
    },
    redButton: {
      color: red["600"]
    },
    noPadding: {
      padding: 0
    },
    dataUsageCard: { width: "100%" },
    dataUsageHeader: { paddingBottom: 0 },
    dataUsageIcon: { color: "var(--status-warning)" },
    dataUsageContent: { paddingTop: 0 }
  })
});

interface Props {
  device: Device;
  component: Component;
  componentChanged: (component: Component, formValid: boolean) => void;
  canEdit: boolean;
}

interface ComponentData {
  component: Component;
  measure: boolean;
  report: boolean;
  partiallyFilled: boolean;
  coefficient: string;
  offset: string;
  //these are for the new calibrations
  coefficients: string[];
  offsets: string[];
  length: string;
  height: string;
  width: string;
  sensorDistance: string;
}

export default function ComponentForm(props: Props) {
  const classes = useStyles();
  const grainOptions = GrainOptions();
  const [{ user }] = useGlobalState();
  const { device, component, componentChanged, canEdit } = props;
  const [reportExpanded, setReportExpanded] = useState(false);
  const [dataUsageWarningDismissed, setDataUsageWarningDismissed] = useState(true);
  const [form, setForm] = useState<ComponentData>({
    component: cloneDeep(component),
    measure: false,
    report: false,
    partiallyFilled: false,
    coefficient: "1",
    offset: "0",
    coefficients: [""],
    offsets: [""],
    length: "0",
    height: "0",
    width: "0",
    sensorDistance: "0"
  });
  const [compMode, setCompMode] = useState<any>();
  //const [numCalibrations, setNumCalibrations] = useState(0)

  useEffect(() => {
    let formComponent = cloneDeep(component);
    let describer = describeMeasurement(
      primaryMeasurement(formComponent.settings.type),
      formComponent.settings.type,
      formComponent.settings.subtype
    );
    let offset = describer.toDisplay(formComponent.settings.calibrationOffset);
    let formCoefficients: string[] = [];
    let formOffsets: string[] = [];
    //setNumCalibrations(formComponent.settings.calibrations.length)
    formComponent.settings.calibrations.forEach(calibration => {
      formCoefficients.push(calibration.calibrationCoefficient.toString());
      formOffsets.push(describer.toDisplay(calibration.calibrationOffset).toString());
    });
    if (
      formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE &&
      formComponent.settings.grainType === pond.Grain.GRAIN_INVALID
    ) {
      formComponent.settings.grainType = pond.Grain.GRAIN_NONE;
    }
    setReportExpanded(
      formComponent.settings.measurementPeriodMs !== formComponent.settings.reportPeriodMs
    );
    setDataUsageWarningDismissed(localStorage.getItem("dataUsageWarningDismissed") !== null);
    CompModes.forEach((mode: any) => {
      if (formComponent.type() === mode.type && formComponent.subType() === mode.subtype) {
        setCompMode(mode);
      }
    });

    //get the dimension values for the form and convert them to the form display values (m or ft)
    let length = formComponent.settings.containerDimensions?.lengthCm ?? 0;
    let width = formComponent.settings.containerDimensions?.widthCm ?? 0;
    let height = formComponent.settings.containerDimensions?.heightCm ?? 0;

    if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
      length = length / 100;
      width = width / 100;
      height = height / 100;
    } else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
      length = length / 30.48;
      width = width / 30.48;
      height = height / 30.48;
    }

    let sensorDistance = formComponent.settings.sensorDistanceCm ?? 0;

    if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
      sensorDistance = sensorDistance / 100;
    } else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
      sensorDistance = sensorDistance / 30.48;
    }

    setForm({
      component: formComponent,
      measure: formComponent.settings.measurementPeriodMs > 0,
      report: formComponent.settings.reportPeriodMs > 0,
      partiallyFilled: formComponent.settings.grainFilledTo > 0,
      coefficient: formComponent.settings.calibrationCoefficient.toString(),
      offset: offset.toString(),
      coefficients: formCoefficients,
      offsets: formOffsets,
      length: length.toFixed(2),
      width: width.toFixed(2),
      height: height.toFixed(2),
      sensorDistance: sensorDistance.toFixed(2)
    });
  }, [component]);

  const tidyComponent = (component: Component): Component => {
    const { measure, report } = form;
    if (!report) {
      component.settings.reportPeriodMs = 0;
    }
    if (!measure) {
      component.settings.measurementPeriodMs = 0;
    }
    return component;
  };

  const isFormValid = () => {
    let { component } = form;
    return (
      isNameValid(component.settings.name) &&
      isMeasurementPeriodValid() &&
      isReportPeriodValid() &&
      isFilledToValid() &&
      isCoefficientValid() &&
      isOffsetValid() &&
      isMinCycleTimeValid() &&
      isLengthDimensionValid() &&
      isHeightDimensionValid() &&
      isWidthDimensionValid() &&
      isSensorDistanceValid()
    );
  };

  useEffect(() => {
    componentChanged(form.component, isFormValid());
  }, [form.component, componentChanged]); // eslint-disable-line react-hooks/exhaustive-deps

  const isNameValid = (name: string): boolean => {
    return name.length > 0;
  };

  const isMeasurementPeriodValid = () => {
    const { measure, component } = form;
    const ext = extension(component.settings.type, component.settings.subtype);
    let valid = true;
    if (
      measure &&
      (component.settings.measurementPeriodMs < 1000 ||
        component.settings.measurementPeriodMs < ext.minMeasurementPeriodMs)
    ) {
      valid = false;
    }
    return valid;
  };

  const isFilledToValid = () => {
    const { partiallyFilled, component } = form;
    return (partiallyFilled && component.settings.grainFilledTo > 0) || !partiallyFilled;
  };

  const minMeasurementPeriodDescription = () => {
    const { component } = form;
    const ext = extension(component.settings.type, component.settings.subtype);
    const min = Math.max(1000, ext.minMeasurementPeriodMs);
    const unit = bestUnit(min);
    const value = milliToX(min, unit).toString();
    return value + " " + unit;
  };

  const isReportPeriodValid = () => {
    const { report, component } = form;
    const ext = extension(component.settings.type, component.settings.subtype);
    let valid = true;
    if (
      report &&
      (component.settings.reportPeriodMs < 10000 ||
        component.settings.reportPeriodMs < ext.minMeasurementPeriodMs)
    ) {
      valid = false;
    }
    return valid;
  };

  const minReportPeriodDescription = () => {
    const { component } = form;
    const ext = extension(component.settings.type, component.settings.subtype);
    const min = Math.max(10000, ext.minMeasurementPeriodMs);
    const unit = bestUnit(min);
    const value = milliToX(min, unit).toString();
    return value + " " + unit;
  };

  const isMinCycleTimeValid = () => {
    const { component } = form;
    const ext = extension(component.settings.type, component.settings.subtype);
    if (!ext.isController) return true;

    let min = Math.max(0, ext.minCycleTimeMs ? ext.minCycleTimeMs : 0);
    return component.settings.minCycleTimeMs >= min;
  };

  const minCycleTimeDescription = () => {
    const { component } = form;
    const ext = extension(component.settings.type, component.settings.subtype);
    if (!ext.isController) return "";

    const min = Math.max(0, ext.minCycleTimeMs ? ext.minCycleTimeMs : 0);
    const unit = bestUnit(min);
    const value = milliToX(min, unit).toString();
    return "Must be at least " + value + " " + unit;
  };

  const handleNameChanged = (event: any) => {
    let f = cloneDeep(form);
    f.component.settings.name = event.target.value;
    setForm(f);
  };

  const handleOutputModeChanged = (event: any) => {
    let f = cloneDeep(form);
    f.component.settings.defaultOutputState = event.target.value;
    setForm(f);
  };

  const handleReportPeriodChanged = (ms: number) => {
    let f = cloneDeep(form);
    f.component.settings.reportPeriodMs = ms;
    setForm(f);
  };

  const handleMeasurementPeriodChanged = (ms: number) => {
    let f = cloneDeep(form);
    f.component.settings.measurementPeriodMs = ms;
    if (!reportExpanded) {
      f.component.settings.reportPeriodMs = ms;
    }
    setForm(f);
  };

  const handleMinCycleTimeChanged = (ms: number) => {
    let f = cloneDeep(form);
    f.component.settings.minCycleTimeMs = Number(ms);
    setForm(f);
  };

  const toggleMeasure = (event: any) => {
    let f = cloneDeep(form);
    f.measure = event.target.checked;
    if (!reportExpanded) {
      f.report = event.target.checked;
    }
    setForm(f);
  };

  const toggleReport = (event: any) => {
    let f = cloneDeep(form);

    f.report = event.target.checked;
    setForm(f);
  };

  const togglePartiallyFilled = (event: any) => {
    let f = cloneDeep(form);

    f.partiallyFilled = event.target.checked;
    if (event.target.checked && f.component.settings.grainFilledTo <= 0) {
      f.component.settings.grainFilledTo = 1;
    }
    if (!event.target.checked) {
      f.component.settings.grainFilledTo = 0;
    }
    setForm(f);
  };

  const updateGrainType = (option: Option | null) => {
    let f = cloneDeep(form);

    if (!option) {
      f.component.settings.grainType = pond.Grain.GRAIN_INVALID;
    } else {
      let grainType = pond.Grain[option.value as keyof typeof pond.Grain];
      f.component.settings.grainType = grainType;
      if (grainType === pond.Grain.GRAIN_NONE) {
        f.component.settings.defaultMutations.splice(
          f.component.settings.defaultMutations.indexOf(pond.Mutator.MUTATOR_EMC, 1)
        );
      } else if (!f.component.settings.defaultMutations.includes(pond.Mutator.MUTATOR_EMC)) {
        f.component.settings.defaultMutations.push(pond.Mutator.MUTATOR_EMC);
      }
    }
    setForm(f);
  };

  const updateGrainFilledTo = (event: any) => {
    let f = cloneDeep(form);
    f.component.settings.grainFilledTo = Number(event.target.value);
    setForm(f);
  };

  const toggleCalibrate = () => {
    let f = cloneDeep(form);
    f.component.settings.calibrate = !f.component.settings.calibrate;
    let isZero = f.coefficient === "0" || f.coefficient === "";
    if (f.component.settings.calibrate && isZero) {
      f.coefficient = "1";
      f.component.settings.calibrationCoefficient = 1;
    }
    let offsetZero = f.offset === "0" || f.offset === "";
    if (f.component.settings.calibrate && offsetZero) {
      f.offset = "1";
      f.component.settings.calibrationOffset = 1;
    }
    setForm(f);
  };

  const isCoefficientValid = () => {
    let calibrate = form.component.settings.calibrate;
    let coefficient = Number(form.coefficient);
    let valid = !isNaN(coefficient) && (!calibrate || (calibrate && coefficient > 0));
    return valid;
  };

  const changeCoefficient = (event: any) => {
    let f = cloneDeep(form);
    let value = event.target.value;
    f.coefficient = value;
    if (!isNaN(Number(value))) {
      f.component.settings.calibrationCoefficient = +Number(value);
    }
    setForm(f);
  };

  const updateCoefficients = (event: any, index: number) => {
    let f = cloneDeep(form);
    let value = event.target.value;
    f.coefficients[index] = value;
    if (!isNaN(Number(value))) {
      f.component.settings.calibrations[index].calibrationCoefficient = +Number(value);
    }
    setForm(f);
  };

  const isOffsetValid = (min?: number, max?: number) => {
    let minimum = min ?? 0;
    let maximum = max ?? 100;
    if (compMode && form.component.settings.calibrate) {
      return Number(form.offset) <= maximum && Number(form.offset) > minimum;
    }
    return !isNaN(Number(form.offset));
  };

  const changeOffset = (event: any) => {
    let f = cloneDeep(form);
    f.offset = event.target.value;
    if (!isNaN(Number(event.target.value))) {
      f.component.settings.calibrationOffset = describeMeasurement(
        primaryMeasurement(f.component.settings.type),
        f.component.settings.type,
        f.component.settings.subtype
      ).toStored(+event.target.value);
    }
    setForm(f);
  };

  const updateOffsets = (event: any, index: number) => {
    let f = cloneDeep(form);
    f.offsets[index] = event.target.value;
    if (!isNaN(+event.target.value)) {
      f.component.settings.calibrations[index].calibrationOffset = describeMeasurement(
        primaryMeasurement(f.component.settings.type),
        f.component.settings.type,
        f.component.settings.subtype
      ).toStored(+event.target.value);
    }
    setForm(f);
  };

  const updateCalibrationTypes = (event: any, index: number) => {
    let f = cloneDeep(form);
    f.component.settings.calibrations[index].measurementType = event.target.value as quack.MeasurementType;
    setForm(f);
  };

  const changeAverages = (event: any) => {
    let f = cloneDeep(form);
    if (!isNaN(Number(event.target.value))) {
      f.component.settings.smoothingAverages = event.target.value;
    }
    setForm(f);
  };

  const changeLengthDimension = (event: any) => {
    let f = cloneDeep(form);
    f.length = event.target.value;
    if (!isNaN(Number(event.target.value))) {
      let dimensions = f.component.settings.containerDimensions;
      let value = Number(event.target.value);
      if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
        value = value * 100;
      } else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
        value = value * 30.48;
      }
      if (dimensions !== undefined && dimensions !== null) {
        dimensions.lengthCm = value;
      } else {
        dimensions = quack.Dimensions.create({
          heightCm: 0,
          lengthCm: value,
          widthCm: 0
        });
      }
      f.component.settings.containerDimensions = dimensions;
    }
    setForm(f);
  };

  const isLengthDimensionValid = () => {
    return !isNaN(Number(form.length));
  };

  const changeHeightDimension = (event: any) => {
    let f = cloneDeep(form);
    f.height = event.target.value;
    if (!isNaN(Number(event.target.value))) {
      let dimensions = f.component.settings.containerDimensions;
      let value = Number(event.target.value);
      if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
        value = value * 100;
      } else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
        value = value * 30.48;
      }
      if (dimensions !== undefined && dimensions !== null) {
        dimensions.heightCm = value;
      } else {
        dimensions = quack.Dimensions.create({
          heightCm: value,
          lengthCm: 0,
          widthCm: 0
        });
      }
      f.component.settings.containerDimensions = dimensions;
    }
    setForm(f);
  };

  const isHeightDimensionValid = () => {
    return !isNaN(Number(form.height));
  };

  const changeWidthDimension = (event: any) => {
    let f = cloneDeep(form);
    f.width = event.target.value;
    if (!isNaN(Number(event.target.value))) {
      let dimensions = f.component.settings.containerDimensions;
      let value = Number(event.target.value);
      if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
        value = value * 100;
      } else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
        value = value * 30.48;
      }
      if (dimensions !== undefined && dimensions !== null) {
        dimensions.widthCm = value;
      } else {
        dimensions = quack.Dimensions.create({
          heightCm: 0,
          lengthCm: 0,
          widthCm: value
        });
      }
      f.component.settings.containerDimensions = dimensions;
    }
    setForm(f);
  };

  const isWidthDimensionValid = () => {
    return !isNaN(Number(form.width));
  };

  const changeSensorDistance = (event: any) => {
    let f = cloneDeep(form);
    f.sensorDistance = event.target.value;
    if (!isNaN(Number(event.target.value))) {
      let value = Number(event.target.value);
      if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
        value = value * 100;
      } else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
        value = value * 30.48;
      }
      f.component.settings.sensorDistanceCm = value;
    }
    setForm(f);
  };

  const isSensorDistanceValid = () => {
    return !isNaN(Number(form.sensorDistance));
  };

  const selectText = (event: any) => {
    event.target.select();
  };

  const findSelectedGrain = (options: Option[]): Option | undefined => {
    const { component } = form;

    return options.find(o => {
      let target = pond.Grain[o.value as keyof typeof pond.Grain];
      return component.settings.grainType === target;
    });
  };

  const grainSelect = () => {
    let selected = findSelectedGrain(grainOptions);
    return (
      <SearchSelect
        selected={selected}
        changeSelection={updateGrainType}
        label="Grain Type"
        options={grainOptions}
        group
      />
    );
  };

  const dismissDataUsageWarning = () => {
    localStorage.setItem("dataUsageWarningDismissed", "true");
    setDataUsageWarningDismissed(true);
  };

  const blanketCalibration = () => {
    const { component, coefficient, offset } = form;
    return (
      <React.Fragment>
        <Grid size={{ xs: 4, sm: 3 }} >
          <Tooltip
            title="Calibrates the primary measurement by multiplying by the coefficient and adding the offset"
            placement="bottom">
            <FormControlLabel
              control={
                <Switch
                  checked={component.settings.calibrate}
                  onChange={toggleCalibrate}
                  name="calibrate"
                  aria-label="calibrate"
                  color="secondary"
                />
              }
              label="Calibrate"
              labelPlacement="top"
              className={classes.switchControl}
              disabled={!canEdit}
            />
          </Tooltip>
        </Grid>
        <Grid size={{ xs: 8, sm: 4 }}>
          <TextField
            id="coefficient"
            label="Coefficient"
            disabled={!component.settings.calibrate}
            error={!isCoefficientValid()}
            value={coefficient}
            onChange={changeCoefficient}
            onFocus={selectText}
            margin="normal"
            fullWidth
            type="text"
            variant="outlined"></TextField>
        </Grid>
        <Grid size={{ xs: 12, sm: 5 }}>
          <TextField
            id="offset"
            label="Offset"
            disabled={!component.settings.calibrate}
            error={!isOffsetValid}
            value={offset}
            onChange={changeOffset}
            onFocus={selectText}
            margin="normal"
            fullWidth
            type="text"
            variant="outlined"
            InputProps={{
              endAdornment: (
                <InputAdornment position="end">
                  {describeMeasurement(
                    primaryMeasurement(component.settings.type),
                    component.settings.type,
                    component.settings.subtype
                  ).unit()}
                </InputAdornment>
              )
            }}></TextField>
        </Grid>
      </React.Fragment>
    );
  };

  const componentMode = () => {
    const { component, coefficient, offset } = form;
    return (
      <React.Fragment>
              <Grid size={{ xs: 4, sm: 3 }}>
                <Tooltip
                  title="Switch to be able to customize the mode of the component"
                  placement="bottom">
                  <FormControlLabel
                    control={
                      <Switch
                        checked={component.settings.calibrate}
                        onChange={toggleCalibrate}
                        name="mode"
                        aria-label="mode"
                        color="secondary"
                      />
                    }
                    label="Mode"
                    labelPlacement="top"
                    className={classes.switchControl}
                    disabled={!canEdit}
                  />
                </Tooltip>
              </Grid>
              <Grid size={{ xs: 10, sm: 4 }}>
                {compMode.mode.entryA && (
                  <TextField
                    id={compMode.mode.labelA}
                    label={compMode.mode.labelA}
                    disabled={!component.settings.calibrate}
                    error={!isCoefficientValid()}
                    value={coefficient}
                    onChange={changeCoefficient}
                    margin="normal"
                    fullWidth
                    select={compMode.mode.entryA === "select"}
                    type={compMode.mode.entryA}
                    variant="outlined">
                    {compMode.mode.entryA === "select" &&
                      compMode.mode.dictionaryA.map((elem: any) => {
                        if (!elem.restricted || (elem.restricted && user.hasAdmin())) {
                          return (
                            <MenuItem key={elem.key} value={elem.value}>
                              {elem.key}
                            </MenuItem>
                          );
                        }
                        return undefined; //TODO-CS: Time permitting re-think the structure of this function so that I don't have to return undefined
                      })}
                  </TextField>
                )}
              </Grid>
              <Grid size={{ xs: 10, sm: 5 }}>
                {compMode.mode.entryB && (
                  <TextField
                    id={compMode.mode.labelB}
                    label={compMode.mode.labelB}
                    disabled={!component.settings.calibrate}
                    error={!isOffsetValid(compMode.mode.minB, compMode.mode.maxB)}
                    value={offset}
                    onChange={changeOffset}
                    onFocus={selectText}
                    margin="normal"
                    fullWidth
                    type={compMode.mode.entryB}
                    variant="outlined">
                    {compMode.mode.entryB === "select" &&
                      compMode.mode.dictionaryB.map((elem: any) => (
                        <MenuItem key={elem.key} value={elem.value}>
                          {elem.key}
                        </MenuItem>
                      ))}
                  </TextField>
                )}
              </Grid>
            </React.Fragment>
    )
  }

  const describeSource = (measurementType: quack.MeasurementType): MeasurementDescriber => {
    const { component } = form;
    return describeMeasurement(measurementType, component.type(), component.subType());
  };

  const availableMeasurementTypes = (type: quack.ComponentType): quack.MeasurementType[] => {
    return getMeasurements(type).map(m => m.measurementType);
  };

  const measurementTypeMenuItems = () => {
    return availableMeasurementTypes(form.component.type()).map(measurementType => (
      <MenuItem key={measurementType} value={measurementType}>
        {describeSource(measurementType).label()}
      </MenuItem>
    ));
  };

  const individualCalibration = () => {
    const { component, coefficients, offsets } = form;
    return (
      <React.Fragment>
        <Grid size={{ xs: 4, sm: 3 }}>
          <Tooltip
            title="Calibrates the primary measurement by multiplying by the coefficient and adding the offset"
            placement="bottom">
            <FormControlLabel
              control={
                <Switch
                  checked={component.settings.calibrate}
                  onChange={toggleCalibrate}
                  name="calibrate"
                  aria-label="calibrate"
                  color="secondary"
                />
              }
              label="Calibrate"
              labelPlacement="top"
              className={classes.switchControl}
              disabled={!canEdit}
            />
          </Tooltip>
        </Grid>
        {component.settings.calibrations.map((calibration, i) => {
          return (
            <React.Fragment key={"calibration" + i}>
              {i > 0 && <Grid size={{ xs: 4, sm: 3 }}></Grid>}
              <Grid size={{ xs: i > 0 ? 12 : 8, sm: 3 }} >
                <TextField
                  id="coefficient"
                  label="Coefficient"
                  disabled={!component.settings.calibrate}
                  //error={!isCoefficientValid()}
                  value={coefficients[i] ?? "0"}
                  onChange={event => {
                    updateCoefficients(event, i);
                  }}
                  onFocus={selectText}
                  margin="normal"
                  fullWidth
                  type="text"
                  variant="outlined"
                />
              </Grid>
              <Grid size={{ xs: 12, sm: 3 }}>
                <TextField
                  id="offset"
                  label="Offset"
                  disabled={!component.settings.calibrate}
                  //error={!isOffsetValid}
                  value={offsets[i] ?? 0}
                  onChange={event => {
                    updateOffsets(event, i);
                  }}
                  onFocus={selectText}
                  margin="normal"
                  fullWidth
                  type="text"
                  variant="outlined"
                />
              </Grid>
              <Grid size={{ xs: 12, sm: 3 }}>
                <TextField
                  select
                  id="measurementType"
                  label="Measurement"
                  // error={
                  //   !isMeasurementTypeValid(interaction.settings.conditions[index].measurementType)
                  // }
                  disabled={!component.settings.calibrate}
                  value={calibration.measurementType ?? quack.MeasurementType.MEASUREMENT_TYPE_INVALID}
                  onChange={event => updateCalibrationTypes(event, i)}
                  autoFocus={false}
                  margin="normal"
                  variant="outlined"
                  fullWidth
                  InputLabelProps={{ shrink: true }}>
                  <MenuItem
                    key={quack.MeasurementType.MEASUREMENT_TYPE_INVALID}
                    value={quack.MeasurementType.MEASUREMENT_TYPE_INVALID}>
                    All
                  </MenuItem>
                  {measurementTypeMenuItems()}
                </TextField>
              </Grid>
            </React.Fragment>
          );
        })}
        <Button
          variant="contained"
          color="primary"
          onClick={() => {
            let f = cloneDeep(form);
            f.coefficients.push("0");
            f.offsets.push("0");
            f.component.settings.calibrations.push(
              quack.Calibration.create({
                measurementType: quack.MeasurementType.MEASUREMENT_TYPE_INVALID,
                calibrationOffset: 0,
                calibrationCoefficient: 0
              })
            );
            setForm(f);
          }}>
          Add Calibration
        </Button>
      </React.Fragment>
    );
  };

  const generalForm = () => {
    const {
      component,
      measure,
      report,
      partiallyFilled,
      coefficient,
      offset,
      length,
      height,
      width,
      sensorDistance
    } = form;
    let reportDesc = "";
    let reportPeriod = component.settings.reportPeriodMs;
    let measurementPeriod = component.settings.measurementPeriodMs;
    if (reportPeriod === measurementPeriod) {
      reportDesc = "Every measurement";
    } else {
      reportDesc = "Independently";
    }
    if (!form.report) {
      reportDesc = "Not reporting";
    }
    let showDataUsageWarning =
      !dataUsageWarningDismissed &&
      form.report &&
      component.settings.reportPeriodMs < 300000 &&
      device.settings.platform === pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON;
    return (
      <React.Fragment>
        <TextField
          id="name"
          name="name"
          label="Name"
          value={component.settings.name}
          onChange={handleNameChanged}
          margin="normal"
          variant="outlined"
          error={!isNameValid(component.settings.name)}
          helperText={isNameValid(component.settings.name) ? "" : "Name is required"}
          type="text"
          fullWidth
          InputLabelProps={{ shrink: true }}
          disabled={!canEdit}
        />
        {/* TODO-CS: Change this to have a parameter in the extensions that gets checked rather then checking for the component type */}
        {hasFan(component.type()) && (
          <FanPicker
             fanID={component.settings.fanId}
             updateFan={(fanID: number) => {
               component.settings.fanId = fanID;
               if (
                 fanID > 0 &&
                 !component.settings.defaultMutations.includes(pond.Mutator.MUTATOR_CFM)
               ) {
                 component.settings.defaultMutations.push(pond.Mutator.MUTATOR_CFM);
               } else if (fanID === 0 || !fanID) {
                 component.settings.defaultMutations.splice(
                   component.settings.defaultMutations.indexOf(pond.Mutator.MUTATOR_CFM)
                 );
               }
             }}
           />
        )}
        {component.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE &&
          grainSelect()}
        {component.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE && (
          <Grid
            container
            direction="row"
            justifyContent="space-between"
            alignItems="center"
            alignContent="center"
            spacing={1}>
            <Grid container size={{ xs: 4, sm: 3 }} justifyContent="center">
              <FormControlLabel
                control={
                  <Switch
                    checked={partiallyFilled}
                    onChange={togglePartiallyFilled}
                    name="partiallyFilled"
                    aria-label="partiallyFilled"
                    color="secondary"
                  />
                }
                label={<Typography variant="caption">Partially Filled</Typography>}
                labelPlacement="top"
                className={classes.switchControl}
                disabled={!canEdit}
              />
            </Grid>
            <Grid size={{ xs: 8, sm: 9 }}>
              <FormControl fullWidth>
                <TextField
                  id="bin-filled-to"
                  label="Filled Up To Node"
                  value={form.component.settings.grainFilledTo}
                  onChange={updateGrainFilledTo}
                  disabled={!partiallyFilled}
                  error={!isFilledToValid()}
                  helperText={!isFilledToValid() ? "Must be at least 1" : ""}
                  margin="normal"
                  fullWidth
                  variant="outlined"></TextField>
              </FormControl>
            </Grid>
          </Grid>
        )}
        <Grid
          container
          direction="row"
          justifyContent="space-between"
          alignItems="center"
          alignContent="center"
          spacing={1}>
          {
            <Grid container size={{ xs: 4, sm: 3 }} justifyContent="center">
              <FormControlLabel
                control={
                  <Switch
                    checked={measure} 
                    onChange={toggleMeasure}
                    name="measure"
                    aria-label="measure"
                    color="secondary"
                  />
                }
                label={<Typography variant="caption">Measure</Typography>}
                labelPlacement="top"
                className={classes.switchControl}
                disabled={!canEdit}
              />
            </Grid>
          }
          {
            <Grid size={{ xs: 8, sm: 9 }}>
              <FormControl fullWidth>
                <PeriodSelect
                  id="measurementPeriodMs"
                  label="Period"
                  units={["seconds", "minutes", "hours"]}
                  isDisabled={!measure || !canEdit}
                  isError={!isMeasurementPeriodValid()}
                  helperText={
                    !isMeasurementPeriodValid()
                      ? "Must be at least " + minMeasurementPeriodDescription()
                      : "How often the component is measured"
                  }
                  initialMs={component.settings.measurementPeriodMs}
                  onChange={handleMeasurementPeriodChanged}
                />
              </FormControl>
            </Grid>
          }
          {
            <Accordion
              className={classes.fullWidth}
              expanded={reportExpanded}
              onChange={(_, expanded) => setReportExpanded(expanded)}>
              <AccordionSummary expandIcon={<ExpandMore />}>
                <Typography className={classes.heading}>Report</Typography>
                <Typography className={classes.secondaryHeading}>{reportDesc}</Typography>
              </AccordionSummary>
              <AccordionDetails>
                <Grid
                  container
                  direction="row"
                  justifyContent="space-between"
                  alignItems="center"
                  alignContent="center"
                  spacing={1}>
                  <Grid size={{ xs: 4, sm: 3 }}>
                    <Tooltip title="Reporting measurements uses data" placement="bottom">
                      <FormControlLabel
                        control={
                          <Switch
                            checked={report}
                            onChange={toggleReport}
                            name="report"
                            aria-label="report"
                            color="secondary"
                            disabled={!measure}
                          />
                        }
                        label={<Typography variant="caption">Enabled</Typography>}
                        labelPlacement="top"
                        className={classes.switchControl}
                        disabled={!canEdit}
                      />
                    </Tooltip>
                  </Grid>
                  <Grid size={{ xs: 8, sm: 9 }}>
                    <FormControl fullWidth>
                      <PeriodSelect
                        id="reportPeriodMs"
                        label="Period"
                        units={["seconds", "minutes", "hours"]}
                        isDisabled={!measure || !report || !canEdit}
                        isError={!isReportPeriodValid()}
                        helperText={
                          !isReportPeriodValid()
                            ? "Must be at least " + minReportPeriodDescription()
                            : "How often the component reports to the cloud"
                        }
                        initialMs={
                          component.settings.reportPeriodMs
                            ? component.settings.reportPeriodMs
                            : undefined
                        }
                        onChange={handleReportPeriodChanged}
                      />
                    </FormControl>
                  </Grid>
                </Grid>
              </AccordionDetails>
            </Accordion>
          }
          {device.featureSupported("individualCalibrations") ? (
            <React.Fragment>
              {compMode && componentMode()}
              {individualCalibration()}
            </React.Fragment>
          ) : (
            !compMode ? (
              blanketCalibration()
            ):(
              componentMode()
            )
          )}
          <TextField
            id="dataAveraging"
            name="dataAveraging"
            label="Data Averaging"
            value={component.settings.smoothingAverages}
            onChange={changeAverages}
            margin="normal"
            variant="outlined"
            helperText={"The number of points to average together"}
            type="number"
            fullWidth
            InputLabelProps={{ shrink: true }}
            disabled={!canEdit}
          />
          {hasDimensions(component.settings.type) && (
            <React.Fragment>
              <Typography>Dimensions</Typography>
              <Grid container direction="row" spacing={1}>
                <Grid size={{ xs: 12, sm: 4 }}>
                  <TextField
                    id="dimension-length"
                    name="dimensionLength"
                    //type="number"
                    label={"Length"}
                    variant="outlined"
                    fullWidth
                    margin="normal"
                    value={length}
                    onChange={changeLengthDimension}
                    error={!isLengthDimensionValid()}
                    disabled={!canEdit}
                    InputProps={{
                      endAdornment: (
                        <InputAdornment position="end">
                          {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
                            ? "m"
                            : "ft"}
                        </InputAdornment>
                      )
                    }}
                  />
                </Grid>
                <Grid size={{ xs: 12, sm: 4 }}>
                  <TextField
                    id="dimension-height"
                    name="dimensionHeight"
                    //type="number"
                    label={"Height"}
                    variant="outlined"
                    fullWidth
                    margin="normal"
                    value={height}
                    onChange={changeHeightDimension}
                    error={!isHeightDimensionValid()}
                    disabled={!canEdit}
                    InputProps={{
                      endAdornment: (
                        <InputAdornment position="end">
                          {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
                            ? "m"
                            : "ft"}
                        </InputAdornment>
                      )
                    }}
                  />
                </Grid>
                <Grid size={{ xs: 12, sm: 4 }}>
                  <TextField
                    id="dimension-width"
                    name="dimensionWidth"
                    //type="number"
                    label={"Width"}
                    variant="outlined"
                    fullWidth
                    margin="normal"
                    value={width}
                    onChange={changeWidthDimension}
                    error={!isWidthDimensionValid()}
                    disabled={!canEdit}
                    InputProps={{
                      endAdornment: (
                        <InputAdornment position="end">
                          {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
                            ? "m"
                            : "ft"}
                        </InputAdornment>
                      )
                    }}
                  />
                </Grid>
              </Grid>
            </React.Fragment>
          )}
          {isMultiSensor(component.settings.type) && (
            <TextField
              id="sensor-distance"
              name="sensorDistance"
              //type="number"
              label={"Sensor Distance"}
              variant="outlined"
              fullWidth
              helperText="The direct distance between the sensors"
              margin="normal"
              value={sensorDistance}
              onChange={changeSensorDistance}
              error={!isSensorDistanceValid()}
              disabled={!canEdit}
              InputProps={{
                endAdornment: (
                  <InputAdornment position="end">
                    {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
                  </InputAdornment>
                )
              }}
            />
          )}
          {isController(component.settings.type) && (
            <React.Fragment>
              <Grid size={{ xs: 12 }}>
                <TextField
                  id="output-mode"
                  name="outputMode"
                  select
                  label={"Output Mode"}
                  value={component.settings.defaultOutputState}
                  onChange={handleOutputModeChanged}
                  margin="normal"
                  fullWidth
                  variant="outlined"
                  disabled={!canEdit}>
                  <MenuItem value={0}>Auto</MenuItem>
                  <MenuItem value={1}>On</MenuItem>
                  <MenuItem value={2}>Off</MenuItem>
                </TextField>
              </Grid>
              <Grid size={{ xs: 12 }}>
                <FormControl fullWidth>
                  <PeriodSelect
                    id="minCycleTimeMs"
                    label="Minimum cycle time"
                    units={["seconds", "minutes"]}
                    isDisabled={!canEdit}
                    isError={!isMinCycleTimeValid()}
                    initialMs={
                      component.settings.minCycleTimeMs
                        ? component.settings.minCycleTimeMs
                        : undefined
                    }
                    onChange={handleMinCycleTimeChanged}
                    helperText={
                      isMinCycleTimeValid()
                        ? "The minimum amount of time that the component spends in a state"
                        : minCycleTimeDescription()
                    }
                  />
                </FormControl>
              </Grid>
            </React.Fragment>
          )}
          {showDataUsageWarning && (
            <Grid size={{ xs: 12 }}>
              <Collapse in={showDataUsageWarning}>
                <Alert
                  severity="warning"
                  action={
                    <IconButton
                      aria-label="close"
                      color="inherit"
                      size="small"
                      onClick={dismissDataUsageWarning}>
                      <CloseIcon fontSize="inherit" />
                    </IconButton>
                  }>
                  <AlertTitle>High Data Usage</AlertTitle>A report period less than 5 minutes will
                  use a large amount of cellular data
                </Alert>
              </Collapse>
            </Grid>
          )}
        </Grid>
      </React.Fragment>
    );
  };

  return <React.Fragment>{generalForm()}</React.Fragment>;
}
