package ibmmq

/*
  Copyright (c) IBM Corporation 2016

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific

   Contributors:
     Mark Taylor - Initial Contribution
*/

/*
#include <stdlib.h>
#include <cmqc.h>
#include <cmqcfc.h>
*/
import "C"

import (
	"bytes"
	"encoding/binary"
	"fmt"
)

/*
MQCFH is a structure containing the MQ PCF Header fields
*/
type MQCFH struct {
	Type           int32
	StrucLength    int32
	Version        int32
	Command        int32
	MsgSeqNumber   int32
	Control        int32
	CompCode       int32
	Reason         int32
	ParameterCount int32
}

var endian binary.ByteOrder

/*
PCFParameter is a structure containing the data associated with
various types of PCF element. Use the Type field to decide which
of the data fields is relevant.
*/
type PCFParameter struct {
	Type           int32
	Parameter      int32
	Int64Value     []int64 // Always store as 64; cast to 32 when needed
	String         []string
	CodedCharSetId int32
	ParameterCount int32
	GroupList      []*PCFParameter
	strucLength    int32 // Do not need to expose these
	stringLength   int32 // lengths
}

/*
NewMQCFH returns a PCF Command Header structure with correct initialisation
*/
func NewMQCFH() *MQCFH {
	cfh := new(MQCFH)
	cfh.Type = C.MQCFT_COMMAND
	cfh.StrucLength = C.MQCFH_STRUC_LENGTH
	cfh.Version = C.MQCFH_VERSION_1
	cfh.Command = C.MQCMD_NONE
	cfh.MsgSeqNumber = 1
	cfh.Control = C.MQCFC_LAST
	cfh.CompCode = C.MQCC_OK
	cfh.Reason = C.MQRC_NONE
	cfh.ParameterCount = 0

	if (C.MQENC_NATIVE % 2) == 0 {
		endian = binary.LittleEndian
	} else {
		endian = binary.BigEndian
	}

	return cfh
}

/*
Bytes serialises an MQCFH structure as if it were the corresponding C structure
*/
func (cfh *MQCFH) Bytes() []byte {

	buf := make([]byte, cfh.StrucLength)
	offset := 0

	endian.PutUint32(buf[offset:], uint32(cfh.Type))
	offset += 4
	endian.PutUint32(buf[offset:], uint32(cfh.StrucLength))
	offset += 4
	endian.PutUint32(buf[offset:], uint32(cfh.Version))
	offset += 4
	endian.PutUint32(buf[offset:], uint32(cfh.Command))
	offset += 4
	endian.PutUint32(buf[offset:], uint32(cfh.MsgSeqNumber))
	offset += 4
	endian.PutUint32(buf[offset:], uint32(cfh.Control))
	offset += 4
	endian.PutUint32(buf[offset:], uint32(cfh.CompCode))
	offset += 4
	endian.PutUint32(buf[offset:], uint32(cfh.Reason))
	offset += 4
	endian.PutUint32(buf[offset:], uint32(cfh.ParameterCount))
	offset += 4

	return buf
}

/*
Bytes serialises a PCFParameter into the C structure
corresponding to its type
*/
func (p *PCFParameter) Bytes() []byte {
	var buf []byte

	switch p.Type {
	case C.MQCFT_INTEGER:
		buf = make([]byte, C.MQCFIN_STRUC_LENGTH)
		offset := 0

		endian.PutUint32(buf[offset:], uint32(p.Type))
		offset += 4
		endian.PutUint32(buf[offset:], uint32(len(buf)))
		offset += 4
		endian.PutUint32(buf[offset:], uint32(p.Parameter))
		offset += 4
		endian.PutUint32(buf[offset:], uint32(p.Int64Value[0]))
		offset += 4

	case C.MQCFT_STRING:
		buf = make([]byte, C.MQCFST_STRUC_LENGTH_FIXED+roundTo4(int32(len(p.String[0]))))
		offset := 0
		endian.PutUint32(buf[offset:], uint32(p.Type))
		offset += 4
		endian.PutUint32(buf[offset:], uint32(len(buf)))
		offset += 4
		endian.PutUint32(buf[offset:], uint32(p.Parameter))
		offset += 4
		endian.PutUint32(buf[offset:], uint32(C.MQCCSI_DEFAULT))
		offset += 4
		endian.PutUint32(buf[offset:], uint32(len(p.String[0])))
		offset += 4
		copy(buf[offset:], []byte(p.String[0]))
	}
	return buf
}

/*
ReadPCFHeader extracts the MQCFH from an MQ message
*/
func ReadPCFHeader(buf []byte) (*MQCFH, int) {
	cfh := new(MQCFH)
	fullLen := len(buf)
	p := bytes.NewBuffer(buf)

	binary.Read(p, endian, &cfh.Type)
	binary.Read(p, endian, &cfh.StrucLength)
	binary.Read(p, endian, &cfh.Version)
	binary.Read(p, endian, &cfh.Command)
	binary.Read(p, endian, &cfh.MsgSeqNumber)
	binary.Read(p, endian, &cfh.Control)
	binary.Read(p, endian, &cfh.CompCode)
	binary.Read(p, endian, &cfh.Reason)
	binary.Read(p, endian, &cfh.ParameterCount)

	bytesRead := fullLen - p.Len()
	return cfh, bytesRead
}

/*
ReadPCFParameter extracts the next PCF parameter element from an
MQ message.
*/
func ReadPCFParameter(buf []byte) (*PCFParameter, int) {
	var i32 int32
	var i64 int64
	var mqlong int32
	var count int32

	pcfParm := new(PCFParameter)
	fullLen := len(buf)
	p := bytes.NewBuffer(buf)

	binary.Read(p, endian, &pcfParm.Type)
	binary.Read(p, endian, &pcfParm.strucLength)

	switch pcfParm.Type {
	// There are more PCF element types but the samples only
	// needed a subset
	case C.MQCFT_INTEGER:
		binary.Read(p, endian, &pcfParm.Parameter)
		binary.Read(p, endian, &i32)
		pcfParm.Int64Value = append(pcfParm.Int64Value, int64(i32))

	case C.MQCFT_INTEGER_LIST:
		binary.Read(p, endian, &pcfParm.Parameter)
		binary.Read(p, endian, &count)
		for i := 0; i < int(count); i++ {
			binary.Read(p, endian, &i32)
			pcfParm.Int64Value = append(pcfParm.Int64Value, int64(i32))
		}

	case C.MQCFT_INTEGER64:
		binary.Read(p, endian, &pcfParm.Parameter)
		binary.Read(p, endian, &mqlong) // Used for alignment
		binary.Read(p, endian, &i64)
		pcfParm.Int64Value = append(pcfParm.Int64Value, i64)

	case C.MQCFT_INTEGER64_LIST:
		binary.Read(p, endian, &pcfParm.Parameter)
		binary.Read(p, endian, &count)
		for i := 0; i < int(count); i++ {
			binary.Read(p, endian, &i64)
			pcfParm.Int64Value = append(pcfParm.Int64Value, i64)
		}

	case C.MQCFT_STRING:
		offset := int32(C.MQCFST_STRUC_LENGTH_FIXED)
		binary.Read(p, endian, &pcfParm.Parameter)
		binary.Read(p, endian, &pcfParm.CodedCharSetId)
		binary.Read(p, endian, &pcfParm.stringLength)
		s := string(buf[offset : pcfParm.stringLength+offset])
		pcfParm.String = append(pcfParm.String, s)
		p.Next(int(pcfParm.strucLength - offset))

	case C.MQCFT_STRING_LIST:
		binary.Read(p, endian, &pcfParm.Parameter)
		binary.Read(p, endian, &pcfParm.CodedCharSetId)
		binary.Read(p, endian, &count)
		binary.Read(p, endian, &pcfParm.stringLength)
		for i := 0; i < int(count); i++ {
			offset := C.MQCFSL_STRUC_LENGTH_FIXED + i*int(pcfParm.stringLength)
			s := string(buf[offset : int(pcfParm.stringLength)+offset])
			pcfParm.String = append(pcfParm.String, s)
		}
		p.Next(int(pcfParm.strucLength - C.MQCFSL_STRUC_LENGTH_FIXED))

	case C.MQCFT_GROUP:
		binary.Read(p, endian, &pcfParm.Parameter)
		binary.Read(p, endian, &pcfParm.ParameterCount)
	default:
		fmt.Println("mqiPCF.go: Unknown PCF type ", pcfParm.Type)
		// Skip the remains of this structure, assuming it really is
		// PCF and we just don't know how to process the element type
		p.Next(int(pcfParm.strucLength - 8))
	}

	bytesRead := fullLen - p.Len()
	return pcfParm, bytesRead
}

func roundTo4(u int32) int32 {
	return ((u) + ((4 - ((u) % 4)) % 4))
}
