#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 <clib/locale_protos.h>
#include <devices/timer.h>
#include <clib/timer_protos.h>
#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/locale_protos.h>
#include <clib/macros.h>

#define NTP_PORT 123

UBYTE *version = "$VER: NTPSync 2.1 (20.1.98)";

struct Timestamp {
  unsigned long seconds;
  unsigned 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;

/* Calculates dst = s1+s2 */
void TSAdd(struct Timestamp *s1,struct Timestamp *s2, struct Timestamp *dst)
{
  dst->fractions = s1->fractions+s2->fractions;

  if(dst->fractions < MAX(s1->fractions,s2->fractions)) {
    dst->seconds = s1->seconds + s2->seconds + 1;
  } else 
    dst->seconds = s1->seconds + s2->seconds;
}
/* Calculates dst = s1-s2 */
void TSSub(struct Timestamp *s1,struct Timestamp *s2, struct Timestamp *dst)
{
  if(s1->fractions<s2->fractions) {
    dst->seconds = s1->seconds - s2->seconds - 1;
  } else {
    dst->seconds = s1->seconds - s2->seconds;
  }
  dst->fractions = s1->fractions - s2->fractions;
}

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

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

struct Library *LocaleBase;

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;
  struct Timestamp roundtrip;

  if(argc!=3 && argc!=2) {
    printf("NTPSync <NTP server>[:port] [diff from UTC in minutes]\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);

  if(argc==3) {
    UTCdiff = atoi(argv[2])*60;
  } else {
    struct Locale *locale;

    if(LocaleBase = OpenLibrary("locale.library",0)) {
      if(locale = OpenLocale(NULL)) {
        UTCdiff = -locale->loc_GMTOffset*60;
        printf("UTC difference not given, locale.library told %d mins...\n",-locale->loc_GMTOffset);
        CloseLocale(locale);
      } else {
        retval=11;
        printf("Can't open locale!\n");
      }
      CloseLibrary(LocaleBase);
    } else {
      retval=12;
      printf("Can't open locale.library!\n");
    }
    if(retval) return(retval);
  }
  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 ) {
      cli_addr.sin_port        = htons(0);
      if(bind(sockfd, (struct sockaddr *) &cli_addr,sizeof(cli_addr)) < 0 ) {
        printf("Can't bind()!\n");
        retval=9;
      }
    }
    if(!retval) {
      if(TimerMP = CreateMsgPort()) {
        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);

            printf("Sending request...\n");
            sendto(sockfd,(char *)&msg,sizeof(msg),0,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
            TimerIO->tr_node.io_Command = TR_GETSYSTIME;
            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) {
                DoIO((struct IORequest *) TimerIO);

                Ami2NTP(&TimerIO->tr_time,&roundtrip);

                TSSub(&roundtrip,&msg.OriginateTimestamp,&roundtrip);
                TSSub(&msg.TransmitTimestamp,&msg.ReceiveTimestamp,&msg.ReferenceTimestamp);
                TSSub(&roundtrip,&msg.ReferenceTimestamp,&roundtrip);
                roundtrip.seconds = roundtrip.seconds / 2;
                roundtrip.fractions = roundtrip.fractions / 2;
                TSAdd(&roundtrip,&msg.TransmitTimestamp,&msg.TransmitTimestamp);

                NTP2Ami(&TimerIO->tr_time,&msg.TransmitTimestamp);
                TimerIO->tr_node.io_Command = TR_SETSYSTIME;
                DoIO((struct IORequest *) TimerIO);

                TimerIO->tr_time.tv_secs = roundtrip.seconds;
                TimerIO->tr_time.tv_micro = roundtrip.fractions/4295;

                printf("Time set according to %s (Stratum %d, roundtrip delay %d µs)!\n",argv[1],msg.Stratum,TimerIO->tr_time.tv_secs*100000+TimerIO->tr_time.tv_micro);
              }
              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;
        }
        DeleteMsgPort(TimerMP);
      } else {
        printf("Error in allocating resources!\n");
        retval=8;
      }
    }
    CloseSocket(sockfd);
  }
  return(retval);
}
