/*
 *  Teletext parsing functions
 *  Copyright (C) 2007 Andreas Öman
 *
 *  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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include <pthread.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#include "tvheadend.h"
#include "teletext.h"
#include "packet.h"
#include "streaming.h"
#include "service.h"

/**
 *
 */
typedef struct tt_mag {
  int ttm_curpage;
  int64_t ttm_current_pts;
  uint8_t ttm_lang;
  uint8_t ttm_page[23*40 + 1];
} tt_mag_t;


/**
 *
 */
typedef struct tt_private {
  tt_mag_t ttp_mags[8];

  int ttp_rundown_valid;
  uint8_t ttp_rundown[23*40 + 1];

} tt_private_t;


static void teletext_rundown_copy(tt_private_t *ttp, tt_mag_t *ttm);

static void teletext_rundown_scan(service_t *t, tt_private_t *ttp);

#define bitreverse(b) \
(((b) * 0x0202020202ULL & 0x010884422010ULL) % 1023)

static const uint8_t hamtable[] = {
  0x01, 0xff, 0x81, 0x01, 0xff, 0x00, 0x01, 0xff, 
  0xff, 0x02, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x07, 
  0xff, 0x00, 0x01, 0xff, 0x00, 0x80, 0xff, 0x00, 
  0x06, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x03, 0xff, 
  0xff, 0x0c, 0x01, 0xff, 0x04, 0xff, 0xff, 0x07, 
  0x06, 0xff, 0xff, 0x07, 0xff, 0x07, 0x07, 0x87, 
  0x06, 0xff, 0xff, 0x05, 0xff, 0x00, 0x0d, 0xff, 
  0x86, 0x06, 0x06, 0xff, 0x06, 0xff, 0xff, 0x07, 
  0xff, 0x02, 0x01, 0xff, 0x04, 0xff, 0xff, 0x09, 
  0x02, 0x82, 0xff, 0x02, 0xff, 0x02, 0x03, 0xff, 
  0x08, 0xff, 0xff, 0x05, 0xff, 0x00, 0x03, 0xff, 
  0xff, 0x02, 0x03, 0xff, 0x03, 0xff, 0x83, 0x03, 
  0x04, 0xff, 0xff, 0x05, 0x84, 0x04, 0x04, 0xff, 
  0xff, 0x02, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x07, 
  0xff, 0x05, 0x05, 0x85, 0x04, 0xff, 0xff, 0x05, 
  0x06, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x03, 0xff, 
  0xff, 0x0c, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x09, 
  0x0a, 0xff, 0xff, 0x0b, 0x8a, 0x0a, 0x0a, 0xff, 
  0x08, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x0d, 0xff, 
  0xff, 0x0b, 0x0b, 0x8b, 0x0a, 0xff, 0xff, 0x0b, 
  0x0c, 0x8c, 0xff, 0x0c, 0xff, 0x0c, 0x0d, 0xff, 
  0xff, 0x0c, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x07, 
  0xff, 0x0c, 0x0d, 0xff, 0x0d, 0xff, 0x8d, 0x0d, 
  0x06, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x0d, 0xff, 
  0x08, 0xff, 0xff, 0x09, 0xff, 0x09, 0x09, 0x89, 
  0xff, 0x02, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x09, 
  0x88, 0x08, 0x08, 0xff, 0x08, 0xff, 0xff, 0x09, 
  0x08, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x03, 0xff, 
  0xff, 0x0c, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x09, 
  0x0f, 0xff, 0x8f, 0x0f, 0xff, 0x0e, 0x0f, 0xff, 
  0x08, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x0d, 0xff, 
  0xff, 0x0e, 0x0f, 0xff, 0x0e, 0x8e, 0xff, 0x0e, 
};

static const uint8_t laG0_nat_opts_lookup[16][8] = {
  {1, 4, 11, 5, 3, 8, 0, 1},
  {7, 4, 11, 5, 3, 1, 0, 1},
  {1, 4, 11, 5, 3, 8, 12, 1},
  {1, 1, 1, 1, 1, 10, 1, 9},
  {1, 4, 2, 6, 1, 1, 0, 1},
  {1, 1, 1, 1, 1, 1, 1, 1}, // 5 - reserved
  {1, 1, 1, 1, 1, 1, 12, 1},
  {1, 1, 1, 1, 1, 1, 1, 1}, // 7 - reserved
  {1, 1, 1, 1, 3, 1, 1, 1},
  {1, 1, 1, 1, 1, 1, 1, 1}, // 9 - reserved
  {1, 1, 1, 1, 1, 1, 1, 1},
  {1, 1, 1, 1, 1, 1, 1, 1}, // 11 - reserved
  {1, 1, 1, 1, 1, 1, 1, 1}, // 12 - reserved
  {1, 1, 1, 1, 1, 1, 1, 1}, // 13 - reserved
  {1, 1, 1, 1, 1, 1, 1, 1}, // 14 - reserved
  {1, 1, 1, 1, 1, 1, 1, 1}  // 15 - reserved
};


static const uint8_t laG0_nat_replace_map[128] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 6, 7, 8,
9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 0
};


/*
 * Latin National Option Sub-Sets
 * ETSI EN 300 706 Table 36
 */
static const uint16_t laG0_nat_opts16[13][14] = {
{0, '#',    'u',    'c',    't',    'z',    0xc3bd, 0xc3ad, 'r',    0xc3a9, 0xc3a1, 'e',    0xc3ba, 's'   }, // 0 - Czech/Slovak
{0, 0xc2a3, '$',    '@',    '-',    0xc2bd, '-',    '|',    '#',    '-',    0xc2bc, '#',    0xc2be, 0xc3b7}, // 1 - English
{0, '#',    0xc3b5, 'S',    0xc384, 0xc396, 'Z',    0xc39c, 0xc395, 's',    0xc3a4, 0xc3b6, 'z',    0xc3bc}, // 2 - Estonian
{0, 0xc3a9, 0xc3af, 0xc3a0, 0xc3ab, 0xc3aa, 0xc3b9, 0xc3ae, '#',    0xc3a8, 0xc3a2, 0xc3b4, 0xc3bb, 0xc3a7}, // 3 - French
{0, '#',    '$',    0xc2a7, 0xc384, 0xc396, 0xc39c, '^',    '_',    0xc2ba, 0xc3a4, 0xc3b6, 0xc3bc, 0xc39f}, // 4 - German
{0, 0xc2a3, '$',    0xc3a9, 0xc2ba, 0xc3a7, '-',    '|',    '#',    0xc3b9, 0xc3a0, 0xc3b2, 0xc3a8, 0xc3ac}, // 5 - Italian
{0, '#',    '$',    'S',    'e',    'e',    'Z',    'c',    'u',    's',    'a',    'u',    'z',    'i'   }, // 6 - Lettish/Lithuanian
{0, '#',    'n',    'a',    'Z',    'S',    'L',    'c',    0xc3b3, 'e',    'z',    's',    'l',    'z'   }, // 7 - Polish
{0, 0xc3a7, '$',    'i',    0xc3a1, 0xc3a9, 0xc3ad, 0xc3b3, 0xc3ba, 0xc2bf, 0xc3bc, 0xc3b1, 0xc3a8, 0xc3a0}, // 8 - Portuguese/Spanish
{0, '#',    0xc2a4, 'T',    0xc382, 'S',    'A',    0xc38e, 'i',    't',    0xc3a2, 's',    'a',    0xc3ae}, // 9 - Rumanian
{0, '#',    0xc38b, 'C',    'C',    'Z',    'D',    'S',    0xc3ab, 'c',    'c',    'z',    'd',    's'   }, // 10 - Serbian/Croation/Slovenian
{0, '#',    0xc2a4, 0xc389, 0xc384, 0xc396, 0xc385, 0xc39c, '_',    0xc3a9, 0xc3a4, 0xc3b6, 0xc3a5, 0xc3bc}, // 11 - Swedish/Finnish/Hungarian
{0, 'T',    'g',    'I',    'S',    0xc396, 0xc387, 0xc39c, 'G',    'i',    's',    0xc3b6, 0xc3a7, 0xc3bc}  // 12 - Turkish
};

/*
 * Map Latin G0 teletext characters into a ISO-8859-1 approximation.
 * Trying to use similar looking or similar meaning characters.
 * Gtriplet - 4 bits = triplet-1 bits 14-11 (14-8) of a X/28 or M/29 packet, if unknown - use 0
 * natopts - 3 bits = national_options field of a Y/0 packet (or triplet-1 bits 10-8 as above?)
 * inchar - 7 bits = characted to remap
 * Also strips parity
 */

static uint16_t
tt_convert_char(int Gtriplet, int natopts, uint8_t inchar)
{
  int no = laG0_nat_opts_lookup[Gtriplet & 0xf][natopts & 0x7];
  uint8_t c = inchar & 0x7f;

  if(!laG0_nat_replace_map[c])
    return c;
  else
    return laG0_nat_opts16[no][laG0_nat_replace_map[c]];
}

static uint8_t
ham_decode(uint8_t a, uint8_t b)
{
  a = hamtable[a];
  b = hamtable[b];

  return (b << 4) | (a & 0xf);
}

/**
 *
 */
static time_t
tt_construct_unix_time(uint8_t *buf)
{
  time_t t, r[3], v[3];
  int i;
  struct tm tm;

  t = dispatch_clock;
  localtime_r(&t, &tm);

  tm.tm_hour = atoi((char *)buf);
  tm.tm_min = atoi((char *)buf + 3);
  tm.tm_sec = atoi((char *)buf + 6);

  r[0] = mktime(&tm);
  tm.tm_mday--;
  r[1] = mktime(&tm);
  tm.tm_mday += 2;
  r[2] = mktime(&tm);

  for(i = 0; i < 3; i++)
    v[i] = labs(r[i] - t);
  
  if(v[0] < v[1] && v[0] < v[2])
    return r[0];

  if(v[1] < v[2] && v[1] < v[0])
    return r[1];

  return r[2];
}


/**
 *
 */
static int
is_tt_clock(const uint8_t *str)
{
  return 
    isdigit(str[0]) && isdigit(str[1]) && str[2] == ':' &&
    isdigit(str[3]) && isdigit(str[4]) && str[5] == ':' &&
    isdigit(str[6]) && isdigit(str[7]);
}


/**
 *
 */
static int
update_tt_clock(service_t *t, const uint8_t *buf)
{
  uint8_t str[10];
  int i;
  time_t ti;

  for(i = 0; i < 8; i++)
    str[i] = buf[i] & 0x7f;
  str[8] = 0;

  if(!is_tt_clock(str))
    return 0;

  ti = tt_construct_unix_time(str);
  if(t->s_tt_clock == ti)
    return 0;

  t->s_tt_clock = ti;
  //  printf("teletext clock is: %s", ctime(&ti));
  return 1;
}


static void
extract_subtitle(service_t *t, elementary_stream_t *st,
		 tt_mag_t *ttm, int64_t pts)
{
  int i, j, off = 0;
  uint8_t sub[2000];
  int is_box = 0;

  for (i = 0; i < 23; i++) {
    int start = off;

    for (j = 0; j < 40; j++) {
      char ch = ttm->ttm_page[40 * i + j];

      switch(ch) {
      case 0 ... 7:
	break;

      case 0x0a:
	is_box = 0;
	break;

      case 0x0b:
	is_box = 1;
	break;

      default:
	if (ch >= 0x20 && is_box && (start != off || ch > 0x20)) {
	  uint16_t code = tt_convert_char(0, ttm->ttm_lang, ch);

	  if(code & 0xff00)
	    sub[off++] = (code & 0xff00) >> 8;
	  sub[off++] = code;
	}
      }
    }
    if(start != off)
      sub[off++] = '\n';
  }

  if(off == 0 && st->es_blank)
    return; // Avoid multiple blank subtitles

  st->es_blank = !off;

  if(st->es_curpts == pts)
    pts++; // Avoid non-monotonic PTS

  st->es_curpts = pts;

  sub[off++] = 0;
  
  th_pkt_t *pkt = pkt_alloc(sub, off, pts, pts);
  pkt->pkt_componentindex = st->es_index;

  streaming_message_t *sm = streaming_msg_create_pkt(pkt);
  streaming_pad_deliver(&t->s_streaming_pad, sm);
  streaming_msg_free(sm);

  /* Decrease our own reference to the packet */
  pkt_ref_dec(pkt);
}

/**
 *
 */
#if 0
static void
dump_page(tt_mag_t *ttm)
{
  int i, j, v;
  char buf[41];
  
  printf("------------------------------------------------\n");
  printf("------------------------------------------------\n");
  for(i = 0; i < 23; i++) {

    for(j = 0; j < 40; j++) {
      v = ttm->ttm_page[40 * i + j];
      v &= 0x7f;
      if(v < 32)
	v = ' ';
      buf[j] = v;
    }
    buf[j] = 0;
    printf("%s   | %x %x %x\n", buf,
	   ttm->ttm_page[40 * i + 0],
	   ttm->ttm_page[40 * i + 1],
	   ttm->ttm_page[40 * i + 2]);
  }
}
#endif


static void
tt_subtitle_deliver(service_t *t, elementary_stream_t *parent, tt_mag_t *ttm)
{
  elementary_stream_t *st;

  if(ttm->ttm_current_pts == PTS_UNSET)
    return;

  TAILQ_FOREACH(st, &t->s_components, es_link) {
     if(parent->es_pid == st->es_parent_pid &&
	ttm->ttm_curpage == st->es_pid -  PID_TELETEXT_BASE) {
       extract_subtitle(t, st, ttm, ttm->ttm_current_pts);
     }
  }
}

/**
 *
 */
static void
tt_decode_line(service_t *t, elementary_stream_t *st, uint8_t *buf)
{
  uint8_t mpag, line, s12, c;
  int page, magidx, i;
  tt_mag_t *ttm;
  tt_private_t *ttp;

  if(st->es_priv == NULL) {
    /* Allocate privdata for reassembly */
    ttp = st->es_priv = calloc(1, sizeof(tt_private_t));
  } else {
    ttp = st->es_priv;
  }

  mpag = ham_decode(buf[0], buf[1]);
  magidx = mpag & 7;
  ttm = &ttp->ttp_mags[magidx];

  line = mpag >> 3;

  switch(line) {
  case 0:
    if(ttm->ttm_curpage != 0) {

      tt_subtitle_deliver(t, st, ttm);

      if(ttm->ttm_curpage == 192)
	teletext_rundown_copy(ttp, ttm);

      memset(ttm->ttm_page, ' ', 23 * 40);
      ttm->ttm_curpage = 0;
    }

    if((page = ham_decode(buf[2], buf[3])) == 0xff)
      return;

    /* The page is BDC encoded, mag 0 is displayed as page 800+ */
    page = (magidx ?: 8) * 100 + (page >> 4) * 10 + (page & 0xf);

    ttm->ttm_curpage = page;

    s12 = ham_decode(buf[4], buf[5]);
    c = ham_decode(buf[8], buf[9]);

    ttm->ttm_lang = c >> 5;

    if(s12 & 0x80) {
      /* Erase page */
      memset(ttm->ttm_page, ' ', 23 * 40);
    }

    if(update_tt_clock(t, buf + 34))
      teletext_rundown_scan(t, ttp);

    ttm->ttm_current_pts = t->s_current_pts;
    break;

  case 1 ... 23:
    for(i = 0; i < 40; i++) {
      c = buf[i + 2] & 0x7f;
      ttm->ttm_page[i + 40 * (line - 1)] = c;
    }
    break;

  default:
    break;
  }
}


/**
 *
 */
void
teletext_input(service_t *t, elementary_stream_t *st, const uint8_t *tsb)
{
  int i, j;
  const uint8_t *x;
  uint8_t buf[42];

  x = tsb + 4;
  for(i = 0; i < 4; i++) {
    if(*x == 2 || *x == 3) {
      for(j = 0; j < 42; j++)
	buf[j] = bitreverse(x[4 + j]);
      tt_decode_line(t, st, buf);
    }
    x += 46;
  }
}



/**
 * Swedish TV4 rundown dump (page 192)
 *

  Starttid Titel                L{ngd      | 3 3 53
 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,   | 14 2c 2c
  19:00:00 TV4Nyheterna         00:14:00   | d 7 31
                                           | 20 20 20
 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,   | 14 2c 2c
  19:14:00 Lokala nyheter       00:03:00   | 1 1 31
  19:17:00 TV4Nyheterna 19.00 2 00:02:02   | 7 7 31
  19:19:02 Vinjett              00:00:03   | 6 6 31
  19:19:05 Reklam               00:02:30   | 3 3 31
  19:21:35 Vinjett              00:00:06   | 6 6 31
  19:21:41 LOKAL BILLBOARD      00:00:16   | 1 1 31
  19:21:57 Lokalt v{der         00:01:00   | 1 1 31
  19:22:57 S4NVMT1553           00:00:15   | 6 6 31
  19:23:12 V{der                00:02:00   | 7 7 31
  19:25:12 Trailer              00:01:00   | 2 2 31
  19:26:12 Vinjett              00:00:03   | 6 6 31
  19:26:15 Reklam               00:02:20   | 3 3 31
  19:28:35 Vinjett              00:00:07   | 6 6 31
  19:28:42 Hall} 16:9           00:00:30   | 7 7 31
 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,   | 14 2c 2c
                                           | 20 20 20
TV4                                        | 54 56 34
                                           | 20 20 20
*/


static int
tt_time_to_len(const uint8_t *buf)
{
  int l;
  char str[10];


  memcpy(str, buf, 8);
  str[2] = 0;
  str[5] = 0;
  str[8] = 0;
  
  l = atoi(str + 0) * 3600 + atoi(str + 3) * 60 + atoi(str + 6);
  return l;
}

/*
 * Decode the Swedish TV4 teletext rundown page to figure out if we are 
 * currently in a commercial break
 */
static void
teletext_rundown_copy(tt_private_t *ttp, tt_mag_t *ttm)
{
  /* Sanity check */
  if(memcmp((char *)ttm->ttm_page + 2, "Starttid", strlen("Starttid")) ||
     memcmp((char *)ttm->ttm_page + 11, "Titel", strlen("Titel")) ||
     memcmp((char *)ttm->ttm_page + 21 * 40, "TV4", strlen("TV4")))
    return;
  
  memcpy(ttp->ttp_rundown, ttm->ttm_page, 23 * 40);
  ttp->ttp_rundown_valid = 1;
}


static void
teletext_rundown_scan(service_t *t, tt_private_t *ttp)
{
  int i;
  uint8_t *l;
  time_t now = t->s_tt_clock, start, stop;
  th_commercial_advice_t ca;

  if(ttp->ttp_rundown_valid == 0)
    return;

  if(t->s_svcname &&
     strcmp("TV4", t->s_svcname) &&
     strcmp("TV4 HD", t->s_svcname))
    return;

  for(i = 0; i < 23; i++) {
    l = ttp->ttp_rundown + 40 * i;
    if((l[1] & 0xf0) != 0x00 || !is_tt_clock(l + 32) || !is_tt_clock(l + 2))
      continue;
    
    if(!memcmp(l + 11, "Nyhetspuff", strlen("Nyhetspuff")))
      ca = COMMERCIAL_YES;
    else
      ca = (l[1] & 0xf) == 7 ? COMMERCIAL_NO : COMMERCIAL_YES;

    start = tt_construct_unix_time(l + 2);
    stop  = start + tt_time_to_len(l + 32);
    
    if(start <= now && stop > now)
      t->s_tt_commercial_advice = ca;
  }
}
