# code developed in lab Friday, February 10 (week 3)

#--------------------------------------------------------------------
# developed previously in class

import math

# the mathematical function f(x) = x + |sin(8x)|
def f(x):
    return x + abs(math.sin(8*x))

# converts a binary string to its decimal (base 10) equivalent
def binary_to_decimal(b):
    return int(b, base=2)
    
# converts a genome to its corresponding floating-point x value
def genome_to_x(g):
    x = binary_to_decimal(g) * math.pi/(2**len(g))
    return x

def fitness(g):
    return f(genome_to_x(g))

# generates a list of all binary strings of length n
def binary_strings(n):
    assert n >= 1, "length must be at least one"
    if n == 1:
        return ['0', '1']
    else:
        shorter_strings = binary_strings(n-1)
        with_0 = ['0'+b for b in shorter_strings]
        with_1 = ['1'+b for b in shorter_strings]
        all = with_0 + with_1
        return all

def show_encodings(code_length):
    print("Binary -> x")
    for g in binary_strings(code_length):
        print(f"{g} -> {genome_to_x(g)}")

# generates a list of binary gray code strings of length n
def gray_codes(n):
    assert n >= 1, "length must be at least one"
    if n == 1:
        return ['0', '1']
    else:
        shorter_strings = gray_codes(n-1)
        with_0 = ['0'+b for b in shorter_strings]
        with_1 = ['1'+b for b in reversed(shorter_strings)]
        all = with_0 + with_1
        return all

#--------------------------------------------------------------------
# schemas

# generates a list of all binary schemas of length n
def schemas(n):
    if n == 1:
        return ['0', '1', '*']
    else:
        shorter_schemas = schemas(n-1)
        with_0 = ['0'+s for s in shorter_schemas]
        with_1 = ['1'+s for s in shorter_schemas]
        with_star = ['*'+s for s in shorter_schemas]
        all = with_0 + with_1 + with_star
        return all

# generates a list of all strings that are representatives of schema s
def all_schema_reps(s):
    if len(s) == 1:
        if s == '*':
            return ['0', '1']
        else:
            return [s]
    else:
        shorter_reps = all_schema_reps(s[1:])
        if s[0] == '0':
            return ['0'+r for r in shorter_reps]
        elif s[0] == '1':
            return ['1'+r for r in shorter_reps]
        elif s[0] == '*':
            with_0 = ['0'+r for r in shorter_reps]
            with_1 = ['1'+r for r in shorter_reps]
            all = with_0 + with_1
            return all
        else:
            raise Exception("invalid schema")

# calculates the average fitness of all representatives of schema s
def exact_schema_fitness(s):
    fitnesses = [fitness(g) for g in all_schema_reps(s)]
    average = sum(fitnesses) / len(fitnesses)
    return average

# returns a list of all schemas represented by genome g
def schemas_represented(g):
    if g == '0':
        return ['0', '*']
    elif g == '1':
        return ['1', '*']
    else:
        shorter_schemas = schemas_represented(g[1:])
        with_first = [g[0]+s for s in shorter_schemas]
        with_star = ['*'+s for s in shorter_schemas]
        all = with_first + with_star
        return all

# returns all schemas represented by a population of genomes
def unique_schemas(population):
    all = set()
    for g in population:
        for s in schemas_represented(g):
            all.add(s)
    return all

#--------------------------------------------------------------------
# reading, writing, and plotting data in text files

import random

def write_data(filename, numlines=100):
    with open(filename, 'w') as file:
        for i in range(numlines):
            a = random.uniform(-10, 10)
            b = random.uniform(50, 200)
            file.write(f"{i:5} {a:10.3f} {b:10.3f}\n")
    print(f"wrote {numlines} lines of data to {filename}")

def read_data(filename):
    with open(filename, 'r') as file:
        all_lines = file.readlines()
        for line in all_lines:
            x, y, z = line.split()
            i = int(x)
            a = float(y)
            b = float(z)
            #print(f"i = {i}, a = {a}, and b = {b}")
            print(f"{i:5} {a:10.3f} {b:10.3f}")
            
import PyGnuplot

def make_plot(filename):
    write_data(filename)
    # create plot
    fig = PyGnuplot.gp() # Mac
    #fig = PyGnuplot.gp("C:\\Program Files\\gnuplot\\bin\\gnuplot.exe") # Windows
    fig.c("set style data lines")
    fig.c("set title 'My GA results'")
    fig.c("set xlabel 'generation number'")
    fig.c("set ylabel 'fitness value'")
    # constructs gnuplot command string:
    fig.c(f"plot '{filename}' using 2 title 'average'," +
          f"'{filename}' using 3 title 'best'")
