package org.freertr.clnt;

import org.freertr.addr.addrIP;
import org.freertr.cfg.cfgAll;
import org.freertr.pack.packHolder;
import org.freertr.pack.packNtp;
import org.freertr.pipe.pipeSide;
import org.freertr.serv.servGeneric;
import org.freertr.util.bits;
import org.freertr.util.debugger;
import org.freertr.util.logger;

/**
 * network time protocol (rfc5905) client
 *
 * @author matecsaba
 */
public class clntNtp implements Runnable {

    /**
     * name of ntp server
     */
    public String serverName;

    /**
     * got time
     */
    public long tim1;

    /**
     * got diff
     */
    public long tim2;

    /**
     * new diff
     */
    public long tim3;

    private boolean need2run;

    /**
     * create new client
     *
     * @param srv server name
     */
    public clntNtp(String srv) {
        serverName = srv;
    }

    public String toString() {
        return serverName;
    }

    /**
     * start work
     */
    public void startWork() {
        need2run = true;
        logger.startThread(this);
    }

    /**
     * stop work
     */
    public void stopWork() {
        need2run = false;
    }

    /**
     * do one poll
     *
     * @return false on success, true on error
     */
    public boolean doWork() {
        addrIP serverAddr = clntDns.justResolv(serverName, 0);
        if (serverAddr == null) {
            return true;
        }
        clntProxy prx = cfgAll.getClntPrx(cfgAll.timeProxy);
        if (prx == null) {
            return true;
        }
        pipeSide pipe = prx.doConnect(servGeneric.protoUdp, serverAddr, packNtp.port, "ntp");
        if (pipe == null) {
            return true;
        }
        packHolder pckBin = new packHolder(true, true);
        packNtp pckNtp = new packNtp();
        pckNtp.ver = 3;
        pckNtp.mode = packNtp.modClnt;
        tim1 = bits.getTime();
        pckNtp.refTime = packNtp.encode(tim1 + cfgAll.timeServerOffset);
        pckNtp.sendTime = packNtp.encode(tim1 + cfgAll.timeServerOffset);
        pckNtp.createPacket(pckBin);
        if (debugger.clntNtpTraf) {
            logger.debug("tx " + pckNtp);
        }
        pckBin.pipeSend(pipe, 0, pckBin.dataSize(), 2);
        pipe.setTime(5000);
        pckBin = pipe.readPacket(true);
        pipe.setClose();
        if (pckBin == null) {
            return true;
        }
        tim2 = bits.getTime();
        pckNtp = new packNtp();
        if (pckNtp.parsePacket(pckBin)) {
            return true;
        }
        tim1 = packNtp.decode(pckNtp.sendTime) + ((tim2 - tim1) / 2);
        tim2 = tim1 - tim2;
        tim3 = cfgAll.timeServerOffset - tim2;
        if (debugger.clntNtpTraf) {
            logger.debug("rx " + pckNtp);
            logger.debug("offsets: old=" + cfgAll.timeServerOffset + " new=" + tim2 + " diff=" + tim3);
        }
        if (tim3 < 0) {
            tim3 = -tim3;
        }
        return false;
    }

    /**
     * do one sync
     */
    public void doSync() {
        if (tim3 > 1000) {
            logger.info("setting clock to " + bits.time2str(cfgAll.timeZoneName, bits.getTime() + tim2, 3));
        }
        cfgAll.timeServerOffset = tim2;
    }

    public void run() {
        if (debugger.clntNtpTraf) {
            logger.debug("started");
        }
        for (;;) {
            bits.sleep(60000);
            if (!need2run) {
                break;
            }
            try {
                if (doWork()) {
                    continue;
                }
                doSync();
            } catch (Exception e) {
                logger.traceback(e);
            }
        }
        if (debugger.clntNtpTraf) {
            logger.debug("stopped");
        }
    }

}
