/*
 * Copyright (c) 2011, Kustaa Nyholm / SpareTimeLabs
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list 
 * of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright notice, this 
 * list of conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution.
 *  
 * Neither the name of the Kustaa Nyholm or SpareTimeLabs nor the names of its 
 * contributors may be used to endorse or promote products derived from this software 
 * without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

package jtermios.macosx;

import com.sun.jna.*;
import java.io.File;

import java.util.*;
import java.util.regex.Pattern;

import jtermios.JTermios;
import jtermios.Pollfd;
import jtermios.Termios;
import jtermios.TimeVal;

import static jtermios.JTermios.*;
import static jtermios.JTermios.JTermiosLogging.log;

public class JTermiosImpl implements jtermios.JTermios.JTermiosInterface {
	private static int IOSSIOSPEED = 0x80045402;
	private static String DEVICE_DIR_PATH = "/dev/";
	static C_lib_DirectMapping m_ClibDM;
	static C_lib m_Clib;
//	static NonDirectCLib m_ClibND;

	static {
                Native.setPreserveLastError(true);
//		m_ClibND = (NonDirectCLib) Native.loadLibrary(Platform.C_LIBRARY_NAME, NonDirectCLib.class);
		Native.register(C_lib_DirectMapping.class, NativeLibrary.getInstance(Platform.C_LIBRARY_NAME));
		m_ClibDM = new C_lib_DirectMapping();
		m_Clib = m_ClibDM;
	}

	public static class C_lib_DirectMapping implements C_lib {

		native public int pipe(int[] fds);

		native public int tcdrain(int fd);

		native public void cfmakeraw(termios termios);

		native public int fcntl(int fd, int cmd, int arg);

		native public int ioctl(int fd, NativeLong cmd);

		native public int ioctl(int fd, NativeLong cmd, int arg);

		native public int ioctl(int fd, NativeLong cmd, int[] arg);

		native public int open(String path, int flags);

		native public int close(int fd);

		native public int tcgetattr(int fd, termios termios);

		native public int tcsetattr(int fd, int cmd, termios termios);

		native public int cfsetispeed(termios termios, NativeLong i);

		native public int cfsetospeed(termios termios, NativeLong i);

		native public NativeLong cfgetispeed(termios termios);

		native public NativeLong cfgetospeed(termios termios);

		native public NativeSize write(int fd, byte[] buffer, NativeSize count);

		native public NativeSize read(int fd, byte[] buffer, NativeSize count);

		native public int tcflush(int fd, int qs);

		native public void perror(String msg);

		native public int tcsendbreak(int fd, int duration);

		native public int select(int n, fd_set read, fd_set write, fd_set error, timeval timeout);

	}

	public interface C_lib extends com.sun.jna.Library {

		public int pipe(int[] fds);

		public int tcdrain(int fd);

		public void cfmakeraw(termios termios);

		public int fcntl(int fd, int cmd, int arg);

		public int ioctl(int fd, NativeLong cmd, int[] arg);

		public int open(String path, int flags);

		public int close(int fd);

		public int tcgetattr(int fd, termios termios);

		public int tcsetattr(int fd, int cmd, termios termios);

		public int cfsetispeed(termios termios, NativeLong i);

		public int cfsetospeed(termios termios, NativeLong i);

		public NativeLong cfgetispeed(termios termios);

		public NativeLong cfgetospeed(termios termios);

		public NativeSize write(int fd, byte[] buffer, NativeSize count);

		public NativeSize read(int fd, byte[] buffer, NativeSize count);

		public int tcflush(int fd, int qs);

		public void perror(String msg);

		public int tcsendbreak(int fd, int duration);

		public int select(int n, fd_set read, fd_set write, fd_set error, timeval timeout);

	}

//	public interface NonDirectCLib extends com.sun.jna.Library {
//
//		public int ioctl(int fd, NativeLong cmd, NativeLong[] arg);
//
//		//       public int poll(pollfd.ByReference pfds, int nfds, int timeout);
//	}

	static public class timeval extends Structure {

		public NativeLong tv_sec;
		public NativeLong tv_usec;

		@Override
		protected List getFieldOrder() {
			return Arrays.asList(//
					"tv_sec",//
					"tv_usec"//
			);
		}

		public timeval(jtermios.TimeVal timeout) {
			tv_sec = new NativeLong(timeout.tv_sec);
			tv_usec = new NativeLong(timeout.tv_usec);
		}
	}

	static public class pollfd extends Structure {

		public static class ByReference extends pollfd implements Structure.ByReference {
		}

		public int fd;
		public short events;
		public short revents;

		@Override
		protected List getFieldOrder() {
			return Arrays.asList(//
					"fd",//
					"events",//
					"revents"//
			);
		}

		public pollfd() {
		}

		public pollfd(Pollfd pfd) {
			fd = pfd.fd;
			events = pfd.events;
			revents = pfd.revents;
		}
	}

	static public class fd_set extends Structure implements FDSet {

		private final static int NFBBITS = 32;
		private final static int fd_count = 1024;
		public int[] fd_array = new int[(fd_count + NFBBITS - 1) / NFBBITS];

		@Override
		protected List getFieldOrder() {
			return Arrays.asList(//
					"fd_array"//
			);
		}

		public void FD_SET(int fd) {
			fd_array[fd / NFBBITS] |= (1 << (fd % NFBBITS));
		}

		public boolean FD_ISSET(int fd) {
			return (fd_array[fd / NFBBITS] & (1 << (fd % NFBBITS))) != 0;
		}

		public void FD_ZERO() {
			Arrays.fill(fd_array, 0);
		}

		public void FD_CLR(int fd) {
			fd_array[fd / NFBBITS] &= ~(1 << (fd % NFBBITS));
		}

	}

	static public class termios extends Structure {

		public NativeLong c_iflag;
		public NativeLong c_oflag;
		public NativeLong c_cflag;
		public NativeLong c_lflag;
		public byte[] c_cc = new byte[20];
		public NativeLong c_ispeed;
		public NativeLong c_ospeed;

		@Override
		protected List getFieldOrder() {
			return Arrays.asList(//
					"c_iflag",//
					"c_oflag",//
					"c_cflag",//
					"c_lflag",//
					"c_cc",//
					"c_ispeed",//
					"c_ospeed"//
			);
		}

		public termios() {
		}

		public termios(jtermios.Termios t) {
			c_iflag.setValue(t.c_iflag);
			c_oflag.setValue(t.c_oflag);
			c_cflag.setValue(t.c_cflag);
			c_lflag.setValue(t.c_lflag);
			System.arraycopy(t.c_cc, 0, c_cc, 0, Math.min(t.c_cc.length, c_cc.length));
			c_ispeed.setValue(t.c_ispeed);
			c_ospeed.setValue(t.c_ospeed);
		}

		public void update(jtermios.Termios t) {
			t.c_iflag = c_iflag.intValue();
			t.c_oflag = c_oflag.intValue();
			t.c_cflag = c_cflag.intValue();
			t.c_lflag = c_lflag.intValue();
			System.arraycopy(c_cc, 0, t.c_cc, 0, Math.min(t.c_cc.length, c_cc.length));
			t.c_ispeed = c_ispeed.intValue();
			t.c_ospeed = c_ospeed.intValue();
		}
	}

	public JTermiosImpl() {
		log = log && log(1, "instantiating %s\n", getClass().getCanonicalName());
	}

	public int errno() {
		return Native.getLastError();
	}

	public void cfmakeraw(Termios termios) {
		termios t = new termios(termios);
		m_Clib.cfmakeraw(t);
		t.update(termios);
	}

	public int fcntl(int fd, int cmd, int arg) {
		return m_Clib.fcntl(fd, cmd, arg);
	}

	public int tcdrain(int fd) {
		return m_Clib.tcdrain(fd);
	}

	public int cfgetispeed(Termios termios) {
		return m_Clib.cfgetispeed(new termios(termios)).intValue();
	}

	public int cfgetospeed(Termios termios) {
		return m_Clib.cfgetospeed(new termios(termios)).intValue();
	}

	public int cfsetispeed(Termios termios, int speed) {
		termios t = new termios(termios);
		int ret = m_Clib.cfsetispeed(t, new NativeLong(speed));
		t.update(termios);
		return ret;
	}

	public int cfsetospeed(Termios termios, int speed) {
		termios t = new termios(termios);
		int ret = m_Clib.cfsetospeed(t, new NativeLong(speed));
		t.update(termios);
		return ret;
	}

	public int open(String s, int t) {
		if (s != null && !s.startsWith("/")) {
			s = DEVICE_DIR_PATH + s;
		}
		return m_Clib.open(s, t);
	}

	public int read(int fd, byte[] buffer, int len) {
		return m_Clib.read(fd, buffer, new NativeSize(len)).intValue();
	}

	public int write(int fd, byte[] buffer, int len) {
		return m_Clib.write(fd, buffer, new NativeSize(len)).intValue();
	}

	public int close(int fd) {
		return m_Clib.close(fd);
	}

	public int tcflush(int fd, int b) {
		return m_Clib.tcflush(fd, b);
	}

	public int tcgetattr(int fd, Termios termios) {
		termios t = new termios();
		int ret = m_Clib.tcgetattr(fd, t);
		t.update(termios);
		return ret;
	}

	public void perror(String msg) {
		m_Clib.perror(msg);
	}

	public int tcsendbreak(int fd, int duration) {
		// If duration is not zero, it sends zero-valued bits for duration*N seconds,
		// where N is at least 0.25, and not more than 0.5.
		return m_Clib.tcsendbreak(fd, duration / 250);
	}

	public int tcsetattr(int fd, int cmd, Termios termios) {
		return m_Clib.tcsetattr(fd, cmd, new termios(termios));
	}

	public int select(int nfds, FDSet rfds, FDSet wfds, FDSet efds, TimeVal timeout) {
		timeval tout = null;
		if (timeout != null) {
			tout = new timeval(timeout);
		}

		return m_Clib.select(nfds, (fd_set) rfds, (fd_set) wfds, (fd_set) efds, tout);
	}

	public int poll(Pollfd fds[], int nfds, int timeout) {
		throw new UnsupportedOperationException("Poll not supported");
	}

	public boolean canPoll() {
		return false;
	}

	public FDSet newFDSet() {
		return new fd_set();
	}

	public int ioctl(int fd, int cmd, int... data) {
                // At this time, all ioctl commands we have defined are either no parameter or 4 byte parameter.
                return m_Clib.ioctl(fd, new NativeLong(0xFFFFFFFFL & cmd), data);
	}

	public List<String> getPortList() {
		File dir = new File(DEVICE_DIR_PATH);
		if (!dir.isDirectory()) {
			log = log && log(1, "device directory %s does not exist\n", DEVICE_DIR_PATH);
			return null;
		}
		String[] devs = dir.list();
		LinkedList<String> list = new LinkedList<String>();

		Pattern p = JTermios.getPortNamePattern(this);
		if (devs != null) {
			for (int i = 0; i < devs.length; i++) {
				String s = devs[i];
				if (p.matcher(s).matches())
					list.add(s);
			}
		}
		return list;
	}

	public String getPortNamePattern() {
		return "^(tty\\.|cu\\.).*";
	}

	public void shutDown() {

	}

	public int setspeed(int fd, Termios termios, int speed) {
		int r;
		r = cfsetispeed(termios, speed);
		if (r == 0)
			r = cfsetospeed(termios, speed);
		if (r == 0)
			r = tcsetattr(fd, TCSANOW, termios);
		if (r != 0) {
			// Darell Tan had patched RXTX with this sequence, so lets try this
			if (cfsetispeed(termios, B9600) == 0 && cfsetospeed(termios, B9600) == 0 && tcsetattr(fd, TCSANOW, termios) == 0)
				r = ioctl(fd, IOSSIOSPEED, speed);
		}
		return r;
	}

	public int pipe(int[] fds) {
		return m_Clib.pipe(fds);
	}
}