/*
 * Copyright 2017 Google Inc.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

package com.google.android.sambadocumentsprovider.browsing.broadcast;

import android.util.Log;

import com.google.android.sambadocumentsprovider.BuildConfig;
import com.google.android.sambadocumentsprovider.browsing.BrowsingException;

import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

class BroadcastUtils {
  private static final String TAG = "BroadcastUtils";

  private static final int FILE_SERVER_NODE_TYPE = 0x20;
  private static final int SERVER_NAME_LENGTH = 15;
  private static final String SERVER_NAME_CHARSET = "US-ASCII";

  /**
   * Generates a NetBIOS name query request.
   * https://tools.ietf.org/html/rfc1002
   * Section 4.2.12
   */
  static byte[] createPacket(int transId) {
    ByteBuffer os = ByteBuffer.allocate(50);

    char broadcastFlag = 0x0010;
    char questionCount = 1;
    char answerResourceCount = 0;
    char authorityResourceCount = 0;
    char additionalResourceCount = 0;

    os.putChar((char) transId);
    os.putChar(broadcastFlag);
    os.putChar(questionCount);
    os.putChar(answerResourceCount);
    os.putChar(authorityResourceCount);
    os.putChar(additionalResourceCount);

    // Length of name. 16 bytes of name encoded to 32 bytes.
    os.put((byte) 0x20);

    // '*' character encodes to 2 bytes.
    os.put((byte) 0x43);
    os.put((byte) 0x4b);

    // Write the remaining 15 nulls which encode to 30* 0x41
    for (int i = 0; i < 30; i++) {
      os.put((byte) 0x41);
    }

    // Length of next segment.
    os.put((byte) 0);

    // Question type: Node status
    os.putChar((char) 0x21);

    // Question class: Internet
    os.putChar((char) 0x01);

    return os.array();
  }

  /**
   * Parses a positive response to NetBIOS name request query.
   * https://tools.ietf.org/html/rfc1002
   * Section 4.2.13
   */
  static List<String> extractServers(byte[] data, int expectedTransId) throws BrowsingException {
    try {
      ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);

      int transId = buffer.getChar();
      if (transId != expectedTransId) {
        // This response is not to our broadcast.

        if (BuildConfig.DEBUG) Log.d(TAG, "Irrelevant broadcast response");

        return Collections.emptyList();
      }

      skipBytes(buffer, 2); // Skip flags.

      skipBytes(buffer, 2); // No questions.
      skipBytes(buffer, 2); // Skip answers count.
      skipBytes(buffer, 2); // No authority resources.
      skipBytes(buffer, 2); // No additional resources.

      int nameLength = buffer.get();
      skipBytes(buffer, nameLength);

      skipBytes(buffer, 1);

      int nodeStatus = buffer.getChar();
      if (nodeStatus != 0x20 && nodeStatus != 0x21) {
        throw new BrowsingException("Received negative response for the broadcast");
      }

      skipBytes(buffer, 2);
      skipBytes(buffer, 4);
      skipBytes(buffer, 2);

      int addressListEntryCount = buffer.get();

      List<String> servers = new ArrayList<>();
      for (int i = 0; i < addressListEntryCount; i++) {
        byte[] nameArray = new byte[SERVER_NAME_LENGTH];
        buffer.get(nameArray, 0, SERVER_NAME_LENGTH);

        final String serverName = new String(nameArray, Charset.forName(SERVER_NAME_CHARSET));
        final int type = buffer.get();

        if (type == FILE_SERVER_NODE_TYPE) {
          servers.add(serverName.trim());
        }

        skipBytes(buffer, 2);
      }

      return servers;
    } catch (BufferUnderflowException e) {
      Log.e(TAG, "Malformed incoming packet");

      return Collections.emptyList();
    }
  }

  static List<String> getBroadcastAddress() throws BrowsingException, SocketException {
    Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();

    List<String> broadcastAddresses = new ArrayList<>();

    while (interfaces.hasMoreElements()) {
      NetworkInterface networkInterface = interfaces.nextElement();
      if (networkInterface.isLoopback()) {
        continue;
      }

      for (InterfaceAddress interfaceAddress :
              networkInterface.getInterfaceAddresses()) {
        InetAddress broadcast = interfaceAddress.getBroadcast();

        if (broadcast != null) {
          broadcastAddresses.add(broadcast.toString().substring(1));
        }
      }
    }

    return broadcastAddresses;
  }

  private static void skipBytes(ByteBuffer buffer, int bytes) {
    for (int i = 0; i < bytes; i++) {
      buffer.get();
    }
  }
}