/*
 * wiringpi setup [MODE]
 *     MODE is one of -g -1 -p, as for gpio(1)
 *     optional, if not called is equivalent to saying just "setup"
 *     may not be called after any other wiringpi use in whole program
 *     there is no way to un-setup
 *
 * Most of the rest are very similar to gpio(1):
 *   boardRev => integer (see wiringPi.h)
 *   boardId => [list MODEL REV MEM MAKER OVERVOLTED]
 *   mode PIN MODE
 *       MODEs are
 *             in out pwm pwmTone clock up down tri off
 *             alt0 alt1 alt2 alt3 alt4 alt5
 *   read PIN => 0|1
 *   write PIN 0|1
 *   aread PIN => VAL
 *   write PIN VAL
 *   pwm PIN VAL
 *   clock PIN FREQ
 *   pwm-bal|pwm-ms
 *   pwmr RANGE
 *   pwmc DIVIDER
 *   pwmTone PIN FREQ
 */
/* ---8<--- end of documentation comment --8<-- */

/*
 * wiringpi.c - wiringPi binding for Tcl
 * Copyright 2016 Ian Jackson
 *
 * 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 library; if not, see <http://www.gnu.org/licenses/>.
 */

#define _GNU_SOURCE

#include <stdio.h>

#include "chiark_tcl_wiringpi.h"

#include "wiringPi.h"

/*---------- important types and forward declarations ----------*/

#define CHECK_SETUP				\
  int rc;					\
  rc = ensure_setup(ip);			\
  if (rc) return rc; else

#define CHECK_RANGE(val, min, max, what, VAL) do{		\
    if (val < (min))						\
      return cht_staticerr(ip, what " too small"		\
			   " (must be at least" #min ")",	\
			   "WIRINGPI RANGE " VAL " MIN " #min);	\
    if (val > (max))						\
      return cht_staticerr(ip, what " too large"		\
			   " (must be at most" #max ")",	\
			   "WIRINGPI RANGE " VAL " MAX " #max);	\
  }while(0)

static int setup_done;

/*---------- operations ----------*/

static int setup_failure(Tcl_Interp *ip, int r) {
#define N 4
  Tcl_Obj *codelist[N];
  codelist[0]= cht_ret_string(ip,"WIRINGPI");
  codelist[1]= cht_ret_string(ip,"ERROR");
  codelist[2]= cht_ret_string(ip,"SETUP");
  codelist[3]= cht_ret_int(ip,r);
  Tcl_SetObjResult(ip, Tcl_NewListObj(N, codelist));
#undef N
  Tcl_SetResult(ip, (char*)"wiringpi initialisation error", TCL_STATIC);
  return TCL_ERROR;
}

static int ensure_setup(Tcl_Interp *ip) {
  if (!setup_done) {
    int r = wiringPiSetup();
    if (r) return setup_failure(ip, r);
    setup_done = 1;
  }
  return TCL_OK;
}

int cht_do_wiringpitcl_setup(ClientData cd, Tcl_Interp *ip,
			     int objc, Tcl_Obj *const *objv) {
  int r;
  int rc;

  if (setup_done)
    return cht_staticerr(ip,"wiringpi setup called again",
			 "WIRINGPI FORBIDDEN-REINIT SETUP");

  if (*objv) {
    const char *modeopt;
    rc = cht_pat_string(ip,*objv++,&modeopt);
    if (rc) return rc;
    if (modeopt[0] != '-' || !modeopt[1] || modeopt[2])
      return cht_staticerr(ip,"wiringpi setup option wrong syntax", 0);
    if (*objv)
      return cht_staticerr(ip,"wiringpi setup too many arguments", 0);
    switch (modeopt[1]) {
    case 'g':  r = wiringPiSetupGpio();                             break;
    case '1':  r = wiringPiSetupPhys();                             break;
    default:
      return cht_staticerr(ip,"wiringpi setup option unknown value", 0);
    }
  } else {
    r = wiringPiSetup();
  }
  if (r) return setup_failure(ip, r);
  return TCL_OK;
}

int cht_do_wiringpitcl_boardId(ClientData cd, Tcl_Interp *ip,
			       Tcl_Obj **result) {
#define N 5
  CHECK_SETUP;
  int ints[N];
  Tcl_Obj *objl[N];
  piBoardId(&ints[0],
	    &ints[1],
	    &ints[2],
	    &ints[3],
	    &ints[4]);
  int i;
  for (i=0; i<N; i++) objl[i]= cht_ret_int(ip, ints[i]);
  *result= Tcl_NewListObj(N,objl);
  return TCL_OK;
#undef N
}

int cht_do_wiringpitcl_boardRev(ClientData cd, Tcl_Interp *ip, int *result) {
  CHECK_SETUP;
  *result = piBoardRev();
  return TCL_OK;
}

int cht_do_wiringpitcl_pwmc(ClientData cd, Tcl_Interp *ip, int divider) {
  CHECK_SETUP;
  CHECK_RANGE(divider,1,4095,"divider for pwmc","DIVIDER");
  pwmSetClock(divider);
  return TCL_OK;
}

int cht_do_wiringpitcl_pwmr(ClientData cd, Tcl_Interp *ip, int range) {
  CHECK_SETUP;
  CHECK_RANGE(range,1,INT_MAX,"pwm range","RANGE");
  pwmSetRange(range);
  return TCL_OK;
}

int cht_do_wiringpitcl_pwm_bal(ClientData cd, Tcl_Interp *ip) {
  CHECK_SETUP;
  pwmSetMode(PWM_MODE_BAL);
  return TCL_OK;
}

int cht_do_wiringpitcl_pwm_ms(ClientData cd, Tcl_Interp *ip) {
  CHECK_SETUP;
  pwmSetMode(PWM_MODE_MS);
  return TCL_OK;
}

/*---------- families of operations ----------*/

#define SIMPLE_READER_OP(op, wpicall)				\
  int cht_do_wiringpitcl_##op(ClientData cd, Tcl_Interp *ip,	\
				int pin, int *result) {		\
    CHECK_SETUP;						\
    *result = wpicall(pin);					\
    return TCL_OK;						\
  }

SIMPLE_READER_OP(read,  digitalRead)
SIMPLE_READER_OP(aread, analogRead)

#define SIMPLE_WRITER_OP(op, min, max, wpicall)			\
  int cht_do_wiringpitcl_##op(ClientData cd, Tcl_Interp *ip,	\
			      int pin, int val) {		\
    CHECK_SETUP							\
    CHECK_RANGE(val,min,max, "value for " #op, "VAL");		\
    wpicall(pin, val);						\
    return TCL_OK;						\
  }

SIMPLE_WRITER_OP(pwm,     INT_MIN,INT_MAX, pwmWrite)
SIMPLE_WRITER_OP(awrite,  INT_MIN,INT_MAX, analogWrite)
SIMPLE_WRITER_OP(write,   0,      1,       digitalWrite)
SIMPLE_WRITER_OP(pwmTone, INT_MIN,INT_MAX, pwmToneWrite)
SIMPLE_WRITER_OP(clock,   INT_MIN,INT_MAX, gpioClockSet)

#define MODES(M)						\
  M(in,       { pinMode        (pin, INPUT); })			\
  M(input,    { pinMode        (pin, INPUT); })			\
  M(out,      { pinMode        (pin, OUTPUT); })		\
  M(output,   { pinMode        (pin, OUTPUT); })		\
  M(pwm,      { pinMode        (pin, PWM_OUTPUT); })		\
  M(pwmTone,  { pinMode        (pin, PWM_TONE_OUTPUT); })	\
  M(clock,    { pinMode        (pin, GPIO_CLOCK); })		\
  M(up,       { pullUpDnControl(pin, PUD_UP); })		\
  M(down,     { pullUpDnControl(pin, PUD_DOWN); })		\
  M(tri,      { pullUpDnControl(pin, PUD_OFF); })		\
  M(off,      { pullUpDnControl(pin, PUD_OFF); })		\
  M(alt0,     { pinModeAlt(pin, 0b100); })			\
  M(alt1,     { pinModeAlt(pin, 0b101); })			\
  M(alt2,     { pinModeAlt(pin, 0b110); })			\
  M(alt3,     { pinModeAlt(pin, 0b111); })			\
  M(alt4,     { pinModeAlt(pin, 0b011); })			\
  M(alt5,     { pinModeAlt(pin, 0b010); })

#define MODE_FUNC(name, body) static void mode_##name(int pin) { body }
  MODES(MODE_FUNC)

const WiringPiTclModeInfo cht_wiringpitclmodeinfo_entries[] = {
#define MODEINFO_ENTRY(name, body) { #name, mode_##name },
  MODES(MODEINFO_ENTRY)
  { 0 },
};

int cht_do_wiringpitcl_mode(ClientData cd, Tcl_Interp *ip, int pin,
			    const WiringPiTclModeInfo *mode) {
  CHECK_SETUP;
  mode->func(pin);
  return TCL_OK;
}


/*---------- main hooks for tcl ----------*/

CHT_INIT(wiringpi, {}, CHTI_COMMANDS(cht_wiringpitoplevel_entries))
