Nomenclatura:
# Comentario.
- Paso que siempre ocurre.
+ Paso que puede ocurrir (condicional).
* Condicional que puede romper el flujo.
@ Bucle.
---- Pasamos a otra secuencia, y luego seguimos donde estbamos.
---> Hemos creado un evento e indicamos la secuencia que activa.

Arranque
--------
# MAIN::main()
- Configuramos las seales que nos pueden interrumpir.
  - Las que se puedan, a travs de DEMUX, y que llamen a dmx_stop() para salir.
- Cargamos la configuracin con cf_load().
  + Si no existe, se pone el puerto de escucha por defecto, y como nick
    se pone el nombre de usuario.
- Cargamos la lista de contactos con cl_load(). Las IPs se inicializan a 0.
  + Si el fichero no existe, la lista queda vaca.
- Iniciamos la interface con ui_init(). Se muestran los contactos.
  - La entrada estndar se asocia a un callback de USER_IFACE.
    ---> Secuencia "Entrada teclado".
  - La seal de cambio de tamao de la terminal tambin se asocia a un callback.
    ---> Secuencia "Cambio de tamao".
---- Secuencia "Resolver nombres".
- Cada hora resolvemos los nombres (asociamos un timer con DEMUX).
# TODO periodo configurable?
  ---> Secuencia "Resolver nombres".
- Inicializamos el servidor con tr_init_server(), que usa tcp_listen().
  + Si va bien, se asocia el socket a un callback de TRANSPORT.
    ---> Secuencia "Conexin entrante".
  + Si falla, avisamos al usuario, pero no cerramos el programa porque aun
    podemos iniciar conexiones nosotros.
- Creamos una alarma peridica (por defecto de 1 minuto).
  ---> Secuencia "Chequeo contactos".
- Entramos en el main loop de DEMUX.

Resolver nombres
----------------
# TODO ???::cb_resolv()
# Llamada en main() y por demux cada TODO hora.
# TODO En teora ste es el nico sitio donde se puede bloquear el programa
# esperando las respuestas del servidor DNS. En el futuro podemos mirar con
# un #ifdef si tenemos pthreads (config.h del autoconf), y hacer esto con
# un thread. Tambin podramos hacer que el usuario pueda activar o
# desactivar la resolucin para cada contacto, as slo lo activara para
# los contactos que tienen IP dinmica.
- Mostramos un mensaje diciendo lo que vamos a hacer. # TODO desactivable?
@ Para cada contacto:
  + Si no est conectado (socket=-1):
    - Resolvemos su hname y guardamos la IP en state.ip.
      # Slo lo hacemos si no est conectado, porque si est conectado
      # podemos asumir que la IP no ha cambiado.
    + Si no se puede resolver, mostramos un aviso.

Entrada teclado
---------------
# USER_IFACE::cb_read_key()
# DEMUX llama a este callback cuando el usuario pulsa una tecla.
- Leemos la tecla pulsada.
+ Si es una tecla de funcin, cambiamos de ventana.
  ---- Secuencia "Cambiar ventana".
+ Si es otra tecla, excepto INTRO, se modifica la lnea de edicin.
+ Si es INTRO:
  - Se vaca la lnea de edicin.
  - Se aade la lnea al historial, eliminando la ms antigua si hace falta.
  - La lnea se enva a PARSE.
    # PARSE::pa_parse_line()
    + Si es un comando (empieza por '/'):
      - Separamos el comando en palabras.
      + Si es incorrecto sacamos un mensaje de error.
      + Si es 'add', creamos un nuevo contacto con el nick, la direccin y el
        puerto (opcional, por defecto: el puerto de escucha por defecto).
        # CONTACT_LIST::cl_add_contact()
        - Nos aseguramos de que no existe el nick, ni un IP+puerto igual.
        - Aadimos el contacto y guardamos el fichero con cl_save().
          ---- Secuencia "Grabar contactos".
        - Mostramos un mensaje sobre el contacto creado.
        ---- Secuencia "Actualizar vista de contactos".
      + Si es 'del':
        # CONTACT_LIST::cl_remove_contact()
        - Buscamos el nick en la lista.
        + Si est conectado (el socket del contact_t no es -1):
          ---- Secuencia "Desconectar contacto".
        - Borramos el contacto y guardamos el fichero con cl_save().
          ---- Secuencia "Grabar contactos".
        - Mostramos un mensaje sobre el contacto borrado.
        ---- Secuencia "Actualizar vista de contactos".
      + Si es 'help', mostramos un resumen de los comandos.
      + Si es 'nick':
        - Cambiamos el nick en el config_t.
        ---- Secuencia "Grabar configuracin".
        ---- Secuencia "Enviar HELLO a todos".
      + Si es 'port':
        - Cambiamos el puerto de escucha.
        ---- Secuencia "Grabar configuracin".
        ---- Secuencia "Enviar HELLO a todos".
      + Si es 'quit', llamamos a dmx_stop().
      # FUTURO: 'me', 'away' ...
    + Si no es un comando, pasamos el texto a PROTOCOL.
      * Si 'win_contact' es nulo, no hacemos nada.
      # PROTOCOL::pr_send_text()
      - Preparamos el ic_message esttico que tiene el mdulo. Le ponemos
	el tipo TEXT.
      - Pasamos a TRANSPORT un puntero al ic_message, la longitud del
	ic_message, el puntero al texto (como payload) y la longitud del
	texto.
        ---- Secuencia "Enviar mensaje".

Conexin entrante
-----------------
# TRANSPORT::cb_server_data()
# DEMUX llama a este callback cuando hay una peticin de conexin.
- Aceptamos la conexin.
- Buscamos la IP origen en CONTACT_LIST (cl_find_by_ip()).
  + Si existe ms de una, nos quedamos con el primero.
    # TODO Esto dar problemas con el tema de tener dos ipchat's en la misma
    # mquina, pero en el futuro deberamos identificar a los contactos por
    # su clave pblica y no por su IP.
+ Si no existe, o existe pero ya est conectado:
  - Creamos un contacto temporal con nick vacio.
    # cl_save() lo ignora por este motivo.
+ Si ya est conectado (su socket no es -1):
  # Suponemos que se ha caido y est intentando volver a conectar.
  ---- Secuencia "Desconectar contacto".
  - Avisamos al usuario de lo que ha pasado.
---- Secuencia "Enviar HELLO".
  # Lo enviamos ahora porque si las versiones no coinciden, el otro ipchat
  # puede ver que hemos cerrado porque la versin no coincide.
- Se asocia el socket a la estructura del contacto.
- Se activa el hello_pending del contacto.
  # Ya debera estar activo, porque la conexin estaba cerrada.
- Se asocia el socket a un callback de TRANSPORT. El callback es el
  mismo para todos los sockets conectados. Como puntero asociado tiene
  un puntero al contact_t.
  ---> Secuencia "Entrada red".
---- Secuencia "Crear HELLO timeout".

Chequeo contactos
-----------------
# TRANSPORT::cb_check_contacts()
# Llamada peridicamente para intentar conectar con los contactos.
@ Para cada contacto:
  + Si no est conectado (socket != -1):
    # TRANSPORT::tr_connect()
    - Creamos un socket no-bloqueante conectando al contacto.
      # TCP::tcp_connect()
    - Asociamos el socket a un callback de TRANSPORT.
      ---> Secuencia "Conexin saliente".
    # TODO Creamos un timeout o usamos el de tcp del kernel?

Conexin saliente
-----------------
# TRANSPORT::cb_out_connection()
# Llamada cuando un socket se conecta o no se ha podido conectar.
# Si no se ha podido conectar, no hacemos nada.
- Comprobamos si el socket se ha conectado, mediante tcp_connect_result().
+ Si se ha conectado:
  # Aqu hello_pending debera ya ser TRUE, podemos comprobarlo.
  ---- Secuencia "Crear HELLO timeout".
  ---- Secuencia "Enviar HELLO".

Enviar HELLO
------------
# PROTOCOL::pr_send_hello()
# Llamada cuando hay que enviar un HELLO a un contacto.
- Preparamos el ic_message esttico que tiene el mdulo. Le ponemos el tipo
  HELLO, la versin del protocolo, nuestro nick, y el puerto que usamos
  para escuchar. # TODO El nick es payload o est en el ic_message?
  # El otro ipchat guardar este puerto en el contact_t de nuestro nick.
- Lo pasamos a TRANSPORT.
  ---- Secuencia "Enviar mensaje".

Enviar HELLO a todos
--------------------
# Esta secuencia es la misma que "Enviar HELLO" pero pasando BROADCAST como
# contact_t* a PROTOCOL::pr_send_hello().

Enviar mensaje
--------------
# TRANSPORT::tr_send_msg()
# Llamada cuando hay que enviar un mensaje a un contacto.
# Recibe: contact_t*, ic_message*, longitud del ic_message, payload*,
# longitud del payload. Estos dos ltimos son 0 si no hay payload.
# Si el contact_t* es BROADCAST, se enva el mensaje a todos los contactos
# conectados actualmente.
- Variable temporal len_msg = longitud_ic_message + longitud_payload.
- Variable temporal len_reserv = sizeof (len_msg) + len_msg.
- Hacemos un realloc(out_buf_size+len_reserv) del out_buffer.
  # As mantenemos los datos de un mensaje anterior que pueda haber, y
  # obtenemos espacio para meter este nuevo mensaje.
- Actualizamos out_buf_size al nmero de bytes reservados que tenemos
  ahora.
- Copiamos len_msg (en network order), el ic_message, y el payload al buffer.
  # Esto encaja con que al recibir, recibimos primero la longitud del msg.
+ Si el contact_t* es BROADCAST, las copias anteriores las hacemos para el
  primer contacto conectado con nick no vaco, y el resto de la secuencia
  se repite para los dems, copiando el mensaje ya preparado del buffer del
  primero.
+ Si al principio de la secuencia el buffer estaba vaco:
  # Si el buffer no estaba vaco, el socket ya est asociado al callback.
  ---- Secuencia "Salida red".
    # Llamamos al callback ahora, aunque no sepamos si vamos a poder enviar
    # algo, porque en el 99% de los casos s que vamos a poder.
  + Si ahora el buffer no est vaco:
    # El callback no pudo enviarlo todo.
    - Asociamos el socket al callback, con el contact_t* como puntero
      asociado.
    ---> Secuencia "Salida red".

Crear HELLO timeout
-------------------
# TRANSPORT::create_hello_timeout()
# Llamada siempre que tenemos que crear un timer (asociado a un contacto),
# para ver si pasa demasiado tiempo desde que conectamos sin recibir el HELLO.
# Llamada cuando iniciamos nosotros la conexin y cuando lo hace el otro ipchat.
# Cuando llega el HELLO borramos el timer.
- Creamos un timer para cerrar la conexin si pasa un tiempo sin recibir HELLO.
  Asociamos al timer un puntero al contact_t.
  ---> Secuencia "HELLO timeout".

HELLO timeout
-------------
# TODO TRANSPORT::cb_hello_timeout()
# Llamada cuando un socket est demasiado tiempo conectado sin que hayamos
# recibido el HELLO del otro ipchat.
- Mostramos un mensaje diciendo lo que ha pasado.
---- Secuencia "Desconectar contacto".

Entrada red
-----------
# TRANSPORT::cb_net_input()
# Llamada cuando hay datos disponibles en algn socket. DEMUX nos pasa un
# puntero al contact_t que tiene ese socket asociado.
- Le pasamos el contact_t* a la funcin esttica recv_msg().
  # TRANSPORT::recv_msg()
  + Si in_buffer es nulo:
    # Todava no hemos empezado a recibir el siguiente mensaje (pero
    # posiblemente si que hemos empezado a recibir su longitud).
    - Leemos del socket la longitud del mensaje, usando como buffer
      &in_buf_size + in_buf_rcvd, y como longitud mxima,
      sizeof(in_buf_size) - in_buf_rcvd. El nmero de bytes leidos lo
      sumamos a in_buf_rcvd.
    + Si read() devolvi 0 (el peer desconect), devolvemos un error a
      TRANSPORT.
    * Si in_buf_rcvd es menor a sizeof(in_buf_size), es decir, que aun
      no tenemos la longitud completa, nos vamos. Fin de secuencia.
    - Convertimos la longitud a host order.
    + Si es menor o igual a cero, le devolvemos un error a TRANSPORT.
      # TODO Seguramente interpretaremos la longitud como unsigned, as
      # que quiz baste comprobar que no sea cero. Seguramente la longitud
      # ser de 16 bits y no necesitamos comprobar el lmite de la longitud
      # del mensaje (el lmite es 2^16 - 1 si es unsigned).
    - Ponemos in_buf_rcvd = 0
      # Ahora empezamos a contar bytes del mensaje. La situacin se
      # distingue porque ahora in_buffer ya no ser nulo.
    - Reservamos in_buf_size bytes en in_buffer.
  # Los datos que vayamos leyendo los guardamos en el buffer.
  - Leemos del socket en (&in_buffer + in_buf_rcvd), con longitud mxima
    in_buf_size - in_buf_rcvd.
  + Si el read() devuelve la longitud mxima, devolvemos un valor
    indicando que el mensaje ya est completo.
  + Si el read() devuelve 0 (peer desconect), devolvemos error.
+ Si ha devuelto error:
  - Mostramos un mensaje.
  ---- Secuencia "Desconectar contacto".
+ Si nos indica que el mensaje ya est completo, lo pasamos a PROTOCOL.
  # PROTOCOL::pr_msg_received()
  * Si la longitud del mensaje es menor a la del ic_message para el tipo de
    mensaje, o si el mensaje ni siquiera incluye el tipo, o si el tipo es
    desconocido, mostramos un mensaje y devolvemos error a TRANSPORT.
  - Convertimos todos los campos a host order (habr que hacerlo segn el
    tipo de mensaje).
  + Si es un HELLO:
    * Si la versin del protocolo es diferente:
      - Mostramos un mensaje indicando lo que ha pasado.
      - Devolvemos error a TRANSPORT.
    - Cambiamos el nick y el puerto en el contact_t.
    + Si hello_pending del contacto es TRUE:
      - Borramos el timeout que activa "HELLO timeout".
      - Lo desactivamos.
    + En el caso anterior, o si el nick ha cambiado:
      ---- Secuencia "Actualizar vista de contactos".
  + Si es un TEXT:
    - Llamamos a USER_IFACE::ui_output_msg().
  # TRANSPORT (al volver de pr_msg_received())
  + Si PROTOCOL devuelve error:
    ---- Secuencia "Desconectar contacto".
  + Sino:
    - Liberamos el buffer, y ponemos in_buffer, in_buf_size y in_buf_rcvd a 0.

Salida red
----------
# TRANSPORT::cb_net_output()
# Llamada cuando podemos enviar datos a un socket. DEMUX nos pasa un puntero
# al contact_t que tiene ese socket asociado.
- Enviamos los datos que podamos del buffer de salida.
+ Si hemos podido enviarlo todo:
  - Liberamos el buffer.
  - Desasociamos el socket del callback.
+ Si no hemos podido enviarlo todo:
  - Movemos los bytes no enviados al principio del buffer.
  - Utilizamos realloc() para que siempre la memoria reservada coincida
    con el nmero de bytes a enviar.
    # Esto implica restar los bytes enviados a out_buf_size, y hacer
    # out_buffer = realloc (out_buffer, out_buf_size);

Desconectar contacto
--------------------
# TRANSPORT::tr_disconnect()
# Llamada cuando hay que desconectar de un contacto.
- Desasociamos el socket de DEMUX (puede estar como entrada y como salida).
- Si tiene un timer para "HELLO timeout", lo borramos.
- Cerramos el socket y eliminamos los buffers de entrada y salida.
- Activamos el hello_pending.
---- Secuencia "Cerrar ventana".
- Mostramos un mensaje avisando de la desconexin.

Cambiar ventana
---------------
# TODO

Cerrar ventana
--------------
# USER_IFACE::ui_close_window()
# TODO

Actualizar vista de contactos
-----------------------------
# USER_IFACE::ui_redraw_contacts()
# Llamada cuando hay cambios en los contactos, para asociar teclas y mostrarlos.
@ Para cada contacto:
  + Si tiene tecla asociada en el contact_t, pero hello_pending est activo:
    # El contacto se acaba de desconectar.
    - Desasociamos la tecla.
  + Si no tiene tecla asociada, pero hello_pending est desactivado:
    # El contacto se acaba de conectar.
    - Asociamos la primera tecla de funcin libre a la estructura del contacto.
      Las teclas son F1..F12, shift+F1..F12, alt+1..0, ctrl+1..0, ...
  + Si tiene tecla asociada (incluido el caso anterior), dibujamos la tecla
    y el contacto a la altura que toca para esta tecla.
# Ahora dibujamos los contactos no conectados.
@ Para cada contacto, mientras no lleguemos a la ltima fila visible:
  + Si hello_pending est activo:
    - Dibujamos el contacto en la siguiente fila libre.
- Vaciamos las filas que quedan.
- Redibujamos la pantalla.

Cambio de tamao
----------------
# TODO

Grabar contactos
----------------
# CONTACT_LIST::cl_save()
- Abrimos el fichero de contactos, creando el directorio ~/.ipchat si no
  existe, y creando el fichero si no existe.
@ Para cada contacto:
  - Si el nick no es vaco, guardamos la estructura (excepto state).
  # Si es vaco, es un contacto temporal.
  # TODO: El nick es char* o char[algo_fijo]?

Grabar configuracin
--------------------
# UCONFIG::cf_save()
- Abrimos el fichero de contactos, creando el directorio ~/.ipchat si no
  existe, y creando el fichero si no existe.
- Grabamos el config_t.
