/*
 * Copyright (c) 2024, Alliance for Open Media. All rights reserved.
 *
 * Encoder tools configuration - runtime control of coding tools
 */

#include "av1/encoder/tools_config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

// Global instance
EncoderToolsCfg g_encoder_tools_cfg;

void aom_tools_cfg_init(EncoderToolsCfg *cfg) {
  memset(cfg, 0, sizeof(*cfg));
  cfg->loaded = 0;

  // Enable all partitions by default
  cfg->partition.partition_none = 1;
  cfg->partition.partition_horz = 1;
  cfg->partition.partition_vert = 1;
  cfg->partition.partition_split = 1;
  cfg->partition.partition_horz_a = 1;
  cfg->partition.partition_horz_b = 1;
  cfg->partition.partition_vert_a = 1;
  cfg->partition.partition_vert_b = 1;
  cfg->partition.partition_horz_4 = 1;
  cfg->partition.partition_vert_4 = 1;

  // Enable all intra modes by default
  cfg->intra.dc_pred = 1;
  cfg->intra.v_pred = 1;
  cfg->intra.h_pred = 1;
  cfg->intra.d45_pred = 1;
  cfg->intra.d135_pred = 1;
  cfg->intra.d113_pred = 1;
  cfg->intra.d157_pred = 1;
  cfg->intra.d203_pred = 1;
  cfg->intra.d67_pred = 1;
  cfg->intra.smooth_pred = 1;
  cfg->intra.smooth_v_pred = 1;
  cfg->intra.smooth_h_pred = 1;
  cfg->intra.paeth_pred = 1;

  // Enable all reference frames by default
  cfg->inter.last_frame = 1;
  cfg->inter.last2_frame = 1;
  cfg->inter.last3_frame = 1;
  cfg->inter.golden_frame = 1;
  cfg->inter.bwdref_frame = 1;
  cfg->inter.altref2_frame = 1;
  cfg->inter.altref_frame = 1;
  cfg->inter.compound = 1;

  // Enable all inter prediction modes by default
  cfg->inter_modes.nearestmv = 1;
  cfg->inter_modes.nearmv = 1;
  cfg->inter_modes.globalmv = 1;
  cfg->inter_modes.newmv = 1;
  cfg->inter_modes.nearest_nearestmv = 1;
  cfg->inter_modes.near_nearmv = 1;
  cfg->inter_modes.new_newmv = 1;
  cfg->inter_modes.nearest_newmv = 1;
  cfg->inter_modes.new_nearestmv = 1;
  cfg->inter_modes.near_newmv = 1;
  cfg->inter_modes.new_nearmv = 1;
  cfg->inter_modes.global_globalmv = 1;

  // Enable all compound types by default
  cfg->compound_types.average = 1;
  cfg->compound_types.distwtd = 1;
  cfg->compound_types.wedge = 1;
  cfg->compound_types.diffwtd = 1;

  // Enable all TX sizes by default
  cfg->tx_sizes.tx_4x4 = 1;
  cfg->tx_sizes.tx_8x8 = 1;
  cfg->tx_sizes.tx_16x16 = 1;
  cfg->tx_sizes.tx_32x32 = 1;
  cfg->tx_sizes.tx_64x64 = 1;

  // Enable all transform types by default
  cfg->transform.dct_dct = 1;
  cfg->transform.adst_dct = 1;
  cfg->transform.dct_adst = 1;
  cfg->transform.adst_adst = 1;
  cfg->transform.flipadst_dct = 1;
  cfg->transform.dct_flipadst = 1;
  cfg->transform.flipadst_flipadst = 1;
  cfg->transform.adst_flipadst = 1;
  cfg->transform.flipadst_adst = 1;
  cfg->transform.idtx = 1;
  cfg->transform.v_dct = 1;
  cfg->transform.h_dct = 1;
  cfg->transform.v_adst = 1;
  cfg->transform.h_adst = 1;
  cfg->transform.v_flipadst = 1;
  cfg->transform.h_flipadst = 1;
}

static char *trim(char *str) {
  while (isspace((unsigned char)*str)) str++;
  if (*str == 0) return str;
  char *end = str + strlen(str) - 1;
  while (end > str && isspace((unsigned char)*end)) end--;
  end[1] = '\0';
  return str;
}

static void set_partition(EncoderToolsCfg *cfg, const char *name, int val) {
  if (strcmp(name, "PARTITION_NONE") == 0) cfg->partition.partition_none = val;
  else if (strcmp(name, "PARTITION_HORZ") == 0) cfg->partition.partition_horz = val;
  else if (strcmp(name, "PARTITION_VERT") == 0) cfg->partition.partition_vert = val;
  else if (strcmp(name, "PARTITION_SPLIT") == 0) cfg->partition.partition_split = val;
  else if (strcmp(name, "PARTITION_HORZ_A") == 0) cfg->partition.partition_horz_a = val;
  else if (strcmp(name, "PARTITION_HORZ_B") == 0) cfg->partition.partition_horz_b = val;
  else if (strcmp(name, "PARTITION_VERT_A") == 0) cfg->partition.partition_vert_a = val;
  else if (strcmp(name, "PARTITION_VERT_B") == 0) cfg->partition.partition_vert_b = val;
  else if (strcmp(name, "PARTITION_HORZ_4") == 0) cfg->partition.partition_horz_4 = val;
  else if (strcmp(name, "PARTITION_VERT_4") == 0) cfg->partition.partition_vert_4 = val;
}

static void set_intra(EncoderToolsCfg *cfg, const char *name, int val) {
  if (strcmp(name, "DC_PRED") == 0) cfg->intra.dc_pred = val;
  else if (strcmp(name, "V_PRED") == 0) cfg->intra.v_pred = val;
  else if (strcmp(name, "H_PRED") == 0) cfg->intra.h_pred = val;
  else if (strcmp(name, "D45_PRED") == 0) cfg->intra.d45_pred = val;
  else if (strcmp(name, "D135_PRED") == 0) cfg->intra.d135_pred = val;
  else if (strcmp(name, "D113_PRED") == 0) cfg->intra.d113_pred = val;
  else if (strcmp(name, "D157_PRED") == 0) cfg->intra.d157_pred = val;
  else if (strcmp(name, "D203_PRED") == 0) cfg->intra.d203_pred = val;
  else if (strcmp(name, "D67_PRED") == 0) cfg->intra.d67_pred = val;
  else if (strcmp(name, "SMOOTH_PRED") == 0) cfg->intra.smooth_pred = val;
  else if (strcmp(name, "SMOOTH_V_PRED") == 0) cfg->intra.smooth_v_pred = val;
  else if (strcmp(name, "SMOOTH_H_PRED") == 0) cfg->intra.smooth_h_pred = val;
  else if (strcmp(name, "PAETH_PRED") == 0) cfg->intra.paeth_pred = val;
}

static void set_inter(EncoderToolsCfg *cfg, const char *name, int val) {
  if (strcmp(name, "LAST_FRAME") == 0) cfg->inter.last_frame = val;
  else if (strcmp(name, "LAST2_FRAME") == 0) cfg->inter.last2_frame = val;
  else if (strcmp(name, "LAST3_FRAME") == 0) cfg->inter.last3_frame = val;
  else if (strcmp(name, "GOLDEN_FRAME") == 0) cfg->inter.golden_frame = val;
  else if (strcmp(name, "BWDREF_FRAME") == 0) cfg->inter.bwdref_frame = val;
  else if (strcmp(name, "ALTREF2_FRAME") == 0) cfg->inter.altref2_frame = val;
  else if (strcmp(name, "ALTREF_FRAME") == 0) cfg->inter.altref_frame = val;
  else if (strcmp(name, "COMPOUND") == 0) cfg->inter.compound = val;
}

static void set_inter_mode(EncoderToolsCfg *cfg, const char *name, int val) {
  if (strcmp(name, "NEARESTMV") == 0) cfg->inter_modes.nearestmv = val;
  else if (strcmp(name, "NEARMV") == 0) cfg->inter_modes.nearmv = val;
  else if (strcmp(name, "GLOBALMV") == 0) cfg->inter_modes.globalmv = val;
  else if (strcmp(name, "NEWMV") == 0) cfg->inter_modes.newmv = val;
  else if (strcmp(name, "NEAREST_NEARESTMV") == 0) cfg->inter_modes.nearest_nearestmv = val;
  else if (strcmp(name, "NEAR_NEARMV") == 0) cfg->inter_modes.near_nearmv = val;
  else if (strcmp(name, "NEW_NEWMV") == 0) cfg->inter_modes.new_newmv = val;
  else if (strcmp(name, "NEAREST_NEWMV") == 0) cfg->inter_modes.nearest_newmv = val;
  else if (strcmp(name, "NEW_NEARESTMV") == 0) cfg->inter_modes.new_nearestmv = val;
  else if (strcmp(name, "NEAR_NEWMV") == 0) cfg->inter_modes.near_newmv = val;
  else if (strcmp(name, "NEW_NEARMV") == 0) cfg->inter_modes.new_nearmv = val;
  else if (strcmp(name, "GLOBAL_GLOBALMV") == 0) cfg->inter_modes.global_globalmv = val;
}

static void set_compound_type(EncoderToolsCfg *cfg, const char *name, int val) {
  if (strcmp(name, "COMPOUND_AVERAGE") == 0) cfg->compound_types.average = val;
  else if (strcmp(name, "COMPOUND_DISTWTD") == 0) cfg->compound_types.distwtd = val;
  else if (strcmp(name, "COMPOUND_WEDGE") == 0) cfg->compound_types.wedge = val;
  else if (strcmp(name, "COMPOUND_DIFFWTD") == 0) cfg->compound_types.diffwtd = val;
}

static void set_tx_size(EncoderToolsCfg *cfg, const char *name, int val) {
  if (strcmp(name, "TX_4X4") == 0) cfg->tx_sizes.tx_4x4 = val;
  else if (strcmp(name, "TX_8X8") == 0) cfg->tx_sizes.tx_8x8 = val;
  else if (strcmp(name, "TX_16X16") == 0) cfg->tx_sizes.tx_16x16 = val;
  else if (strcmp(name, "TX_32X32") == 0) cfg->tx_sizes.tx_32x32 = val;
  else if (strcmp(name, "TX_64X64") == 0) cfg->tx_sizes.tx_64x64 = val;
}

static void set_transform(EncoderToolsCfg *cfg, const char *name, int val) {
  if (strcmp(name, "DCT_DCT") == 0) cfg->transform.dct_dct = val;
  else if (strcmp(name, "ADST_DCT") == 0) cfg->transform.adst_dct = val;
  else if (strcmp(name, "DCT_ADST") == 0) cfg->transform.dct_adst = val;
  else if (strcmp(name, "ADST_ADST") == 0) cfg->transform.adst_adst = val;
  else if (strcmp(name, "FLIPADST_DCT") == 0) cfg->transform.flipadst_dct = val;
  else if (strcmp(name, "DCT_FLIPADST") == 0) cfg->transform.dct_flipadst = val;
  else if (strcmp(name, "FLIPADST_FLIPADST") == 0) cfg->transform.flipadst_flipadst = val;
  else if (strcmp(name, "ADST_FLIPADST") == 0) cfg->transform.adst_flipadst = val;
  else if (strcmp(name, "FLIPADST_ADST") == 0) cfg->transform.flipadst_adst = val;
  else if (strcmp(name, "IDTX") == 0) cfg->transform.idtx = val;
  else if (strcmp(name, "V_DCT") == 0) cfg->transform.v_dct = val;
  else if (strcmp(name, "H_DCT") == 0) cfg->transform.h_dct = val;
  else if (strcmp(name, "V_ADST") == 0) cfg->transform.v_adst = val;
  else if (strcmp(name, "H_ADST") == 0) cfg->transform.h_adst = val;
  else if (strcmp(name, "V_FLIPADST") == 0) cfg->transform.v_flipadst = val;
  else if (strcmp(name, "H_FLIPADST") == 0) cfg->transform.h_flipadst = val;
}

int aom_tools_cfg_load(EncoderToolsCfg *cfg, const char *filename) {
  FILE *f = fopen(filename, "r");
  if (!f) {
    fprintf(stderr, "Error: Cannot open tools config file: %s\n", filename);
    return -1;
  }

  // Start with defaults
  aom_tools_cfg_init(cfg);

  char line[256];
  int line_num = 0;

  while (fgets(line, sizeof(line), f)) {
    line_num++;
    char *trimmed = trim(line);

    // Skip empty lines and comments
    if (trimmed[0] == '\0' || trimmed[0] == '#') continue;

    // Parse CSV: category,name,value
    char *category = strtok(trimmed, ",");
    char *name = strtok(NULL, ",");
    char *value_str = strtok(NULL, ",");

    if (!category || !name || !value_str) {
      fprintf(stderr, "Warning: Invalid line %d in %s\n", line_num, filename);
      continue;
    }

    category = trim(category);
    name = trim(name);
    value_str = trim(value_str);

    int val = atoi(value_str);

    if (strcmp(category, "partition") == 0) {
      set_partition(cfg, name, val);
    } else if (strcmp(category, "intra") == 0) {
      set_intra(cfg, name, val);
    } else if (strcmp(category, "inter") == 0) {
      set_inter(cfg, name, val);
    } else if (strcmp(category, "inter_mode") == 0) {
      set_inter_mode(cfg, name, val);
    } else if (strcmp(category, "compound_type") == 0) {
      set_compound_type(cfg, name, val);
    } else if (strcmp(category, "tx_size") == 0) {
      set_tx_size(cfg, name, val);
    } else if (strcmp(category, "transform") == 0) {
      set_transform(cfg, name, val);
    }
  }

  fclose(f);
  cfg->loaded = 1;

  fprintf(stderr, "Loaded encoder tools config from: %s\n", filename);
  return 0;
}

void aom_tools_cfg_print(const EncoderToolsCfg *cfg) {
  fprintf(stderr, "=== Encoder Tools Configuration ===\n");

  fprintf(stderr, "Partitions: NONE=%d HORZ=%d VERT=%d SPLIT=%d\n",
          cfg->partition.partition_none, cfg->partition.partition_horz,
          cfg->partition.partition_vert, cfg->partition.partition_split);
  fprintf(stderr, "           HORZ_A=%d HORZ_B=%d VERT_A=%d VERT_B=%d\n",
          cfg->partition.partition_horz_a, cfg->partition.partition_horz_b,
          cfg->partition.partition_vert_a, cfg->partition.partition_vert_b);
  fprintf(stderr, "           HORZ_4=%d VERT_4=%d\n",
          cfg->partition.partition_horz_4, cfg->partition.partition_vert_4);

  fprintf(stderr, "Intra: DC=%d V=%d H=%d D45=%d D135=%d D113=%d D157=%d D203=%d D67=%d\n",
          cfg->intra.dc_pred, cfg->intra.v_pred, cfg->intra.h_pred,
          cfg->intra.d45_pred, cfg->intra.d135_pred, cfg->intra.d113_pred,
          cfg->intra.d157_pred, cfg->intra.d203_pred, cfg->intra.d67_pred);
  fprintf(stderr, "       SMOOTH=%d SMOOTH_V=%d SMOOTH_H=%d PAETH=%d\n",
          cfg->intra.smooth_pred, cfg->intra.smooth_v_pred,
          cfg->intra.smooth_h_pred, cfg->intra.paeth_pred);

  fprintf(stderr, "Inter: LAST=%d LAST2=%d LAST3=%d GOLDEN=%d BWD=%d ALT2=%d ALT=%d COMPOUND=%d\n",
          cfg->inter.last_frame, cfg->inter.last2_frame, cfg->inter.last3_frame,
          cfg->inter.golden_frame, cfg->inter.bwdref_frame, cfg->inter.altref2_frame,
          cfg->inter.altref_frame, cfg->inter.compound);

  fprintf(stderr, "InterModes: NEAREST=%d NEAR=%d GLOBAL=%d NEW=%d\n",
          cfg->inter_modes.nearestmv, cfg->inter_modes.nearmv,
          cfg->inter_modes.globalmv, cfg->inter_modes.newmv);
  fprintf(stderr, "            NN=%d NrNr=%d NwNw=%d NtNw=%d NwNt=%d NrNw=%d NwNr=%d GG=%d\n",
          cfg->inter_modes.nearest_nearestmv, cfg->inter_modes.near_nearmv,
          cfg->inter_modes.new_newmv, cfg->inter_modes.nearest_newmv,
          cfg->inter_modes.new_nearestmv, cfg->inter_modes.near_newmv,
          cfg->inter_modes.new_nearmv, cfg->inter_modes.global_globalmv);

  fprintf(stderr, "CompoundTypes: AVG=%d DIST=%d WEDGE=%d DIFF=%d\n",
          cfg->compound_types.average, cfg->compound_types.distwtd,
          cfg->compound_types.wedge, cfg->compound_types.diffwtd);

  fprintf(stderr, "TxSizes: 4x4=%d 8x8=%d 16x16=%d 32x32=%d 64x64=%d\n",
          cfg->tx_sizes.tx_4x4, cfg->tx_sizes.tx_8x8,
          cfg->tx_sizes.tx_16x16, cfg->tx_sizes.tx_32x32,
          cfg->tx_sizes.tx_64x64);

  fprintf(stderr, "Transform: DCT_DCT=%d ADST_DCT=%d DCT_ADST=%d ADST_ADST=%d IDTX=%d\n",
          cfg->transform.dct_dct, cfg->transform.adst_dct,
          cfg->transform.dct_adst, cfg->transform.adst_adst, cfg->transform.idtx);
  fprintf(stderr, "========================================\n");
}

int aom_tools_cfg_partition_enabled(const EncoderToolsCfg *cfg, PARTITION_TYPE p) {
  if (!cfg->loaded) return 1;  // All enabled if no config loaded

  switch (p) {
    case PARTITION_NONE: return cfg->partition.partition_none;
    case PARTITION_HORZ: return cfg->partition.partition_horz;
    case PARTITION_VERT: return cfg->partition.partition_vert;
    case PARTITION_SPLIT: return cfg->partition.partition_split;
    case PARTITION_HORZ_A: return cfg->partition.partition_horz_a;
    case PARTITION_HORZ_B: return cfg->partition.partition_horz_b;
    case PARTITION_VERT_A: return cfg->partition.partition_vert_a;
    case PARTITION_VERT_B: return cfg->partition.partition_vert_b;
    case PARTITION_HORZ_4: return cfg->partition.partition_horz_4;
    case PARTITION_VERT_4: return cfg->partition.partition_vert_4;
    default: return 1;
  }
}

int aom_tools_cfg_intra_mode_enabled(const EncoderToolsCfg *cfg, PREDICTION_MODE m) {
  if (!cfg->loaded) return 1;

  switch (m) {
    case DC_PRED: return cfg->intra.dc_pred;
    case V_PRED: return cfg->intra.v_pred;
    case H_PRED: return cfg->intra.h_pred;
    case D45_PRED: return cfg->intra.d45_pred;
    case D135_PRED: return cfg->intra.d135_pred;
    case D113_PRED: return cfg->intra.d113_pred;
    case D157_PRED: return cfg->intra.d157_pred;
    case D203_PRED: return cfg->intra.d203_pred;
    case D67_PRED: return cfg->intra.d67_pred;
    case SMOOTH_PRED: return cfg->intra.smooth_pred;
    case SMOOTH_V_PRED: return cfg->intra.smooth_v_pred;
    case SMOOTH_H_PRED: return cfg->intra.smooth_h_pred;
    case PAETH_PRED: return cfg->intra.paeth_pred;
    default: return 1;  // Inter modes etc.
  }
}

int aom_tools_cfg_ref_frame_enabled(const EncoderToolsCfg *cfg, int ref_frame) {
  if (!cfg->loaded) return 1;

  // ref_frame uses MV_REFERENCE_FRAME enum: LAST_FRAME=1, LAST2=2, etc.
  switch (ref_frame) {
    case 1: return cfg->inter.last_frame;    // LAST_FRAME
    case 2: return cfg->inter.last2_frame;   // LAST2_FRAME
    case 3: return cfg->inter.last3_frame;   // LAST3_FRAME
    case 4: return cfg->inter.golden_frame;  // GOLDEN_FRAME
    case 5: return cfg->inter.bwdref_frame;  // BWDREF_FRAME
    case 6: return cfg->inter.altref2_frame; // ALTREF2_FRAME
    case 7: return cfg->inter.altref_frame;  // ALTREF_FRAME
    default: return 1;
  }
}

int aom_tools_cfg_tx_type_enabled(const EncoderToolsCfg *cfg, TX_TYPE t) {
  if (!cfg->loaded) return 1;

  switch (t) {
    case DCT_DCT: return cfg->transform.dct_dct;
    case ADST_DCT: return cfg->transform.adst_dct;
    case DCT_ADST: return cfg->transform.dct_adst;
    case ADST_ADST: return cfg->transform.adst_adst;
    case FLIPADST_DCT: return cfg->transform.flipadst_dct;
    case DCT_FLIPADST: return cfg->transform.dct_flipadst;
    case FLIPADST_FLIPADST: return cfg->transform.flipadst_flipadst;
    case ADST_FLIPADST: return cfg->transform.adst_flipadst;
    case FLIPADST_ADST: return cfg->transform.flipadst_adst;
    case IDTX: return cfg->transform.idtx;
    case V_DCT: return cfg->transform.v_dct;
    case H_DCT: return cfg->transform.h_dct;
    case V_ADST: return cfg->transform.v_adst;
    case H_ADST: return cfg->transform.h_adst;
    case V_FLIPADST: return cfg->transform.v_flipadst;
    case H_FLIPADST: return cfg->transform.h_flipadst;
    default: return 1;
  }
}

int aom_tools_cfg_compound_enabled(const EncoderToolsCfg *cfg) {
  if (!cfg->loaded) return 1;
  return cfg->inter.compound;
}

int aom_tools_cfg_inter_mode_enabled(const EncoderToolsCfg *cfg, PREDICTION_MODE m) {
  if (!cfg->loaded) return 1;

  switch (m) {
    case NEARESTMV: return cfg->inter_modes.nearestmv;
    case NEARMV: return cfg->inter_modes.nearmv;
    case GLOBALMV: return cfg->inter_modes.globalmv;
    case NEWMV: return cfg->inter_modes.newmv;
    case NEAREST_NEARESTMV: return cfg->inter_modes.nearest_nearestmv;
    case NEAR_NEARMV: return cfg->inter_modes.near_nearmv;
    case NEW_NEWMV: return cfg->inter_modes.new_newmv;
    case NEAREST_NEWMV: return cfg->inter_modes.nearest_newmv;
    case NEW_NEARESTMV: return cfg->inter_modes.new_nearestmv;
    case NEAR_NEWMV: return cfg->inter_modes.near_newmv;
    case NEW_NEARMV: return cfg->inter_modes.new_nearmv;
    case GLOBAL_GLOBALMV: return cfg->inter_modes.global_globalmv;
    default: return 1;
  }
}

int aom_tools_cfg_compound_type_enabled(const EncoderToolsCfg *cfg, int compound_type) {
  if (!cfg->loaded) return 1;

  switch (compound_type) {
    case 0: return cfg->compound_types.average;   // COMPOUND_AVERAGE
    case 1: return cfg->compound_types.distwtd;    // COMPOUND_DISTWTD
    case 2: return cfg->compound_types.wedge;      // COMPOUND_WEDGE
    case 3: return cfg->compound_types.diffwtd;    // COMPOUND_DIFFWTD
    default: return 1;
  }
}

int aom_tools_cfg_tx_size_enabled(const EncoderToolsCfg *cfg, TX_SIZE s) {
  if (!cfg->loaded) return 1;

  // Map any TX_SIZE to its square dimension for checking
  // Rectangular sizes are allowed if their largest square component is allowed
  switch (s) {
    case TX_4X4: return cfg->tx_sizes.tx_4x4;
    case TX_8X8: return cfg->tx_sizes.tx_8x8;
    case TX_16X16: return cfg->tx_sizes.tx_16x16;
    case TX_32X32: return cfg->tx_sizes.tx_32x32;
    case TX_64X64: return cfg->tx_sizes.tx_64x64;
    // Rectangular: check based on the larger dimension
    case TX_4X8: case TX_8X4: return cfg->tx_sizes.tx_8x8;
    case TX_8X16: case TX_16X8: return cfg->tx_sizes.tx_16x16;
    case TX_16X32: case TX_32X16: return cfg->tx_sizes.tx_32x32;
    case TX_32X64: case TX_64X32: return cfg->tx_sizes.tx_64x64;
    case TX_4X16: case TX_16X4: return cfg->tx_sizes.tx_16x16;
    case TX_8X32: case TX_32X8: return cfg->tx_sizes.tx_32x32;
    case TX_16X64: case TX_64X16: return cfg->tx_sizes.tx_64x64;
    default: return 1;
  }
}
