Transposition

// transposition.go
// description: Transposition cipher
// details:
// Implementation "Transposition cipher" is a method of encryption by which the positions held by units of plaintext (which are commonly characters or groups of characters) are shifted according to a regular system, so that the ciphertext constitutes a permutation of the plaintext [Transposition cipher](https://en.wikipedia.org/wiki/Transposition_cipher)
// time complexity: O(n)
// space complexity: O(n)
// author(s) [red_byte](https://github.com/i-redbyte)
// see transposition_test.go

package transposition

import (
	"errors"
	"fmt"
	"sort"
	"strings"
)

var ErrNoTextToEncrypt = errors.New("no text to encrypt")
var ErrKeyMissing = errors.New("missing Key")

const placeholder = ' '

func getKey(keyWord string) []int {
	keyWord = strings.ToLower(keyWord)
	word := []rune(keyWord)
	var sortedWord = make([]rune, len(word))
	copy(sortedWord, word)
	sort.Slice(sortedWord, func(i, j int) bool { return sortedWord[i] < sortedWord[j] })
	usedLettersMap := make(map[rune]int)
	wordLength := len(word)
	resultKey := make([]int, wordLength)
	for i := 0; i < wordLength; i++ {
		char := word[i]
		numberOfUsage := usedLettersMap[char]
		resultKey[i] = getIndex(sortedWord, char) + numberOfUsage + 1 //+1 -so that indexing does not start at 0
		numberOfUsage++
		usedLettersMap[char] = numberOfUsage
	}
	return resultKey
}

func getIndex(wordSet []rune, subString rune) int {
	n := len(wordSet)
	for i := 0; i < n; i++ {
		if wordSet[i] == subString {
			return i
		}
	}
	return 0
}

func Encrypt(text []rune, keyWord string) ([]rune, error) {
	key := getKey(keyWord)
	keyLength := len(key)
	textLength := len(text)
	if keyLength <= 0 {
		return nil, ErrKeyMissing
	}
	if textLength <= 0 {
		return nil, ErrNoTextToEncrypt
	}
	if text[len(text)-1] == placeholder {
		return nil, fmt.Errorf("%w: cannot encrypt a text, %q, ending with the placeholder char %q", ErrNoTextToEncrypt, text, placeholder)
	}
	n := textLength % keyLength

	for i := 0; i < keyLength-n; i++ {
		text = append(text, placeholder)
	}
	textLength = len(text)
	var result []rune
	for i := 0; i < textLength; i += keyLength {
		transposition := make([]rune, keyLength)
		for j := 0; j < keyLength; j++ {
			transposition[key[j]-1] = text[i+j]
		}
		result = append(result, transposition...)
	}
	return result, nil
}

func Decrypt(text []rune, keyWord string) ([]rune, error) {
	key := getKey(keyWord)
	textLength := len(text)
	if textLength <= 0 {
		return nil, ErrNoTextToEncrypt
	}
	keyLength := len(key)
	if keyLength <= 0 {
		return nil, ErrKeyMissing
	}
	n := textLength % keyLength
	for i := 0; i < keyLength-n; i++ {
		text = append(text, placeholder)
	}
	var result []rune
	for i := 0; i < textLength; i += keyLength {
		transposition := make([]rune, keyLength)
		for j := 0; j < keyLength; j++ {
			transposition[j] = text[i+key[j]-1]
		}
		result = append(result, transposition...)
	}
	result = []rune(strings.TrimRight(string(result), string(placeholder)))
	return result, nil
}