/* dvbtune - tune.c

   Copyright (C) Dave Chapman 2001,2002

   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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
   Or, point your browser to http://www.gnu.org/copyleft/gpl.html

*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <unistd.h>
#include <error.h>
#include <time.h>
#include <errno.h>

#include <linux/dvb/dmx.h>
#include <linux/dvb/frontend.h>

#ifdef USE_CURSES
#include <curses.h>
#else
#define wprintw(window, format, args...) printf(format, ## args)
#define printw(format, args...) printf(format, ## args)
#define WINDOW void
#define wrefresh(window)
#endif

#include "tune.h"


void print_status(WINDOW *pWindow, fe_status_t festatus) {
  wprintw(pWindow,"FE_STATUS:");
  if (festatus & FE_HAS_SIGNAL) wprintw(pWindow," FE_HAS_SIGNAL");
  if (festatus & FE_TIMEDOUT) wprintw(pWindow," FE_TIMEDOUT");
  if (festatus & FE_HAS_LOCK) wprintw(pWindow," FE_HAS_LOCK");
  if (festatus & FE_HAS_CARRIER) wprintw(pWindow," FE_HAS_CARRIER");
  if (festatus & FE_HAS_VITERBI) wprintw(pWindow," FE_HAS_VITERBI");
  if (festatus & FE_HAS_SYNC) wprintw(pWindow," FE_HAS_SYNC");
  wprintw(pWindow,"\n");
  wrefresh(pWindow);
}


struct diseqc_cmd {
   struct dvb_diseqc_master_cmd cmd;
   uint32_t wait;
};

static int diseqc_send_msg(WINDOW *pWindow, int fd, fe_sec_voltage_t v, struct diseqc_cmd *cmd,
		     fe_sec_tone_mode_t t, unsigned char sat_no)
{
	int nCounter = 0;
   if(sat_no >= 1 && sat_no <= 4)	//1.x compatible equipment
   {
		wprintw(pWindow, "Sending command: ");
		for (nCounter = 0; nCounter < cmd->cmd.msg_len; nCounter++) {
			wprintw(pWindow, "0x%02x ", cmd->cmd.msg[nCounter]);
		}
		wprintw(pWindow, "\n");
		wrefresh(pWindow);
    if(ioctl(fd, FE_DISEQC_SEND_MASTER_CMD, &cmd->cmd) < 0)
   	return -1;
    //usleep(cmd->wait * 1000);
    //cmd->cmd.msg[0] = 0xe1;
    //if(ioctl(fd, FE_DISEQC_SEND_MASTER_CMD, &cmd->cmd) < 0)
   	//return -1;
    //usleep(cmd->wait * 1000);
    //usleep(1000 * 1000);
   }
   else	//A or B simple diseqc
   {
    wprintw(pWindow, "SETTING SIMPLE %c BURST\n", sat_no);
    wrefresh(pWindow);
    if(ioctl(fd, FE_DISEQC_SEND_BURST, (sat_no == 'B' ? SEC_MINI_B : SEC_MINI_A)) < 0)
   	return -1;
    usleep(15 * 1000);
   }

   return 0;
}

/* digital satellite equipment control,
 * specification is available from http://www.eutelsat.com/
 */

static int do_diseqc(WINDOW *pWindow, int fd, unsigned char sat_no, int polv, int hi_lo, int nPositionerNumber, int nMove, int nStore)
{
    struct diseqc_cmd cmd =  { {{0xe0, 0x10, 0x38, 0xf0, 0x00, 0x00}, 4}, 0 };
	int nReturn = 0;
	int nCounter = 0;

   wprintw(pWindow, "Setting sat no %d\n", sat_no);
   wrefresh(pWindow);
   if(ioctl(fd, FE_SET_VOLTAGE, SEC_VOLTAGE_18) < 0)
   	return -1;
   if(ioctl(fd, FE_SET_TONE, SEC_TONE_OFF) < 0)
   	return -1;
    usleep(200000);
    if(sat_no != 0) {
		unsigned char d = sat_no;

		/* param: high nibble: reset bits, low nibble set bits,
		* bits are: option, position, polarizaion, band
		*/
		sat_no--;
		cmd.cmd.msg[3] =
				0xf0 | (((sat_no * 4) & 0x0f) | (polv ? 0 : 2) | (hi_lo ? 1 : 0));

		nReturn = diseqc_send_msg(pWindow, fd,
				SEC_VOLTAGE_18,
				&cmd,
				SEC_TONE_OFF,
				d);
		if (nReturn != 0) {
			return nReturn;
		}
		usleep(1000000);
	}
	if (nPositionerNumber != 0) {
		// Diseq positioner request
		wprintw(pWindow, "Moving postitioner to stored position %d\n", nPositionerNumber);
		wrefresh(pWindow);
		cmd.cmd.msg[1] = 0x31; // Address of any positioner
		cmd.cmd.msg[2] = 0x6b; // Drive motor to Position
		cmd.cmd.msg[3] = nPositionerNumber;
		nReturn =  diseqc_send_msg(pWindow, fd,
				SEC_VOLTAGE_18,
				&cmd,
				SEC_TONE_OFF,
				1);
		if (nReturn != 0) {
			return nReturn;
		}
		usleep(1000000);
	   wprintw(pWindow, "Setting tone %s and voltage %dV\n", (hi_lo ? "ON" : "OFF"), (polv ? 13 : 18));
	   wrefresh(pWindow);

	   if(ioctl(fd, FE_SET_VOLTAGE, polv ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18) < 0)
		return -1;
	   if(ioctl(fd, FE_SET_TONE, hi_lo ? SEC_TONE_ON : SEC_TONE_OFF) < 0)
		return -1;
	   return nReturn;
 	} else if (nMove != 0) {
		// Diseq positioner request
		wprintw(pWindow, "Moving postitioner %s by %d steps\n", nMove > 0 ? "east" : "west", abs(nMove));
		wrefresh(pWindow);
		cmd.cmd.msg[1] = 0x31; // Address of any positioner
		cmd.cmd.msg[2] = nMove > 0 ? 0x68 : 0x69; // Drive motor to east or west
		cmd.cmd.msg[3] = (signed char) (0x7f & abs(nMove)) * -1;
		nReturn =  diseqc_send_msg(pWindow, fd,
				SEC_VOLTAGE_18,
				&cmd,
				SEC_TONE_OFF,
				1);
		if (nReturn != 0) {
			return nReturn;
		}
	wprintw(pWindow, "Setting tone %s and voltage %dV\n", (hi_lo ? "ON" : "OFF"), (polv ? 13 : 18));
	wrefresh(pWindow);

	   if(ioctl(fd, FE_SET_VOLTAGE, polv ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18) < 0)
		return -1;
	   if(ioctl(fd, FE_SET_TONE, hi_lo ? SEC_TONE_ON : SEC_TONE_OFF) < 0)
		return -1;
	   return nReturn;
	 } else if (nStore != 0) {
		// Diseq positioner request
		wprintw(pWindow, "Storing position in %d\n", nStore);
		wrefresh(pWindow);
		cmd.cmd.msg[1] = 0x31; // Address of any positioner
		cmd.cmd.msg[2] = 0x6a; // Store position
		cmd.cmd.msg[3] = nStore;
		nReturn =  diseqc_send_msg(pWindow, fd,
				SEC_VOLTAGE_18,
				&cmd,
				SEC_TONE_OFF,
				1);
		if (nReturn != 0) {
			return nReturn;
		}
	   if(ioctl(fd, FE_SET_VOLTAGE, polv ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18) < 0)
		return -1;
	   if(ioctl(fd, FE_SET_TONE, hi_lo ? SEC_TONE_ON : SEC_TONE_OFF) < 0)
		return -1;
	   return nReturn;
	} else 	//only tone and voltage
    {
	fe_sec_voltage_t voltage;

	wprintw(pWindow, "Setting tone %s and voltage %dV\n", (hi_lo ? "ON" : "OFF"), (polv ? 13 : 18));
	wrefresh(pWindow);

	if(ioctl(fd, FE_SET_VOLTAGE, (polv ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18)) < 0)
   	    return -1;

	if(ioctl(fd, FE_SET_TONE, (hi_lo ? SEC_TONE_ON : SEC_TONE_OFF)) < 0)
   	    return -1;

	usleep(15 * 1000);

	return 0;
    }
}

int diseqc_move(WINDOW *pWindow, int fd, int nSteps, int nTone) {
    struct diseqc_cmd cmd =  { {{0xe0, 0x10, 0x38, 0xf0, 0x00, 0x00}, 4}, 0 };
	int nReturn = 0;
	   if(ioctl(fd, FE_SET_TONE, SEC_TONE_OFF) < 0)
		return -1;
		usleep(10000);
                wprintw(pWindow, "Moving postitioner %s by %d steps\n", nSteps > 0 ? "east" : "west", abs(nSteps));
                wrefresh(pWindow);
                cmd.cmd.msg[1] = 0x31; // Address of any positioner
                cmd.cmd.msg[2] = nSteps > 0 ? 0x68 : 0x69; // Drive motor to east or west
                cmd.cmd.msg[3] = (signed char) (0x7f & abs(nSteps)) * -1;
                nReturn =  diseqc_send_msg(pWindow, fd,
                                SEC_VOLTAGE_18,
                                &cmd,
                                SEC_TONE_OFF,
                                1);
				if (nReturn != 0) {
					return nReturn;
				}
	   if(ioctl(fd, FE_SET_TONE, nTone ? SEC_TONE_ON : SEC_TONE_OFF) < 0)
		return -1;

    return nReturn;
}

int check_status(WINDOW *pWindow, int fd_frontend,int type, struct dvb_frontend_parameters* feparams,unsigned int base) {
  int32_t strength;
  fe_status_t festatus;
  struct pollfd pfd[1];
  int status, locks=0, ok=0;
  time_t tm1, tm2;

  if (ioctl(fd_frontend,FE_SET_FRONTEND,feparams) < 0) {
    perror("ERROR tuning channel\n");
    return -1;
  }

  pfd[0].fd = fd_frontend;
  pfd[0].events = POLLPRI;

  tm1 = tm2 = time((time_t*) NULL);
  printw("Getting frontend status\n");
  wrefresh(pWindow);
  while (!ok) {
    festatus = 0;
    if (poll(pfd,1,3000) > 0){
      if (pfd[0].revents & POLLPRI){
        if(ioctl(fd_frontend,FE_READ_STATUS,&festatus) >= 0)
          if(festatus & FE_HAS_LOCK)
	    locks++;
      }
    }
    usleep(10000);
    tm2 = time((time_t*) NULL);
    if((festatus & FE_TIMEDOUT) || (locks >= 2) || (tm2 - tm1 >= 3))
	    ok = 1;
  }

  if (festatus & FE_HAS_LOCK) {
      if(ioctl(fd_frontend,FE_GET_FRONTEND,feparams) >= 0) {
        switch(type) {
         case FE_OFDM:
           printw("Event:  Frequency: %d\n",feparams->frequency);
	   wrefresh(pWindow);
           break;
         case FE_QPSK:
           printw("Event:  Frequency: %d\n",(unsigned int)(feparams->frequency + base));
           printw("        SymbolRate: %d\n",feparams->u.qpsk.symbol_rate);
           printw("        FEC_inner:  %d\n",feparams->u.qpsk.fec_inner);
           printw("\n");
	   wrefresh(pWindow);
           break;
         case FE_QAM:
           printw("Event:  Frequency: %d\n",feparams->frequency);
           printw("        SymbolRate: %d\n",feparams->u.qpsk.symbol_rate);
           printw("        FEC_inner:  %d\n",feparams->u.qpsk.fec_inner);
	   wrefresh(pWindow);
           break;
#ifdef DVB_ATSC
	case FE_ATSC:
	   printw( "Event:  Frequency: %d\n",feparams->frequency);
	   printw( "        Modulation: %d\n",feparams->u.vsb.modulation);
	   wrefresh(pWindow);
	   break;
#endif
         default:
           break;
        }
      }

      strength=0;
      if(ioctl(fd_frontend,FE_READ_BER,&strength) >= 0)
        printw("Bit error rate: %d\n",strength);

      strength=0;
      if(ioctl(fd_frontend,FE_READ_SIGNAL_STRENGTH,&strength) >= 0)
        printw("Signal strength: %d\n",strength);

      strength=0;
      if(ioctl(fd_frontend,FE_READ_SNR,&strength) >= 0)
        printw("SNR: %d\n",strength);

      strength=0;
      if(ioctl(fd_frontend,FE_READ_UNCORRECTED_BLOCKS,&strength) >= 0)
        printw("UNC: %d\n",strength);

      wrefresh(pWindow);
      print_status(pWindow, festatus);
    } else {
    printw("Not able to lock to the signal on the given frequency\n");
    wrefresh(pWindow);
    return -1;
  }
  return 0;
}

int tune_it(WINDOW *pWindow, 
		int fd_frontend,
		unsigned int freq,
		unsigned int srate,
		char pol,
		int tone,
		fe_spectral_inversion_t specInv,
		unsigned char diseqc,
		fe_modulation_t modulation,
		fe_code_rate_t HP_CodeRate,
		fe_transmit_mode_t TransmissionMode,
		fe_guard_interval_t guardInterval,
		fe_bandwidth_t bandwidth,
		fe_code_rate_t LP_CodeRate,
		fe_hierarchy_t hier,
		fe_delivery_system_t delivery_system,
		int nPositionerNumber,
		int nMove,
		int nStore,
		int nSearch,
		int nPilot,
		int nTuneCont,
		int disable_power
		) {

	uint16_t strength;
	uint32_t ber;
	uint16_t snr;
	int i, j;
	int nWaitTime = 50;
	int nMoves = 0;

	pol = toupper(pol);

	if (disable_power) {
		if ((ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_OFF)))
			return -1;
		wprintw(pWindow, "Voltage set to OFF\n");
		wrefresh(pWindow);
	} else {
		if ((ioctl(fd_frontend, FE_SET_VOLTAGE, pol == 'V' ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18)))
			return -1;
		wprintw(pWindow, "Voltage set to %s\n", pol == 'V' ? "13 (V)" : "18 (H)");
		wrefresh(pWindow);
	}

	sleep(1);

	if (!disable_power) {
		if(do_diseqc(pWindow, fd_frontend, diseqc, (pol == 'V' ? 1 : 0), tone, nPositionerNumber, nMove, 0) == 0) {
			wprintw(pWindow, "DISEQC SETTING SUCCEDED\n");
			wrefresh(pWindow);
		} else  {
			wprintw(pWindow, "DISEQC SETTING FAILED\n");
			wrefresh(pWindow);
			return -1;
		}
	} else {
		nSearch = 0;
		nStore = 0;
	}

	struct dvb_frontend_event ev;
	struct dtv_property p_tune[] = {
		{ .cmd = DTV_DELIVERY_SYSTEM,	.u.data = delivery_system },
		{ .cmd = DTV_FREQUENCY,			.u.data = freq },
		{ .cmd = DTV_MODULATION,		.u.data = modulation },
		{ .cmd = DTV_SYMBOL_RATE,		.u.data = srate },
		{ .cmd = DTV_INNER_FEC,			.u.data = HP_CodeRate },
		{ .cmd = DTV_INVERSION,			.u.data = specInv },
		{ .cmd = DTV_ROLLOFF,			.u.data = ROLLOFF_AUTO },
		{ .cmd = DTV_BANDWIDTH_HZ,		.u.data = bandwidth },
		{ .cmd = DTV_PILOT,				.u.data = nPilot },
		{ .cmd = DTV_TUNE },
	};
	struct dtv_properties cmdseq_tune = {
		.num = 10,
		.props = p_tune
	};

	/* discard stale QPSK events */
	while (1) {
		if (ioctl(fd_frontend, FE_GET_EVENT, &ev) == -1)
			break;
	}

	if ((ioctl(fd_frontend, FE_SET_PROPERTY, &cmdseq_tune)) == -1) {
		wprintw(pWindow, "FE_SET_PROPERTY TUNE failed. Error %d", errno);
		wrefresh(pWindow);
		return -1;
	}
	// wait for zero status indicating start of tunning
	do {
		ioctl(fd_frontend, FE_GET_EVENT, &ev);
	}
	while(ev.status != 0);

	if (nPositionerNumber > 0 || nMove != 0) {
		nWaitTime = 200;
	}

	if (nSearch != 0) {
		wprintw(pWindow, "Search mode going %s for %d steps\n", nSearch > 0 ? "east" : "west", abs(nSearch));
		wrefresh(pWindow);
	}

	// Wait for tunning
	for (i = 0; (i < nWaitTime || nTuneCont || (nSearch != 0 && (nMoves < abs(nSearch)))); i++) {
		//wprintw(pWindow, "i: %d nWaitTime: %d nSearch: %d nMoves: %d abs(nSearch): %d\n", i, nWaitTime, nSearch, nMoves, abs(nSearch));
		if ((nSearch != 0) && (nMoves < abs(nSearch)) && ((i+1) % 10 == 0)) {
			wprintw(pWindow, "Moving 1 step %s\n", nSearch > 0 ? "east" : "west");
			wrefresh(pWindow);
			if(do_diseqc(pWindow, fd_frontend, diseqc, (pol == 'V' ? 1 : 0), tone, 0, nSearch < 0 ? -1 : 1, 0) == 0) {
				wprintw(pWindow, "DISEQC SETTING SUCCEDED\n");
				wrefresh(pWindow);
			} else  {
				wprintw(pWindow, "DISEQC SETTING FAILED\n");
				wrefresh(pWindow);
				return -1;
			}
			nMoves++;
			sleep(2);
		}

		usleep (1000000);

		if (ioctl(fd_frontend, FE_GET_EVENT, &ev) == -1) {
			// no answer, consider it as not locked situation
			ev.status = 0;
		}

		wprintw(pWindow, ">>> tuning status == 0x%02X\n", ev.status);
		wrefresh(pWindow);

		// Tuning succeed
		if(ev.status & FE_HAS_LOCK) {
			if (nStore != 0) {
				if(do_diseqc(pWindow, fd_frontend, diseqc, (pol == 'V' ? 1 : 0), tone, 0, 0, nStore) == 0) {
					wprintw(pWindow, "DISEQC SETTING SUCCEDED\n");
					wrefresh(pWindow);
				} else  {
					wprintw(pWindow, "DISEQC SETTING FAILED\n");
					wrefresh(pWindow);
					return -1;
				}
			}
			/* Remove duplicate entries for the same frequency that were created for other delivery systems */

			struct dtv_property p[] = {
				{ .cmd = DTV_DELIVERY_SYSTEM },
				{ .cmd = DTV_MODULATION },
				{ .cmd = DTV_INNER_FEC },
				{ .cmd = DTV_INVERSION },
				{ .cmd = DTV_ROLLOFF },
			};

			struct dtv_properties cmdseq = {
				.num = 5,
				.props = p
			};

			// get the actual parameters from the driver for that channel
			if ((ioctl(fd_frontend, FE_GET_PROPERTY, &cmdseq)) == -1) {
				wprintw(pWindow, "FE_GET_PROPERTY failed");
				wrefresh(pWindow);
				return -1;
			}

			return 0;
		}
	}

	wprintw(pWindow, ">>> tuning failed!!!\n");
	wrefresh(pWindow);

	exit(-1);
}

int get_signal(int nFrontEnd, int *nStrength, int *nBER, int *nSNR, int *nUncorrected) {
	uint16_t strength;
	uint32_t ber;
	uint16_t snr;
	uint32_t uncorrected;

	if ((ioctl(nFrontEnd, FE_READ_SIGNAL_STRENGTH, &strength)) == -1) {
		return -1;
	}
	if ((ioctl(nFrontEnd, FE_READ_BER, &ber)) == -1) {
		return -1;
	}
	if ((ioctl(nFrontEnd, FE_READ_SNR, &snr)) == -1) {
		return -1;
	}
	if ((ioctl(nFrontEnd, FE_READ_UNCORRECTED_BLOCKS, &uncorrected)) == -1) {
		uncorrected = 0;
	}

	*nStrength = strength;
	*nBER = ber;
	*nSNR = snr;
	*nUncorrected = uncorrected;
	return 0;
}

