
"""
Creates a multiprocess, multithread application to detect high
readings.

See https://www.assemblesoftware.com/examples/

"""

import sys
import os
import math
import time

sys.path.append(os.path.abspath("../../IoTPy/concurrency"))
sys.path.append(os.path.abspath("../../IoTPy/core"))
sys.path.append(os.path.abspath("../../IoTPy/agent_types"))
sys.path.append(os.path.abspath("../../IoTPy/helper_functions"))

# multicore is in concurrency
from multicore import multicore
# stream is in core
from stream import Stream
# op, merge, source, sink are in agent_types
#from merge import zip_map
#from source import source_float_file
from sink import stream_to_file
# accelerometer_agents are in ./accelerometer_agents
from accelerometer_agents import subtract_mean, magnitude_of_vector
from accelerometer_agents import simple_anomalies, quench
# basics is in ../../IoTPy/helper_functions
from basics import merge_e
# multicore is in "../../IoTPy/multiprocessing"
from multicore import *
from print_stream import print_stream

#-------------------------------------------------------------
#  A SOURCE
# Thread that puts data from a file on a source stream.
#-------------------------------------------------------------
class read_file(object):
    """
    Parameters
    ----------
    filename: str, name of the file
    filetype: one of the following
              float, int, char,...
    window_size: the file is read in chunks of window_size.
            Large window_size is faster.

    """
    def __init__(self, filename, filetype=float, window_size=100):
        self.filename = filename
        self.filetype = filetype
        self.window_size = window_size
    def generate_source_data(self, source):
        with open(self.filename) as the_file:
            data = list(map(self.filetype, the_file))
            for i in range(0, len(data), self.window_size):
                # Generate a segment of length window_size
                # on this source and then yield thread.
                window = data[i : i+self.window_size]
                copy_data_to_source(window, source)
                # Sleep for an arbitrarily small positive value
                # to yield this thread.
                time.sleep(0.0001)
        # Indicate that this source has finished execution.
        # This enables the system to detect termination of computation.
        source_finished(source)
        return


# ----------------------------------------------------------------
# detect_large_magnitudes
# ----------------------------------------------------------------
def detect_large_magnitudes(in_streams, out_streams):
    """
    Detects anomalies in streams generated by triaxial sensors.

    Parameters
    ----------
    in_streams: list of Stream
      in_streams is a list of 3 streams indicating measurements
      in e, n, and z (for east, north, vertical) directions.
      These streams are generated by a triaxial sensor.
    out_streams: list of Stream
      out_streams has only one element, which is a
      Stream of int. An element of this stream is either
      1.0 or 0.0. An element is 1.0 to indicate that an
      anomaly was detected in in_streams and is 0.0 otherwise.

    """

    #------------------------------------------------------------------
    # DECLARE INTERNAL STREAMS
    #------------------------------------------------------------------
    # demeaned is a list of 3 streams which are the streams from the
    # source with their means subtracted.
    demeaned = [Stream('demeaned_'+str(i)) for i in range(3)]
    # magnitudes is a stream of magnitudes of samples
    magnitudes = Stream('magnitudes')
    # Times are integers.
    # anomaly_times_before_quenching are times of anomalously high
    # magnitudes
    anomaly_times_before_quenching = Stream('prior quench')
    # anomaly_times_after_quenching is the same as
    # anomaly_times_before_quenching after discarding anomalies that
    # are too close together in time.
    anomaly_times_after_quenching = Stream('after quench')

    #----------------------------------------------------
    # CREATE AGENTS
    #----------------------------------------------------
    # Subtract means from source streams.
    for i in range(3):
        subtract_mean(
            in_stream = in_streams[i],
            out_stream=demeaned[i],
            window_size=100, step_size=100)

    # Compute magnitudes of vector samples.
    # This agent generates streams of magnitudes of vectors
    # from streams of the components of the vectors.
    # demeaned is a list of streams with zero means.
    # magnitudes is a single stream of vector magnitudes.
    magnitude_of_vector(in_streams=demeaned, out_stream=magnitudes)

    # This agent generates a stream of anomaly times from
    # streams of magnitudes. The stream of anomaly times is
    # a stream of timestamps where each timestamp is the
    # time at which an anomaly occurred.
    simple_anomalies(
        in_stream=magnitudes,
        out_stream=anomaly_times_before_quenching,
        MAGNITUDE_THRESHOLD=0.0001)

    # This agent discards timestamps that are closer
    # together than QUENCH_TIME.
    # anomaly_times_after_quenching is a stream that contains
    # timestamps at which anomalies occur and that are separated
    # by at least QUENCH_TIME.
    quench(
        in_stream=anomaly_times_before_quenching,
        out_stream=anomaly_times_after_quenching,
        QUENCH_TIME=5)

    # Agents that copy streams into files for later analysis.
    stream_to_file(anomaly_times_after_quenching, 'local_anomalies.txt')
    return

def detect_large_signal_from_sensor(sensor_name, filenames):

    # (1) SPECIFY PROCESSES AND (2) CONNECTIONS AMONG STREAMS.

    # STEP 1. SPECIFY PROCESSES.
    #
    # This application consists of a single process called 'process'
    # The processes executes the function detect_large_magnitudes.
    # This function has 3 input streams called 'e', 'n' and 'z'.
    # Each of these input streams consists of floats, as indicated by
    # 'f', the notation used in multiprocessing.Array.
    # The function has no output streams.
    # It has 3 sources called 'east', 'north', 'vertical' each running
    # in its own thread, and each of which generates a stream.
    #
    # This process has no actuators.
    processes = \
      {
        'process':
           {'in_stream_names_types': [ ('e', 'f'), ('n', 'f'), ('z', 'f') ],
            'out_stream_names_types': [],
            'compute_func': detect_large_magnitudes,
            'sources':
              {'east':
                  {'type': 'f',
                   'func': read_file(filename=filenames[0]).generate_source_data
                  },
               'north':
                  {'type': 'f',
                   'func': read_file(
                       filename=filenames[1]).generate_source_data
                  },
              'vertical':
                  {'type': 'f',
                   'func': read_file(
                       filename=filenames[2]).generate_source_data
                  }
               },
            'actuators': {}
           }
      }

    # STEP 2. CONNECTIONS AMONG STREAMS AND SOURCES.
    # Connect sources and outputs of processes to inputs of processes.
    #
    # The source 'east' of the process feeds the input stream called
    # 'e' of the process.
    # Likewise, the source 'north' of the process feeds the input
    # stream called 'n' of the process, and,
    # the source 'vertical' of the process feeds the input stream called
    # 'z' of the process. 
    connections = \
      {
          'process' :
            {
                'east' : [ ('process', 'e') ],
                'north' : [  ('process', 'n') ],
                'vertical' : [  ('process', 'z') ]
            }
      }

    multicore(processes, connections)

if __name__ == '__main__':
    detect_large_signal_from_sensor(sensor_name='S3',
                                    filenames= ['S3_short.e.txt',
                                                'S3_short.n.txt',
                                                'S3_short.z.txt'])








    
    ## # filenames has data recorded from east, north,
    ## # and vertical directions from a sensor
    ## args = sys.argv
    
    ## detect_large_signal_from_sensor(
    ##     sys.argv[1], sys.argv[2:]
    ##     )
    ## # Example of a call to this function
    ## # detect_large_signal_from_sensor S1 S1.e.txt S1.n.txt S1.z.txt
    ## # Here S1 is the name of the publication and 
    ## # S1.e.txt S1.n.txt S1.z.txt are the source files for the
    ## # accelerometer readings in the three axes.
    
