import 'dart:io';
import 'dart:math';

List<dynamic> extractEdgeCoordinate(
  String imageFilePath,
  String outputFileName,
  double syringeWidth,
) {
  // Detect the edges of the image.
  // All edge pixels will white(255) and rest of the pixels will be black(0).
  List<List<dynamic>> Image = CV2.imread(imageFilePath, CV2.IMREAD_COLOR);
  List<List<int>> EdgeImage = CV2.Canny(Image, 100, 200);

  // Find the first edge point (white pixel).
  // It is assumed that first edge point part of the syringe.
  int NumOfRow = EdgeImage.length;
  int NumOfCol = EdgeImage[0].length;
  List<int> EdgePoint = [0, 0];
  int EdgePointFlag = 0;
  for (int RowIndex = 0; RowIndex < NumOfRow; RowIndex++) {
    for (int ColIndex = 0; ColIndex < NumOfCol; ColIndex++) {
      if (EdgeImage[RowIndex][ColIndex] == 255) {
        EdgePoint = [RowIndex, ColIndex];
        EdgePointFlag = 1;
        break;
      }
    }
    if (EdgePointFlag == 1) {
      break;
    }
  }

  // Color the all the syringe and droplet pixels differently (70).
  // Determine the left-most, right-most, and bottom droplet pixels.
  EdgeImage[EdgePoint[0]][EdgePoint[1]] = 70;
  List<List<int>> DropletEdgePoints = [];
  DropletEdgePoints.add(EdgePoint);
  int LeftMostCol = 9999999999;
  int RightMostCol = 0;
  int BottomMostRow = 0;
  int BottomMostRow2 = 0;
  int CenterCol = 0;

  while (DropletEdgePoints.length != 0) {
    List<int> CurrentPoint = DropletEdgePoints[0];
    DropletEdgePoints.remove(CurrentPoint);
    List<List<int>> NeighborPoints = [];
    for (int RowOffset = -3; RowOffset < 4; RowOffset++) {
      for (int ColOffset = -3; ColOffset < 4; ColOffset++) {
        if (RowOffset != 0 || ColOffset != 0) {
          NeighborPoints.add([
            CurrentPoint[0] + RowOffset,
            CurrentPoint[1] + ColOffset,
          ]);
        }
      }
    }
    for (List<int> point in NeighborPoints) {
      int PointRow = point[0];
      int PointCol = point[1];
      if ((0 <= PointRow && PointRow < NumOfRow) &&
          (0 <= PointCol && PointCol < NumOfCol)) {
        if (EdgeImage[PointRow][PointCol] == 255) {
          DropletEdgePoints.add([PointRow, PointCol]);
          EdgeImage[PointRow][PointCol] = 70;
          if (PointRow > BottomMostRow) {
            BottomMostRow = PointRow;
          }
          if (PointCol > RightMostCol) {
            RightMostCol = PointCol;
          }
          if (PointCol < LeftMostCol) {
            LeftMostCol = PointCol;
          }
        }
      }
    }
  }

  CenterCol = (LeftMostCol + RightMostCol) ~/ 2;
  for (int Row = 0; Row < BottomMostRow + 1; Row++) {
    if (EdgeImage[Row][CenterCol] == 70) {
      BottomMostRow2 = Row;
    }
  }
  BottomMostRow = BottomMostRow2;

  // Determine the transition row between the syringe pixels and the droplet pixels.
  int PreviousWidth = 0;
  int WidthIncCount = 5;
  List<int> WidthHistory = List.filled(WidthIncCount + 1, 0);
  int TransitionRow = 0;

  for (int Row = 0; Row < BottomMostRow; Row++) {
    int LeftEdgeCol = 0;
    int RightEdgeCol = 9999999;
    for (int Col = LeftMostCol - 1; Col < RightMostCol + 1; Col++) {
      if (EdgeImage[Row][Col] == 70) {
        LeftEdgeCol = Col;
        break;
      }
    }
    for (int Col = RightMostCol + 1; Col >= LeftMostCol - 1; Col--) {
      if (EdgeImage[Row][Col] == 70) {
        RightEdgeCol = Col;
        break;
      }
    }
    int CurrentWidth = RightEdgeCol - LeftEdgeCol;
    if ((CurrentWidth != PreviousWidth) &&
        (0 < CurrentWidth) &&
        (CurrentWidth < (9999999 - NumOfCol))) {
      for (int i = 0; i < WidthIncCount; i++) {
        WidthHistory[i] = WidthHistory[i + 1];
      }
      WidthHistory[WidthIncCount] = CurrentWidth;
    }
    int WidthIncreaseCount = 0;
    for (int i = 0; i < WidthIncCount; i++) {
      if (WidthHistory[i] < WidthHistory[i + 1]) {
        WidthIncreaseCount += 1;
      }
    }
    if (WidthIncreaseCount == WidthIncCount) {
      TransitionRow = Row;
      break;
    }
    if ((0 < CurrentWidth) && (CurrentWidth < (9999999 - NumOfCol))) {
      PreviousWidth = CurrentWidth;
    }
  }

  // Determine the width of the syringe in terms of pixel count
  int WidthSum = 0;
  int WidthCount = 0;
  int SyringeLevel = (0.75 * TransitionRow).toInt();
  for (int Row = 0; Row < TransitionRow; Row++) {
    int RightEdgeCol = 9999999;
    int LeftEdgeCol = 0;
    for (int Col = LeftMostCol; Col < CenterCol + 1; Col++) {
      if (EdgeImage[Row][Col] == 70) {
        EdgeImage[Row][Col] = 255;
        LeftEdgeCol = Col;
        break;
      }
    }
    for (int Col = RightMostCol; Col > CenterCol - 1; Col--) {
      if (EdgeImage[Row][Col] == 70) {
        EdgeImage[Row][Col] = 255;
        RightEdgeCol = Col;
        break;
      }
    }
    int CurrentWidth = RightEdgeCol - LeftEdgeCol;
    if (WidthCount <= SyringeLevel) {
      if ((0 < CurrentWidth) && (CurrentWidth < (9999999 - NumOfCol))) {
        WidthSum += CurrentWidth;
        WidthCount += 1;
      }
    }
  }
  double AverageWidth = (WidthSum.toDouble()) / (WidthCount.toDouble());

  // Extract the pixel coordinates of all droplet pixels (color: 70)
  List<List<int>> PositiveEdgePoints = [];
  List<List<int>> NegativeEdgePoints = [];
  for (int Row = BottomMostRow; Row > TransitionRow - 1; Row--) {
    for (int Col = LeftMostCol; Col < CenterCol + 1; Col++) {
      if (EdgeImage[Row][Col] == 70) {
        NegativeEdgePoints.add([Row, Col]);
        break;
      }
    }
    for (int Col = RightMostCol; Col > CenterCol; Col--) {
      if (EdgeImage[Row][Col] == 70) {
        PositiveEdgePoints.add([Row, Col]);
        break;
      }
    }
  }

  // Scale the point coordinates by using the syringe length
  double SyringeWidth = syringeWidth;
  double ScalingFactor = SyringeWidth / AverageWidth;
  List<List<double>> ScaledNegativeEdgePoints = [];
  String negativeFileName = outputFileName + "_Negative.dat";
  IOSink NegativeCoordinatesFile = File(negativeFileName).openWrite();
  for (List<int> point in NegativeEdgePoints) {
    int Row = point[0];
    int Col = point[1];
    double Row2 = (ScalingFactor * (BottomMostRow - Row).toDouble()) + 0.0001;
    double Col2 = ScalingFactor * (Col - CenterCol).toDouble();
    NegativeCoordinatesFile.write("$Col2 $Row2\n");
    ScaledNegativeEdgePoints.add([Col2, Row2]);
  }
  NegativeCoordinatesFile.close();

  List<List<double>> ScaledPositiveEdgePoints = [];
  String positiveFileName = "${outputFileName}_Positive.dat";
  IOSink PositiveCoordinatesFile = File(positiveFileName).openWrite();
  for (List<int> point in PositiveEdgePoints) {
    int Row = point[0];
    int Col = point[1];
    double Row2 = (ScalingFactor * (BottomMostRow - Row).toDouble()) + 0.0001;
    double Col2 = ScalingFactor * (Col - CenterCol).toDouble();
    PositiveCoordinatesFile.write("$Col2 $Row2\n");
    ScaledPositiveEdgePoints.add([Col2, Row2]);
  }
  PositiveCoordinatesFile.close();

  // Draw the detected edge points of the droplet.
  // List<List<dynamic>> Image2 = deepCopy(Image);
  for (int Row = 0; Row < NumOfRow; Row++) {
    for (int Col = 0; Col < NumOfCol; Col++) {
      Image[Row][Col] = 0;
      // For EdgeImage, since it is a separate 2D list of ints, set to 0.
      EdgeImage[Row][Col] = 0;
    }
  }
  for (List<int> Point in PositiveEdgePoints) {
    Image[Point[0]][Point[1]] = [0, 0, 255];
    EdgeImage[Point[0]][Point[1]] = 255;
  }
  for (List<int> Point in NegativeEdgePoints) {
    Image[Point[0]][Point[1]] = [0, 0, 255];
    EdgeImage[Point[0]][Point[1]] = 255;
  }

  // Draw the center column of the drop
  CV2.line(
    Image,
    Point(CenterCol.toDouble(), TransitionRow.toDouble()),
    Point(CenterCol.toDouble(), BottomMostRow.toDouble()),
    [0, 255, 0],
    1,
  ); // image, start, end, color(BGR), thickness

  double MaxWidthRatio = 0.0;
  for (int Row = BottomMostRow; Row > TransitionRow - 1; Row--) {
    int RightCol = 99999;
    int LeftCol = -1;
    for (int Col = LeftMostCol; Col < CenterCol + 1; Col++) {
      if (EdgeImage[Row][Col] == 255) {
        LeftCol = Col;
        break;
      }
    }
    for (int Col = RightMostCol; Col > CenterCol; Col--) {
      if (EdgeImage[Row][Col] == 255) {
        RightCol = Col;
        break;
      }
    }
    double WidthRatio =
        (RightCol - LeftCol).toDouble() /
        (RightMostCol - LeftMostCol).toDouble();
    if (WidthRatio < 1.0 && WidthRatio > MaxWidthRatio) {
      MaxWidthRatio = WidthRatio;
    }
  }

  List<List<int>> RowWithMaxWidth = [];
  for (int Row = BottomMostRow; Row > TransitionRow - 1; Row--) {
    int RightCol = 99999;
    int LeftCol = -1;
    for (int Col = LeftMostCol; Col < CenterCol + 1; Col++) {
      if (EdgeImage[Row][Col] == 255) {
        LeftCol = Col;
        break;
      }
    }
    for (int Col = RightMostCol; Col > CenterCol; Col--) {
      if (EdgeImage[Row][Col] == 255) {
        RightCol = Col;
        break;
      }
    }
    double WidthRatio =
        (RightCol - LeftCol).toDouble() /
        (RightMostCol - LeftMostCol).toDouble();
    if (WidthRatio == MaxWidthRatio) {
      RowWithMaxWidth.add([Row, LeftCol, RightCol]);
    }
  }

  // Draw the main diameter
  int n = RowWithMaxWidth.length;
  n = (n / 2).toInt();
  int Row = RowWithMaxWidth[n][0];
  int LeftCol = RowWithMaxWidth[n][1];
  int RightCol = RowWithMaxWidth[n][2];
  CV2.line(
    Image,
    Point(LeftCol.toDouble(), Row.toDouble()),
    Point(RightCol.toDouble(), Row.toDouble()),
    [0, 255, 0],
    1,
  );

  // Measure and draw the ten diameters
  List<int> Diameters = [];
  int MaxWidth = RightMostCol - LeftMostCol;
  for (int i = 1; i < 13; i++) {
    for (int Col = LeftMostCol; Col < CenterCol + 1; Col++) {
      if (EdgeImage[BottomMostRow - (i * MaxWidth ~/ 10)][Col] == 255) {
        LeftCol = Col;
        break;
      }
    }
    for (int Col = RightMostCol; Col > CenterCol; Col--) {
      if (EdgeImage[BottomMostRow - (i * MaxWidth ~/ 10)][Col] == 255) {
        RightCol = Col;
        break;
      }
    }
    Diameters.add(RightCol - LeftCol);
    CV2.line(
      Image,
      Point(
        LeftCol.toDouble(),
        (BottomMostRow - (i * MaxWidth ~/ 10)).toDouble(),
      ),
      Point(
        RightCol.toDouble(),
        (BottomMostRow - (i * MaxWidth ~/ 10)).toDouble(),
      ),
      [255, 0, 0],
      1,
    );
  }

  // Scale the 12 drop diameters
  for (int i = 0; i < 12; i++) {
    Diameters[i] = (Diameters[i] * ScalingFactor).toInt();
  }
  double ScaledMaxWidth = ScalingFactor * MaxWidth.toDouble();
  // double ScaledUpWidth = ScalingFactor * (RightCol - LeftCol).toDouble();
  return [
    ScaledPositiveEdgePoints,
    ScaledNegativeEdgePoints,
    ScaledMaxWidth,
    Diameters,
  ];
}
