/*
 *  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.
 *
*/
#include <termios.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/timex.h>
#include <unistd.h>

#define BAUDRATE B19200
#define SERIALDEVICE "/dev/ttyS0"
#define _POSIX_SOURCE 1 /* POSIX compliant source */

#define CONFIGFILE "/usr/local/etc/dcf.conf"
#define LOGFILE    "/var/log/dcf.log"

#define YES 1
#define NO  2

extern char *tzname[2];

char MsgError[256];

char SerialDevice[50];
int Exclusive=NO;
int HardwareSet=NO;
int SystemSet=NO;
int LogConsole=YES;
int PrintTime=NO;
int PrintVersion=NO;
long start_sec,stop_sec,start_usec,stop_usec;

/* Read config file */
int ReadConfig(void);

/* Read parameters */
int ReadParameters(int argc_d, char **argv_d);

/* Write message to log file */
void WriteLog();

/* Get curent date from system clock */
char *GetSystemDate(long *sec,long *usec);

/* Get time from DCF clock */
int GetDCFTime(time_t *tt);

/* Test Checksum in receive data */
int CheckSum(char *buf);

/* Compare system time to DCF clock time */
int ComSysToDCF(time_t dcf_time);

/* Set System Clock */
int SetSystemClock(time_t *tt);

/***************************************************************************/
/*     Main Function                                                       */
/***************************************************************************/

int main(int argc, char **argv)
{
  time_t dcf_tt;
  struct tm dcf_tm;
  struct tm *dcf_tmp=&dcf_tm;
  char TimeToSet[256];

  sprintf(SerialDevice,"%s",SERIALDEVICE);

  /* Read config file */
  if (!ReadConfig()){
    WriteLog();
    exit(-1);
  }

  /* Read parameters */
  if (!ReadParameters(argc,argv)){
    WriteLog();
    exit(-1);
  }

  if (PrintVersion==YES){
    printf("Copyright (C) 2000 ELPROMA Elektronika\nhttp://www.zegar.net.pl\n");
    printf("This is free software; see the source for copying conditions.  There is NO\n");
    printf("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
    return 0;
  }

 
  /* Get date and time from DCF clock */
  if (!GetDCFTime(&dcf_tt)){
    WriteLog();
    exit(-1);
  }

  /* Print DCF date */
  if ( PrintTime==YES ){
    printf("%s",ctime(&dcf_tt));
    return 0;
  }
  
  /* set system clock */
  if (SystemSet==YES){
    if ((ComSysToDCF(dcf_tt))>0){
      if (Exclusive==NO){
	sprintf(MsgError,"INFO: Can't back system clock - use Exclusive mode.");
	WriteLog();
	exit(-1);
      }
      if (!SetSystemClock(&dcf_tt)){
	WriteLog();
	exit(-1);
      }
    }
    if (!SetSystemClock(&dcf_tt)){
      WriteLog();
      exit(-1);
    }
  }
  
  /* set hardware clock */
  if (HardwareSet==YES){
    if (SystemSet==YES){
      execl("/sbin/hwclock","hwclock","--systohc",NULL);
      return 0;
    }
    else{ 
      dcf_tmp=gmtime(&dcf_tt);
      sprintf(TimeToSet,"--date=%d/%d/%d %d:%d:%d",dcf_tmp->tm_mon+1,dcf_tmp->tm_mday,dcf_tmp->tm_year+1900,
	      dcf_tmp->tm_hour,dcf_tmp->tm_min,dcf_tmp->tm_sec);
      execl("/sbin/hwclock","hwclock","--set",TimeToSet,NULL);
      return 0;
    }
  }

  return 0;
 
}
/**************************************************************************************************************/

/**************************************************************************************************************/
int ReadConfig(void)
{
  char ConfigFile[]=CONFIGFILE;
  FILE *fd;
  char buf[256];
  char command[256];
  char data[256];
  int ln=0;

  if ((fd=fopen(CONFIGFILE,"r"))==NULL){
    sprintf(MsgError,"ERROR: Can't open config file: '%s'.",ConfigFile);
    return 0;
  }
  while (fgets(buf,255,fd)!=NULL){
    buf[255]='\0';
    ln++;
    
    if (isalpha(*buf)){
      command[0]='\0';
      data[0]='\0';
      sscanf(buf,"%[^= \t]%*[ =\t]%[^ \t\n]",command,data);
      if (data[0]=='\0'){
	sprintf(MsgError,"ERROR: In config file, line %d - data miss!",ln);
	return 0;
      }
      
      if (strcmp(command,"SERIAL") == 0)
        /* SERIAL command */
	sprintf(SerialDevice,"%s",data);
      else if (strcmp(command,"EXCLUSIVE") == 0){
      /* EXCLUSIVE command */
	if (strcmp(data,"YES") == 0)
	  Exclusive=YES;
	else if (strcmp(data,"NO") == 0)
	  Exclusive=NO;
	else{
	  sprintf(MsgError,"ERROR: In config file, line %d - unknow data for command %s!",ln,command);
	  return 0;
	} 
      }
      else if (strcmp(command,"HARDWARE_SET") == 0){
      /* HARDWARE_SET command */
	if (strcmp(data,"YES") == 0)
	  HardwareSet=YES;
	else if (strcmp(data,"NO") == 0)
	  HardwareSet=NO;
	else{
	  sprintf(MsgError,"ERROR: In config file, line %d - unknow data for command %s!",ln,command);
	  return 0;
	} 
      }
      else if (strcmp(command,"SYSTEM_SET") == 0){
      /* SYSTEM_SET command */
	if (strcmp(data,"YES") == 0)
	  SystemSet=YES;
	else if (strcmp(data,"NO") == 0)
	  SystemSet=NO;
	else{
	  sprintf(MsgError,"ERROR: In config file, line %d - unknow data for command %s!",ln,command);
	  return 0;
	} 
      }
      else if (strcmp(command,"LOG_TO_CONSOLE") == 0){
      /* LOG_TO_CONSOLE command */
	if (strcmp(data,"YES") == 0)
	  LogConsole=YES;
	else if (strcmp(data,"NO") == 0)
	  LogConsole=NO;
	else{
	  sprintf(MsgError,"ERROR: In config file, line %d - unknow data for command %s!",ln,command);
	  return 0;
	} 
      }
      else{
      /* unknow command */
	sprintf(MsgError,"ERROR: In config file, line %d - unknow command '%s'!",ln,command);
	return 0;
      }

    }
  }
  fclose(fd);
  return 1;
}

/**************************************************************************************************************/
int ReadParameters(int argc_d, char **argv_d)
{
  int i;

  for (i=1;i<argc_d;i++){
    if ((strcmp(argv_d[i],"-d"))==0){
      /* -d parameter */ 
      if ((i+1)<argc_d && argv_d[i+1][0]!='-'){
	sprintf(SerialDevice,"%s",argv_d[++i]);
      }
      else{
	sprintf(MsgError,"ERROR: No arguments for option '-d'");
	return 0;
      }
    }
    else if ((strcmp(argv_d[i],"-X"))==0)
      Exclusive=YES;
    else if ((strcmp(argv_d[i],"-h"))==0)
      HardwareSet=NO;
    else if ((strcmp(argv_d[i],"+h"))==0)
      HardwareSet=YES;
    else if ((strcmp(argv_d[i],"-s"))==0)
      SystemSet=NO;
    else if ((strcmp(argv_d[i],"+s"))==0)
      SystemSet=YES;
    else if ((strcmp(argv_d[i],"-c"))==0)
      LogConsole=YES;
    else if ((strcmp(argv_d[i],"-p"))==0)
      PrintTime=YES;
    else if ((strcmp(argv_d[i],"-v"))==0)
      PrintVersion=YES;
    else{
      /* unknow parameter */ 
      sprintf(MsgError,"ERROR: Unknow option: '%s'",argv_d[i]);
      return 0;
    }
  }
  return 1;
}


/**************************************************************************************************************/
void WriteLog()
{
  int fd;
  char *MsgLog=NULL;
  char LogFile[]=LOGFILE;
  long tmp1,tmp2;

  if ((fd=open(LOGFILE,O_CREAT | O_APPEND | O_WRONLY,S_IRUSR | S_IWUSR))<0){
    fd=2; /* stderr */
    if ((MsgLog=(char *)malloc(256))==NULL) exit(-1);
    sprintf(MsgLog,"%s  ERROR: Can't open/create log file: '%s'.\n",GetSystemDate(&tmp1,&tmp2),LogFile);
    write(fd,MsgLog,strlen(MsgLog)); 
    free(MsgLog);
  }

  if ((MsgLog=(char *)malloc(50+strlen(MsgError)))==NULL) exit(-1);
  sprintf(MsgLog,"%s  %s\n",GetSystemDate(&tmp1,&tmp2),MsgError);
  write(fd,MsgLog,strlen(MsgLog));
  if (fd!=2){ 
    close(fd);
    if (LogConsole==YES){
      fd=2;
      write(fd,MsgLog,strlen(MsgLog));
    }
  }
  free(MsgLog);
}

/**************************************************************************************************************/
char *GetSystemDate(long *sec, long *usec)
{
  struct timeval tv;
  time_t tt;
  struct timezone tz;  

  gettimeofday(&tv,&tz);
  tt=tv.tv_sec;

  *sec=tv.tv_sec;
  *usec=tv.tv_usec;

  return ctime(&tt);
}

/**************************************************************************************************************/
int GetDCFTime(time_t *tt)
{
  int ttyS,res;
  unsigned char buf[10];
  struct termios oldtio,newtio;
  
  struct tm tm_d;

  /* open serial port */
  if ((ttyS = open(SerialDevice, O_RDWR | O_NOCTTY))<0){
    sprintf(MsgError,"ERROR: Can't open serial device name '%s'.",SerialDevice);
    return 0;
  }

  /* save current settings */
  tcgetattr(ttyS,&oldtio);

  /* and set a new one */
  newtio.c_cflag = BAUDRATE | CS8 | PARENB | CLOCAL | CREAD;
  newtio.c_iflag = IGNPAR; /* parity check */
  newtio.c_oflag = 0;
  newtio.c_lflag = 0;
  newtio.c_cc[VMIN]=0; /* blocking read until 10 char arrives */
  newtio.c_cc[VTIME]=10; /* with 1s char-to-char timeout */
 
  /* now clean the ttySx line and activate the settings for ttySx */
  tcflush(ttyS, TCIOFLUSH);
  tcsetattr(ttyS,TCSANOW,&newtio); 

  /* handle input/output */
  buf[0]=0x78;
  if (write(ttyS,&buf,1)<=0){
    sprintf(MsgError,"ERROR: Can't write to device name '%s'.",SerialDevice);
    return 0;
  }
  
  res=0;
  while(res<10){
    if ( read(ttyS,&buf[res++],1)<=0 ){
      sprintf(MsgError,"ERROR: Read error from device name '%s'.",SerialDevice);
      return 0;
    }
    if ( res==1 ) GetSystemDate(&start_sec,&start_usec);
  }

  /* check for error */
  if ( buf[9]==0xd2 ){
    sprintf(MsgError,"ERROR: Time is invalid. Check DSP clock.");
    return 0;  
  }

  if ( buf[0]!=0xbb || buf[9]!=0xc5 || !CheckSum(buf) ){
    sprintf(MsgError,"ERROR: Data transmit fail. Check serial cable.");
    return 0;  
  } 

  /* restore old ttySx setings */
  tcsetattr(ttyS,TCSANOW,&oldtio); 
  close(ttyS);
  
  /* convert date to the Unix time */
  tzset();
  tm_d.tm_sec=buf[3];         /* seconds */
  tm_d.tm_min=buf[2];         /* minutes */
  tm_d.tm_hour=buf[1];        /* hours */
  tm_d.tm_mday=buf[6];        /* day of the month */
  tm_d.tm_mon=buf[5]-1;         /* month */
  tm_d.tm_year=buf[4]+100;    /* year */
  tm_d.tm_yday=0;             /* day in the year */
  tm_d.tm_isdst=0;            /* daylight saving time */

  if ((*tt=mktime(&tm_d))<970516000){
    sprintf(MsgError,"DCF ERROR: Unknow date format.");
    return 0;  
  } 

  return 1;
}

/**************************************************************************************************************/
int CheckSum(char *buf)
{
  int i;
  unsigned char res=0;

  for (i=1;i<8;i++) res+=buf[i];
  res=res & 0x7f;
  if (res!=buf[8]) return 0;
  return 1;
}

/**************************************************************************************************************/
int ComSysToDCF(time_t dcf_time)
{  
  struct timeval tv;
  struct timezone tz;  
  
  gettimeofday(&tv,&tz);
  if (tv.tv_sec==dcf_time) return 0;
  else if (tv.tv_sec>(dcf_time+5)) return 1;
  else return -1;
}

/**************************************************************************************************************/
int SetSystemClock(time_t *tt)
{
  struct timeval tv;
  struct timezone tz;

  GetSystemDate(&stop_sec,&stop_usec);

  tv.tv_sec=*tt;
  if (stop_sec==start_sec) tv.tv_usec=stop_usec-start_usec;
  else tv.tv_usec=stop_usec+(1000000-start_usec);
  tz.tz_dsttime=0;
  if ((settimeofday(&tv,&tz))<0){
    sprintf(MsgError,"ERROR: Can't set system clock!");
    return 0;
  }
  sprintf(MsgError,"OK: Changed system clock.");
  WriteLog();
  return 1;
}
