# -------------------------------------
# Task 2: Decryption and Encryption with Polybius Square
# -------------------------------------

# Define the Polybius matrix
# Reference: Chapter 3, Matrices and Arrays (p. 59-63), Matrix Creation
# Create a matrix that maps letters to rows and columns for encryption and decryption
polybius <- matrix(c(" ", "Z", "M", "D", " ", "K", "W", "Q", " ", "G", 
                     "V", " ", "A", " ", "R", " ", "E", " ", "T", "O", 
                     " ", "J", "I", "S", "B", "P", "Y", " ", "F", " ", 
                     "L", " ", " ", "C", " ", "U", "H", "N", " ", "X"), 
                      nrow = 4, byrow = TRUE)    # Fill the matrix row by row

rownames(polybius) <- 0:3  # Assign row numbers (0-based indexing for decryption logic)
colnames(polybius) <- 0:9  # Assign column numbers (0-based indexing)

# -------------------------------------
# Part 1: Decrypt the given ciphertext using the Polybius matrix
# -------------------------------------

# Input ciphertext to be decrypted
# Reference: Chapter 11, String Manipulation (p. 251-253), Combining Strings

ciphertext <- c("24160", "63616", "14162", 
                "61935", "14163", "71602", 
                "26222", "33719", "18008")

# Combine all ciphertext numbers into a continuous string for easier processing

ciphertext_string <- paste(ciphertext, collapse = "")

# Create an empty vector to store the decrypted plaintext message
# Reference: Chapter 2, Vectors (p. 25-27), Initializing Vectors

plaintext_message <- c()

# Decrypt the ciphertext
# Reference: Chapter 7, R Programming Structures (p. 139-140), Loops

for (i in seq(1, nchar(ciphertext_string), by = 2)) 
{
  # Extract row and column indices from ciphertext pairs
  row <- as.integer(substr(ciphertext_string, i, i))
  col <- as.integer(substr(ciphertext_string, i + 1, i + 1))
  
  # Check if indices are valid and access the Polybius matrix
  # Reference: Chapter 3, Matrix Indexing (p. 62)
  if (!is.na(row) && !is.na(col) && row >= 0 && row <= 3 && col >= 0 && col <= 9) 
  {
    character <- polybius[row + 1, col + 1]  # Adjust for R's 1-based indexing
    plaintext_message <- c(plaintext_message, character)  # Add the character to plaintext
  } 
  else 
  {
    plaintext_message <- c(plaintext_message, " ")  # Add space for invalid pairs
  }
}

# Combine plaintext characters into a single string

plaintext <- paste(plaintext_message, collapse = "")

#Display Final Plain text
plaintext

# -------------------------------------
# Part 2: Encrypt the given message with the keyword "APPLE"
# -------------------------------------
# Define the message and keyword

message <- "RAISETHEFLAG"  # The plaintext message to encrypt
keyword <- "APPLE"         # The keyword used for encryption

# Split message and keyword into individual letters
# Reference: Chapter 11, String Manipulation (p. 251)

message_chars <- sapply(seq_len(nchar(message)), function(i) substring(message, i, i))  # Split message into characters
keyword_chars <- sapply(seq_len(nchar(keyword)), function(i) substring(keyword, i, i))  # Split keyword into characters

# Get numeric coordinates for the message from the Polybius matrix
# Reference: Chapter 3.2.2, Matrix Indexing (p. 62)

message_coords <- sapply(message_chars, function(char)  # Loop through each character in the message
{
  for (row in 0:3)  # Iterate over rows of the Polybius matrix
  {
    for (col in 0:9)  # Iterate over columns of the Polybius matrix
    {
      if (polybius[row + 1, col + 1] == char)  # Check if the character matches in the matrix
      {
        return(row * 10 + col)  # Return numeric coordinate as row * 10 + col
      }
    }
  }
})

# Repeat the keyword to match the length of the message
# Reference: Chapter 2, Recycling in Vectors (p. 29)

keyword_coords <- sapply(rep(keyword_chars,length.out = length(message_chars)), function(char)  # Loop through repeated keyword characters
{
  for (row in 0:3)  # Iterate over rows of the Polybius matrix
  {
    for (col in 0:9)  # Iterate over columns of the Polybius matrix
    {
       if (polybius[row + 1, col + 1] == char)  # Check if the character matches in the matrix
       {
          return(row * 10 + col)  # Return numeric coordinate as row * 10 + col
       }
    }
  }
})

# Add message and keyword coordinates to encrypt
# Reference: Chapter 8, Math and Simulations (p. 189-190), Modular Arithmetic

ciphertext_coords <- (message_coords + keyword_coords)  # Add message and keyword coordinates for encryption

# Convert numeric coordinates to a single ciphertext string
# Reference: Chapter 11.1.3, String Concatenation with paste() (p. 252)

ciphertext_string <- paste(ciphertext_coords, collapse = "")  # Combine all numeric coordinates into a single string

# Divide the ciphertext into groups of 5 digits
# Reference: Chapter 11.1.5, substr() Function (p. 253)

finaltext <- c()  # Initialize vector for storing ciphertext groups

for (i in seq(1, nchar(ciphertext_string), by = 5))  # Loop through ciphertext in steps of 5
{
  group <- substring(ciphertext_string, i, min(i + 4, nchar(ciphertext_string)))  # Extract a group of up to 5 digits
  
  # Add random digits to the last group if less than 5 digits
  if (nchar(group) < 5)  # Check if the group is shorter than 5 digits
  {
    group <- paste0(group, paste(sample(0:9, 5 - nchar(group), replace = TRUE), collapse = ""))  # Add random digits
  }
  
  finaltext <- c(finaltext, group)  # Append the group to the final ciphertext vector
}

# Display the final ciphertext in groups
finaltext  # Print the ciphertext groups


# -------------------------------------
# Part 3: Decrypt the ciphertext using the keyword "LEMON"
# -------------------------------------

# Input ciphertext and keyword
# Reference: Chapter 11, String Manipulation (p. 251-253), Combining Strings

ciphertext <- "48521 82559 53320 83151 44382 13349 40352 42260 48521 84349 48343 23518"
keyword1 <- "LEMON"

# Step 1: Split ciphertext into pairs
# Reference: Chapter 11.1, substr() Function (p. 253), Removing Spaces and Processing Substrings
# Ciphertext is cleaned (spaces removed) and split into 2-digit numeric pairs

ciphertext_clean <- paste(strsplit(ciphertext, " ")[[1]], collapse = "") # Remove spaces

pairs <- c()  # Initialize vector for storing numeric pairs

for (i in seq(1, nchar(ciphertext_clean), by = 2)) # Loop through ciphertext in steps of 2
{ 
  p <- as.numeric(substr(ciphertext_clean, i, i + 1)) # Extract 2-digit pair
  pairs <- c(pairs, p)  # Append the numeric pair to the vector
}

# Step 2: Convert keyword to numeric values
# Reference: Chapter 5, Matrix Indexing (p. 62), Accessing Matrix Elements
# Each letter in the keyword is mapped to a numeric value using the Polybius matrix

k_num <- c()  # Initialize vector for numeric values of keyword

for (char in strsplit(keyword1, "")[[1]]) # Split the keyword into individual letters
{ 
  for (r in 0:3) # Loop through rows of the Polybius matrix
  {  
    for (c in 0:9) 
    {  # Loop through columns of the Polybius matrix
      if (polybius[r + 1, c + 1] == char) # Match letter in matrix (adjusting for 1-based indexing)
      { 
        k_num <- c(k_num, r * 10 + c) # Compute numeric value as row * 10 + column
      }
    }
  }
}

# Step 3: Decrypt the ciphertext pairs
# Reference: Chapter 8, Modular Arithmetic (p. 189-190)
# Subtract the keyword numeric value from each ciphertext pair, wrap negatives, and map back to the Polybius matrix

plaintext <- ""  # Initialize an empty string for the final plaintext

k_len <- length(k_num)  # Length of the numeric keyword sequence

for (j in 1:length(pairs)) # Loop through all ciphertext pairs
{ 
  c_val <- pairs[j]  # Current ciphertext pair value
  k_val <- k_num[(j - 1) %% k_len + 1] # Cycle through keyword values based on current position
  
  # Adjust cipher value and handle negative wrapping
  adj_val <- c_val - k_val  # Subtract the numeric value of the keyword
  if (adj_val < 0)  # If the adjusted value is negative
  { 
    adj_val <- adj_val + 40 # Wrap into valid range (0-39)
  }
  
  # Extract row and column from the adjusted value
  r <- adj_val %/% 10  # Integer division to get the row
  c <- adj_val %% 10  # Modulo operation to get the column
  
  # Fetch the corresponding letter from the Polybius matrix
  letter <- polybius[r + 1, c + 1]  # Adjust for R's 1-based indexing
  if (letter != " ") 
  {  # Ignore spaces in the matrix
    plaintext <- paste0(plaintext, letter) 
  }
}

# Final Output
# Display the final decrypted plaintext
plaintext  
