/*------------------------------------------------------------------------*/
/* Copyright (C) SIEMENS CORP., 2018 All rights reserved.*/
/* All rights reserved.                                                   */
/* Redistribution and use in source and binary forms, with or without     */
/* modification, are permitted provided that the following conditions     */
/* are met:                                                               */
/* 1. Redistributions of source code must retain the above copyright      */
/* notice, this list of conditions and the following disclaimer.          */
/* 2. Redistributions in binary form must reproduce the above copyright   */
/* notice, this list of conditions and the following disclaimer in the    */
/* documentation and/or other materials provided with the distribution.   */
/* 3. Neither the name of the copyright holder nor the names of its       */
/* contributors may be used to endorse or promote products derived from   */
/* this software without specific prior written permission.               */
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS    */
/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT      */
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS      */
/* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE         */
/* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,   */
/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,   */
/* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS  */
/* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)                    */
/* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,    */
/* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING  */
/* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE     */
/* POSSIBILITY OF SUCH DAMAGE.                                            */
/*------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
/* Attention : Callbacks are running concurrent in other threads so all   */
/*             printf statements should be synchronized. But this sample  */
/*             application doesn't synchronize for simplicity  !          */
/*------------------------------------------------------------------------*/

#include "im.h"
#include "dev_certify_energy.h"

#ifdef WIN32
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#else
#define _BSD_SOURCE /* for usleep */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>
#include <termios.h>
#include <string.h>
#include <time.h>
#define Sleep(x)  usleep(x*1000)
#endif

#include "servusrx.h"

/*----------------------------------------------------------------------------------------------------*/
/*    DEFINES                                                                                         */
/*----------------------------------------------------------------------------------------------------*/

#define STRNCMP_EQUAL 0
#define LINE_LENGTH_MAX           255   /* Maximum line length of persistent im data file */
#define NUMOF_PERSISTENT_IM_ITEMS 6     /* persistent: IM_Revision_Counter, IM_Tag_Function, IM_Tag_Location, IM_Date, IM_Descriptor, IM_Signature */

#define LEN_IM_ORDERID 20
#define LEN_IM_SERIAL_NUMBER 16
#define LEN_IM_SOFTWARE_REVISION 4
#define LEN_IM_TAG_FUNCTION 32
#define LEN_IM_TAG_LOCATION 22
#define LEN_IM_DATE 16
#define LEN_IM_DESCRIPTOR 54
#define LEN_IM_SIGNATURE 54

/*----------------------------------------------------------------------------------------------------*/
/*    TYPES                                                                                           */
/*----------------------------------------------------------------------------------------------------*/

typedef struct IM_PERMANENT_tag   /*size: 142*/
{
	PNIO_UINT16     VendorID;
	PNIO_UINT16     OrderID;
	PNIO_UINT8      pIM_Serial_Number[16];
	PNIO_UINT16     IM_Hardware_Revision;
	PNIO_UINT8      IM_Software_Revision[4];
	PNIO_UINT16     IM_Profile_ID;
	PNIO_UINT16     IM_Profile_Specific_Type;
	PNIO_UINT16     IM_Version;
	PNIO_UINT16     IM_Supported;
	PNIO_UINT8      IM_Tag_Function[32];
	PNIO_UINT8      IM_Tag_Location[22];
	PNIO_UINT8      IM_Date[16];
	PNIO_UINT8      IM_Descriptor[54];
	PNIO_UINT8      IM_Signature[54];
} IM_PERMANENT;

typedef struct tag_IM_DATA
{
	PNIO_IM0_TYPE   im0;
	PNIO_IM1_TYPE   im1;
	PNIO_IM2_TYPE   im2;
	PNIO_IM3_TYPE   im3;
	PNIO_IM4_TYPE   im4;
} IM_DATA;

typedef enum
{
	IM_Revision_Counter =       0,
	IM_Tag_Function =           1,
	IM_Tag_Location =           2,
	IM_Date =                   3,
	IM_Descriptor =             4,
	IM_Signature =              5
} IM_PERSISTENT_DATA_e;

typedef enum
{
	IM_EQUAL = 0,
	IM_READ_DIFFER = 1,
	IM_WRITE_DIFFER = 2,
	IM_READ_WRITE_DIFFER = 3
} IM_CMP_E;

typedef struct tag_IM_PERSISTENT_DATA   /* persistent IM data */
{
	PNIO_UINT16     IM_Revision_Counter;
	PNIO_UINT8      IM_Tag_Function[LEN_IM_TAG_FUNCTION];
	PNIO_UINT8      IM_Tag_Location[LEN_IM_TAG_LOCATION];
	PNIO_UINT8      IM_Date[LEN_IM_DATE];
	PNIO_UINT8      IM_Descriptor[LEN_IM_DESCRIPTOR];
	PNIO_UINT8      IM_Signature[LEN_IM_SIGNATURE];
} IM_PERSISTENT_DATA;

/*----------------------------------------------------------------------------------------------------*/
/*    I&M DATA                                                                                        */
/*----------------------------------------------------------------------------------------------------*/

static IM_DATA* im_work = 0;             /* current IM data */
static const char* name_IM_PERSISTENT_DATA[] =      /* names of persistent IM data items */
{
	"IM_Revision_Counter",
	"IM_Tag_Function",
	"IM_Tag_Location",
	"IM_Date",
	"IM_Descriptor",
	"IM_Signature"
};

static const IM_DATA im_default =   /* default IM data */
{
	{   /* I&M0 */
		{
			0x0020,                     /* BlockHeader.BlockType */
			0x0038,                     /* BlockHeader.BlockLength */
			0x01,                       /* BlockHeader.BlockVersionHigh */
			0x00						/* BlockHeader.BlockVersionLow */
		},                      
		0x00,							/* VendorIDHigh - will be set to value from readConfig! */
		0x2A,							/* VendorIDLow  - will be set to value from readConfig! */
		{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', }, /* OrderID[20] -> will be set to selected device annotation! */
		{ '0', '0', '0', '-', '0', '0', '0', '-', '0', '0', '0', '-', '0', '0', '0', '1' }, /* IM_Serial_Number[16] -> will be set to actual serial number! */
		9,					            /* IM_Hardware_Revision - will be set to selected device annotation! */
		{ 'V', 2, 7, 0 },               /* IM_Software_Revision[4] - will be set to selected device annotation! */
		0x0000,							/* IM_Revision_Counter */
		0x0000,							/* IM_Profile_ID -> Generic Device */
		0x0003,							/* IM_Profile_Specific_Type -> Communication Module -> www.profibus.com/IM/Device_ID_Table_6102.xml */
		0x01,							/* IM_Version_Major */
		0x01,							/* IM_Version_Minor */
		0x003e							/* IM_Supported -> IM0 bis IM4 */
	},
	{   /* I&M1 */
		{
			0x0021,                     /* BlockHeader.BlockType */
			0x0038,                     /* BlockHeader.BlockLength */
			0x01,                       /* BlockHeader.BlockVersionHigh */
			0x00						/* BlockHeader.BlockVersionLow */
		},
		{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }, /* IM_Tag_Function[32] */
		{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' } /* IM_Tag_Location[22] */
	},
	{  /* I&M2 */
		{
			0x0022,                     /* BlockHeader.BlockType */
			0x0012,                     /* BlockHeader.BlockLength */
			0x01,                       /* BlockHeader.BlockVersionHigh */
			0x00						/* BlockHeader.BlockVersionLow */
		},
		{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' } /* IM_Date[16] */
	},
	{  /* I&M3 */
		{
			0x0023,                     /* BlockHeader.BlockType */
			0x0038,                     /* BlockHeader.BlockLength */
			0x01,                       /* BlockHeader.BlockVersionHigh */
			0x00						/* BlockHeader.BlockVersionLow */
		},
		{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' } /* IM_Descriptor[54] */
	},
	{   /* I&M4 */
		{
			0x0024,                     /* BlockHeader.BlockType */
			0x0038,                     /* BlockHeader.BlockLength */
			0x01,                       /* BlockHeader.BlockVersionHigh */
			0x00						/* BlockHeader.BlockVersionLow */
		},
		{ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' } /* IM_Signature[54] */
	}
};




/*----------------------------------------------------------------------------------------------------*/
/*                  I&M internal functions                                                            */
/*----------------------------------------------------------------------------------------------------*/

void IM_incRevCounter(PNIO_UINT16* pRevCounter)
{
	if (*pRevCounter == 0)
	{
		*pRevCounter = 1;
	}
}

IM_CMP_E IM_CmpIM0(const PNIO_IM0_TYPE* pLeft, const PNIO_IM0_TYPE* pRight)
{
	if (
		pLeft->BlockHeader.BlockLength == pRight->BlockHeader.BlockLength ||
		pLeft->BlockHeader.BlockType == pRight->BlockHeader.BlockType ||
		pLeft->BlockHeader.BlockVersionHigh == pRight->BlockHeader.BlockVersionHigh ||
		pLeft->BlockHeader.BlockVersionLow == pRight->BlockHeader.BlockVersionLow ||
		pLeft->IM_Hardware_Revision == pRight->IM_Hardware_Revision ||
		pLeft->IM_Profile_ID == pRight->IM_Profile_ID ||
		pLeft->IM_Profile_Specific_Type == pRight->IM_Profile_Specific_Type ||
		pLeft->IM_Revision_Counter == pRight->IM_Revision_Counter ||
		pLeft->IM_Supported == pRight->IM_Supported ||
		pLeft->IM_Version_Major == pRight->IM_Version_Major ||
		pLeft->IM_Version_Minor == pRight->IM_Version_Minor ||
		STRNCMP_EQUAL == strncmp((char*)pLeft->OrderID, (char*)pRight->OrderID, LEN_IM_ORDERID) ||
		pLeft->VendorIDHigh == pRight->VendorIDHigh ||
		pLeft->VendorIDLow == pRight->VendorIDLow ||
		STRNCMP_EQUAL == strncmp((char*)pLeft->IM_Serial_Number, (char*)pRight->IM_Serial_Number, LEN_IM_SERIAL_NUMBER) ||
		STRNCMP_EQUAL == strncmp((char*)pLeft->IM_Software_Revision, (char*)pRight->IM_Software_Revision, LEN_IM_SOFTWARE_REVISION)
		)
	{
		return IM_EQUAL;
	}
	return IM_READ_DIFFER;
}

IM_CMP_E IM_CmpIM1(const PNIO_IM1_TYPE* pLeft, const PNIO_IM1_TYPE* pRight)
{
	PNIO_BOOL readDiffer = PNIO_TRUE;
	PNIO_BOOL writeDiffer = PNIO_TRUE;
	if (pLeft->BlockHeader.BlockLength == pRight->BlockHeader.BlockLength ||
		pLeft->BlockHeader.BlockType == pRight->BlockHeader.BlockType ||
		pLeft->BlockHeader.BlockVersionHigh == pRight->BlockHeader.BlockVersionHigh ||
		pLeft->BlockHeader.BlockVersionLow == pRight->BlockHeader.BlockVersionLow)
	{
		readDiffer = PNIO_FALSE;
	}
	if (STRNCMP_EQUAL == strncmp((char*)pLeft->IM_Tag_Function, (char*)pRight->IM_Tag_Function, LEN_IM_TAG_FUNCTION) &&
		STRNCMP_EQUAL == strncmp((char*)pLeft->IM_Tag_Location, (char*)pRight->IM_Tag_Location, LEN_IM_TAG_LOCATION))
	{
		writeDiffer = PNIO_FALSE;
	}
	if ((readDiffer == PNIO_TRUE) && (writeDiffer == PNIO_TRUE))
	{
		return IM_READ_WRITE_DIFFER;
	}
	if ((readDiffer == PNIO_TRUE) && (writeDiffer == PNIO_FALSE))
	{
		return IM_READ_DIFFER;
	}
	if ((readDiffer == PNIO_FALSE) && (writeDiffer == PNIO_TRUE))
	{
		return IM_WRITE_DIFFER;
	}
	return IM_EQUAL;
}

IM_CMP_E IM_CmpIM2(const PNIO_IM2_TYPE* pLeft, const PNIO_IM2_TYPE* pRight)
{
	PNIO_BOOL readDiffer = PNIO_TRUE;
	PNIO_BOOL writeDiffer = PNIO_TRUE;
	if (pLeft->BlockHeader.BlockLength == pRight->BlockHeader.BlockLength ||
		pLeft->BlockHeader.BlockType == pRight->BlockHeader.BlockType ||
		pLeft->BlockHeader.BlockVersionHigh == pRight->BlockHeader.BlockVersionHigh ||
		pLeft->BlockHeader.BlockVersionLow == pRight->BlockHeader.BlockVersionLow)
	{
		readDiffer = PNIO_FALSE;
	}
	if (STRNCMP_EQUAL == strncmp((char*)pLeft->IM_Date, (char*)pRight->IM_Date, LEN_IM_DATE))
	{
		writeDiffer = PNIO_FALSE;
	}
	if ((readDiffer == PNIO_TRUE) && (writeDiffer == PNIO_TRUE))
	{
		return IM_READ_WRITE_DIFFER;
	}
	if ((readDiffer == PNIO_TRUE) && (writeDiffer == PNIO_FALSE))
	{
		return IM_READ_DIFFER;
	}
	if ((readDiffer == PNIO_FALSE) && (writeDiffer == PNIO_TRUE))
	{
		return IM_WRITE_DIFFER;
	}
	return IM_EQUAL;
}

IM_CMP_E IM_CmpIM3(const PNIO_IM3_TYPE* pLeft, const PNIO_IM3_TYPE* pRight)
{
	PNIO_BOOL readDiffer = PNIO_TRUE;
	PNIO_BOOL writeDiffer = PNIO_TRUE;
	if (pLeft->BlockHeader.BlockLength == pRight->BlockHeader.BlockLength ||
		pLeft->BlockHeader.BlockType == pRight->BlockHeader.BlockType ||
		pLeft->BlockHeader.BlockVersionHigh == pRight->BlockHeader.BlockVersionHigh ||
		pLeft->BlockHeader.BlockVersionLow == pRight->BlockHeader.BlockVersionLow)
	{
		readDiffer = PNIO_FALSE;
	}
	if (STRNCMP_EQUAL == strncmp((char*)pLeft->IM_Descriptor, (char*)pRight->IM_Descriptor, LEN_IM_DESCRIPTOR))
	{
		writeDiffer = PNIO_FALSE;
	}
	if ((readDiffer == PNIO_TRUE) && (writeDiffer == PNIO_TRUE))
	{
		return IM_READ_WRITE_DIFFER;
	}
	if ((readDiffer == PNIO_TRUE) && (writeDiffer == PNIO_FALSE))
	{
		return IM_READ_DIFFER;
	}
	if ((readDiffer == PNIO_FALSE) && (writeDiffer == PNIO_TRUE))
	{
		return IM_WRITE_DIFFER;
	}
	return IM_EQUAL;
}

IM_CMP_E IM_CmpIM4(const PNIO_IM4_TYPE* pLeft, const PNIO_IM4_TYPE* pRight)
{
	PNIO_BOOL readDiffer = PNIO_TRUE;
	PNIO_BOOL writeDiffer = PNIO_TRUE;
	if (pLeft->BlockHeader.BlockLength == pRight->BlockHeader.BlockLength ||
		pLeft->BlockHeader.BlockType == pRight->BlockHeader.BlockType ||
		pLeft->BlockHeader.BlockVersionHigh == pRight->BlockHeader.BlockVersionHigh ||
		pLeft->BlockHeader.BlockVersionLow == pRight->BlockHeader.BlockVersionLow)
	{
		readDiffer = PNIO_FALSE;
	}
	if (STRNCMP_EQUAL == strncmp((char*)pLeft->IM_Signature, (char*)pRight->IM_Signature, LEN_IM_SIGNATURE))
	{
		writeDiffer = PNIO_FALSE;
	}
	if ((readDiffer == PNIO_TRUE) && (writeDiffer == PNIO_TRUE))
	{
		return IM_READ_WRITE_DIFFER;
	}
	if ((readDiffer == PNIO_TRUE) && (writeDiffer == PNIO_FALSE))
	{
		return IM_READ_DIFFER;
	}
	if ((readDiffer == PNIO_FALSE) && (writeDiffer == PNIO_TRUE))
	{
		return IM_WRITE_DIFFER;
	}
	return IM_EQUAL;
}

PNIO_UINT8* IM_RemTrailBlanks(const PNIO_UINT8* pBuf, int bufLen)
{
	static PNIO_UINT8 pNoBlanks[LINE_LENGTH_MAX];
	int idx = bufLen - 1;
	int copyLen = 0;
	if (bufLen >= LINE_LENGTH_MAX)
	{
		idx = LINE_LENGTH_MAX - 2;
	}
	for (; idx >= 0; idx--)
	{
		if (pBuf[idx] != ' ')
		{
			copyLen = idx + 1;
			break;
		}
		if (idx == 0)         /* solely blanks found */
		{
			copyLen = 0;
			break;
		}
	}
	strncpy((char*)pNoBlanks, (char*)pBuf, copyLen);
	pNoBlanks[copyLen] = '\0';
	return pNoBlanks;
}

/* pSrc has to be null-terminated */
PNIO_BOOL IM_CopyQuotedString(PNIO_UINT8* pDst, PNIO_UINT8* pSrc, size_t maxSize)
{
	PNIO_UINT8* pTmp;
	size_t copyLen;
	pSrc = (PNIO_UINT8*) strchr((char*)pSrc, '"');
	if (!pSrc)
	{
		return PNIO_FALSE;
	}
	else
	{
		pSrc++;
	}
	pTmp = (PNIO_UINT8*)strrchr((char*)pSrc, '"');
	if (!pTmp)
	{
		return PNIO_FALSE;
	}
	copyLen = pTmp - pSrc;
	if (copyLen > maxSize)
	{
		copyLen = maxSize;
	}
	strncpy((char*)pDst, (char*)pSrc, copyLen);
	return PNIO_TRUE;
}

void IM_CopySwapIm1FromBuf(
	PNIO_IM1_TYPE*      pIm, /* [out] */
	PNIO_UINT8*  const pBuf) /* [in]  */
{
	*pIm =                          *(PNIO_IM1_TYPE*)pBuf;
	pIm->BlockHeader.BlockType =                SWAP_16(((PNIO_IM1_TYPE*)pBuf)->BlockHeader.BlockType);
	pIm->BlockHeader.BlockLength =              SWAP_16(((PNIO_IM1_TYPE*)pBuf)->BlockHeader.BlockLength);
}

void IM_CopySwapIm2FromBuf(
	PNIO_IM2_TYPE*      pIm,        /* [out] */
	PNIO_UINT8*  const pBuf) /* [in]  */
{
	*pIm =                          *(PNIO_IM2_TYPE*)pBuf;
	pIm->BlockHeader.BlockType =                SWAP_16(((PNIO_IM2_TYPE*)pBuf)->BlockHeader.BlockType);
	pIm->BlockHeader.BlockLength =              SWAP_16(((PNIO_IM2_TYPE*)pBuf)->BlockHeader.BlockLength);
}

void IM_CopySwapIm3FromBuf(
	PNIO_IM3_TYPE*      pIm, /* [out] */
	PNIO_UINT8*  const pBuf) /* [in]  */
{
	*pIm =                          *(PNIO_IM3_TYPE*)pBuf;
	pIm->BlockHeader.BlockType =                SWAP_16(((PNIO_IM3_TYPE*)pBuf)->BlockHeader.BlockType);
	pIm->BlockHeader.BlockLength =              SWAP_16(((PNIO_IM3_TYPE*)pBuf)->BlockHeader.BlockLength);
}

void IM_CopySwapIm4FromBuf(
	PNIO_IM4_TYPE*      pIm, /* [out] */
	PNIO_UINT8*  const pBuf) /* [in]  */
{
	*pIm =                          *(PNIO_IM4_TYPE*)pBuf;
	pIm->BlockHeader.BlockType =                SWAP_16(((PNIO_IM4_TYPE*)pBuf)->BlockHeader.BlockType);
	pIm->BlockHeader.BlockLength =              SWAP_16(((PNIO_IM4_TYPE*)pBuf)->BlockHeader.BlockLength);
}

PNIO_BOOL IM_CopySwapBufFromIm(
	PNIO_UINT8*    pBuf, /* [out]  */
	IM_DATA const* pIm,  /* [in] */
	IM0_idx_e     idx)   /* [in]  */
{
	switch (idx)
	{
	case IM0:
		*((PNIO_IM0_TYPE*)pBuf)           =                pIm->im0;
		((PNIO_IM0_TYPE*)pBuf)->BlockHeader.BlockType =    SWAP_16(pIm->im0.BlockHeader.BlockType);
		((PNIO_IM0_TYPE*)pBuf)->BlockHeader.BlockLength =  SWAP_16(pIm->im0.BlockHeader.BlockLength);
		((PNIO_IM0_TYPE*)pBuf)->IM_Hardware_Revision =     SWAP_16(pIm->im0.IM_Hardware_Revision);
		/* ((PNIO_IM0_TYPE *)pBuf)->IM_Software_Revision =     SWAP_32(pIm->im0.IM_Software_Revision);*/
		((PNIO_IM0_TYPE*)pBuf)->IM_Revision_Counter =      SWAP_16(pIm->im0.IM_Revision_Counter);
		((PNIO_IM0_TYPE*)pBuf)->IM_Profile_Specific_Type = SWAP_16(pIm->im0.IM_Profile_Specific_Type);
		((PNIO_IM0_TYPE*)pBuf)->IM_Profile_ID =            SWAP_16(pIm->im0.IM_Profile_ID);
		((PNIO_IM0_TYPE*)pBuf)->IM_Supported =             SWAP_16(pIm->im0.IM_Supported);
		break;
	case IM1:
		*((PNIO_IM1_TYPE*)pBuf)           =                pIm->im1;
		((PNIO_IM1_TYPE*)pBuf)->BlockHeader.BlockType =     SWAP_16(pIm->im1.BlockHeader.BlockType);
		((PNIO_IM1_TYPE*)pBuf)->BlockHeader.BlockLength =   SWAP_16(pIm->im1.BlockHeader.BlockLength);
		break;
	case IM2:
		*((PNIO_IM2_TYPE*)pBuf)           =                pIm->im2;
		((PNIO_IM2_TYPE*)pBuf)->BlockHeader.BlockType =     SWAP_16(pIm->im2.BlockHeader.BlockType);
		((PNIO_IM2_TYPE*)pBuf)->BlockHeader.BlockLength =   SWAP_16(pIm->im2.BlockHeader.BlockLength);
		break;
	case IM3:
		*((PNIO_IM3_TYPE*)pBuf)           =                pIm->im3;
		((PNIO_IM3_TYPE*)pBuf)->BlockHeader.BlockType =     SWAP_16(pIm->im3.BlockHeader.BlockType);
		((PNIO_IM3_TYPE*)pBuf)->BlockHeader.BlockLength =   SWAP_16(pIm->im3.BlockHeader.BlockLength);
		break;
	case IM4:
		*((PNIO_IM4_TYPE*)pBuf)           =                pIm->im4;
		((PNIO_IM4_TYPE*)pBuf)->BlockHeader.BlockType =     SWAP_16(pIm->im4.BlockHeader.BlockType);
		((PNIO_IM4_TYPE*)pBuf)->BlockHeader.BlockLength =   SWAP_16(pIm->im4.BlockHeader.BlockLength);
		break;
	default:
		break;
	}
	return PNIO_TRUE;
}
/* Diese Funktion gibt die Groesse des Datensatzes zurueck */
PNIO_UINT32 IM_SizeofImDatarec(PNIO_UINT32  RecordIndex)
{
	switch (RecordIndex)
	{
	case IM0:
		return (sizeof(PNIO_IM0_TYPE));
	case IM1:
		return (sizeof(PNIO_IM1_TYPE));
	case IM2:
		return (sizeof(PNIO_IM2_TYPE));
	case IM3:
		return (sizeof(PNIO_IM3_TYPE));
	case IM4:
		return (sizeof(PNIO_IM4_TYPE));
	default:
		return 0;
	}
}

/* check if the given RacordIndex is supported */
PNIO_BOOL IM_IndexSupported(PNIO_UINT32 RecordIndex, PNIO_UINT16 IM_Supported)
{
	/* && (IM_Supported&0x01) */
	if (((RecordIndex == IM0)) ||
		((RecordIndex == IM1) && (IM_Supported & 0x02)) ||
		((RecordIndex == IM2) && (IM_Supported & 0x04)) ||
		((RecordIndex == IM3) && (IM_Supported & 0x08)) ||
		((RecordIndex == IM4) && (IM_Supported & 0x10)))
	{
		return PNIO_TRUE;
	}
	return PNIO_FALSE;
}

/* set error code */
void setPnioRwError(PNIO_ERR_STAT* pPnioState, PNIO_UINT8 ErrCode1)
{
	/**** if an error occured, you must specify it according IEC 61158-6 */
	pPnioState->ErrCode   = 0xdf;  /* IODWriteRes with ErrorDecode = PNIORW */
	pPnioState->ErrDecode = 0x80;  /* PNIORW                                */
	pPnioState->ErrCode1  = ErrCode1;
	pPnioState->ErrCode2  = 0;     /* here dont care                                                             */
	pPnioState->AddValue1 = 0;     /* here dont care                                                             */
	pPnioState->AddValue2 = 0;     /* here dont care                                                             */
}

/*----------------------------------------------------------------------------------------------------*/
/*                  I&M external functions                                                            */
/*----------------------------------------------------------------------------------------------------*/

/*  Read I&M data from pBuf */
void IM_recWrite(PNIO_UINT32 RecordIndex, PNIO_DEV_ADDR Addr, PNIO_UINT32* pBufLen, PNIO_UINT8* pBuf, PNIO_ERR_STAT* pPnioState)
{
	IM_CMP_E    imCmp = IM_EQUAL;
	IM_DATA*     pIm = NULL;
	int id = 0;

	*pBufLen = 0;
	id = addrToId(Addr);
	if (id < 0)
	{
		setPnioRwError(pPnioState, 0xb2); /* access: invalid slot/subslot */
		return;
	}
	pIm = &im_work[id];
	if (!IM_IndexSupported(RecordIndex, im_work[id].im0.IM_Supported))
	{
		setPnioRwError(pPnioState, 0xa9); /* application: feature not supported */
		return;
	}
	switch (RecordIndex)
	{
	case IM0:
		setPnioRwError(pPnioState, 0xb6); /* access: access denied */
		return;
	case IM1:
		{
			PNIO_IM1_TYPE im;
			if (pBuf == 0)
			{
				im = im_default.im1;
			}
			else
			{
				IM_CopySwapIm1FromBuf(&im, pBuf);
			}
			imCmp = IM_CmpIM1(&im, &pIm->im1);
			if ((imCmp == IM_READ_DIFFER) || (imCmp == IM_READ_WRITE_DIFFER))
			{
				setPnioRwError(pPnioState, 0xb4); /* Access: invalid area */
			}
			else
				if (imCmp == IM_WRITE_DIFFER)
				{
					pIm->im1 = im;
				}
		}
		break;
	case IM2:
		{
			PNIO_IM2_TYPE im;
			if (pBuf == 0)
			{
				im = im_default.im2;
			}
			else
			{
				IM_CopySwapIm2FromBuf(&im, pBuf);
			}
			imCmp = IM_CmpIM2(&im, &pIm->im2);
			if ((imCmp == IM_READ_DIFFER) || (imCmp == IM_READ_WRITE_DIFFER))
			{
				setPnioRwError(pPnioState, 0xb4); /* Access: invalid area */
			}
			else
				if (imCmp == IM_WRITE_DIFFER)
				{
					pIm->im2 = im;
				}
		}
		break;
	case IM3:
		{
			PNIO_IM3_TYPE im;
			if (pBuf == 0)
			{
				im = im_default.im3;
			}
			else
			{
				IM_CopySwapIm3FromBuf(&im, pBuf);
			}
			imCmp = IM_CmpIM3(&im, &pIm->im3);
			if ((imCmp == IM_READ_DIFFER) || (imCmp == IM_READ_WRITE_DIFFER))
			{
				setPnioRwError(pPnioState, 0xb4); /* Access: invalid area */
			}
			else
				if (imCmp == IM_WRITE_DIFFER)
				{
					pIm->im3 = im;
				}
		}
		break;
	case IM4:
		setPnioRwError(pPnioState, 0xb6); /* access: access denied */
		break;
	default:
		break;
	}

	*pBufLen = IM_SizeofImDatarec(RecordIndex);

	/* store data in any case (e.g. switched off) */
	if( imCmp == IM_WRITE_DIFFER)
	{
        /* do not increment. always 0 */
        /* IM_incRevCounter(&pIm->im0.IM_Revision_Counter); */
	    IM_DataToStore("im.conf");
	}

	return;
}

/*  Write I&M data into pBuf */
void IM_RecRead(PNIO_UINT32 RecordIndex, PNIO_DEV_ADDR Addr, PNIO_UINT32* pBufLen, PNIO_UINT8* pBuf, PNIO_ERR_STAT* pPnioState)
{
	int id = addrToId(Addr);
	if (id < 0)
	{
		setPnioRwError(pPnioState, 0xb2); /* access: invalid slot/subslot */
		return;
	}

	if (!IM_IndexSupported(RecordIndex, im_work[id].im0.IM_Supported))
	{
		setPnioRwError(pPnioState, 0xa9); /* application: feature not supported */
		return;
	}
	*pBufLen = IM_SizeofImDatarec(RecordIndex);
	IM_CopySwapBufFromIm(pBuf, &im_work[id], (IM0_idx_e)RecordIndex);
	return;
}

int copy_string(char* dstBuf, const char* srcBuf, size_t len)
{
	int n;
	for (n=0; n<len; n++)
	{
		if ((dstBuf[n] < 0x20) || (srcBuf[n] > 0x7E))
		{
			/* Control character */
			dstBuf[n] = ' ';
		}
		else
		{
			/* Visible character */
			dstBuf[n] = srcBuf[n];
		}
	}

    return 1;
}

void set_serial_string( char* serial_str)
{    
	int n = 0;
    size_t serial_length = 0;

    if( !g_annot_serial)
    {
        g_annot_serial = (PNIO_UINT8*)malloc(MAX_SERIAL_LENGTH+1);
    }

    memset( g_annot_serial, ' ' , MAX_SERIAL_LENGTH+1);
    g_annot_serial[MAX_SERIAL_LENGTH+1] = 0;

    serial_length = strlen(serial_str);
    if( serial_length > MAX_SERIAL_LENGTH-1) serial_length = MAX_SERIAL_LENGTH-1;
    copy_string((char*)g_annot_serial,serial_str,serial_length);
}

void IM_initImRecords(void)
{
	int i;
	SERV_CP_FW_INFO_TYPE servInfo;
    SERV_CP_ANNOTATION_TYPE annoInfo;
    
	/* get actual serial number only on first call to this function, since the serial number can't change during runtime */
	if (g_annot_serial == 0)
    { 
        g_annot_serial = (PNIO_UINT8*)malloc(sizeof(annoInfo.serial_number));
        memset(g_annot_serial,0,sizeof(annoInfo.serial_number));
        if( SERV_CP_get_annotation_data(1, &annoInfo) == PNIO_OK &&
            SERV_CP_get_fw_info(1,&servInfo) == PNIO_OK)
	    {
            set_serial_string( annoInfo.mode == PNIO_CP_ANNO_COMPLETE ?
                                    (char*)&annoInfo.serial_number[0] :
                                    (char*)&servInfo.cp_ser_nr[0]);
#if 0
            size_t length = 0;
        
            if( annoInfo.mode != PNIO_CP_ANNO_NOT_USED)
            {
                length = strlen(annoInfo.serial_number);
                if( length > sizeof(servInfo.cp_ser_nr)) /* maximum of 16 */
                {
                        length = sizeof(servInfo.cp_ser_nr); 
                }

                memcpy(&g_annot_serial[0], &annoInfo.serial_number[0], length);
            }
            else if ((SERV_CP_get_fw_info(1, &servInfo) == PNIO_OK))
            {
                length = strlen(servInfo.cp_ser_nr);
                if( length > sizeof(servInfo.cp_ser_nr)) /* maximum of 16 */
                {
                    length = sizeof(servInfo.cp_ser_nr);
                }
            
                memcpy(&g_annot_serial[0], &servInfo.cp_ser_nr[0], length);
		    }
            else
            {
                /* failed */
            }
#endif
	    }
        else
        {
            /* failed to get fw info */
        }
    }

	free(im_work);
	im_work = (IM_DATA*)malloc(sizeof(IM_DATA) * g_device_data.nrOfSubmodules);
	for (i = 0; i < g_device_data.nrOfSubmodules; i++)
	{
		im_work[i] = im_default;
		
		im_work[i].im0.VendorIDHigh = (g_device_data.VendorId >> 8) & 0xff;
		im_work[i].im0.VendorIDLow = g_device_data.VendorId & 0xff;
		memcpy(&im_work[i].im0.OrderID, g_annot_selected.OrderId, MAX_ORDER_ID_LENGTH);
		im_work[i].im0.IM_Hardware_Revision = g_annot_selected.HwRevision;
		im_work[i].im0.IM_Software_Revision[0] = (PNIO_UINT8)g_annot_selected.SwRevisionPrefix;
		im_work[i].im0.IM_Software_Revision[1] = (PNIO_UINT8)g_annot_selected.SwRevision1;
		im_work[i].im0.IM_Software_Revision[2] = (PNIO_UINT8)g_annot_selected.SwRevision2;
		im_work[i].im0.IM_Software_Revision[3] = (PNIO_UINT8)g_annot_selected.SwRevision3;
		memcpy(&im_work[i].im0.IM_Serial_Number, g_annot_serial, sizeof(im_work[i].im0.IM_Serial_Number));

		if ((g_device_data.pDeviceData[i].Addr.u.Geo.Slot == 1) &&
			((g_device_data.pDeviceData[i].Addr.u.Geo.Subslot == 0x8000)
			|| (g_device_data.pDeviceData[i].Addr.u.Geo.Subslot == 0x8001)
			|| (g_device_data.pDeviceData[i].Addr.u.Geo.Subslot == 0x8002)
			|| (g_device_data.pDeviceData[i].Addr.u.Geo.Subslot == 0x8003)
			|| (g_device_data.pDeviceData[i].Addr.u.Geo.Subslot == 0x8004)))
		{
			im_work[i].im0.IM_Profile_Specific_Type = 0x04; /* communication module -> www.profibus.com/IM/Device_ID_Table_6102.xml */
		}
	}
}

void IM_DataFromStore(const char* pName)
{
	FILE* pFile;
	PNIO_UINT8 line[LINE_LENGTH_MAX];
	IM_PERSISTENT_DATA_e idx;
	int sModIdx;
	pFile = fopen(pName, "r");
	if (pFile == NULL)
	{
		return;
	}

	for (sModIdx = 0; sModIdx < g_device_data.nrOfSubmodules; sModIdx++)
	{
		IM_DATA* pIm = &im_work[sModIdx];
		/* override default IM values by stored values */
		while (fgets((char*)line, LINE_LENGTH_MAX, pFile) != NULL)
		{
			char* pPos = NULL;
			int sectionIdx;
			/* ignore comments -> read next line */
			if ((line[0] == ';') || (line[0] == '#'))
			{
				continue;
			}
			/* continue until submodule section is found */
			sscanf((char*)line, "[SUBMOD%i]", &sectionIdx);
			if (sectionIdx != sModIdx)
			{
				continue;
			}
			/* find IM item in current line */
			for (idx = (IM_PERSISTENT_DATA_e)0; idx < NUMOF_PERSISTENT_IM_ITEMS; idx = (IM_PERSISTENT_DATA_e)((int)idx + 1))
			{
				size_t size = strlen(name_IM_PERSISTENT_DATA[idx]);
				if (STRNCMP_EQUAL ==  strncmp((char*)line, name_IM_PERSISTENT_DATA[idx], size))
				{
					pPos = (char*)line + size;
					/* ignore blanks etc */
					while ((*pPos == ' ') || (*pPos == '='))
					{
						pPos++;
					}
					break;
				}
			}
			if (pPos == NULL)     /* no IM item found */
			{
				continue;
			}
			/* read IM value */
			switch (idx)
			{
			case IM_Revision_Counter:
				{
					int val;
					sscanf(pPos, "%i", &val);
					if ((val > 0) && (val <= 0xffff))
					{
						pIm->im0.IM_Revision_Counter = (PNIO_UINT16) val;
					}
					break;
				}
			case IM_Tag_Function:
				IM_CopyQuotedString(pIm->im1.IM_Tag_Function, line, sizeof(pIm->im1.IM_Tag_Function));
				break;
			case IM_Tag_Location:
				IM_CopyQuotedString(pIm->im1.IM_Tag_Location, line, sizeof(pIm->im1.IM_Tag_Location));
				break;
			case IM_Date:
				IM_CopyQuotedString(pIm->im2.IM_Date, line, sizeof(pIm->im2.IM_Date));
				break;
			case IM_Descriptor:
				IM_CopyQuotedString(pIm->im3.IM_Descriptor, line, sizeof(pIm->im3.IM_Descriptor));
				break;
			case IM_Signature:
				IM_CopyQuotedString(pIm->im4.IM_Signature, line, sizeof(pIm->im4.IM_Signature));
				break;
			}
		}
	}
	fclose(pFile);
}

void IM_DataToStore(const char* pName)
{
	FILE* pFile;
	int sModIdx;
	pFile = fopen(pName, "w");
	if (pFile == NULL)
    {
		return;
	}


	for (sModIdx = 0; sModIdx < g_device_data.nrOfSubmodules; sModIdx++)
	{
		const IM_DATA* pIm = &im_work[sModIdx];
		if (sModIdx)
		{
			fprintf(pFile, "\n");
		}

		fprintf(pFile, "[SUBMOD%i]\n", sModIdx);
		fprintf(pFile, "%s = 0x%04x\n", name_IM_PERSISTENT_DATA[IM_Revision_Counter],
			pIm->im0.IM_Revision_Counter);
		if (pIm->im0.IM_Supported & 0x02)
		{
			fprintf(pFile, "%s = \"%s\"\n", name_IM_PERSISTENT_DATA[IM_Tag_Function],
				IM_RemTrailBlanks(pIm->im1.IM_Tag_Function, sizeof(pIm->im1.IM_Tag_Function)));
			fprintf(pFile, "%s = \"%s\"\n", name_IM_PERSISTENT_DATA[IM_Tag_Location],
				IM_RemTrailBlanks(pIm->im1.IM_Tag_Location, sizeof(pIm->im1.IM_Tag_Location)));
		}
		if (pIm->im0.IM_Supported & 0x04)
		{
			fprintf(pFile, "%s = \"%s\"\n", name_IM_PERSISTENT_DATA[IM_Date],
				IM_RemTrailBlanks(pIm->im2.IM_Date, sizeof(pIm->im2.IM_Date)));
		}
		if (pIm->im0.IM_Supported & 0x08)
		{
			fprintf(pFile, "%s = \"%s\"\n", name_IM_PERSISTENT_DATA[IM_Descriptor],
				IM_RemTrailBlanks(pIm->im3.IM_Descriptor, sizeof(pIm->im3.IM_Descriptor)));
		}
		if (pIm->im0.IM_Supported & 0x10)
		{
			fprintf(pFile, "%s = \"%s\"\n", name_IM_PERSISTENT_DATA[IM_Signature],
				IM_RemTrailBlanks(pIm->im4.IM_Signature, sizeof(pIm->im4.IM_Signature)));
		}
	}
	fclose(pFile);
}
