/* Copyright 1999 by Herve Regad-Pellagru, E-mail: regad@micronet.fr

    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., 675 Mass Ave, Cambridge, MA 02139, USA. */

#include <stdio.h>
#include <io.h>
#include <process.h>
#include <time.h>
#include <string.h>


#include "error.h"
#include "proto.h"
#include "cm12a.h"

/* The house code to CM11 translation table */
static unsigned char house_code[] = {
  0x6,0xe,0x2,0xa,0x1,0x9,0x5,0xd,0x7,0xf,0x3,0xb,0x0,0x8,0x4,0xc
};

/* The  CM11 to house code translation table */
static unsigned char house_code_inv[] = {
  0xc,0x4,0x2,0xa,0xe,0x6,0x0,0x8,0xd,0x5,0x3,0xb,0xf,0x7,0x1,0x9
};

/* The unit code to CM11 translation table */
static unsigned char unit_code[] = {
  0x6,0xe,0x2,0xa,0x1,0x9,0x5,0xd,0x7,0xf,0x3,0xb,0x0,0x8,0x4,0xc
};

/* The CM11 to unit code  translation table */
static unsigned char unit_code_inv[] = {
  0xc,0x4,0x2,0xa,0xe,0x6,0x0,0x8,0xd,0x5,0x3,0xb,0xf,0x7,0x1,0x9
};

/* The function code to CM11 translation table. Yeah, I know it's an
   identity but it's more accurate this way */
static unsigned char function_code[] = {
  0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf
};

/* After digging in my maths books, I found the inverse of an identitty is also
   an identity. Gosh, thoses guys were really smart :-) */
static unsigned char function_code_inv[] = {
  0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf
};


/* Here, we translate CM11 day of week to human reading. Quite BFI. */
static char *x10_cm11todayweek(unsigned char code)
{
  switch(code)
    {
    case 64:
      return("Saturday");
    case 32:
      return("Friday");
    case 16:
      return("Thursday");
    case 8:
      return("Wednesday");
    case 4:
      return("Tuesday");
    case 2:
      return("Monday");
    case 1:
      return("Sunday");
    default:
      return("Unknown");
    }
}


/* The checksum for near every tranmission to the CM11 */
static int x10_checksum(unsigned char *buf,int size)
{
  register int i, sum;

  for (i = 0, sum = 0; i < size ; i++)
    sum += buf[i];

  return (sum & 0xff);
}


/* This function decodes buffers from the CM11 telling which X10 actions occurs
   on the AC bus. It returns 1 if the last decoded thing is a function, 0 otherwise. */
static int x10_decode_action(unsigned char mask,unsigned char action[9],unsigned char length,struct x10_status_t *status)
{
  int indice;
  unsigned char house_code;
  int ret_code=0;
  int dim_level;

  P_DEBUG1("x10_decode_action: decoding with mask %x\n",mask);
  P_DEBUG2("x10_decode_action: decoding %x %x\n",action[0],action[1]);
  P_DEBUG2("x10_decode_action: decoding %x %x\n",action[2],action[3]);
  P_DEBUG2("x10_decode_action: decoding %x %x\n",action[4],action[5]);
  P_DEBUG2("x10_decode_action: decoding %x %x\n",action[6],action[7]);
  P_DEBUG1("x10_decode_action: decoding %x\n",action[8]);

  /* We cycle on every bytes */
  for(indice=0;indice<length;indice++)
    {
      /* For every byte, an X10 function or address can occur */
      if(mask & (1 << indice))
	{
	  /* This is a function code */
	  house_code='A'+house_code_inv[action[indice]>>4];
	  status->last_func_code=function_code_inv[action[indice] & 0xf];

	  P_DEBUG2("x10_decode_action: decoding func: %c %d\n",house_code,status->last_func_code);

	  /* If this is a DIM or BRIGHT, then the following byte is amount of
	     dim/bright */
	  if(status->last_func_code == X10_CMD_DIM || status->last_func_code == X10_CMD_BRIGHT)
	    {
	      dim_level=(action[indice+1] * 100) / 210;
	      indice++;
	    }
	  else
	    dim_level=0;

	  /* We trigger the hook on this action */
	  if(status->function_hook)
	    status->function_hook(house_code,status->last_unit_code,
				status->last_func_code,dim_level,status);
	  ret_code=1;
	}
      else
	{
	  /* This is an address code. We just note it in the status structure */
	  status->last_house_code='A'+house_code_inv[action[indice]>>4];
	  status->last_unit_code=1+unit_code_inv[action[indice] & 0xf];

	  P_DEBUG2("x10_decode_action: decoding address: %c %d\n",status->last_house_code,status->last_unit_code);
	  ret_code=0;
	}
    }

  return(ret_code);
}


/* This function just codes an X10 address+function into a CM11 aware buffer,
   ready to transmit */
static int x10_code_action(char house,int unit,int cmd,int arg,unsigned char out[6])
{
  unsigned char dim_level;

  dim_level = (arg << 3);

  /* First, the address */
  out[0] = X10_ADDRESS_CODE | dim_level;
  out[1] = (house_code[house-'A'] << 4) | unit_code[unit-1];
  
  /* And the function */
  out[2] =  X10_FUNCTION_CODE | dim_level;
  out[3] = (house_code[house-'A'] << 4) | function_code[cmd];

  /* We return the buffer length */
  return(4);
}


/* After reading a polling byte from the CM11, we need to flush it, in order
   to know what event(s) occured. This is done here. */
int x10_complete_events(struct x10_status_t *status)
{
  unsigned char codes_read[10];
  unsigned char code_write=X10_PC_POLL_ACK;
  unsigned char leng;
  int ret_read,last_code_func;

  P_DEBUG1("x10_complete_events: sending %x as POLL_ACK\n",code_write);

  /* We aknowledge the polling */
  if(x10_write(status,&code_write,1) != 1)
    {
      status->error(WRITE_FAILED,"in x10_complete_events",status); 
      return(WRITE_FAILED);
    }

  /* We read the length of the buffer. We don't poll for events because this is the middle
   of a transaction and hardware engineers won't interrupt the whole thing just for a poll.
  They won't do something that stupid, will they ? ;-) */
  ret_read=x10_read(status,&leng,1,status->timeout_chat,X10_CHECK_POLL_FALSE);

  if(ret_read < 1)
    {
      status->error(READ_FAILED,"in x10_complete_events",status); 
      return(READ_FAILED);
    }

  P_DEBUG1("x10_complete_events: receiving %x as length\n",leng);

  /* We read the whole buffer */
  // bad message ?
  if (leng > 10)
  {
    status->error(READ_FAILED,"in x10_complete_events",status);
    return(READ_FAILED);
  }

  if(x10_read(status,codes_read,leng,status->timeout_chat,X10_CHECK_POLL_FALSE) < leng)
    {
      status->error(READ_FAILED,"in x10_complete_events",status);
      return(READ_FAILED);
    }

  /* We decode the event(s) */
  last_code_func=x10_decode_action(codes_read[0],codes_read+1,leng-1,status);

  return(last_code_func);
}


/* This function tells the CM11A the PC is ready and reads the CM11A's
   aknoledge */
static int x10_set_interface_ready(struct x10_status_t *status)
{
  unsigned char ready = X10_PC_READY_CODE;
  unsigned char code_read;
  int ret_read;

  P_DEBUG1("x10_set_interface_ready: sending %x as READY\n",ready);
  if(x10_write(status,&ready,1) != 1)
    {
      status->error(WRITE_FAILED,"in x10_set_interface_ready",status);
      return(WRITE_FAILED);
    }
	
  ret_read=x10_read(status,&code_read,1,status->timeout_chat,X10_CHECK_POLL_TRUE);

  if(ret_read == CHAT_INTERRUPTED)
    {
      P_DEBUG1("x10_set_interface_ready: chat interrupted %x.\n",ret_read);
      return(CHAT_INTERRUPTED);
     }

  if(ret_read < 1)
    {
      status->error(READ_FAILED,"in x10_set_interface_ready",status);
      return(READ_FAILED);
    }
      
  P_DEBUG1("x10_set_interface_ready: receiving %x as ACK\n",code_read);

  if(code_read != X10_CM_ACK)
    {
      P_DEBUG1("x10_set_interface_ready: Bad ack %x. Retrying ...\n",code_read);
      return(TRANSMIT_FAILED);
    }

  return(0);
}


/* This function belongs to the API. It sends an X10 code to a target, or to EVERY
   target if it's a broadcast-like code (ALL* codes). More than that, if the code is
   X10_CMD_STATUS_REQUEST, it waits for the answer.
   Note that the CHAT INTERRUPTED return code are handled recursively (try again) */
int x10_send_cmd(char house,int unit,int cmd,int arg,struct x10_status_t *status)
{
  unsigned char codes_write[6];
  unsigned char code_read;
  int i,leng;
  int ret_read,ret_ready;

  /* We translate the action into CM11-land */
  leng = x10_code_action(house,unit,cmd,arg,codes_write);
  
  /* We iterate by trunks of 2 octets, whicj is the basic X10 transaction length */
  for(i=0;i<leng;i+=2)
    {
      P_DEBUG2("x10_send_cmd: sending %x %x\n",codes_write[i],codes_write[i+1]);
      if(x10_write(status,codes_write+i,2) != 2)
	{
	  status->error(WRITE_FAILED,"in x10_send_cmd",status);
	  return(WRITE_FAILED);
	}

      ret_read = x10_read(status,&code_read,1,status->timeout_chat,X10_CHECK_POLL_FALSE);

      P_DEBUG1("x10_send_cmd: receiving %x as checksum\n",code_read);
	    
      /* same thing if x10_read() has timeouted */
      if(ret_read == TIMEOUT_READ)
	{
	  P_DEBUG1("x10__send_cmd: chat timeouted %x. Retrying\n",ret_read);
	  return(x10_send_cmd(house,unit,cmd,arg,status));
	}
  
      /* big error here */
      if(ret_read < 1)
	{
	  status->error(READ_FAILED,"in x10_send_cmd",status);
	  return(READ_FAILED);
	}

      /* We terminate the X10 chat (PC ready -> ack) */
      ret_ready=x10_set_interface_ready(status);
      
      /* we restart everything if interrupted by a poll */
      if(ret_ready == CHAT_INTERRUPTED)
	return(x10_send_cmd(house,unit,cmd,arg,status));

      /* and last, we verify the checksum */
      if(x10_checksum(codes_write+i,2) != code_read)
	{
	  P_DEBUG1("x10_send_cmd: Bad checksum %x. Retrying ...\n",code_read);
	  return(x10_send_cmd(house,unit,cmd,arg,status));
	}

      /* if error encountered, we return it, otherwise we go to the next iteration */
      if(ret_ready < 0)
	return(ret_ready);
    }

  /* if the code was a request of status from a smart module, we wait for the answer,
     but first we put a dummy code into status->last_func_code to be sure we get
     the right answer. Actually, this is not true as 2 or more modules asking status on
     the AC bus can collide. But there's no way to avoid it in X10 as the reply status
     doesn't contain the originator's full address (only the house code).
     So it only works if the CM11A is the only to ask status, which is a reasonable assumption */
  if(cmd == X10_CMD_STATUS_REQUEST)
    {
      status->last_func_code=X10_CMD_DUMMY;
      
      do
	x10_listen_to_events(status,status->timeout_chat);
      while(status->last_func_code != X10_CMD_STATUS_ON &&
	    status->last_func_code != X10_CMD_STATUS_OFF);

      /* We put the result (house + unit + effective status) in a separate part, to avoid
         conflicts with future x10_read() */
      status->last_requested_house_code=house;
      status->last_requested_unit_code=unit;
      status->last_requested_func_code=status->last_func_code;
    }

  return(0);
}


/* This function only gets clock from the CM11A. We don't consider the 
   last accessed modules, last known status and last dimmed devices as
   we'll get them with x10_listen_to_events() and, generally, every function
   from the API */
int x10_get_status_interface(struct x10_status_t *status)
{
  unsigned char code_write = X10_STATUS_CODE;
  unsigned char code_read[14];
  int ret_read;

  P_DEBUG1("x10_get_status_interface: sending %x\n",code_write);
  if(x10_write(status,&code_write,1) != 1)
    {
      status->error(WRITE_FAILED,"in x10_get_status_interface",status);
      return(WRITE_FAILED);
    }

  /* We read the 14 data bytes from the CM11A. No check for poll. */
  ret_read=x10_read(status,code_read,14,status->timeout_chat,X10_CHECK_POLL_FALSE);

  P_DEBUG1("x10_get_status_interface: receiving %x octets\n",ret_read);
  P_DEBUG2("x10_get_status_interface: Receiving %x %x",code_read[0],code_read[1]);
  P_DEBUG2(" %x %x",code_read[2],code_read[3]);
  P_DEBUG2(" %x %x",code_read[4],code_read[5]);
  P_DEBUG2(" %x %x\n",code_read[6],code_read[7]);

  if(ret_read == TIMEOUT_READ)
    {
      P_DEBUG1("x10_get_status_interface: chat interrupted ou timeouted %x. Retrying\n",ret_read);
      return(x10_get_status_interface(status));
    }
  
  if(ret_read < 1)
    {
      status->error(READ_FAILED,"in x10_get_status_interface",status); 
      return(READ_FAILED);
    }

  /* We've got every bytes of the result: we decode that in a human reading way */
  status->battery_life = ((code_read[1] << 8) | code_read[0]) + 1;
  status->seconds = code_read[2] & 0xff;
  status->minutes = (code_read[3] & 0xff) % 60;
  status->hours = ((code_read[4] & 0xff) * 2) + ((code_read[3] & 0xff) / 60);
  status->julien_days = code_read[5] + ((code_read[6] & 0x080) << 1);
  status->day_of_week = x10_cm11todayweek(code_read[6] & 0x07f);
  status->revision = code_read[7] & 0xf;

  return(0);
}


/* This function just does the opposite of x10_get_status_interface(), that is,
   set clock of the CM11A */
int x10_set_status_interface(struct x10_status_t *status)
{
  unsigned char code_write[7];
  unsigned char code_read;
  int return_read,ret_ready;
  time_t unix_time;
  struct tm *ltime;

  /* We get the local time */
  unix_time = time(NULL);
  ltime = localtime(&unix_time);

  /* We pack everything in the weird CM11A-land */
  code_write[0] = X10_SETCLOCK_CODE;
  code_write[1] = ltime->tm_sec;
  code_write[2] = ltime->tm_min + ((ltime->tm_hour % 2) * 60 );
  code_write[3] = ltime->tm_hour / 2;
  code_write[4] = ltime->tm_yday % 256;
  code_write[5] = (ltime->tm_yday / 256 ) << 7;
  code_write[5] |= 1 << ltime->tm_wday;
  code_write[6] = 0;

  P_DEBUG2("x10_set_status_interface: sending %x %x",code_write[0],code_write[1]);
  P_DEBUG2(" %x %x",code_write[2],code_write[3]);
  P_DEBUG2(" %x %x",code_write[4],code_write[5]);
  P_DEBUG1(" %x\n",code_write[6]);

  /* We throw everything to the CM11A */
  if(x10_write(status,code_write,7) != 7)
    {
      status->error(WRITE_FAILED,"in x10_set_status_interface",status);
      return(WRITE_FAILED);
    }

  return_read=x10_read(status,&code_read,1,status->timeout_chat,X10_CHECK_POLL_FALSE);

  P_DEBUG1("x10_set_status_interface: receiving %x as checksum\n",code_read);

  /* timeouted, try again */
  if(return_read == TIMEOUT_READ)
    {
      P_DEBUG1("x10_set_status_interface: chat interrupted ou timeouted %x. Retrying\n",return_read);
      return(x10_set_status_interface(status));
    }
  
  if(return_read<1)
    {
      status->error(return_read,"in x10_set_status_interface",status); 
      return(READ_FAILED);
    }

  /* We terminate the X10 chat (PC ready -> ack) */
  ret_ready=x10_set_interface_ready(status);

  /* Again, we restart everything if interrupted by a poll */
  if(ret_ready == CHAT_INTERRUPTED)
    return(x10_set_status_interface(status));
  
  /* Yes, we do the checksum on every bytes transmittedm except the
     first ! Don't ask me why :-) And don't ask the doc why ;-) */
  if(code_read != x10_checksum(code_write+1,6))
    {
      P_DEBUG1("x10_set_status_interface: Bad checksum %x. Retrying ...\n",code_read);
      return(x10_set_status_interface(status));
    }

  if(ret_ready < 0)
    return(ret_ready);
  
  return(0);
}


/* This is another API function. It listen to the AC bus for useconds
   micro-seconds, and note the X10 events. It cycles until nothing is to be
   downloaded from the CM11A. This is because downloading an address
   without its corresponding function will lockup the CM11A.
   Game of the day: find the section where it is explained in protocol2.txt ;-) */
int x10_listen_to_events(struct x10_status_t *status,int useconds)
{
  unsigned char code_read;
  int return_read;

  /* As the x10_read()/x10_write() layer has been designed to take into account
     the poll, we have nothing to do than just x10_read() ! */
  return_read=x10_read(status,&code_read,1,useconds,X10_CHECK_POLL_TRUE);

  /* TIMEOUT_READ and CHAT_INTERRUPTED are a normal situation -> 0 */
  if(return_read == TIMEOUT_READ || return_read == CHAT_INTERRUPTED)
    return(0);

  /* Everything else is bad */
  status->error(READ_FAILED,"in x10_listen_to_events",status); 
  return(READ_FAILED);
}


