using System;
using System.Collections.Generic;
using System.Linq;

public class ProcessCapability
{
	public static void Main(string[] args)
	{
		// Example data (replace with your actual data, organized in subgroups)
		double[][] dataSubgroups = new double[][]
		{
			new double[]
			{
				10.2,
				10.5,
				9.8,
				10.1,
				10.3
			},
			new double[]
			{
				9.9,
				10.4,
				10.0,
				10.6,
				10.2
			},
			new double[]
			{
				9.7,
				10.1,
				10.5,
				10.3,
				9.9
			}
		};
		double USL = 11.0; // Upper Specification Limit
		double LSL = 9.5; // Lower Specification Limit
		double target = 10.25; // Target value
		// Calculate short-term standard deviation using pooled std dev
		double shortTermStdDev = CalculatePooledStdDev(dataSubgroups);
		// Calculate short-term standard deviation (using range method)
		//double shortTermStdDev = CalculateShortTermStdDev(data);
		// Calculate long-term standard deviation
		double longTermStdDev = CalculateLongTermStdDev(dataSubgroups.SelectMany(subgroup => subgroup).ToArray());
		// Calculate Cp and Cpk
		double Cp = CalculateCp(USL, LSL, shortTermStdDev);
		double Cpk = CalculateCpk(USL, LSL, dataSubgroups.SelectMany(subgroup => subgroup).Average(), shortTermStdDev);
		// Calculate Pp and Ppk
		double Pp = CalculatePp(USL, LSL, longTermStdDev);
		double Ppk = CalculatePpk(USL, LSL, dataSubgroups.SelectMany(subgroup => subgroup).Average(), longTermStdDev);
		// Calculate Cpm and Ppm
		double Cpm = CalculateCpm(USL, LSL, target, dataSubgroups.SelectMany(subgroup => subgroup).Average(), shortTermStdDev);
		double Ppm = CalculatePpm(USL, LSL, target, dataSubgroups.SelectMany(subgroup => subgroup).Average(), longTermStdDev);
		// Calculate Cpkm and Ppkm
		double Cpkm = CalculateCpkm(USL, LSL, target, dataSubgroups.SelectMany(subgroup => subgroup).Average(), shortTermStdDev);
		double Ppkm = CalculatePpkm(USL, LSL, target, dataSubgroups.SelectMany(subgroup => subgroup).Average(), longTermStdDev);
		// Calculate Cr and Pr
		double Cr = CalculateCr(USL, LSL, shortTermStdDev);
		double Pr = CalculatePr(USL, LSL, longTermStdDev);
		// Output results
		Console.WriteLine("Short-term Standard Deviation (σ): " + shortTermStdDev);
		Console.WriteLine("Long-term Standard Deviation (s): " + longTermStdDev);
		Console.WriteLine("Cp: " + Cp);
		Console.WriteLine("Cpk: " + Cpk);
		Console.WriteLine("Pp: " + Pp);
		Console.WriteLine("Ppk: " + Ppk);
		Console.WriteLine("Cpm: " + Cpm);
		Console.WriteLine("Ppm: " + Ppm);
		Console.WriteLine("Cpkm: " + Cpkm);
		Console.WriteLine("Ppkm: " + Ppkm);
		Console.WriteLine("Cr: " + Cr);
		Console.WriteLine("Pr: " + Pr);
	}

	// Calculate pooled standard deviation (Minitab method)
	public static double CalculatePooledStdDev(double[][] dataSubgroups)
	{
		double sumOfSquares = 0;
		int totalDegreesOfFreedom = 0;
		int m = dataSubgroups.Length; // number of subgroups
		foreach (double[] subgroup in dataSubgroups)
		{
			double subgroupAverage = subgroup.Average();
			int subgroupSize = subgroup.Length;
			for (int i = 0; i < subgroupSize; i++)
			{
				sumOfSquares += Math.Pow(subgroup[i] - subgroupAverage, 2);
			}

			totalDegreesOfFreedom += subgroupSize - 1;
		}

		double Sp = Math.Sqrt(sumOfSquares / totalDegreesOfFreedom);
		// For unequal sample sizes, return Sp. For constant subgroup sizes, return unbiased σ.
		return Sp / c4_table(totalDegreesOfFreedom + 1);
	}

	// Lookup table for c4 values (replace with more comprehensive table if needed)
	public static double c4_table(int subgroupSize)
	{
		switch (subgroupSize)
		{
			case 2:
				return 0.7979;
			case 3:
				return 0.8862;
			case 4:
				return 0.9213;
			case 5:
				return 0.9400;
			case 6:
				return 0.9515;
			case 7:
				return 0.9594;
			case 8:
				return 0.9650;
			case 9:
				return 0.9693;
			case 10:
				return 0.9727;
			case 11:
				return 0.9754;
			case 12:
				return 0.9776;
			case 13:
				return 0.9794;
			case 14:
				return 0.9809;
			case 15:
				return 0.9823;
			case 16:
				return 0.9835;
			case 17:
				return 0.9845;
			case 18:
				return 0.9854;
			case 19:
				return 0.9862;
			case 20:
				return 0.9869;
			// Add more cases as needed
			default:
				return 1.0; // Return 1 for large sample sizes where the bias is negligible
		}
	}

	// Calculate short-term standard deviation using range method
	public static double CalculateShortTermStdDev(double[] data)
	{
		// Assuming subgroup size of 5 (adjust d2 accordingly)
		double d2 = 2.326;
		double averageRange = CalculateAverageRange(data, 5);
		return averageRange / d2;
	}

	// Calculate average range of subgroups
	public static double CalculateAverageRange(double[] data, int subgroupSize)
	{
		double sumOfRanges = 0;
		int numSubgroups = data.Length / subgroupSize;
		for (int i = 0; i < numSubgroups; i++)
		{
			double[] subgroup = data.Skip(i * subgroupSize).Take(subgroupSize).ToArray();
			sumOfRanges += subgroup.Max() - subgroup.Min();
		}

		return sumOfRanges / numSubgroups;
	}

	// Calculate long-term standard deviation
	public static double CalculateLongTermStdDev(double[] data)
	{
		double average = data.Average();
		double sumOfSquares = data.Sum(x => Math.Pow(x - average, 2));
		return Math.Sqrt(sumOfSquares / (data.Length - 1));
	}

	// Calculate Cp
	public static double CalculateCp(double USL, double LSL, double stdDev)
	{
		return (USL - LSL) / (6 * stdDev);
	}

	// Calculate Cpk
	public static double CalculateCpk(double USL, double LSL, double mean, double stdDev)
	{
		double CpkUpper = (USL - mean) / (3 * stdDev);
		double CpkLower = (mean - LSL) / (3 * stdDev);
		return Math.Min(CpkUpper, CpkLower);
	}

	// Calculate Pp
	public static double CalculatePp(double USL, double LSL, double stdDev)
	{
		return (USL - LSL) / (6 * stdDev);
	}

	// Calculate Ppk
	public static double CalculatePpk(double USL, double LSL, double mean, double stdDev)
	{
		double PpkUpper = (USL - mean) / (3 * stdDev);
		double PpkLower = (mean - LSL) / (3 * stdDev);
		return Math.Min(PpkUpper, PpkLower);
	}

	// Calculate Cpm
	public static double CalculateCpm(double USL, double LSL, double target, double mean, double stdDev)
	{
		double numerator = Math.Min(USL - target, target - LSL);
		double denominator = 3 * Math.Sqrt(Math.Pow(stdDev, 2) + Math.Pow(mean - target, 2));
		return numerator / denominator;
	}

	// Calculate Ppm
	public static double CalculatePpm(double USL, double LSL, double target, double mean, double stdDev)
	{
		double numerator = Math.Min(USL - target, target - LSL);
		double denominator = 3 * Math.Sqrt(Math.Pow(stdDev, 2) + Math.Pow(mean - target, 2));
		return numerator / denominator;
	}

	// Calculate Cpkm
	public static double CalculateCpkm(double USL, double LSL, double target, double mean, double stdDev)
	{
		double numerator = Math.Min(USL - mean, mean - LSL);
		double denominator = 3 * Math.Sqrt(Math.Pow(stdDev, 2) + Math.Pow(mean - target, 2));
		return numerator / denominator;
	}

	// Calculate Ppkm
	public static double CalculatePpkm(double USL, double LSL, double target, double mean, double stdDev)
	{
		double numerator = Math.Min(USL - mean, mean - LSL);
		double denominator = 3 * Math.Sqrt(Math.Pow(stdDev, 2) + Math.Pow(mean - target, 2));
		return numerator / denominator;
	}

	// Calculate Cr (Capability Ratio)
	public static double CalculateCr(double USL, double LSL, double stdDev)
	{
		return 1.0 / CalculateCp(USL, LSL, stdDev); // Cr is the reciprocal of Cp
	}

	// Calculate Pr (Performance Ratio)
	public static double CalculatePr(double USL, double LSL, double stdDev)
	{
		return 1.0 / CalculatePp(USL, LSL, stdDev); // Pr is the reciprocal of Pp
	}
}