#include <math.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct {
  double cold;
  double comfortable;
  double hot;
} TemperatureDegrees;

typedef struct {
  double dry;
  double normal;
  double humid;
} HumidityDegrees;

typedef struct {
  TemperatureDegrees temperature;
  HumidityDegrees humidity;
  double low_height;
  double medium_height;
  double high_height;
  double fuzzy_output;
  double crisp_output;
} FuzzyResult;

static double min2(double a, double b) {
  return a < b ? a : b;
}

static double max2(double a, double b) {
  return a > b ? a : b;
}

static double trimf(double x, double a, double b, double c) {
  if (x <= a || x >= c) {
    return 0.0;
  }
  if (x == b) {
    return 1.0;
  }
  if (x < b) {
    return (x - a) / (b - a);
  }
  return (c - x) / (c - b);
}

static double trapmf(double x, double a, double b, double c, double d) {
  if (x < a || x > d) {
    return 0.0;
  }
  if (x >= b && x <= c) {
    return 1.0;
  }
  if (x == a && a == b) {
    return 1.0;
  }
  if (x == d && c == d) {
    return 1.0;
  }
  if (x > a && x < b) {
    return (x - a) / (b - a);
  }
  if (x > c && x < d) {
    return (d - x) / (d - c);
  }
  return 0.0;
}

static TemperatureDegrees get_temperature_degrees(double temperature) {
  TemperatureDegrees degrees;
  degrees.cold = trapmf(temperature, 16.0, 16.0, 19.0, 23.0);
  degrees.comfortable = trimf(temperature, 20.0, 24.0, 28.0);
  degrees.hot = trapmf(temperature, 25.0, 29.0, 34.0, 34.0);
  return degrees;
}

static HumidityDegrees get_humidity_degrees(double humidity) {
  HumidityDegrees degrees;
  degrees.dry = trapmf(humidity, 20.0, 20.0, 30.0, 45.0);
  degrees.normal = trimf(humidity, 35.0, 55.0, 75.0);
  degrees.humid = trapmf(humidity, 60.0, 75.0, 90.0, 90.0);
  return degrees;
}

static double output_membership_low(double x) {
  return trapmf(x, 0.0, 0.0, 20.0, 40.0);
}

static double output_membership_medium(double x) {
  return trimf(x, 30.0, 50.0, 70.0);
}

static double output_membership_high(double x) {
  return trapmf(x, 60.0, 80.0, 100.0, 100.0);
}

static double crisp_output(double temperature, double humidity) {
  if (temperature < 23.0) {
    return 20.0;
  }
  if (temperature < 27.0 && humidity < 45.0) {
    return 20.0;
  }
  if (temperature < 27.0) {
    return 50.0;
  }
  if (humidity < 45.0) {
    return 50.0;
  }
  return 85.0;
}

static FuzzyResult evaluate(double temperature, double humidity) {
  FuzzyResult result;
  result.temperature = get_temperature_degrees(temperature);
  result.humidity = get_humidity_degrees(humidity);

  const double r1 = min2(result.temperature.cold, result.humidity.dry);
  const double r2 = min2(result.temperature.cold, result.humidity.normal);
  const double r3 = min2(result.temperature.cold, result.humidity.humid);
  const double r4 = min2(result.temperature.comfortable, result.humidity.dry);
  const double r5 = min2(result.temperature.comfortable, result.humidity.normal);
  const double r6 = min2(result.temperature.comfortable, result.humidity.humid);
  const double r7 = min2(result.temperature.hot, result.humidity.dry);
  const double r8 = min2(result.temperature.hot, result.humidity.normal);
  const double r9 = min2(result.temperature.hot, result.humidity.humid);

  result.low_height = max2(max2(r1, r2), r4);
  result.medium_height = max2(max2(r3, r5), max2(r6, r7));
  result.high_height = max2(r8, r9);

  double area = 0.0;
  double moment = 0.0;

  for (int x = 0; x <= 100; ++x) {
    double mu = 0.0;

    const double low_mu = min2(result.low_height, output_membership_low((double)x));
    const double medium_mu = min2(result.medium_height, output_membership_medium((double)x));
    const double high_mu = min2(result.high_height, output_membership_high((double)x));

    mu = max2(low_mu, max2(medium_mu, high_mu));
    area += mu;
    moment += (double)x * mu;
  }

  if (area <= 0.0) {
    fprintf(stderr, "error: no rule fired; membership definitions are inconsistent\n");
    exit(EXIT_FAILURE);
  }

  result.fuzzy_output = moment / area;
  result.crisp_output = crisp_output(temperature, humidity);
  return result;
}

int main(int argc, char **argv) {
  if (argc != 3) {
    fprintf(stderr, "usage: %s <temperature_celsius> <humidity_percent>\n", argv[0]);
    return EXIT_FAILURE;
  }

  const double temperature = strtod(argv[1], NULL);
  const double humidity = strtod(argv[2], NULL);

  const FuzzyResult result = evaluate(temperature, humidity);

  printf("temperature=%.2f humidity=%.2f\n", temperature, humidity);
  printf("temp: cold=%.4f comfortable=%.4f hot=%.4f\n",
         result.temperature.cold,
         result.temperature.comfortable,
         result.temperature.hot);
  printf("humidity: dry=%.4f normal=%.4f humid=%.4f\n",
         result.humidity.dry,
         result.humidity.normal,
         result.humidity.humid);
  printf("output heights: low=%.4f medium=%.4f high=%.4f\n",
         result.low_height,
         result.medium_height,
         result.high_height);
  printf("fuzzy_output=%.4f crisp_output=%.4f\n",
         result.fuzzy_output,
         result.crisp_output);

  return EXIT_SUCCESS;
}
