/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id: structec.c 17847 2008-06-28 18:10:04Z bagder $
 *
 * Copyright (C) 2007 by Miika Pekkarinen
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include <ctype.h>
#include <string.h>
#include <inttypes.h>
#include "structec.h"
#include "system.h"
#include "file.h"

#define MAX_STRUCT_SIZE 128

/**
 * Convert the struct endianess with the instructions provided.
 * 
 * For example:
 * struct test {
 *     long par1;
 *     short par2;
 *     short par3;
 * };
 * 
 * structec_convert(instance_of_test, "lss", sizeof(struct test), true);
 * 
 * Structures to be converted must be properly padded.
 * 
 * @param structure Pointer to the struct being converted.
 * @param ecinst Instructions how to do the endianess conversion.
 * @param count Number of structures to write
 * @param enable Conversion is not made unless this is true.
 */
void structec_convert(void *structure, const char *ecinst, 
                      long count, bool enable)
{
    const char *ecinst_ring = ecinst;
    char *buf = (char *)structure;
    
    if (!enable)
        return;
    
    while (count > 0)
    {
        switch (*ecinst_ring)
        {
            /* Swap nothing. */
            case 'c':
            {
                buf++;
                break;
            }
            
            /* Swap 2 bytes. */
            case 's':
            {
                uint16_t *data = (uint16_t *)buf;
                *data = swap16(*data);
                buf += 2;
                break;
            }

            /* Swap 4 bytes. */
            case 'l':
            {
                uint32_t *data = (uint32_t *)buf;
                *data = swap32(*data);
                buf += 4;
                break;
            }
            
            /* Skip N bytes, idea taken from metadata.c */
            default:
            {
                if (isdigit(*ecinst_ring))
                    buf += (*ecinst_ring - '0');

                break;
            }
        }
        
        ecinst_ring++;
        if (*ecinst_ring == '\0')
        {
            ecinst_ring = ecinst;
            count--;
        }
    }
}

/**
 * Determines the size of a struct in bytes by using endianess correction
 * string format.
 * 
 * @param ecinst endianess correction string.
 * @return length of the struct in bytes.
 */
static size_t structec_size(const char *ecinst)
{
    size_t size = 0;
    
    do
    {
        switch (*ecinst)
        {
            case 'c': size += 1; break;
            case 's': size += 2; break;
            case 'l': size += 4; break;
            default: 
                if (isdigit(*ecinst))
                    size += (*ecinst - '0');
        }
    } while (*(++ecinst) != '\0');
    
    return size;
}

/**
 * Reads endianess corrected structure members from the given file.
 * 
 * @param fd file descriptor of the file being read.
 * @param buf endianess corrected data is placed here.
 * @param scount the number of struct members to read.
 * @param ecinst endianess correction string.
 * @param ec if true, endianess correction is enabled.
 */
ssize_t ecread(int fd, void *buf, size_t scount, const char *ecinst, bool ec)
{
    ssize_t ret;
    size_t member_size = structec_size(ecinst);
    
    ret = read(fd, buf, scount * member_size);
    structec_convert(buf, ecinst, scount, ec);
    
    return ret;
}

/**
 * Writes endianess corrected structure members to the given file.
 * 
 * @param fd file descriptor of the file being written to.
 * @param buf endianess corrected data is read here.
 * @param scount the number of struct members to write.
 * @param ecinst endianess correction string.
 * @param ec if true, endianess correction is enabled.
 */
ssize_t ecwrite(int fd, const void *buf, size_t scount, 
                const char *ecinst, bool ec)
{
    char tmp[MAX_STRUCT_SIZE];
    size_t member_size = structec_size(ecinst);
    
    if (ec)
    {
        const char *p = (const char *)buf;
        int maxamount = (int)(MAX_STRUCT_SIZE / member_size);
        int i;
        
        for (i = 0; i < (long)scount; i += maxamount)
        {
            long amount = MIN((int)scount-i, maxamount);
            
            memcpy(tmp, p, member_size * amount);
            structec_convert(tmp, ecinst, amount, true);
            write(fd, tmp, amount * member_size);
            p += member_size * amount;
        }
        
        return scount * member_size;
    }
    
    return write(fd, buf, scount * member_size);
}

