using System;
using System.Linq;
using Algorithms.Numeric;
namespace Algorithms.Encoders;
/// <summary>
/// Lester S. Hill's polygraphic substitution cipher,
/// without representing letters using mod26, using
/// corresponding "(char)value" instead.
/// </summary>
public class HillEncoder : IEncoder<double[,]>
{
private readonly GaussJordanElimination linearEquationSolver;
public HillEncoder() => linearEquationSolver = new GaussJordanElimination(); // TODO: add DI
public string Encode(string text, double[,] key)
{
var preparedText = FillGaps(text);
var chunked = ChunkTextToArray(preparedText);
var splitted = SplitToCharArray(chunked);
var ciphered = new double[chunked.Length][];
for (var i = 0; i < chunked.Length; i++)
{
var vector = new double[3];
Array.Copy(splitted, i * 3, vector, 0, 3);
var product = MatrixCipher(vector, key);
ciphered[i] = product;
}
var merged = MergeArrayList(ciphered);
return BuildStringFromArray(merged);
}
public string Decode(string text, double[,] key)
{
var chunked = ChunkTextToArray(text);
var split = SplitToCharArray(chunked);
var deciphered = new double[chunked.Length][];
for (var i = 0; i < chunked.Length; i++)
{
var vector = new double[3];
Array.Copy(split, i * 3, vector, 0, 3);
var product = MatrixDeCipher(vector, key);
deciphered[i] = product;
}
var merged = MergeArrayList(deciphered);
var str = BuildStringFromArray(merged);
return UnFillGaps(str);
}
/// <summary>
/// Converts elements from the array to their corresponding Unicode characters.
/// </summary>
/// <param name="arr">array of vectors.</param>
/// <returns>Message.</returns>
private static string BuildStringFromArray(double[] arr) => new(arr.Select(c => (char)c).ToArray());
/// <summary>
/// Multiplies the key for the given scalar.
/// </summary>
/// <param name="vector">list of splitted words as numbers.</param>
/// <param name="key">Cipher selected key.</param>
/// <returns>Ciphered vector.</returns>
private static double[] MatrixCipher(double[] vector, double[,] key)
{
var multiplied = new double[vector.Length];
for (var i = 0; i < key.GetLength(1); i++)
{
for (var j = 0; j < key.GetLength(0); j++)
{
multiplied[i] += key[i, j] * vector[j];
}
}
return multiplied;
}
/// <summary>
/// Given a list of vectors, returns a single array of elements.
/// </summary>
/// <param name="list">List of ciphered arrays.</param>
/// <returns>unidimensional list.</returns>
private static double[] MergeArrayList(double[][] list)
{
var merged = new double[list.Length * 3];
for (var i = 0; i < list.Length; i++)
{
Array.Copy(list[i], 0, merged, i * 3, list[0].Length);
}
return merged;
}
/// <summary>
/// Splits the input text message as chunks of words.
/// </summary>
/// <param name="chunked">chunked words list.</param>
/// <returns>spliiter char array.</returns>
private static char[] SplitToCharArray(string[] chunked)
{
var splitted = new char[chunked.Length * 3];
for (var i = 0; i < chunked.Length; i++)
{
for (var j = 0; j < 3; j++)
{
splitted[i * 3 + j] = chunked[i].ToCharArray()[j];
}
}
return splitted;
}
/// <summary>
/// Chunks the input text message.
/// </summary>
/// <param name="text">text message.</param>
/// <returns>array of words.</returns>
private static string[] ChunkTextToArray(string text)
{
// To split the message into chunks
var div = text.Length / 3;
var chunks = new string[div];
for (var i = 0; i < div; i++)
{
chunks.SetValue(text.Substring(i * 3, 3), i);
}
return chunks;
}
/// <summary>
/// Fills a text message with spaces at the end
/// to enable a simple split by 3-length-word.
/// </summary>
/// <param name="text">Text Message.</param>
/// <returns>Modified text Message.</returns>
private static string FillGaps(string text)
{
var remainder = text.Length % 3;
return remainder == 0 ? text : text + new string(' ', 3 - remainder);
}
/// <summary>
/// Removes the extra spaces included on the cipher phase.
/// </summary>
/// <param name="text">Text message.</param>
/// <returns>Deciphered Message.</returns>
private static string UnFillGaps(string text) => text.TrimEnd();
/// <summary>
/// Finds the inverse of the given matrix using a linear equation solver.
/// </summary>
/// <param name="vector">Splitted words vector.</param>
/// <param name="key">Key used for the cipher.</param>
/// <returns>TODO.</returns>
private double[] MatrixDeCipher(double[] vector, double[,] key)
{
// To augment the original key with the given vector.
var augM = new double[3, 4];
for (var i = 0; i < key.GetLength(0); i++)
{
for (var j = 0; j < key.GetLength(1); j++)
{
augM[i, j] = key[i, j];
}
}
for (var k = 0; k < vector.Length; k++)
{
augM[k, 3] = vector[k];
}
_ = linearEquationSolver.Solve(augM);
return new[] { augM[0, 3], augM[1, 3], augM[2, 3] };
}
}