//
//   Copyright 2018-2020  SenX S.A.S.
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//
package io.warp10.plugins.tcp;

import io.warp10.script.MemoryWarpScriptStack;
import io.warp10.script.WarpScriptStack;
import io.warp10.script.WarpScriptStack.Macro;
import io.warp10.script.WarpScriptStackRegistry;
import io.warp10.warp.sdk.AbstractWarp10Plugin;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.LinkedBlockingQueue;

public class TCPClient implements Runnable {

  private static final Logger LOG = LoggerFactory.getLogger(TCPClient.class);

  private final Socket socket;
  private final Macro partitioner;

  private final BufferedReader reader;
  private final LinkedBlockingQueue<List<Object>>[] queues;

  private final String remoteHost;
  private final int remotePort;

  private final MemoryWarpScriptStack stack;

  TCPClient(Socket socket, Macro partitioner, LinkedBlockingQueue<List<Object>>[] queues, String charset) throws IOException {
    this.socket = socket;
    this.partitioner = partitioner;
    this.queues = queues;

    // TODO(tce): set socket timeout?

    remoteHost = this.socket.getInetAddress().getHostAddress();
    remotePort = this.socket.getPort();

    this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), charset));
    this.stack = new MemoryWarpScriptStack(AbstractWarp10Plugin.getExposedStoreClient(), AbstractWarp10Plugin.getExposedDirectoryClient(), new Properties());
    this.stack.setAttribute(WarpScriptStack.ATTRIBUTE_NAME, "[Warp10TCPPlugin " + socket.getLocalPort() + "]");
    stack.maxLimits();
  }

  @Override
  public void run() {
    String line = null;
    try {
      while (!Thread.currentThread().isInterrupted() && null != (line = this.reader.readLine())) {
        try {
          int queueIndex = 0;
          // Apply the partitioning macro if it is defined
          if (null != this.partitioner) {
            this.stack.clear();
            this.stack.push(socket.getInetAddress().getHostAddress());
            this.stack.push((long) socket.getPort());
            this.stack.push(line);
            this.stack.exec(this.partitioner);
            int seq = ((Number) this.stack.pop()).intValue();
            queueIndex = seq % queues.length;
          }

          ArrayList<Object> msg = new ArrayList<Object>();
          msg.add(remoteHost);
          msg.add(remotePort);
          msg.add(line);

          this.queues[queueIndex].put(msg);
        } catch (Exception e) {
          LOG.error("Partitioner failed.", e);
        }
      }
    } catch (IOException e) {
      LOG.error("Problem when receiving text line from tcp on port " + socket.getPort(), e);
    } finally {
      WarpScriptStackRegistry.unregister(stack);
    }
    try {
      this.socket.close();
    } catch (Exception e) {
    }
  }

}