/***************************************************************************
 *   Copyright (C) 2006 by Brian Lauber   *
 *   bml8@case.edu   *
 *                                                                         *
 *   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 program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#include <stdio.h>
#include <stdlib.h>

#include "blError.h"

// I'll define an enumerated type for the various types of messages
// that I might want to send.  EXIT will inform the parent to exit
enum MSG_TYPES {INT = 1, DOUBLE, STRING, EXIT};

// Since gentoo won't let me access struct msgbuf, I'm making my own generic message type
struct msg_none
{
  long    mtype;
  char    value[1];
};

struct msg_int // An integer message
{
  long   mtype;
  int    value;
};

struct msg_double // A double message
{
  long    mtype;
  double  value;
};

struct msg_string // A string message, maximum 64 bytes
{
  long    mtype;
  char    value[64];
};

struct msg_exit   // A message to exit the program
{
  long   mtype;
};

/***********************************************************
 These next four functions are basically constructors for
 the various types of messages
***********************************************************/

void CreateInt(struct msg_none **message, size_t *len)
{
  *len = sizeof(struct msg_int);
  struct msg_int *temp = malloc(*len); // Create a new integer message;
  temp->mtype = INT;
  printf("Type an integer: ");
  scanf("%i", &(temp->value)); // Grab keyboard input, send it to the address of message->value
  *message = (struct msg_none *) temp; // Recast the pointer to a msg_none
}

void CreateDouble(struct msg_none **message, size_t *len)
{
  *len = sizeof(struct msg_double);
  struct msg_double *temp = malloc(*len); // Create a new integer message;
  temp->mtype = DOUBLE;
  printf("Type a double: ");
  scanf("%lf", &(temp->value));
  *message = (struct msg_none *) temp; // Recast the pointer to a msg_none
}

void CreateString(struct msg_none **message, size_t *len)
{
  *len = sizeof(struct msg_string);
  struct msg_string *temp = malloc(*len); // Create a new integer message;
  temp->mtype = STRING;
  printf("Type a string: ");
  fgets(temp->value, 64, stdin);       // The first time you do this, it immediately reads newline and stops
  fgets(temp->value, 64, stdin);       // Now, read the next line of input
  *message = (struct msg_none *) temp; // Recast the pointer to a msg_none
}

void CreateExit(struct msg_none **message, size_t *len)
{
  *len = sizeof(struct msg_exit);
  *message = malloc(*len); // Create a new integer message;
  (*message)->mtype = EXIT;
}




/***********************************************************
 These recast the message so that it is printed properly
***********************************************************/

void PrintInt(struct msg_none *message)
{
  struct msg_int *temp = (struct msg_int *) message;
  printf("int: %li\n", temp->value);
}

void PrintDouble(struct msg_none *message)
{
  struct msg_double *temp = (struct msg_double *) message;
  printf("double: %lf\n", temp->value);
}

void PrintString(struct msg_none *message)
{
  struct msg_string *temp = (struct msg_string *) message;
  printf("string: %s\n", temp->value);
}


// Ask the user for the type he / she wants to create.
long GetType(void)
{
  long retval;
  do
  {
    printf("\n\nSelect the message type to send:\n");
    printf("  %i: int\n", INT);
    printf("  %i: double\n", DOUBLE);
    printf("  %i: string\n", STRING);
    printf("  %i: exit\n", EXIT);
    printf(": ");
    scanf("%li", &retval);
  }
  while( (retval < INT) || (retval > EXIT) );
  return retval;
}

int main(int argc, char *argv[])
{
  // Of course, variables always go up top
  key_t            qid;              // The id of the message queue
  long             type;             // The type of message that will be sent
  struct msg_none *message = NULL;   // The message that will be sent
  size_t           len;              // The length of the message

  // We want a unique message queue (IPC_PRIVATE).  Since this is a new queue,
  // it will need to be created (IPC_CREAT).  User and group should have read-write
  // permissions, while all others cannot access the queue (0660).  IPC_EXCL is just
  // an extra precaution (ie, if the function cannot create a new message queue, it
  // will return an error).
  qid = Error(msgget(IPC_PRIVATE, IPC_CREAT | IPC_EXCL | 0660));

  /*
  // Create a bash prompt so you can play with the ipcs commands
  if(Error(fork()) == 0)
    execlp("bash", "bash", NULL);
  else
    Error(wait(NULL)); // The parent will wait until the child exits
  */

  // Now I'll fork 2 processes.  The child will send messages, the parent will listen and print them

  if( Error(fork()) == 0 ) // Child process
  {

    while(1)
    {
      type = GetType();
      switch(type)
      {
        case INT:
          CreateInt(&message, &len);
        break;
        case DOUBLE:
          CreateDouble(&message, &len);
        break;
        case STRING:
          CreateString(&message, &len);
        break;
        case EXIT:
          CreateExit(&message, &len);
        break;
      }

      Error(msgsnd(qid, message, len, 0));
      printf("Message sent\n");
      sleep(1); // I'll sleep for 1 sec just to make messages print more clearly

        // If the EXIT message was sent, exit this while loop and clean up the message queue
      if(message->mtype == EXIT)
        break;

      free(message); // Don't forget to delete the message when I'm done with it!
    }

  }
  else
  {
    // The parent process will pre-allocate enough memory for the largest expected
    // message (which is a string message)
    message = malloc(sizeof(struct msg_string));

    while(1)
    {
      // Tell the OS you want to listen for any message type.

      len = Error(msgrcv(qid, message, sizeof(struct msg_string), 0, 0));
      printf("Received message of length %i\n", len);
      switch(message->mtype) // Change behavior based of type of message received
      {
        case INT:
          PrintInt(message);
        break;
        case DOUBLE:
          PrintDouble(message);
        break;
        case STRING:
          PrintString(message);
        break;
        case EXIT:
          printf("Parent process is exitting\n");
          return EXIT_SUCCESS;
        break;
      }
    }
  }


  printf("Child process is removing message queue\n");
  // Tell the OS that we want to REMOVE our message queue
  // (otherwise, the queue will stick around after the program
  //  exits, feeding on the soul of the OS)
  msgctl(qid, IPC_RMID, NULL);
  return EXIT_SUCCESS;
}
