#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <devices/timer.h>
#include <clib/timer_protos.h>
#include <clib/exec_protos.h>
#include <clib/alib_protos.h>

#define NTP_PORT 123

UBYTE *version = "$VER: NTPSync 1.1 (5.1.98)";

struct Timestamp {
  long seconds;
  long fractions;
};

struct ntp_message {
  int LI       : 2;
  int VN       : 3;
  int Mode     : 3;
  unsigned char Stratum;
  unsigned char Poll;
  unsigned char Precision;
  long RootDelay;
  long RootDispesion;
  long ReferenceIdentifier;
  struct Timestamp ReferenceTimestamp;
  struct Timestamp OriginateTimestamp;
  struct Timestamp ReceiveTimestamp;
  struct Timestamp TransmitTimestamp;
};

int UTCdiff;

void Ami2NTP(struct timeval *tv,struct Timestamp *ts)
{
  ts->seconds = tv->tv_secs+2461536000-UTCdiff;
  ts->fractions = (tv->tv_micro*4295)|1;
}
void NTP2Ami(struct timeval *tv,struct Timestamp *ts)
{
  tv->tv_secs = ts->seconds-2461536000+UTCdiff;
  tv->tv_micro = ts->fractions/4295;
}

struct timerequest *TimerIO;
struct MsgPort *TimerMP;
struct Message *TimerMSG;

int main(int argc,char *argv[])
{
  int sockfd;
  struct sockaddr_in serv_addr,cli_addr;
  struct ntp_message msg;
  struct hostent *hent;
  int fromlen;
  unsigned int ntpport;
  char servername[80];
  int retval=0;
  int i;

  if(argc!=3) {
    printf("NTPSync <NTP server>:[port] <diff from UTC in hours>\n");
    exit(20);
  }

  for(i=0;argv[1][i] && argv[1][i]!=':';i++) {
    servername[i]=argv[1][i];
  }
  servername[i]=0;
  if(argv[1][i]==':') {
    ntpport=atoi(&argv[1][i+1]);
  } else ntpport=NTP_PORT;

  if(!(hent=gethostbyname(servername))) {
    printf("Can't resolve address %s!\n",argv[1]);
    exit(15);
  }

  bzero((char *)&serv_addr,sizeof(serv_addr));
  serv_addr.sin_family      = AF_INET;
  serv_addr.sin_addr        = *((struct in_addr *)hent->h_addr);
  serv_addr.sin_port        = htons(ntpport);

  UTCdiff = atoi(argv[2])*60*60;

  if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0) {
    printf("Can't create socket!\n");
    retval=10;
  } else {
    bzero((char *)&cli_addr,sizeof(cli_addr));
    cli_addr.sin_family      = AF_INET;
    cli_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    cli_addr.sin_port        = htons(NTP_PORT);

    if(bind(sockfd, (struct sockaddr *) &cli_addr,sizeof(cli_addr)) < 0 ) {
      printf("Can't bind()!\n");
      retval=9;
    } else  {

      if(TimerMP = CreatePort(0,0)) {
        if(TimerIO = (struct timerequest *) CreateExtIO(TimerMP,sizeof(struct timerequest))) {
          if(!(OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)TimerIO,0L))) {
            bzero((char *)&msg,sizeof(struct ntp_message));
            msg.LI = 0;
            msg.Mode = 3;
            msg.VN = 2;

            TimerIO->tr_node.io_Command = TR_GETSYSTIME;
            DoIO((struct IORequest *) TimerIO);

            Ami2NTP(&TimerIO->tr_time,&msg.TransmitTimestamp);

            fromlen = sizeof(serv_addr);

            sendto(sockfd,(char *)&msg,sizeof(msg),0,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
            printf("Sending request...\n");
            TimerIO->tr_node.io_Command = TR_SETSYSTIME;
            retval = recvfrom(sockfd,(char *)&msg,sizeof(msg),0,(struct sockaddr *)&serv_addr,(LONG *)&fromlen);

            if(retval>=sizeof(struct ntp_message) && msg.Mode==4 && msg.VN<5) {
              if(msg.Mode==4 && msg.VN<5) {
                NTP2Ami(&TimerIO->tr_time,&msg.TransmitTimestamp);
                DoIO((struct IORequest *) TimerIO);

                printf("Time set according to %s (Stratum %d)!\n",argv[1],msg.Stratum);
              }
              retval=0;
            } else {
              printf("Invalid reply got from server (or error while receiving), time not set!\n");
              retval=5;
            }
            CloseDevice((struct IORequest *)TimerIO);
          } else {
            printf("Error in allocating resources!\n");
            retval=6;
          }
          DeleteExtIO((struct IORequest *)TimerIO);
        } else {
          printf("Error in allocating resources!\n");
          retval=7;
        }
        DeletePort(TimerMP);
      } else {
        printf("Error in allocating resources!\n");
        retval=8;
      }
    }
    CloseSocket(sockfd);
  }
  return(retval);
}
