/*!
 * @file
 *      main.c
 * @brief
 *      A palindrome implementation and testing application
 *      for A.U.TH. Microprocessors and peripherals Lab.
 *
 *  Created on: May 1, 2020
 *      Author: Christos Choutouridis AEM: 8997
 *      email : <cchoutou@ece.auth.gr>
 */
#include <stdint.h>

/*!
 * The RAM address to use for palindrome result.
 * @note:
 *      Select a RAM location above `__initial_sp`. See your startup_xx.s for details.
 * Keil's RAM layout is:
 *
 *  RAM: |<-Heap_Size-><-Stack_Size->|<-  WASTED RAM SPACE   ->|
 *       ^                           ^                         ^
 *       |                           |                         |
 *  Start:0x20000000            __initial_sp                End of RAM
 *
 */
#define RESULT  0x20001000

 
/*!
 * String length calculation.
 * @arg     src{R0}     Pointer to string
 * @return  {R0}        String length, -1 if src is NULL
 */
__asm uint32_t strLength (const char* src) {
    // R0: length, return value
    // R1: src
    // R2: v = *src
    MOV     R1, R0          // Get variable
    CMP     R1, #0          // if (src{R1} == NULL)
    MOVEQ   R0, #-1         //    return -1;
    BXEQ    LR
    MOV     R0, #0          // length{R0} =0;
len_loop                    // do {
    LDRB    R2, [R1], #1    //    v{R2} = *src++;
    CMP     R2, #0          //    if(v)
    ADDNE   R0, R0, #1      //       ++length{R0};
    BNE     len_loop        // } while (v);
    BX      LR              // return length;
}

/*!
 * A string palindrome predicate.
 * @note
 *      We use a simple O(n), no stack algorithm.
 * @note
 *      This function also writes to RAM address @ref RESULT the return value (which is nasty).
 * @arg     src{R0}     Pointer to string
 * @return  {R0}        True if palindrome, false if not or src is NULL pointer
 * @note
 *      We consider an empty string "" to be a palindrome, as we can see it the same way both
 *      from the front and the back.
 */
__asm uint32_t isPalindrome (const char* src) {
    // R0: src[] 
    // R1: i
    // R2: j
    // R3: v1 = src[i]
    // R4: v2 = src[j]
    PUSH    {R4, LR}
    CMP     R0, #0          // if (src{R0} == NULL)
    BEQ     pal_false       //    return false;
    MOV     R4, R0          // save R0
    BL      strLength       // j{R2} = strLength(src) - 1
    SUB     R2, R0, #1
    MOV     R0, R4          // src{R0}
    MOV     R1, #0          // i{R1} =0;

pal_loop
    CMP     R1, R2          // while (i<j) {
    BGE     pal_true
    LDRB    R3, [R0, R1]    //    v1 = *src[i];
    LDRB    R4, [R0, R2]    //    v2 = *src[j];
    CMP     R3, R4          //    if (v1 == v2) {
    ADDEQ   R1, R1, #1      //       ++i;
    SUBEQ   R2, R2, #1      //       --j;
    BEQ     pal_loop        //       // goto loop;
pal_false                   //    } else
    MOV     R0, #0          //       return *RESULT = 0;
    LDR     R1, =RESULT      
    STRB    R0, [R1]
    POP     {R4, PC}
                            // }
pal_true
    MOV     R0, #1          // return *RESULT = 1;
    LDR     R1, =RESULT      
    STRB    R0, [R1]
    POP     {R4, PC}
}

/*!
 * Main routine.
 * @note
 *      This routine plays the role of debuging-mode unit testing.
 */
int main(void) {
    const char *s1 = "hello world!";    // Not a palindrome
    const char *s2 = "aibohphobia";     // Aibophobia def= "Irational fear of palindromes", palindrome.
    const char *s3 = "";                // Empty string, palindrome.
    const char *s4 = 0;                 // NULL pointer. Out of scope, not palindrome

    uint32_t res1 = isPalindrome (s1);  // Test case 1
    uint32_t res2 = isPalindrome (s2);  // Test case 2
    uint32_t res3 = isPalindrome (s3);  // Test case 3
    uint32_t res4 = isPalindrome (s4);  // Test case 4
    
    while (1);          // Trap forever
    //return 0;         //!< comment out return, to suppress "statement is unreachable" warning.
                        //!^ @note:
                        //!   **Weird** Keil's way for "statement is unreachable", suppresion.

}
