/* JFlexiPix - Java implementation of FlexiPix output library
 *
 * Copyright 2010-2011 Stefan Schuermans <stefan schuermans info>
 *
 * 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, version 3 of the License.
 *
 *
 * 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 Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.blinkenarea.JFlexiPix;

import java.net.*;

/// FlexiPix display
public class Display
{
  /**
   * @brief constructor
   * @param[in] configFile name of config file to read
   * @param[in] messageIf message interface to report messages to or null
   */
  public Display(String configFile, MessageIf messageIf)
    throws Exception
  {
    Config config;

    // set default bind address
    try {
      InetAddress ip = InetAddress.getByAddress(Constants.bindIp);
      m_bindAddr = new InetSocketAddress(ip, Constants.bindPort);
    } catch (java.net.UnknownHostException e) {
      String txt = "internal error: bad default bind address";
      if (messageIf != null)
        messageIf.message(MsgType.Err, txt + "\n");
      throw new Exception(txt, e);
    }

    // allocate pixel object to store size
    m_size = new Pixel();

    // allocate distributor array
    m_distris = new Distri [Constants.distriMaxCnt];

    // no pixels yet
    m_distriCnt = 0;
    m_outputCnt = 0;
    m_pixelCnt  = 0;

    // parse config file
    config = new Config(this, messageIf);
    try {
      config.procFile(configFile);
    } catch (Exception e) {
      String txt = "reading config file failed\n";
      if (messageIf != null)
        messageIf.message(MsgType.Err, txt + "\n");
      throw new Exception(txt, e);
    }
    config = null;

    // create socket and bind it
    try {
      m_sock = new DatagramSocket(m_bindAddr);
    } catch (SocketException e) {
      String txt = "creating and binding socket failed\n";
      if (messageIf != null)
        messageIf.message(MsgType.Err, txt + "\n");
      throw new Exception(txt, e);
    }

    // clear display
    dataClear();
  }

  /// get width of display
  public int getWidth()
  {
    return m_size.m_x;
  }

  /// get height of display
  public int getHeight()
  {
    return m_size.m_y;
  }

  /// clear image data to output
  public void dataClear()
  {
    byte [] black = {(byte)0, (byte)0, (byte)0};
    data(black, 0, 0, 0, 0, 0, m_size.m_x, m_size.m_y);
  }

  /**
   * @brief set image data to output
   * @param[in] data rectangular section of image data,
   *                 pixels need to be in R8G8B8 format
   * @param[in] strideX stride between two pixels in X direction
   * @param[in] strideY stride between two pixels in Y direction
   * @param[in] base array index of top-left pixel
   * @param[in] x X coordinate of left side of rectangular area
   * @param[in] y Y coordinate of top side of rectangular area
   * @param[in] width with of rectangular area
   * @param[in] height height of rectangular area
   */
  public void data(byte [] data, int strideX, int strideY, int base,
                   int x, int y, int width, int height)
  {
    int dist, out, pix, i, c;
    Distri distri;
    int dest, src;
    Pixel pixel;
    int rx, ry;

    // set data for all distributors
    for (dist = 0; dist < Constants.distriMaxCnt; ++dist) {
      distri = m_distris[dist];
      if (distri != null) {

        /* set index to start of RGB data for pixels in message buffer
           (header is already set up and stays the same) */
        dest = Constants.mcufHdr.length;

        // get RGB data of pixels for every output
        for (out = 0, i = 0; out < distri.m_outputCnt; ++out) {
          for (pix = 0; pix < distri.m_pixelCnt; ++pix, ++i) {
            pixel = distri.m_pixels[i];
            if (pixel != null) {

              // get pixel coordinates relative to rectangular area of image
              rx = pixel.m_x - x;
              ry = pixel.m_y - y;
              // check if pixel is within rectangular area of image
              if (rx >= 0 && rx < width && ry >= 0 && ry < height) {
                // get pixel data and map it
                src = rx * strideX + ry * strideY + base;
                distri.m_msgBuf[dest+0] = distri.m_mapRed.m_table[data[src+0] & 0xFF];
                distri.m_msgBuf[dest+1] = distri.m_mapGreen.m_table[data[src+1] & 0xFF];
                distri.m_msgBuf[dest+2] = distri.m_mapBlue.m_table[data[src+2] & 0xFF];
              }

            } // if pixel
            dest += 3;
          } // for pix
        } // for out

      } // if distri
    } // for distri
  }

  /// send image data to distributors
  public void send()
  {
    int dist;
    Distri distri;

    // send data to all distributors
    for (dist = 0; dist < Constants.distriMaxCnt; ++dist) {
      distri = m_distris[dist];
      if (distri != null) {

        if (distri.m_addr != null) {
          try {
            // send message as UDP packet
            DatagramPacket pack = new DatagramPacket(distri.m_msgBuf,
                                                     distri.m_msgBuf.length,
                                                     distri.m_addr);
            m_sock.send(pack);
          } catch (java.io.IOException e) {
          }
        }

      } // if distri
    } // for distri
  }

  InetSocketAddress m_bindAddr;  ///< local network address to bind to
  Pixel             m_size;      ///< size of display
  Distri []         m_distris;   ///< information about distributors
  int               m_distriCnt; ///< total number of distributors
  int               m_outputCnt; ///< total number of outputs
  long              m_pixelCnt;  ///< total number of pixels
  DatagramSocket    m_sock;      ///< socket to send UDP packets
}

