import 'dart:io';
import 'dart:math';
import 'package:scidart/scidart.dart';

import 'extract_edge_coordinate.dart';
import 'find_optimal_params_bashforthadams.dart';
import 'juszas_equation.dart';
import 'minimize_params_optimization.dart';

void computeST(List<String> sysArgs) {
  // Initial variable definitions
  String imageFilePath = "";
  String outputFileName = "";
  String posPointsFileName = "";
  String negPointsFileName = "";
  int processCount = 2;
  double syringeWidth = 0.70 * 1.15;
  double minaRadcurv = 0.0001;
  double maxaRadcurv = 2.0001;
  double minbShapefact = 0.0001;
  double maxbShapefact = 1.0001;
  double minX = 0.0001;
  double maxX = 0.5001;
  double mintAngle = 0.0001;
  double maxtAngle = 0.5001;
  int aStep = 20;
  int bStep = 20;
  int xStep = 5;
  int tStep = 5;

  int imageFlag = 0;
  int posFileFlag = 0;
  int negFileFlag = 0;
  int outFlag = 0;
  int counter = 0;
  int solveMethod = 0;

  // Argument processing loop.
  while (counter <= sysArgs.length - 1) {
    String currentOption = sysArgs[counter];
    String currentValue = "";
    try {
      currentValue = sysArgs[counter + 1];
    } catch (e) {
      exit(0);
    }

    if (currentOption == "-help") {
      exit(0);
    } else if (currentOption == "-img") {
      imageFilePath = currentValue;
      imageFlag = 1;
    } else if (currentOption == "-filep") {
      posPointsFileName = currentValue;
      posFileFlag = 1;
    } else if (currentOption == "-filen") {
      negPointsFileName = currentValue;
      negFileFlag = 1;
    } else if (currentOption == "-out") {
      outputFileName = currentValue;
      outFlag = 1;
    } else if (currentOption == "-proc") {
      processCount = int.parse(currentValue);
    } else if (currentOption == "-syrg") {
      syringeWidth = double.parse(currentValue);
    } else if (currentOption == "-a") {
      minaRadcurv = double.parse(currentValue);
    } else if (currentOption == "-A") {
      maxaRadcurv = double.parse(currentValue);
    } else if (currentOption == "-b") {
      minbShapefact = double.parse(currentValue);
    } else if (currentOption == "-B") {
      maxbShapefact = double.parse(currentValue);
    } else if (currentOption == "-x") {
      minX = double.parse(currentValue);
    } else if (currentOption == "-X") {
      maxX = double.parse(currentValue);
    } else if (currentOption == "-t") {
      mintAngle = double.parse(currentValue);
    } else if (currentOption == "-T") {
      maxtAngle = double.parse(currentValue);
    } else if (currentOption == "-ai") {
      aStep = int.parse(currentValue);
    } else if (currentOption == "-bi") {
      bStep = int.parse(currentValue);
    } else if (currentOption == "-xi") {
      xStep = int.parse(currentValue);
    } else if (currentOption == "-ti") {
      tStep = int.parse(currentValue);
    } else if (currentOption == "-solver") {
      solveMethod = int.parse(currentValue);
    } else {
      exit(0);
    }
    counter = counter + 2;
  }

  if (outFlag == 0 && imageFlag == 1) {
    print("Output file name not set. (-out)");
    exit(0);
  }
  if (imageFlag == 1 && ((posFileFlag == 1) || (negFileFlag == 1))) {
    print("Image(-img) can not be set with files option (-filep, -filen).");
    print("Either set image only or files only.");
    exit(0);
  }
  if (imageFlag == 0 &&
      ((posFileFlag == 1 && negFileFlag == 0) ||
          (posFileFlag == 0 && negFileFlag == 1))) {
    print(
      "File inputs (-filep, -filen) should both be set if image input is not set.",
    );
    exit(0);
  }

  // Calculate ARange_radCurv
  List<double> ARange_radCurv = [];
  double a = minaRadcurv;
  double ahop = (maxaRadcurv - minaRadcurv) / (aStep - 1);
  while (a <= maxaRadcurv) {
    ARange_radCurv.add(a);
    a += ahop;
    if (ahop == 0.0) {
      break;
    }
  }
  if ((maxaRadcurv - ARange_radCurv[ARange_radCurv.length - 1] - ahop).abs() <
          0.000001 &&
      ahop != 0.0) {
    ARange_radCurv.add(maxaRadcurv);
  }

  // Calculate bRange_shapeFact
  List<double> brangeShapefact = [];
  double b = minbShapefact;
  double bhop = (maxbShapefact - minbShapefact) / (bStep - 1);
  while (b <= maxbShapefact) {
    brangeShapefact.add(b);
    b += bhop;
    if (bhop == 0) {
      break;
    }
  }
  if ((maxbShapefact - brangeShapefact[brangeShapefact.length - 1] - bhop)
              .abs() <
          0.000001 &&
      bhop != 0.0) {
    brangeShapefact.add(maxbShapefact);
  }

  // Calculate XRange
  List<double> XRange = [];
  double x = minX;
  double xhop = (maxX - minX) / (xStep - 1);
  while (x <= maxX) {
    XRange.add(x);
    x += xhop;
    if (xhop == 0) {
      break;
    }
  }
  if ((maxX - XRange[XRange.length - 1] - xhop).abs() < 0.000001 &&
      xhop != 0.0) {
    XRange.add(maxX);
  }

  // Calculate TRange_angle
  List<double> TRange_angle = [];
  double t = mintAngle;
  double thop = (maxtAngle - mintAngle) / (tStep - 1);
  while (t <= maxtAngle) {
    TRange_angle.add(t);
    t += thop;
    if (thop == 0.0) {
      break;
    }
  }
  if ((maxtAngle - TRange_angle[TRange_angle.length - 1] - thop).abs() <
          0.000001 &&
      thop != 0.0) {
    TRange_angle.add(maxtAngle);
  }

  print("\n");
  if (imageFlag == 1) {
    print("Image File                : $imageFilePath");
    print("Output Name               : $outputFileName");
    print("Negative Coordinate File  : ${outputFileName}_Negative.dat");
    print("Positive Coordinate File  : ${outputFileName}_Positive.dat");
    print("Syringe Width (millimeter): $syringeWidth");
  } else {
    print("Negative Coordinate File  : $negPointsFileName");
    print("Positive Coordinate File  : $posPointsFileName");
  }
  print(
    "Radius of Curvature Range : $minaRadcurv-$maxaRadcurv (interval: ${ARange_radCurv.length})",
  );
  print(
    "Shape Factor Range        : $minbShapefact-$maxbShapefact (interval: ${brangeShapefact.length})",
  );
  print("Initial X Coordinate Range: $minX-$maxX (interval: ${XRange.length})");
  print(
    "Initial Angle Range       : $mintAngle-$maxtAngle (interval: ${TRange_angle.length})",
  );
  print(
    "Parameter Combinations    : ${ARange_radCurv.length * brangeShapefact.length * XRange.length * TRange_angle.length}",
  );
  print("Number of Process to Use  : $processCount");

  List<List<double>> posEdgePts;
  List<List<double>> negEdgePts;

  if (imageFlag == 1) {
    var result = extractEdgeCoordinate(
      imageFilePath,
      outputFileName,
      syringeWidth,
    );
    posEdgePts = result[0];
    negEdgePts = result[1];
    double deScaledMaxWidth = result[2];
    List<double> dmsDiameters = List<double>.from(result[3]);

    double HInv1 = HJuza(dmsDiameters[7] / deScaledMaxWidth, 0.8);
    double HInv2 = HJuza(dmsDiameters[8] / deScaledMaxWidth, 0.9);
    double HInv3 = HJuza(dmsDiameters[9] / deScaledMaxWidth, 1.0);
    double HInv4 = HJuza(dmsDiameters[10] / deScaledMaxWidth, 1.1);
    double HInv5 = HJuza(dmsDiameters[11] / deScaledMaxWidth, 1.2);
    double HInvM = (0.20) * (HInv1 + HInv2 + HInv3 + HInv4 + HInv5);
    double HInvSD = sqrt(
      (0.20) *
          ((HInv1 - HInvM) * (HInv1 - HInvM) +
              (HInv2 - HInvM) * (HInv2 - HInvM) +
              (HInv3 - HInvM) * (HInv3 - HInvM) +
              (HInv4 - HInvM) * (HInv4 - HInvM) +
              (HInv5 - HInvM) * (HInv5 - HInvM)),
    );
    print("Juza 5-Plane (1/H) Std.Dev: $HInvSD");
  } else {
    posEdgePts = [];
    try {
      List<String> positiveLines = File(posPointsFileName).readAsLinesSync();
      for (var Line in positiveLines) {
        List<String> coordinate = Line.split(RegExp(r'\s+'));
        //print(coordinate);
        posEdgePts.add([
          double.parse(coordinate[0]),
          double.parse(coordinate[1]),
        ]);
      }
    } catch (e) {
      print("Error reading positive points file.");
      exit(0);
    }
    negEdgePts = [];
    try {
      List<String> negativeLines = File(negPointsFileName).readAsLinesSync();
      for (var Line in negativeLines) {
        List<String> coordinate = Line.split(RegExp(r'\s+'));
        //print(Coordinate);
        negEdgePts.add([
          double.parse(coordinate[0]),
          double.parse(coordinate[1]),
        ]);
      }
    } catch (e) {
      print("Error reading negative points file.");
      exit(0);
    }
  }

  // Nested function bruteForce
  List<dynamic> bruteForce() {
    List<List<double>> posList = [];
    List<List<double>> negList = [];
    for (int processNo = 0; processNo < processCount; processNo++) {
      posList.add(posEdgePts);
      negList.add(negEdgePts);
      //print(processNo);
    }

    List<List<double>> aRanges = [];
    for (int processNo = 0; processNo < processCount; processNo++) {
      aRanges.add([]);
    }

    int i = 0;
    for (var A in ARange_radCurv) {
      aRanges[i % processCount].add(A);
      i += 1;
    }

    List<List<dynamic>> bestParams = []; // will store candidate tuples
    List<int> ticksQueue = []; // simulate ticks

    // Simulate process creation and execution.
    for (int processNo = 0; processNo < processCount; processNo++) {
      // In a real multiprocessing environment, each process would run concurrently.
      // Here, we directly call the function.
      findOptimalParameters(
        posList[processNo],
        negList[processNo],
        aRanges[processNo],
        brangeShapefact,
        XRange,
        TRange_angle,
        bestParams,
        ticksQueue,
      );
    }

    stdout.write("\rParameter Search Progress : 0%");
    stdout.flush();
    int count = 0;
    int totalTicks = ARange_radCurv.length;
    // Simulate receiving ticks.
    while (count < totalTicks) {
      // Since ticks have been already added, just increment Count.
      if (ticksQueue.isNotEmpty) {
        ticksQueue.removeAt(0);
        count += 1;
        stdout.write(
          "\rParameter Search Progress : ${(100 * (count / totalTicks)).toInt()}%",
        );
        stdout.flush();
      }
    }
    print("");

    List<dynamic> bestOutput = [999999999, 1, 1, 1, 1];
    for (var output in bestParams) {
      if (output[0] < bestOutput[0]) {
        bestOutput = output;
      }
    }
    return bestOutput;
  }

  // Nested function Optimize
  List<dynamic> optimize() {
    return optimizeParameters(posEdgePts, negEdgePts, 0.95, 0.3, 0.1, 0.2);
  }

  // Record start time
  var startTime = DateTime.now();

  // Choose which solver to use.
  List<dynamic> bestOutput = (solveMethod != 1) ? optimize() : bruteForce();

  print("best output: $bestOutput");

  print("\nLowest RMSD               : ${bestOutput[0]}");
  // Computed Surface Tension: 9.8*bestOutput[1]/bestOutput[2]
  print("Computed Surface Tension  : ${9.8 * bestOutput[1] / bestOutput[2]}");
  print("Best Radius of Curvature  : ${bestOutput[1]}");
  print("Best Shape Factor         : ${bestOutput[2]}");
  print("Best Initial X Coordinate : ${bestOutput[3]}");
  print("Best Initial Angle        : ${bestOutput[4]}");
  var elapsed = DateTime.now().difference(startTime).inSeconds;
  print("Lapsed Time in Seconds    : $elapsed");
}
