Fixed kernel function.

Fixed kernel function.

#define OPENCL_MEMORY_ALIGN 5 // 2**OPENCL_MEMORY_ALIGN * sizeof(float)

typedef struct {
   __global float *learningInputs, *learningOutputs;
   int totalLearningLines;
   __global float *testInputs, *testOutputs;
   int totalTestLines;
} taskData_t;

typedef struct {
    int numOfLayers;
    __local int *layers;
    bool classification;
    float minOutputValue;
    float maxOutputValue;
    float learningFactor;
    float lambda;
} setup_t;

typedef struct {
    int maxEpochs;
    float minProgress;
    float maxGeneralizationLoss;
} criteria_t;

typedef struct {
    int epoch;
    __global float *weights;
    __local float *values;
    __local float *errors;
    int testLine;
    int learningLine;
} state_t;

typedef struct {
    setup_t setup;
    criteria_t criteria;
    state_t state;
    float currentSquareErrorCounter;
    float bestSquareError[2];
    __global float *squareErrorHistory;
} neuralNetwork_t;

typedef struct {
    int setup_numOfLayers;
    int setup_b_offset_layers;
    bool setup_classification;
    float setup_minOutputValue;
    float setup_maxOutputValue;
    float setup_learningFactor;
    float setup_lambda;

    int criteria_maxEpochs;
    float criteria_minProgress;
    float criteria_maxGeneralizationLoss;

    int state_epoch;
    int state_b_offset_weights;
    int state_b_offset_values;
    int state_b_size_values;
    int state_b_offset_errors;
    int state_b_size_errors;
    int state_testLine;
    int state_learningLine;

    float neuralNetwork_currentSquareErrorCounter;
    float neuralNetwork_bestSquareError[2];
    int neuralNetwork_b_offset_squareErrorHistory;
    int neuralNetwork_b_size;
    int neuralNetwork_b_offset;
} neural_network_transform_t;

typedef struct {
    int taskData_b_offset_learningInputs, taskData_b_offset_learningOutputs;
    int taskData_totalLearningLines;
    int taskData_b_offset_testInputs, taskData_b_offset_testOutputs;
    int taskData_totalTestLines;
    int taskData_b_size;
} task_data_transform_t;

size_t get_local_linear_id() {
    return  (get_local_id(2) * get_local_size(1) *
            (0)) + (get_local_id(1) *
            get_local_size(0)) + get_local_id(0);
    }

size_t get_group_linear_id() {
    return  (get_group_id(2) * get_num_groups(1) *
            (0)) + (get_group_id(1) *
            get_num_groups(0)) + get_group_id(0);
    }

/**
     * Returns a learning vector for neural network.
     */
bool getLearningVector(neuralNetwork_t *neuralNetwork, taskData_t *taskData, __local float *expectedOutput) {

    if (neuralNetwork->state.learningLine >= taskData->totalLearningLines) {
        return false;
    }

    event_t e = async_work_group_copy(neuralNetwork->state.values,
                                    (taskData->learningInputs +
                                        neuralNetwork->state.learningLine * neuralNetwork->setup.layers[0]),
                                    neuralNetwork->setup.layers[0], 0);
    wait_group_events(1, &e);
    for (int input = 0; input < neuralNetwork->setup.layers[0]; input++) {
        neuralNetwork->state.values[input] = taskData->learningInputs[input + neuralNetwork->state.learningLine * neuralNetwork->setup.layers[0]];
    }

    event_t e2 = async_work_group_copy(expectedOutput,
                                    (taskData->learningOutputs +
                                        neuralNetwork->state.learningLine * neuralNetwork->setup.layers[neuralNetwork->setup.numOfLayers - 1]),
                                    neuralNetwork->setup.layers[neuralNetwork->setup.numOfLayers - 1], 0);
    wait_group_events(1, &e2);

    neuralNetwork->state.learningLine++;
    return true;
}

/**
     * Returns a test vector for neural network.
     */
bool getTestVector(neuralNetwork_t *neuralNetwork, taskData_t *taskData, __local float *expectedOutput) {

    if (neuralNetwork->state.testLine >= taskData->totalTestLines) {
        return false;
    }
    event_t e = async_work_group_copy(neuralNetwork->state.values,
                                    (taskData->testInputs +
                                        neuralNetwork->state.testLine * neuralNetwork->setup.layers[0]),
                                    neuralNetwork->setup.layers[0], 0);
    wait_group_events(1, &e);
    for (int input = 0; input < neuralNetwork->setup.layers[0]; input++) {
        neuralNetwork->state.values[input] = taskData->testInputs[input + neuralNetwork->state.testLine * neuralNetwork->setup.layers[0]];
    }

    event_t e2 = async_work_group_copy(expectedOutput,
                                    (taskData->testOutputs +
                                        neuralNetwork->state.testLine * neuralNetwork->setup.layers[neuralNetwork->setup.numOfLayers - 1]),
                                    neuralNetwork->setup.layers[neuralNetwork->setup.numOfLayers - 1], 0);
    wait_group_events(1, &e2);
    neuralNetwork->state.testLine++;
    return true;
}

/**
 * Finds lowest square error and sets it as the best one.
 */
void findAndSetBestSquareError(neuralNetwork_t *neuralNetwork) {
    neuralNetwork->bestSquareError[0] = 0;
    neuralNetwork->bestSquareError[1] = neuralNetwork->squareErrorHistory[0];

    for (int i = 1; i <= neuralNetwork->state.epoch; i++) {
        if (neuralNetwork->squareErrorHistory[i] < neuralNetwork->bestSquareError[1]) {
            neuralNetwork->bestSquareError[0] = i;
            neuralNetwork->bestSquareError[1] = neuralNetwork->squareErrorHistory[i];
        }
    }
}

/**
 * Performs a learning cycle of neural network. Input vector is transformed to output vector.
 * Output vector si compared with expected output and error is backpropagated throw net and
 * weights are modified..
 */
void neuralLearnCycle(neuralNetwork_t *neuralNetwork,
                       __local float *expectedOutput,
                       int neuron) {
    int numOfLayers = neuralNetwork->setup.numOfLayers;
    __local int * layers = neuralNetwork->setup.layers;
    __local float * values = neuralNetwork->state.values;
    __global float * weights = neuralNetwork->state.weights;
    __local float * errors = neuralNetwork->state.errors;

    int valueOffset = 0;
    int prevValueOffset = 0;
    int followValueOffset = 0;
    int weightOffset = 0;

    int layer;
    float value;
    int neurons;
    int prevNeurons;
    int prevNeuron;
    int followNeurons;
    int followNeuron;
    int prevNeuronsRounded, neuronsRounded;

    float weightError;

    // foward value computation
    for (layer = 1; layer < numOfLayers; layer++) {
        prevNeurons = layers[layer - 1];
        neurons = layers[layer];
        valueOffset += prevNeurons;
        prevNeuronsRounded = (((prevNeurons - 1) >> OPENCL_MEMORY_ALIGN) + 1) << OPENCL_MEMORY_ALIGN;
        weightOffset += neuron * prevNeuronsRounded;
        prefetch(&weights[weightOffset], prevNeuronsRounded);
        if (neuron < neurons) {
            value = 0;
            for (prevNeuron = 0; prevNeuron < prevNeurons; prevNeuron++) {
                value += values[prevNeuron + prevValueOffset] * weights[prevNeuron + weightOffset];
            }
            values[neuron + valueOffset] = 1. / (1. + exp(-neuralNetwork->setup.lambda * value));
        }
        weightOffset += (neurons - neuron) * prevNeuronsRounded;
        prevValueOffset += prevNeurons;
        barrier(CLK_LOCAL_MEM_FENCE);
    }

    // backwards error computation
    neurons = layers[numOfLayers - 1];
    if (neuron < neurons) {
        value = values[neuron + valueOffset];
        // classValue = round(value);
        // classValue = value;
        // if (neuralNetwork->setup.classification) {
        //     classValue = round(value);
        // }

        // if (expectedOutput[neuron] - classValue) {
            // ex = 1.f / (1.f + exp(-neuralNetwork->setup.lambda * value));
        errors[neuron + valueOffset] = (expectedOutput[neuron] - value) * (value * (1 - value));
        // } else {
        //     errors[neuron + valueOffset] = 0;
        // }
    }
    // weightOffset += (neurons - neuron) * prevNeuronsRounded;
    // neuralNetwork->state.epoch = 99;
    barrier(CLK_LOCAL_MEM_FENCE);

    followValueOffset = valueOffset;
    for (layer = numOfLayers - 2; layer > 0; layer--) {
        followNeurons = layers[layer + 1];
        neurons = layers[layer];
        valueOffset -= neurons;
        neuronsRounded = (((neurons - 1) >> OPENCL_MEMORY_ALIGN) + 1) << OPENCL_MEMORY_ALIGN;
        weightOffset -= neuronsRounded * followNeurons;
        prefetch(&weights[weightOffset], followNeuron * neuronsRounded);
        if (neuron < neurons) {
            weightError = 0.f;
            for (followNeuron = 0; followNeuron < followNeurons; followNeuron++) {
                weightError += errors[followNeuron + followValueOffset] *
                              weights[neuron + followNeuron * neuronsRounded + weightOffset];

            }
            value = values[neuron + valueOffset];
            errors[neuron + valueOffset] = weightError * (value * (1 - value));
        }
        followValueOffset -= neurons;
        barrier(CLK_LOCAL_MEM_FENCE);
    }
    // error propagation
    weightOffset = 0;
    valueOffset = 0;
    for (layer = 1; layer < numOfLayers; layer++) {
        prevNeurons = layers[layer - 1];
        neurons = layers[layer];
        prevNeuronsRounded = (((prevNeurons - 1) >> OPENCL_MEMORY_ALIGN) + 1) << OPENCL_MEMORY_ALIGN;
        weightOffset += neuron * prevNeuronsRounded;
        prefetch(&weights[weightOffset], prevNeuronsRounded);
        if (neuron < neurons) {
            for (prevNeuron = 0; prevNeuron < prevNeurons; prevNeuron++) {
                weights[prevNeuron + weightOffset] = weights[prevNeuron + weightOffset] +
                                                     neuralNetwork->setup.learningFactor * values[prevNeuron + valueOffset] *
                                                     errors[neuron + valueOffset + prevNeurons];
            }
        }
        weightOffset += (neurons - neuron) * prevNeuronsRounded;
        valueOffset += prevNeurons;
    }
    barrier(CLK_GLOBAL_MEM_FENCE);
}

/**
 * Performs a test cycle of neural network. Input vector is transformed to output vector.
 * Output vector si compared with expected output. Accurancy is counted from this error.
 */
void neuralTestCycle(neuralNetwork_t *neuralNetwork,
                     __local float *expectedOutput,
                     int neuron) {

    int numOfLayers = neuralNetwork->setup.numOfLayers;
    __local int *layers = neuralNetwork->setup.layers;
    __local float *values = neuralNetwork->state.values;
    __global float *weights = neuralNetwork->state.weights;

    int valueOffset = 0;
    int prevValueOffset = 0;
    int weightOffset = 0;

    int layer;
    float value;
    int neurons;
    int prevNeurons;
    int prevNeuron;
    int prevNeuronsRounded;
    int classValue;

    // foward value computation
    for (layer = 1; layer < numOfLayers; layer++) {
        neurons = layers[layer];
        prevNeurons = layers[layer - 1];
        valueOffset += prevNeurons;
        prevNeuronsRounded = (((prevNeurons - 1) >> OPENCL_MEMORY_ALIGN) + 1) << OPENCL_MEMORY_ALIGN;
        weightOffset += neuron * prevNeuronsRounded;
        prefetch(&weights[weightOffset], prevNeuronsRounded);
        if (neuron < neurons) {
            value = 0.f;
            for (prevNeuron = 0; prevNeuron < prevNeurons; prevNeuron++) {
                value += values[prevNeuron + prevValueOffset] * weights[prevNeuron + weightOffset];
            }
            values[neuron + valueOffset] = 1.f / (1.f + exp(-neuralNetwork->setup.lambda * value));
        }
        weightOffset += (neurons - neuron) * prevNeuronsRounded;
        prevValueOffset += prevNeurons;
        barrier(CLK_LOCAL_MEM_FENCE);
    }

    // error of prediction
    neurons = layers[numOfLayers - 1];
    float predictionCandidateValue = -1.f;
    int predictionCandidateIndex = 0;
    for (int neur = 0; neur < neurons; neur++) {
        value = values[neur + valueOffset];
        classValue = round(value);
        if (classValue != 0 && predictionCandidateValue < value) {
            predictionCandidateValue = value;
            predictionCandidateIndex = neur;
        }
    }
    if (expectedOutput[predictionCandidateIndex] != 1.f || predictionCandidateValue == -1.f) {
        neuralNetwork->currentSquareErrorCounter += 1.f;
    }
}

void importDataStructures(__global neural_network_transform_t *neural_network_transform,
                          __global task_data_transform_t *task_data_transform,
                          __global float * neural_network_buffer,
                          __global float *task_data_buffer,
                          neuralNetwork_t *neuralNetwork,
                          taskData_t *taskData) {
    taskData->learningInputs = (task_data_buffer + task_data_transform->taskData_b_offset_learningInputs);
    taskData->learningOutputs = (task_data_buffer + task_data_transform->taskData_b_offset_learningOutputs);
    taskData->totalLearningLines = task_data_transform->taskData_totalLearningLines;
    taskData->testInputs = (task_data_buffer + task_data_transform->taskData_b_offset_testInputs);
    taskData->testOutputs = (task_data_buffer + task_data_transform->taskData_b_offset_testOutputs);
    taskData->totalTestLines = task_data_transform->taskData_totalTestLines;

    neuralNetwork->setup.numOfLayers = neural_network_transform->setup_numOfLayers;
    neuralNetwork->setup.classification = neural_network_transform->setup_classification;
    neuralNetwork->setup.minOutputValue = neural_network_transform->setup_minOutputValue;
    neuralNetwork->setup.maxOutputValue = neural_network_transform->setup_maxOutputValue;
    neuralNetwork->setup.learningFactor = neural_network_transform->setup_learningFactor;
    neuralNetwork->setup.lambda = neural_network_transform->setup_lambda;
    event_t e1 = async_work_group_copy(neuralNetwork->setup.layers,
                                    (__global int *)(neural_network_buffer + neural_network_transform->setup_b_offset_layers),
                                    neural_network_transform->setup_numOfLayers, 0);
    wait_group_events(1, &e1);

    neuralNetwork->criteria.maxEpochs = neural_network_transform->criteria_maxEpochs;
    neuralNetwork->criteria.minProgress = neural_network_transform->criteria_minProgress;
    neuralNetwork->criteria.maxGeneralizationLoss = neural_network_transform->criteria_maxGeneralizationLoss;

    neuralNetwork->state.epoch = neural_network_transform->state_epoch;
    neuralNetwork->state.testLine = neural_network_transform->state_testLine;
    neuralNetwork->state.learningLine = neural_network_transform->state_learningLine;
    neuralNetwork->state.weights = (neural_network_buffer + neural_network_transform->state_b_offset_weights);
    // return;
    event_t e2 = async_work_group_copy(neuralNetwork->state.values,
                                    (neural_network_buffer + neural_network_transform->state_b_offset_values),
                                    neural_network_transform->state_b_size_values, 0);
    wait_group_events(1, &e2);

    event_t e3 = async_work_group_copy(neuralNetwork->state.errors,
                                    (neural_network_buffer + neural_network_transform->state_b_offset_errors),
                                    neural_network_transform->state_b_size_errors, 0);
    wait_group_events(1, &e3);
    neuralNetwork->currentSquareErrorCounter = neural_network_transform->neuralNetwork_currentSquareErrorCounter;
    neuralNetwork->bestSquareError[0] = neural_network_transform->neuralNetwork_bestSquareError[0];
    neuralNetwork->bestSquareError[1] = neural_network_transform->neuralNetwork_bestSquareError[1];
    neuralNetwork->squareErrorHistory = (neural_network_buffer + neural_network_transform->neuralNetwork_b_offset_squareErrorHistory);
}

void exportDataStructures(__global neural_network_transform_t *neural_network_transform,
                        __global float * neural_network_buffer,
                        __global float *task_data_buffer,
                        neuralNetwork_t *neuralNetwork,
                        taskData_t *taskData) {
    neural_network_transform->setup_numOfLayers = neuralNetwork->setup.numOfLayers;
    neural_network_transform->setup_classification = neuralNetwork->setup.classification;
    neural_network_transform->setup_minOutputValue = neuralNetwork->setup.minOutputValue;
    neural_network_transform->setup_maxOutputValue = neuralNetwork->setup.maxOutputValue;
    neural_network_transform->setup_learningFactor = neuralNetwork->setup.learningFactor;
    neuralNetwork->setup.lambda = neural_network_transform->setup_lambda;
    event_t e1 = async_work_group_copy((__global int *)(neural_network_buffer + neural_network_transform->setup_b_offset_layers),
                                        neuralNetwork->setup.layers,
                                        neural_network_transform->setup_numOfLayers, 0);
    wait_group_events(1, &e1);

    neural_network_transform->criteria_maxEpochs = neuralNetwork->criteria.maxEpochs;
    neural_network_transform->criteria_minProgress = neuralNetwork->criteria.minProgress;
    neural_network_transform->criteria_maxGeneralizationLoss = neuralNetwork->criteria.maxGeneralizationLoss;

    neural_network_transform->state_epoch = neuralNetwork->state.epoch;
    neural_network_transform->state_testLine = neuralNetwork->state.testLine;
    neural_network_transform->state_learningLine = neuralNetwork->state.learningLine;
    event_t e2 = async_work_group_copy((neural_network_buffer + neural_network_transform->state_b_offset_values),
                                        neuralNetwork->state.values,
                                        neural_network_transform->state_b_size_values, 0);
    wait_group_events(1, &e2);

    event_t e3 = async_work_group_copy((neural_network_buffer + neural_network_transform->state_b_offset_errors),
                                        neuralNetwork->state.errors,
                                        neural_network_transform->state_b_size_errors, 0);
    wait_group_events(1, &e3);
    neural_network_transform->neuralNetwork_currentSquareErrorCounter = neuralNetwork->currentSquareErrorCounter;

    neural_network_transform->neuralNetwork_bestSquareError[0] = neuralNetwork->bestSquareError[0];
    neural_network_transform->neuralNetwork_bestSquareError[1] = neuralNetwork->bestSquareError[1];
}

uint get_interupt_limit(neuralNetwork_t *neuralNetwork) {
    uint number_of_weights = 0;
    for (int i = 1; i < neuralNetwork->setup.numOfLayers; i++) {
        number_of_weights += neuralNetwork->setup.layers[i] * neuralNetwork->setup.layers[i - 1];
    }
    return 1000 / ((number_of_weights / 5000) + 1) + 1;
}

__kernel void run_neural_network(
    __global neural_network_transform_t *neural_network_transform_arr,
    __global task_data_transform_t *task_data_transform,
    __global float *neural_network_buffer_arr,
    __global float *task_data_buffer,
    int number_of_networks
    )
{
    __global float *neural_network_buffer = neural_network_buffer_arr;
    int group_id = get_group_linear_id();
    uint cycle_counter = 0;

    if (group_id < number_of_networks) {
    // if (group_id != 0) {
        int lid = get_local_linear_id();
        taskData_t taskData;
        neuralNetwork_t neuralNetwork;
        __global neural_network_transform_t *neural_network_transform = &neural_network_transform_arr[group_id];
        neural_network_buffer = neural_network_buffer + neural_network_transform->neuralNetwork_b_offset;
        __local float *expectedOutput;
        __local float sharedMemory[SHARED_MEMORY_SIZE];
        neuralNetwork.setup.layers = (__local int*)sharedMemory;
        neuralNetwork.state.values = sharedMemory + neural_network_transform->setup_numOfLayers;
        neuralNetwork.state.errors = sharedMemory + neural_network_transform->setup_numOfLayers + neural_network_transform->state_b_size_values;
        importDataStructures(neural_network_transform, task_data_transform, neural_network_buffer, task_data_buffer, &neuralNetwork, &taskData);
        expectedOutput = sharedMemory + neural_network_transform->setup_numOfLayers + neural_network_transform->state_b_size_values + neural_network_transform->state_b_size_errors;
        uint interupt_limit = get_interupt_limit(&neuralNetwork);
        for (; neuralNetwork.state.epoch < neuralNetwork.criteria.maxEpochs; neuralNetwork.state.epoch++) {
            while (getLearningVector(&neuralNetwork, &taskData, expectedOutput)) {
                neuralLearnCycle(&neuralNetwork, expectedOutput, lid);
                cycle_counter++;
                if (cycle_counter >= interupt_limit) {
                    exportDataStructures(neural_network_transform, neural_network_buffer, task_data_buffer, &neuralNetwork, &taskData);
                    return;
                }
                // if (neuralNetwork.state.learningLine >= 1  && neuralNetwork.state.epoch >= 0) {

                //     exportDataStructures(neural_network_transform, neural_network_buffer, task_data_buffer, &neuralNetwork, &taskData);
                //     return;
                // }
            }

            while (getTestVector(&neuralNetwork, &taskData, expectedOutput)) {
                neuralTestCycle(&neuralNetwork, expectedOutput, lid);
                cycle_counter++;
                if (cycle_counter >= interupt_limit) {
                    exportDataStructures(neural_network_transform, neural_network_buffer, task_data_buffer, &neuralNetwork, &taskData);
                    return;
                }
                // if (neuralNetwork.state.testLine > 348  && neuralNetwork.state.epoch >= 0) {
                    // exportDataStructures(neural_network_transform, neural_network_buffer, task_data_buffer, &neuralNetwork, &taskData);
                    // return;
                // }
            }
            neuralNetwork.squareErrorHistory[neuralNetwork.state.epoch] = (neuralNetwork.setup.maxOutputValue - neuralNetwork.setup.minOutputValue) *
                                                                            neuralNetwork.currentSquareErrorCounter / (neuralNetwork.setup.layers[neuralNetwork.setup.numOfLayers - 1] *
                                                                            taskData.totalTestLines);
            findAndSetBestSquareError(&neuralNetwork);
            neuralNetwork.currentSquareErrorCounter = 0.f;
            neuralNetwork.state.learningLine = 0;
            neuralNetwork.state.testLine = 0;
            // break;
        }
        exportDataStructures(neural_network_transform, neural_network_buffer, task_data_buffer, &neuralNetwork, &taskData);
    }
}

#define OPENCL_MEMORY_ALIGN 5 // 2**OPENCL_MEMORY_ALIGN * sizeof(float)

typedef struct {
   __global float *learningInputs, *learningOutputs;
   int totalLearningLines;
   __global float *testInputs, *testOutputs;
   int totalTestLines;
} taskData_t;

typedef struct {
    int numOfLayers;
    __local int *layers;
    bool classification;
    float minOutputValue;
    float maxOutputValue;
    float learningFactor;
    float lambda;
} setup_t;

typedef struct {
    int maxEpochs;
    float minProgress;
    float maxGeneralizationLoss;
} criteria_t;

typedef struct {
    int epoch;
    __global float *weights;
    __local float *values;
    __local float *errors;
    int testLine;
    int learningLine;
} state_t;

typedef struct {
    setup_t setup;
    criteria_t criteria;
    state_t state;
    float currentSquareErrorCounter;
    float bestSquareError[2];
    __global float *squareErrorHistory;
} neuralNetwork_t;

typedef struct {
    int setup_numOfLayers;
    int setup_b_offset_layers;
    bool setup_classification;
    float setup_minOutputValue;
    float setup_maxOutputValue;
    float setup_learningFactor;
    float setup_lambda;

    int criteria_maxEpochs;
    float criteria_minProgress;
    float criteria_maxGeneralizationLoss;

    int state_epoch;
    int state_b_offset_weights;
    int state_b_offset_values;
    int state_b_size_values;
    int state_b_offset_errors;
    int state_b_size_errors;
    int state_testLine;
    int state_learningLine;

    float neuralNetwork_currentSquareErrorCounter;
    float neuralNetwork_bestSquareError[2];
    int neuralNetwork_b_offset_squareErrorHistory;
    int neuralNetwork_b_size;
    int neuralNetwork_b_offset;
} neural_network_transform_t;

typedef struct {
    int taskData_b_offset_learningInputs, taskData_b_offset_learningOutputs;
    int taskData_totalLearningLines;
    int taskData_b_offset_testInputs, taskData_b_offset_testOutputs;
    int taskData_totalTestLines;
    int taskData_b_size;
} task_data_transform_t;

size_t get_local_linear_id() {
    return  (get_local_id(2) * get_local_size(1) *
            (0)) + (get_local_id(1) *
            get_local_size(0)) + get_local_id(0);
    }

size_t get_group_linear_id() {
    return  (get_group_id(2) * get_num_groups(1) *
            (0)) + (get_group_id(1) *
            get_num_groups(0)) + get_group_id(0);
    }

/**
     * Returns a learning vector for neural network.
     */
bool getLearningVector(neuralNetwork_t *neuralNetwork, taskData_t *taskData, __local float *expectedOutput) {

    if (neuralNetwork->state.learningLine >= taskData->totalLearningLines) {
        return false;
    }

    event_t e = async_work_group_copy(neuralNetwork->state.values,
                                    (taskData->learningInputs +
                                        neuralNetwork->state.learningLine * neuralNetwork->setup.layers[0]),
                                    neuralNetwork->setup.layers[0], 0);
    wait_group_events(1, &e);
    for (int input = 0; input < neuralNetwork->setup.layers[0]; input++) {
        neuralNetwork->state.values[input] = taskData->learningInputs[input + neuralNetwork->state.learningLine * neuralNetwork->setup.layers[0]];
    }

    event_t e2 = async_work_group_copy(expectedOutput,
                                    (taskData->learningOutputs +
                                        neuralNetwork->state.learningLine * neuralNetwork->setup.layers[neuralNetwork->setup.numOfLayers - 1]),
                                    neuralNetwork->setup.layers[neuralNetwork->setup.numOfLayers - 1], 0);
    wait_group_events(1, &e2);

    neuralNetwork->state.learningLine++;
    return true;
}

/**
     * Returns a test vector for neural network.
     */
bool getTestVector(neuralNetwork_t *neuralNetwork, taskData_t *taskData, __local float *expectedOutput) {

    if (neuralNetwork->state.testLine >= taskData->totalTestLines) {
        return false;
    }
    event_t e = async_work_group_copy(neuralNetwork->state.values,
                                    (taskData->testInputs +
                                        neuralNetwork->state.testLine * neuralNetwork->setup.layers[0]),
                                    neuralNetwork->setup.layers[0], 0);
    wait_group_events(1, &e);
    for (int input = 0; input < neuralNetwork->setup.layers[0]; input++) {
        neuralNetwork->state.values[input] = taskData->testInputs[input + neuralNetwork->state.testLine * neuralNetwork->setup.layers[0]];
    }

    event_t e2 = async_work_group_copy(expectedOutput,
                                    (taskData->testOutputs +
                                        neuralNetwork->state.testLine * neuralNetwork->setup.layers[neuralNetwork->setup.numOfLayers - 1]),
                                    neuralNetwork->setup.layers[neuralNetwork->setup.numOfLayers - 1], 0);
    wait_group_events(1, &e2);
    neuralNetwork->state.testLine++;
    return true;
}

/**
 * Finds lowest square error and sets it as the best one.
 */
void findAndSetBestSquareError(neuralNetwork_t *neuralNetwork) {
    neuralNetwork->bestSquareError[0] = 0;
    neuralNetwork->bestSquareError[1] = neuralNetwork->squareErrorHistory[0];

    for (int i = 1; i <= neuralNetwork->state.epoch; i++) {
        if (neuralNetwork->squareErrorHistory[i] < neuralNetwork->bestSquareError[1]) {
            neuralNetwork->bestSquareError[0] = i;
            neuralNetwork->bestSquareError[1] = neuralNetwork->squareErrorHistory[i];
        }
    }
}

/**
 * Performs a learning cycle of neural network. Input vector is transformed to output vector.
 * Output vector si compared with expected output and error is backpropagated throw net and
 * weights are modified..
 */
void neuralLearnCycle(neuralNetwork_t *neuralNetwork,
                       __local float *expectedOutput,
                       int neuron) {
    int numOfLayers = neuralNetwork->setup.numOfLayers;
    __local int * layers = neuralNetwork->setup.layers;
    __local float * values = neuralNetwork->state.values;
    __global float * weights = neuralNetwork->state.weights;
    __local float * errors = neuralNetwork->state.errors;

    int valueOffset = 0;
    int prevValueOffset = 0;
    int followValueOffset = 0;
    int weightOffset = 0;

    int layer;
    float value;
    int neurons;
    int prevNeurons;
    int prevNeuron;
    int followNeurons;
    int followNeuron;
    int prevNeuronsRounded, neuronsRounded;

    float weightError;

    // foward value computation
    for (layer = 1; layer < numOfLayers; layer++) {
        prevNeurons = layers[layer - 1];
        neurons = layers[layer];
        valueOffset += prevNeurons;
        prevNeuronsRounded = (((prevNeurons - 1) >> OPENCL_MEMORY_ALIGN) + 1) << OPENCL_MEMORY_ALIGN;
        weightOffset += neuron * prevNeuronsRounded;
        prefetch(&weights[weightOffset], prevNeuronsRounded);
        if (neuron < neurons) {
            value = 0;
            for (prevNeuron = 0; prevNeuron < prevNeurons; prevNeuron++) {
                value += values[prevNeuron + prevValueOffset] * weights[prevNeuron + weightOffset];
            }
            values[neuron + valueOffset] = 1. / (1. + exp(-neuralNetwork->setup.lambda * value));
        }
        weightOffset += (neurons - neuron) * prevNeuronsRounded;
        prevValueOffset += prevNeurons;
        barrier(CLK_LOCAL_MEM_FENCE);
    }

    // backwards error computation
    neurons = layers[numOfLayers - 1];
    if (neuron < neurons) {
        value = values[neuron + valueOffset];
        // classValue = round(value);
        // classValue = value;
        // if (neuralNetwork->setup.classification) {
        //     classValue = round(value);
        // }

        // if (expectedOutput[neuron] - classValue) {
            // ex = 1.f / (1.f + exp(-neuralNetwork->setup.lambda * value));
        errors[neuron + valueOffset] = (expectedOutput[neuron] - value) * (value * (1 - value));
        // } else {
        //     errors[neuron + valueOffset] = 0;
        // }
    }
    // weightOffset += (neurons - neuron) * prevNeuronsRounded;
    // neuralNetwork->state.epoch = 99;
    barrier(CLK_LOCAL_MEM_FENCE);

    followValueOffset = valueOffset;
    for (layer = numOfLayers - 2; layer > 0; layer--) {
        followNeurons = layers[layer + 1];
        neurons = layers[layer];
        valueOffset -= neurons;
        neuronsRounded = (((neurons - 1) >> OPENCL_MEMORY_ALIGN) + 1) << OPENCL_MEMORY_ALIGN;
        weightOffset -= neuronsRounded * followNeurons;
        prefetch(&weights[weightOffset], followNeuron * neuronsRounded);
        if (neuron < neurons) {
            weightError = 0.f;
            for (followNeuron = 0; followNeuron < followNeurons; followNeuron++) {
                weightError += errors[followNeuron + followValueOffset] *
                              weights[neuron + followNeuron * neuronsRounded + weightOffset];

            }
            value = values[neuron + valueOffset];
            errors[neuron + valueOffset] = weightError * (value * (1 - value));
        }
        followValueOffset -= neurons;
        barrier(CLK_LOCAL_MEM_FENCE);
    }
    // error propagation
    weightOffset = 0;
    valueOffset = 0;
    for (layer = 1; layer < numOfLayers; layer++) {
        prevNeurons = layers[layer - 1];
        neurons = layers[layer];
        prevNeuronsRounded = (((prevNeurons - 1) >> OPENCL_MEMORY_ALIGN) + 1) << OPENCL_MEMORY_ALIGN;
        weightOffset += neuron * prevNeuronsRounded;
        prefetch(&weights[weightOffset], prevNeuronsRounded);
        if (neuron < neurons) {
            for (prevNeuron = 0; prevNeuron < prevNeurons; prevNeuron++) {
                weights[prevNeuron + weightOffset] = weights[prevNeuron + weightOffset] +
                                                     neuralNetwork->setup.learningFactor * values[prevNeuron + valueOffset] *
                                                     errors[neuron + valueOffset + prevNeurons];
            }
        }
        weightOffset += (neurons - neuron) * prevNeuronsRounded;
        valueOffset += prevNeurons;
    }
    barrier(CLK_GLOBAL_MEM_FENCE);
}

/**
 * Performs a test cycle of neural network. Input vector is transformed to output vector.
 * Output vector si compared with expected output. Accurancy is counted from this error.
 */
void neuralTestCycle(neuralNetwork_t *neuralNetwork,
                     __local float *expectedOutput,
                     int neuron) {

    int numOfLayers = neuralNetwork->setup.numOfLayers;
    __local int *layers = neuralNetwork->setup.layers;
    __local float *values = neuralNetwork->state.values;
    __global float *weights = neuralNetwork->state.weights;

    int valueOffset = 0;
    int prevValueOffset = 0;
    int weightOffset = 0;

    int layer;
    float value;
    int neurons;
    int prevNeurons;
    int prevNeuron;
    int prevNeuronsRounded;
    int classValue;

    // foward value computation
    for (layer = 1; layer < numOfLayers; layer++) {
        neurons = layers[layer];
        prevNeurons = layers[layer - 1];
        valueOffset += prevNeurons;
        prevNeuronsRounded = (((prevNeurons - 1) >> OPENCL_MEMORY_ALIGN) + 1) << OPENCL_MEMORY_ALIGN;
        weightOffset += neuron * prevNeuronsRounded;
        prefetch(&weights[weightOffset], prevNeuronsRounded);
        if (neuron < neurons) {
            value = 0.f;
            for (prevNeuron = 0; prevNeuron < prevNeurons; prevNeuron++) {
                value += values[prevNeuron + prevValueOffset] * weights[prevNeuron + weightOffset];
            }
            values[neuron + valueOffset] = 1.f / (1.f + exp(-neuralNetwork->setup.lambda * value));
        }
        weightOffset += (neurons - neuron) * prevNeuronsRounded;
        prevValueOffset += prevNeurons;
        barrier(CLK_LOCAL_MEM_FENCE);
    }

    // error of prediction
    neurons = layers[numOfLayers - 1];
    float predictionCandidateValue = -1.f;
    int predictionCandidateIndex = 0;
    for (int neur = 0; neur < neurons; neur++) {
        value = values[neur + valueOffset];
        classValue = round(value);
        if (classValue != 0 && predictionCandidateValue < value) {
            predictionCandidateValue = value;
            predictionCandidateIndex = neur;
        }
    }
    if (expectedOutput[predictionCandidateIndex] != 1.f || predictionCandidateValue == -1.f) {
        neuralNetwork->currentSquareErrorCounter += 1.f;
    }
}

void importDataStructures(__global neural_network_transform_t *neural_network_transform,
                          __global task_data_transform_t *task_data_transform,
                          __global float * neural_network_buffer,
                          __global float *task_data_buffer,
                          neuralNetwork_t *neuralNetwork,
                          taskData_t *taskData) {
    taskData->learningInputs = (task_data_buffer + task_data_transform->taskData_b_offset_learningInputs);
    taskData->learningOutputs = (task_data_buffer + task_data_transform->taskData_b_offset_learningOutputs);
    taskData->totalLearningLines = task_data_transform->taskData_totalLearningLines;
    taskData->testInputs = (task_data_buffer + task_data_transform->taskData_b_offset_testInputs);
    taskData->testOutputs = (task_data_buffer + task_data_transform->taskData_b_offset_testOutputs);
    taskData->totalTestLines = task_data_transform->taskData_totalTestLines;

    neuralNetwork->setup.numOfLayers = neural_network_transform->setup_numOfLayers;
    neuralNetwork->setup.classification = neural_network_transform->setup_classification;
    neuralNetwork->setup.minOutputValue = neural_network_transform->setup_minOutputValue;
    neuralNetwork->setup.maxOutputValue = neural_network_transform->setup_maxOutputValue;
    neuralNetwork->setup.learningFactor = neural_network_transform->setup_learningFactor;
    neuralNetwork->setup.lambda = neural_network_transform->setup_lambda;
    event_t e1 = async_work_group_copy(neuralNetwork->setup.layers,
                                    (__global int *)(neural_network_buffer + neural_network_transform->setup_b_offset_layers),
                                    neural_network_transform->setup_numOfLayers, 0);
    wait_group_events(1, &e1);

    neuralNetwork->criteria.maxEpochs = neural_network_transform->criteria_maxEpochs;
    neuralNetwork->criteria.minProgress = neural_network_transform->criteria_minProgress;
    neuralNetwork->criteria.maxGeneralizationLoss = neural_network_transform->criteria_maxGeneralizationLoss;

    neuralNetwork->state.epoch = neural_network_transform->state_epoch;
    neuralNetwork->state.testLine = neural_network_transform->state_testLine;
    neuralNetwork->state.learningLine = neural_network_transform->state_learningLine;
    neuralNetwork->state.weights = (neural_network_buffer + neural_network_transform->state_b_offset_weights);
    // return;
    event_t e2 = async_work_group_copy(neuralNetwork->state.values,
                                    (neural_network_buffer + neural_network_transform->state_b_offset_values),
                                    neural_network_transform->state_b_size_values, 0);
    wait_group_events(1, &e2);

    event_t e3 = async_work_group_copy(neuralNetwork->state.errors,
                                    (neural_network_buffer + neural_network_transform->state_b_offset_errors),
                                    neural_network_transform->state_b_size_errors, 0);
    wait_group_events(1, &e3);
    neuralNetwork->currentSquareErrorCounter = neural_network_transform->neuralNetwork_currentSquareErrorCounter;
    neuralNetwork->bestSquareError[0] = neural_network_transform->neuralNetwork_bestSquareError[0];
    neuralNetwork->bestSquareError[1] = neural_network_transform->neuralNetwork_bestSquareError[1];
    neuralNetwork->squareErrorHistory = (neural_network_buffer + neural_network_transform->neuralNetwork_b_offset_squareErrorHistory);
}

void exportDataStructures(__global neural_network_transform_t *neural_network_transform,
                        __global float * neural_network_buffer,
                        __global float *task_data_buffer,
                        neuralNetwork_t *neuralNetwork,
                        taskData_t *taskData) {
    neural_network_transform->setup_numOfLayers = neuralNetwork->setup.numOfLayers;
    neural_network_transform->setup_classification = neuralNetwork->setup.classification;
    neural_network_transform->setup_minOutputValue = neuralNetwork->setup.minOutputValue;
    neural_network_transform->setup_maxOutputValue = neuralNetwork->setup.maxOutputValue;
    neural_network_transform->setup_learningFactor = neuralNetwork->setup.learningFactor;
    neuralNetwork->setup.lambda = neural_network_transform->setup_lambda;
    event_t e1 = async_work_group_copy((__global int *)(neural_network_buffer + neural_network_transform->setup_b_offset_layers),
                                        neuralNetwork->setup.layers,
                                        neural_network_transform->setup_numOfLayers, 0);
    wait_group_events(1, &e1);

    neural_network_transform->criteria_maxEpochs = neuralNetwork->criteria.maxEpochs;
    neural_network_transform->criteria_minProgress = neuralNetwork->criteria.minProgress;
    neural_network_transform->criteria_maxGeneralizationLoss = neuralNetwork->criteria.maxGeneralizationLoss;

    neural_network_transform->state_epoch = neuralNetwork->state.epoch;
    neural_network_transform->state_testLine = neuralNetwork->state.testLine;
    neural_network_transform->state_learningLine = neuralNetwork->state.learningLine;
    event_t e2 = async_work_group_copy((neural_network_buffer + neural_network_transform->state_b_offset_values),
                                        neuralNetwork->state.values,
                                        neural_network_transform->state_b_size_values, 0);
    wait_group_events(1, &e2);

    event_t e3 = async_work_group_copy((neural_network_buffer + neural_network_transform->state_b_offset_errors),
                                        neuralNetwork->state.errors,
                                        neural_network_transform->state_b_size_errors, 0);
    wait_group_events(1, &e3);
    neural_network_transform->neuralNetwork_currentSquareErrorCounter = neuralNetwork->currentSquareErrorCounter;

    neural_network_transform->neuralNetwork_bestSquareError[0] = neuralNetwork->bestSquareError[0];
    neural_network_transform->neuralNetwork_bestSquareError[1] = neuralNetwork->bestSquareError[1];
}

uint get_interupt_limit(__global neuralNetwork_t *neuralNetwork) {
    uint number_of_weights = 0;
    for (int i = 1; i < neuralNetwork->setup.numOfLayers; i++) {
        number_of_weights += neuralNetwork->setup.layers[i] * neuralNetwork->setup.layers[i - 1];
    }
    return 1000 / ((number_of_weights / 5000) + 1) + 1;
}

__kernel void run_neural_network(
    __global neural_network_transform_t *neural_network_transform_arr,
    __global task_data_transform_t *task_data_transform,
    __global float *neural_network_buffer_arr,
    __global float *task_data_buffer,
    int number_of_networks
    )
{
    __global float *neural_network_buffer = neural_network_buffer_arr;
    int group_id = get_group_linear_id();
    uint cycle_counter = 0;

    if (group_id < number_of_networks) {
    // if (group_id != 0) {
        int lid = get_local_linear_id();
        taskData_t taskData;
        neuralNetwork_t neuralNetwork;
        __global neural_network_transform_t *neural_network_transform = &neural_network_transform_arr[group_id];
        neural_network_buffer = neural_network_buffer + neural_network_transform->neuralNetwork_b_offset;
        __local float *expectedOutput;
        __local float sharedMemory[SHARED_MEMORY_SIZE];
        neuralNetwork.setup.layers = (__local int*)sharedMemory;
        neuralNetwork.state.values = sharedMemory + neural_network_transform->setup_numOfLayers;
        neuralNetwork.state.errors = sharedMemory + neural_network_transform->setup_numOfLayers + neural_network_transform->state_b_size_values;
        importDataStructures(neural_network_transform, task_data_transform, neural_network_buffer, task_data_buffer, &neuralNetwork, &taskData);
        expectedOutput = sharedMemory + neural_network_transform->setup_numOfLayers + neural_network_transform->state_b_size_values + neural_network_transform->state_b_size_errors;
        uint interupt_limit = get_interupt_limit(&neuralNetwork);
        for (; neuralNetwork.state.epoch < neuralNetwork.criteria.maxEpochs; neuralNetwork.state.epoch++) {
            while (getLearningVector(&neuralNetwork, &taskData, expectedOutput)) {
                neuralLearnCycle(&neuralNetwork, expectedOutput, lid);
                cycle_counter++;
                if (cycle_counter >= interupt_limit) {
                    exportDataStructures(neural_network_transform, neural_network_buffer, task_data_buffer, &neuralNetwork, &taskData);
                    return;
                }
                // if (neuralNetwork.state.learningLine >= 1  && neuralNetwork.state.epoch >= 0) {

                //     exportDataStructures(neural_network_transform, neural_network_buffer, task_data_buffer, &neuralNetwork, &taskData);
                //     return;
                // }
            }

            while (getTestVector(&neuralNetwork, &taskData, expectedOutput)) {
                neuralTestCycle(&neuralNetwork, expectedOutput, lid);
                cycle_counter++;
                if (cycle_counter >= interupt_limit) {
                    exportDataStructures(neural_network_transform, neural_network_buffer, task_data_buffer, &neuralNetwork, &taskData);
                    return;
                }
                // if (neuralNetwork.state.testLine > 348  && neuralNetwork.state.epoch >= 0) {
                    // exportDataStructures(neural_network_transform, neural_network_buffer, task_data_buffer, &neuralNetwork, &taskData);
                    // return;
                // }
            }
            neuralNetwork.squareErrorHistory[neuralNetwork.state.epoch] = (neuralNetwork.setup.maxOutputValue - neuralNetwork.setup.minOutputValue) *
                                                                            neuralNetwork.currentSquareErrorCounter / (neuralNetwork.setup.layers[neuralNetwork.setup.numOfLayers - 1] *
                                                                            taskData.totalTestLines);
            findAndSetBestSquareError(&neuralNetwork);
            neuralNetwork.currentSquareErrorCounter = 0.f;
            neuralNetwork.state.learningLine = 0;
            neuralNetwork.state.testLine = 0;
            // break;
        }
        exportDataStructures(neural_network_transform, neural_network_buffer, task_data_buffer, &neuralNetwork, &taskData);
    }
}


C-l, puts current prompt on top line, similar to clear

(load "base.cl")
(load "repl-config.cl")

(declaim #+sbcl(sb-ext:muffle-conditions style-warning))
(declaim #+sbcl(sb-ext:muffle-conditions warning))

(in-package arboreta)

(setf *random-state* (make-random-state t))

(defparameter *cursor-blink-status* t)
(defparameter *cursor-blink-time* 0)
(defparameter *cursor-blink-delay* 1000)

;; descriptive function names, huh?
(defun cat (list)
   (format nil "~{~a~}" list))

(defmacro cats (&rest rest)
   `(cat (list ,@rest)))

(defun catn (list)
   (format nil "~{~a~%~}" list))

(defmacro catans (&rest rest)
   `(catn (list ,@rest)))

(defun cursor-blink-timer ()
   (let ((time (get-internal-real-time)))
         (when (>= time *cursor-blink-time*)
               (setf *cursor-blink-status* (not *cursor-blink-status*))
               (setf *cursor-blink-time* (+ (get-internal-real-time) *cursor-blink-delay*))
               (setf *buffer-needs-update* t))))

(defun repl-update-loop ()
   (iter (for x = (+ (get-internal-real-time) 20))
         (cursor-blink-timer)
         (when *buffer-needs-update*
            (draw root-window)
            (cairo::refresh context)
            (setf *buffer-needs-update* nil))
         (iter (while (cairo::handle-event context)))
         (sleep (let ((delay (/ (- x (get-internal-real-time)) 1000)))
                      ;; (print (* 1000.0 delay))
                      (if (> delay 0) delay 0)))))

(defparameter *current-input* "")
(defparameter *cursor-index* 0)
(defparameter *buffer-needs-update* t)

(defun editor-insert (str)
   (setf *current-input*
      (concatenate 'string
         (subseq *current-input* 0 *cursor-index*)
         str
         (subseq *current-input* *cursor-index*)))
   (incf *cursor-index* (length str)))

(defun editor-delete-char ()
   (setf *current-input*
      (concatenate 'string
         (subseq *current-input* 0 (- *cursor-index* 1))
         (subseq *current-input* *cursor-index*)))
   (decf *cursor-index*))

(defun simple-eval ()
   (let* ((str (make-array '(0) :element-type 'base-char :fill-pointer 0 :adjustable t))
          (res (with-output-to-string (stream str)
                 (let* ((*standard-output* stream)
                        (val (format nil "~s" (ignore-errors (eval (read-from-string *current-input*))))))
                        (list val str)))))
          `((,(alexandria:copy-sequence 'string *current-input*) ,@res))))

(defun handle-repl-key-events ()
   (iter (for x = (+ (get-internal-real-time) 17))
      (if *key-events*
         (let ((kev (pop *key-events*)))
               (setf *cursor-blink-status* t)
               (setf *cursor-blink-time* (+ (get-internal-real-time) *cursor-blink-delay*))

               (setf *buffer-needs-update* t)
               (when (and (eql (keypress-code kev) 113) (eql (keypress-mods kev) 4)) ;; C-q
                     (cairo:destroy context)
                     (sb-ext:exit))
               (when (and (eql (keypress-code kev) 97) (eql (keypress-mods kev) 4)) ;; C-a
                     (setf *cursor-index* 0) (center-view))
               (when (and (eql (keypress-code kev) 101) (eql (keypress-mods kev) 4)) ;; C-e
                     (setf *cursor-index* (length *current-input*)) (center-view))
               (when (and (eql (keypress-code kev) 108) (eql (keypress-mods kev) 4)) ;; C-l
                     (setf *scroll-offset* *max-scroll*))
               (alexandria::switch ((keypress-str kev) :test #'equalp)
                  ("Return" (alexandria:appendf *buffer-history* (simple-eval))
                            ;; (print *buffer-history*)
                            ;; (finish-output)
                            (setf *current-input* "")
                            (setf *cursor-index* 0)
                            (center-view))
                  ("left" (unless (eql *cursor-index* 0) (decf *cursor-index*)))
                  ("right" (unless (eql *cursor-index* (length *current-input*)) (incf *cursor-index*)))
                  ("up" (setf *scroll-offset* (alexandria:clamp (- *scroll-offset* 10) *min-scroll* *max-scroll*)))
                  ("down" (setf *scroll-offset* (alexandria:clamp (+ *scroll-offset* 10) *min-scroll* *max-scroll*)))
                  ("Shift_L" nil)
                  ("Control_L" nil)
                  ("Control_R" nil)
                  ("Shift_R" nil)
                  ("BackSpace" (center-view)
                     (when (and (not (eql *cursor-index* 0)) (> (length *current-input*) 0))
                           (editor-delete-char)))
                  (otherwise
                     (center-view)
                     (when (<= #x20 (keypress-code kev) #x13be) ;; latin chars
                        (editor-insert (if (> (length (keypress-str kev)) 1)
                                         (string (code-char (keypress-code kev)))
                                         (keypress-str kev))))))))
      (sleep (let ((delay (/ (- x (get-internal-real-time)) 1000)))
                   ;; (print (* 1000.0 delay))
                   (if (> delay 0) delay 0)))))

(defparameter *heart* "♥")
(defparameter *heart-colors*
   '(("ae82ff" "dc2566" "fa2772" "d4c96e" "e7db75" "8fc029" "a7e22e" "56b7a5" "66efd5" "55bcce" "66d9ee" "9358fe")
     ("1693A5" "02AAB0" "00CDAC" "7FFF24" "C3FF68")
     ("AAFF00" "FFAA00" "FF00AA" "AA00FF" "00AAFF")
     ("F6D76B" "FF9036" "D6254D" "FF5475" "FDEBA9")
     ("FDCFBF" "FEB89F" "E23D75" "742365")))

(defun get-block (index size seq)
   (nth index
      (iter (for x from 0 to (length seq) by size)
            (for y from size to (length seq) by size)
            (collect (subseq seq x y)))))

(defun set-hex-color (hex)
   (let ((colors (mapcar (lambda (x) (/ (parse-integer x :radix 16) 256))
                         (iter (for i from 0 to 2) (collect (get-block i 2 hex))))))
         (set-source-rgb (first colors) (second colors) (third colors))))

(defun basic-write (str color x y)
   (let ((pango-layout (pango:pango_cairo_create_layout (slot-value cairo:*context* 'cairo::pointer))))
         (pango:pango_layout_set_font_description pango-layout font)

         (new-path)
         (move-to x y)
         (set-hex-color color)
         (pango:pango_layout_set_text pango-layout str -1)

         (pango:pango_cairo_update_layout (slot-value *context* 'cairo::pointer) pango-layout)
         (pango:pango_cairo_show_layout (slot-value *context* 'cairo::pointer) pango-layout)
         (pango:g_object_unref pango-layout)))

(defparameter *colorset* (alexandria:random-elt *heart-colors*))
(defparameter *offset* (alexandria:random-elt (alexandria:iota 5 :start -5)))

(defun draw-hearts ()
   (iter (for y from 0 to 65 by 15)
         (for y2 upfrom 0)
      (iter (for x from -7 to 607 by 14)
            (for x2 upfrom 0)
            (basic-write *heart*
               (nth (mod (+ x2 (* *offset* y2)) (length *colorset*)) *colorset*) x y))))

;; (prompt result printed)
(defparameter *buffer-history* nil)

(defun line-count (str)
   (if (equalp str "")
       0
       (+ 1 (iter (for c in-string (string-trim '(#\Newline) str))
                  (counting (eql c #\newline))))))

(defun line-count* (str)
   (+ 1 (iter (for c in-string (string-trim '(#\Newline) str))
              (counting (eql c #\newline)))))

(defun paragraph-height (str)
   (* *font-height* (line-count str)))

(defun total-height (repl-result)
   (+ (prompt-height (first repl-result))
      (paragraph-height (second repl-result))
      (paragraph-height (third repl-result))))

(defun prompt-height (text)
   (+ (* 2 inner-padding) (* 2 outer-padding)
      (* *font-height* (line-count* text))))

(defparameter *font-height* 0)

(defparameter triangle-offset 0)
(defparameter outer-padding 0)
(defparameter inner-padding 0)
(defparameter prompt-offset 0)
(defparameter text-offset 0)

(defun draw-prompt-bg (y-offset)
   (when *draw-prompt-bg?*
      (new-path)
      (set-hex-color *prompt-bg-color*)
      (rectangle 0 (+ outer-padding y-offset) w (+ *font-height* (* 2 inner-padding)))
      (fill-path))

   (move-to 0 (+ outer-padding y-offset))
   (new-path)
   (set-hex-color *triangle-color*)
   (line-to (- (+ (ceiling (/ *font-height* 2)) (* 2 inner-padding)) triangle-offset)
            (+ y-offset outer-padding (ceiling (/ *font-height* 2)) (* 1 inner-padding)))
   (line-to (- triangle-offset) (+ y-offset outer-padding *font-height* (* 2 inner-padding)))
   (line-to (- triangle-offset) (+ outer-padding y-offset))
   (fill-path))

(defun draw-prompt (text y-offset)
   (draw-prompt-bg y-offset)

   (basic-write text *prompt-fg-color* prompt-offset (+ outer-padding inner-padding y-offset))
   (+ *font-height* (* 2 outer-padding) (* 2 inner-padding)))

(defun draw-current-prompt (text y-offset)
   (draw-prompt-bg y-offset)

   (let ((str text) (color *prompt-fg-color*) (x prompt-offset) (y (+ outer-padding inner-padding y-offset)))
      (let ((pango-layout (pango:pango_cairo_create_layout (slot-value cairo:*context* 'cairo::pointer))))
            (pango:pango_layout_set_font_description pango-layout font)

            (new-path)
            (move-to x y)
            (set-hex-color color)
            (pango:pango_layout_set_text pango-layout str -1)

            (pango:pango_cairo_update_layout (slot-value *context* 'cairo::pointer) pango-layout)
            (pango:pango_cairo_show_layout (slot-value *context* 'cairo::pointer) pango-layout)

            (when *cursor-blink-status*
               (new-path)
               (set-hex-color *prompt-fg-color*)
               (let ((cursor (pango:get-cursor-pos pango-layout *cursor-index*)))
                     (rectangle (+ (first cursor) prompt-offset 0) (+ (second cursor) outer-padding inner-padding y-offset)
                                (+ 0.5 (third cursor)) (fourth cursor)))
               (fill-path))
            (pango:g_object_unref pango-layout)))

   (+ *font-height* (* 2 outer-padding) (* 2 inner-padding)))

(defun draw-active-result (str y-offset)
   (incf y-offset (draw-current-prompt str y-offset)))

(defun draw-result (result y-offset)
   (incf y-offset (draw-prompt (first result) y-offset))
   (when (not (equalp (third result) ""))
         (basic-write (string-trim '(#\Newline) (third result)) *printed-fg-color* text-offset y-offset)
         (incf y-offset (paragraph-height (string-trim '(#\Newline) (third result)))))
   (basic-write (second result) *return-form-color* text-offset y-offset)
   (incf y-offset *font-height*))

(defparameter *max-results* 50)
(defparameter *scroll-offset* 0) ;; how far down we're currently scrolled
(defparameter *min-scroll* 0)
(defparameter *max-scroll* 0)

(defun center-view ()
   (iter (for x in *buffer-history*)
         (sum (total-height x) into th)
         (finally
            (setf *max-scroll* th)
            (when (< (+ h *scroll-offset*) th)
                    (setf *scroll-offset*
                       (alexandria:clamp (+ (prompt-height *current-input*) (- *max-scroll* h))
                           *min-scroll* *max-scroll*))))))

;; start of viewport = *scroll-offset*
;; end of viewport = (+ *scroll-offset* h)
(defun draw-repl-body ()
   (iter (for x in *buffer-history*)
         (for rh = (total-height x))
         (when (or (< *scroll-offset* acc (+ *scroll-offset* h))
                   (< *scroll-offset* (+ acc rh)))
               (draw-result x (- acc *scroll-offset*)))
         (sum rh into acc)
         (finally
            (setf *max-scroll* acc)
            (when (or (< *scroll-offset* acc (+ *scroll-offset* h))
                      (< *scroll-offset* (+ acc (prompt-height *current-input*))))
                  (draw-active-result *current-input* (- acc *scroll-offset*))))))

;; get the font height by making a layout and then finding the cursor size
(defun pango-font-height-hack ()
   (let ((pango-layout (pango:pango_cairo_create_layout (slot-value cairo:*context* 'cairo::pointer)))
         (size 0))
     (pango:pango_layout_set_font_description pango-layout font)
     (setf size (fourth (pango:get-cursor-pos pango-layout 0)))
     (pango:g_object_unref pango-layout)
     size))

(defun font-scaled (n) (round (* *font-height* n)))

(defun pull-x-attribute (name)
   (cairo::x-get-defualt cairo::arboreta-display *xrm-application-name* name))

(defun hex-tint (amt hex)
   (let ((col (cl-colors:as-rgb hex)))
         (cl-colors:print-hex-rgb (cl-colors:rgb-combination col (cl-colors:as-rgb "FFFFFF") amt) :hash nil :destination nil)))

(defun handle-x-font (name)
   (when name
      (let* ((fields (cdr (cl-ppcre:split "-" name)))
             (family (nth 1 fields))
             (size (nth 6 fields)))
         (setf font
            (pango:pango_font_description_from_string
               (if (and size (not (equalp size "*")))
                   (cats family " " size)
                   (cats family))))
         (pango:pango_layout_set_font_description layout font)
         (setf *font-height* (+ 0 (pango-font-height-hack))))))

;; todo: make test to determine if light or dark theme from bg/fg, get white/black colors accordingly
(defun pull-x-resources ()
   ;; background and accent colors
   (setf *background-color* (subseq (pull-x-attribute "background") 1)) ;; base
   (setf *triangle-color* (subseq (pull-x-attribute "color4") 1)) ;; color accent 1
   (setf *prompt-bg-color*
      (hex-tint 0.05 (subseq (pull-x-attribute "background") 1))) ;; highlight 1
   ;; font colors
   (setf *prompt-fg-color* (subseq (pull-x-attribute "foreground") 1)) ;; text
   (setf *printed-fg-color* (subseq (pull-x-attribute "color7") 1)) ;; text highlight 1
   (setf *return-form-color* (subseq (pull-x-attribute "color15") 1)) ;; text highlight 2
   (handle-x-font (pull-x-attribute "font")))

(defparameter *default-font* "Fantasque Sans Mono 10")

(defun repl-test ()
   (setf layout (pango:pango_cairo_create_layout (slot-value cairo:*context* 'cairo::pointer)))
   (setf font (pango:pango_font_description_from_string *default-font*))
   (pango:pango_layout_set_font_description layout font)

   (setf *font-height* (+ 0 (pango-font-height-hack)))

   (sb-thread:make-thread 'handle-repl-key-events :name "keyevents-thread")

   (when *load-xresources?* (pull-x-resources))

   (setf triangle-offset (font-scaled 0.35))
   (setf outer-padding (font-scaled 0.25))
   (setf inner-padding (font-scaled 0.15))
   (setf prompt-offset (font-scaled 0.8))
   (setf text-offset prompt-offset)

   (unless *padding?*
      (setf outer-padding 0)
      (setf inner-padding 0)
      (setf triangle-offset (font-scaled 0.20)))

   (setf root-window
      (make-window :draw
         (lambda (window)
            (when *buffer-needs-update*
               (new-path)
               (set-hex-color *background-color*)
               (rectangle 0 0 w h)
               (fill-path)

               (draw-repl-body))
            (draw-subwindows window))))
   (repl-update-loop))

(defparameter *padding?* t)
(defparameter *load-xresources?* nil)
(defparameter *draw-prompt-bg?* t)
(defparameter *xrm-application-name* "arboreta-repl")
(defparameter *args* (cdr sb-ext:*posix-argv*))

;; todo: make general fixed-arity command-line handling library
(defun handle-arguments ()
   (when *args*
    (iter (for i from 0 to (- (length *args*) 1))
          (for x = (nth i *args*))
          (alexandria:switch (x :test #'equalp)
            ("--help"
               (princ (catans
                  "arboreta-repl command line options:"
                  "   --no-padding         display the prompt at normal line spacing"
                  "   --font (name)        font to render, ignored if used with --use-xresources"
                  "   --no-prompt-shading  disable background shading for the prompt"
                  "   --use-xresources     pull font and colors from x resource manager"
                  "   --xrm-name (name)    use x resource settings from a different application, such as URxtv"))
               (sb-ext:exit))
            ("--no-padding" (setf *padding?* nil))
            ("--no-prompt-shading" (setf *draw-prompt-bg?* nil))
            ("--use-xresources" (setf *load-xresources?* t))
            ("--xrm-name"
               (setf *xrm-application-name* (aif (nth (+ i 1) *args*) it (error "no path argument provided to --xrm-name")))
               (incf i))
            ("--font"
               (setf *default-font* (aif (nth (+ i 1) *args*) it (error "no font name argument provided to --font")))
               (incf i))
            (otherwise (warn "unrecognized command line argument '~a', ignoring" x))))))

(defun toplevel-start-render ()
   (handle-arguments)
   (setf context (cairo::create-arboreta-window w h))
   (setf surface (get-target context))
   (with-context (context)
      (repl-test)))

(toplevel-start-render)

(load "base.cl")
(load "repl-config.cl")

(declaim #+sbcl(sb-ext:muffle-conditions style-warning))
(declaim #+sbcl(sb-ext:muffle-conditions warning))

(in-package arboreta)

(setf *random-state* (make-random-state t))

(defparameter *cursor-blink-status* t)
(defparameter *cursor-blink-time* 0)
(defparameter *cursor-blink-delay* 1000)

;; descriptive function names, huh?
(defun cat (list)
   (format nil "~{~a~}" list))

(defmacro cats (&rest rest)
   `(cat (list ,@rest)))

(defun catn (list)
   (format nil "~{~a~%~}" list))

(defmacro catans (&rest rest)
   `(catn (list ,@rest)))

(defun cursor-blink-timer ()
   (let ((time (get-internal-real-time)))
         (when (>= time *cursor-blink-time*)
               (setf *cursor-blink-status* (not *cursor-blink-status*))
               (setf *cursor-blink-time* (+ (get-internal-real-time) *cursor-blink-delay*))
               (setf *buffer-needs-update* t))))

(defun repl-update-loop ()
   (iter (for x = (+ (get-internal-real-time) 20))
         (cursor-blink-timer)
         (when *buffer-needs-update*
            (draw root-window)
            (cairo::refresh context)
            (setf *buffer-needs-update* nil))
         (iter (while (cairo::handle-event context)))
         (sleep (let ((delay (/ (- x (get-internal-real-time)) 1000)))
                      ;; (print (* 1000.0 delay))
                      (if (> delay 0) delay 0)))))

(defparameter *current-input* "")
(defparameter *cursor-index* 0)
(defparameter *buffer-needs-update* t)

(defun editor-insert (str)
   (setf *current-input*
      (concatenate 'string
         (subseq *current-input* 0 *cursor-index*)
         str
         (subseq *current-input* *cursor-index*)))
   (incf *cursor-index* (length str)))

(defun editor-delete-char ()
   (setf *current-input*
      (concatenate 'string
         (subseq *current-input* 0 (- *cursor-index* 1))
         (subseq *current-input* *cursor-index*)))
   (decf *cursor-index*))

(defun simple-eval ()
   (let* ((str (make-array '(0) :element-type 'base-char :fill-pointer 0 :adjustable t))
          (res (with-output-to-string (stream str)
                 (let* ((*standard-output* stream)
                        (val (format nil "~s" (ignore-errors (eval (read-from-string *current-input*))))))
                        (list val str)))))
          `((,(alexandria:copy-sequence 'string *current-input*) ,@res))))

(defun handle-repl-key-events ()
   (iter (for x = (+ (get-internal-real-time) 17))
      (if *key-events*
         (let ((kev (pop *key-events*)))
               (setf *cursor-blink-status* t)
               (setf *cursor-blink-time* (+ (get-internal-real-time) *cursor-blink-delay*))

               (setf *buffer-needs-update* t)
               (when (and (eql (keypress-code kev) 113) (eql (keypress-mods kev) 4)) ;; C-q
                     (cairo:destroy context)
                     (sb-ext:exit))
               (when (and (eql (keypress-code kev) 97) (eql (keypress-mods kev) 4)) ;; C-a
                     (setf *cursor-index* 0) (center-view))
               (when (and (eql (keypress-code kev) 101) (eql (keypress-mods kev) 4)) ;; C-e
                     (setf *cursor-index* (length *current-input*)) (center-view))
               (alexandria::switch ((keypress-str kev) :test #'equalp)
                  ("Return" (alexandria:appendf *buffer-history* (simple-eval))
                            ;; (print *buffer-history*)
                            ;; (finish-output)
                            (setf *current-input* "")
                            (setf *cursor-index* 0)
                            (center-view))
                  ("left" (unless (eql *cursor-index* 0) (decf *cursor-index*)))
                  ("right" (unless (eql *cursor-index* (length *current-input*)) (incf *cursor-index*)))
                  ("up" (setf *scroll-offset* (alexandria:clamp (- *scroll-offset* 10) *min-scroll* *max-scroll*)))
                  ("down" (setf *scroll-offset* (alexandria:clamp (+ *scroll-offset* 10) *min-scroll* *max-scroll*)))
                  ("Shift_L" nil)
                  ("Control_L" nil)
                  ("Control_R" nil)
                  ("Shift_R" nil)
                  ("BackSpace" (center-view)
                     (when (and (not (eql *cursor-index* 0)) (> (length *current-input*) 0))
                           (editor-delete-char)))
                  (otherwise
                     (center-view)
                     (when (<= #x20 (keypress-code kev) #x13be) ;; latin chars
                        (editor-insert (if (> (length (keypress-str kev)) 1)
                                         (string (code-char (keypress-code kev)))
                                         (keypress-str kev))))))))
      (sleep (let ((delay (/ (- x (get-internal-real-time)) 1000)))
                   ;; (print (* 1000.0 delay))
                   (if (> delay 0) delay 0)))))

(defparameter *heart* "♥")
(defparameter *heart-colors*
   '(("ae82ff" "dc2566" "fa2772" "d4c96e" "e7db75" "8fc029" "a7e22e" "56b7a5" "66efd5" "55bcce" "66d9ee" "9358fe")
     ("1693A5" "02AAB0" "00CDAC" "7FFF24" "C3FF68")
     ("AAFF00" "FFAA00" "FF00AA" "AA00FF" "00AAFF")
     ("F6D76B" "FF9036" "D6254D" "FF5475" "FDEBA9")
     ("FDCFBF" "FEB89F" "E23D75" "742365")))

(defun get-block (index size seq)
   (nth index
      (iter (for x from 0 to (length seq) by size)
            (for y from size to (length seq) by size)
            (collect (subseq seq x y)))))

(defun set-hex-color (hex)
   (let ((colors (mapcar (lambda (x) (/ (parse-integer x :radix 16) 256))
                         (iter (for i from 0 to 2) (collect (get-block i 2 hex))))))
         (set-source-rgb (first colors) (second colors) (third colors))))

(defun basic-write (str color x y)
   (let ((pango-layout (pango:pango_cairo_create_layout (slot-value cairo:*context* 'cairo::pointer))))
         (pango:pango_layout_set_font_description pango-layout font)

         (new-path)
         (move-to x y)
         (set-hex-color color)
         (pango:pango_layout_set_text pango-layout str -1)

         (pango:pango_cairo_update_layout (slot-value *context* 'cairo::pointer) pango-layout)
         (pango:pango_cairo_show_layout (slot-value *context* 'cairo::pointer) pango-layout)
         (pango:g_object_unref pango-layout)))

(defparameter *colorset* (alexandria:random-elt *heart-colors*))
(defparameter *offset* (alexandria:random-elt (alexandria:iota 5 :start -5)))

(defun draw-hearts ()
   (iter (for y from 0 to 65 by 15)
         (for y2 upfrom 0)
      (iter (for x from -7 to 607 by 14)
            (for x2 upfrom 0)
            (basic-write *heart*
               (nth (mod (+ x2 (* *offset* y2)) (length *colorset*)) *colorset*) x y))))

;; (prompt result printed)
(defparameter *buffer-history* nil)

(defun line-count (str)
   (if (equalp str "")
       0
       (+ 1 (iter (for c in-string (string-trim '(#\Newline) str))
                  (counting (eql c #\newline))))))

(defun line-count* (str)
   (+ 1 (iter (for c in-string (string-trim '(#\Newline) str))
              (counting (eql c #\newline)))))

(defun paragraph-height (str)
   (* *font-height* (line-count str)))

(defun total-height (repl-result)
   (+ (prompt-height (first repl-result))
      (paragraph-height (second repl-result))
      (paragraph-height (third repl-result))))

(defun prompt-height (text)
   (+ (* 2 inner-padding) (* 2 outer-padding)
      (* *font-height* (line-count* text))))

(defparameter *font-height* 0)

(defparameter triangle-offset 0)
(defparameter outer-padding 0)
(defparameter inner-padding 0)
(defparameter prompt-offset 0)
(defparameter text-offset 0)

(defun draw-prompt-bg (y-offset)
   (when *draw-prompt-bg?*
      (new-path)
      (set-hex-color *prompt-bg-color*)
      (rectangle 0 (+ outer-padding y-offset) w (+ *font-height* (* 2 inner-padding)))
      (fill-path))

   (move-to 0 (+ outer-padding y-offset))
   (new-path)
   (set-hex-color *triangle-color*)
   (line-to (- (+ (ceiling (/ *font-height* 2)) (* 2 inner-padding)) triangle-offset)
            (+ y-offset outer-padding (ceiling (/ *font-height* 2)) (* 1 inner-padding)))
   (line-to (- triangle-offset) (+ y-offset outer-padding *font-height* (* 2 inner-padding)))
   (line-to (- triangle-offset) (+ outer-padding y-offset))
   (fill-path))

(defun draw-prompt (text y-offset)
   (draw-prompt-bg y-offset)

   (basic-write text *prompt-fg-color* prompt-offset (+ outer-padding inner-padding y-offset))
   (+ *font-height* (* 2 outer-padding) (* 2 inner-padding)))

(defun draw-current-prompt (text y-offset)
   (draw-prompt-bg y-offset)

   (let ((str text) (color *prompt-fg-color*) (x prompt-offset) (y (+ outer-padding inner-padding y-offset)))
      (let ((pango-layout (pango:pango_cairo_create_layout (slot-value cairo:*context* 'cairo::pointer))))
            (pango:pango_layout_set_font_description pango-layout font)

            (new-path)
            (move-to x y)
            (set-hex-color color)
            (pango:pango_layout_set_text pango-layout str -1)

            (pango:pango_cairo_update_layout (slot-value *context* 'cairo::pointer) pango-layout)
            (pango:pango_cairo_show_layout (slot-value *context* 'cairo::pointer) pango-layout)

            (when *cursor-blink-status*
               (new-path)
               (set-hex-color *prompt-fg-color*)
               (let ((cursor (pango:get-cursor-pos pango-layout *cursor-index*)))
                     (rectangle (+ (first cursor) prompt-offset 0) (+ (second cursor) outer-padding inner-padding y-offset)
                                (+ 0.5 (third cursor)) (fourth cursor)))
               (fill-path))
            (pango:g_object_unref pango-layout)))

   (+ *font-height* (* 2 outer-padding) (* 2 inner-padding)))

(defun draw-active-result (str y-offset)
   (incf y-offset (draw-current-prompt str y-offset)))

(defun draw-result (result y-offset)
   (incf y-offset (draw-prompt (first result) y-offset))
   (when (not (equalp (third result) ""))
         (basic-write (string-trim '(#\Newline) (third result)) *printed-fg-color* text-offset y-offset)
         (incf y-offset (paragraph-height (string-trim '(#\Newline) (third result)))))
   (basic-write (second result) *return-form-color* text-offset y-offset)
   (incf y-offset *font-height*))

(defparameter *max-results* 50)
(defparameter *scroll-offset* 0) ;; how far down we're currently scrolled
(defparameter *min-scroll* 0)
(defparameter *max-scroll* 0)

(defun center-view ()
   (iter (for x in *buffer-history*)
         (sum (total-height x) into th)
         (finally
            (setf *max-scroll* th)
            (when (< (+ h *scroll-offset*) th)
                    (setf *scroll-offset*
                       (alexandria:clamp (+ (prompt-height *current-input*) (- *max-scroll* h))
                           *min-scroll* *max-scroll*))))))

;; start of viewport = *scroll-offset*
;; end of viewport = (+ *scroll-offset* h)
(defun draw-repl-body ()
   (iter (for x in *buffer-history*)
         (for rh = (total-height x))
         (when (or (< *scroll-offset* acc (+ *scroll-offset* h))
                   (< *scroll-offset* (+ acc rh)))
               (draw-result x (- acc *scroll-offset*)))
         (sum rh into acc)
         (finally
            (setf *max-scroll* acc)
            (when (or (< *scroll-offset* acc (+ *scroll-offset* h))
                      (< *scroll-offset* (+ acc (prompt-height *current-input*))))
                  (draw-active-result *current-input* (- acc *scroll-offset*))))))

;; get the font height by making a layout and then finding the cursor size
(defun pango-font-height-hack ()
   (let ((pango-layout (pango:pango_cairo_create_layout (slot-value cairo:*context* 'cairo::pointer)))
         (size 0))
     (pango:pango_layout_set_font_description pango-layout font)
     (setf size (fourth (pango:get-cursor-pos pango-layout 0)))
     (pango:g_object_unref pango-layout)
     size))

(defun font-scaled (n) (round (* *font-height* n)))

(defun pull-x-attribute (name)
   (cairo::x-get-defualt cairo::arboreta-display *xrm-application-name* name))

(defun hex-tint (amt hex)
   (let ((col (cl-colors:as-rgb hex)))
         (cl-colors:print-hex-rgb (cl-colors:rgb-combination col (cl-colors:as-rgb "FFFFFF") amt) :hash nil :destination nil)))

(defun handle-x-font (name)
   (when name
      (let* ((fields (cdr (cl-ppcre:split "-" name)))
             (family (nth 1 fields))
             (size (nth 6 fields)))
         (setf font
            (pango:pango_font_description_from_string
               (if (and size (not (equalp size "*")))
                   (cats family " " size)
                   (cats family))))
         (pango:pango_layout_set_font_description layout font)
         (setf *font-height* (+ 0 (pango-font-height-hack))))))

;; todo: make test to determine if light or dark theme from bg/fg, get white/black colors accordingly
(defun pull-x-resources ()
   ;; background and accent colors
   (setf *background-color* (subseq (pull-x-attribute "background") 1)) ;; base
   (setf *triangle-color* (subseq (pull-x-attribute "color4") 1)) ;; color accent 1
   (setf *prompt-bg-color*
      (hex-tint 0.05 (subseq (pull-x-attribute "background") 1))) ;; highlight 1
   ;; font colors
   (setf *prompt-fg-color* (subseq (pull-x-attribute "foreground") 1)) ;; text
   (setf *printed-fg-color* (subseq (pull-x-attribute "color7") 1)) ;; text highlight 1
   (setf *return-form-color* (subseq (pull-x-attribute "color15") 1)) ;; text highlight 2
   (handle-x-font (pull-x-attribute "font")))

(defparameter *default-font* "Fantasque Sans Mono 10")

(defun repl-test ()
   (setf layout (pango:pango_cairo_create_layout (slot-value cairo:*context* 'cairo::pointer)))
   (setf font (pango:pango_font_description_from_string *default-font*))
   (pango:pango_layout_set_font_description layout font)

   (setf *font-height* (+ 0 (pango-font-height-hack)))

   (sb-thread:make-thread 'handle-repl-key-events :name "keyevents-thread")

   (when *load-xresources?* (pull-x-resources))

   (setf triangle-offset (font-scaled 0.35))
   (setf outer-padding (font-scaled 0.25))
   (setf inner-padding (font-scaled 0.15))
   (setf prompt-offset (font-scaled 0.8))
   (setf text-offset prompt-offset)

   (unless *padding?*
      (setf outer-padding 0)
      (setf inner-padding 0)
      (setf triangle-offset (font-scaled 0.20)))

   (setf root-window
      (make-window :draw
         (lambda (window)
            (when *buffer-needs-update*
               (new-path)
               (set-hex-color *background-color*)
               (rectangle 0 0 w h)
               (fill-path)

               (draw-repl-body))
            (draw-subwindows window))))
   (repl-update-loop))

(defparameter *padding?* t)
(defparameter *load-xresources?* nil)
(defparameter *draw-prompt-bg?* t)
(defparameter *xrm-application-name* "arboreta-repl")
(defparameter *args* (cdr sb-ext:*posix-argv*))

;; todo: make general fixed-arity command-line handling library
(defun handle-arguments ()
   (when *args*
    (iter (for i from 0 to (- (length *args*) 1))
          (for x = (nth i *args*))
          (alexandria:switch (x :test #'equalp)
            ("--help"
               (princ (catans
                  "arboreta-repl command line options:"
                  "   --no-padding         display the prompt at normal line spacing"
                  "   --font (name)        font to render, ignored if used with --use-xresources"
                  "   --no-prompt-shading  disable background shading for the prompt"
                  "   --use-xresources     pull font and colors from x resource manager"
                  "   --xrm-name (name)    use x resource settings from a different application, such as URxtv"))
               (sb-ext:exit))
            ("--no-padding" (setf *padding?* nil))
            ("--no-prompt-shading" (setf *draw-prompt-bg?* nil))
            ("--use-xresources" (setf *load-xresources?* t))
            ("--xrm-name"
               (setf *xrm-application-name* (aif (nth (+ i 1) *args*) it (error "no path argument provided to --xrm-name")))
               (incf i))
            ("--font"
               (setf *default-font* (aif (nth (+ i 1) *args*) it (error "no font name argument provided to --font")))
               (incf i))
            (otherwise (warn "unrecognized command line argument '~a', ignoring" x))))))

(defun toplevel-start-render ()
   (handle-arguments)
   (setf context (cairo::create-arboreta-window w h))
   (setf surface (get-target context))
   (with-context (context)
      (repl-test)))

(toplevel-start-render)


Transpose kernel now uses vectorized local memory loads and stores

// =================================================================================================
// This file is part of the CLBlast project. The project is licensed under Apache Version 2.0. This
// project loosely follows the Google C++ styleguide and uses a tab-size of two spaces and a max-
// width of 100 characters per line.
//
// Author(s):
//   Cedric Nugteren <www.cedricnugteren.nl>
//
// This file contains the common kernels shared among different BLAS functions. This file contains
// kernels to transpose matrices.
//
// =================================================================================================

// Enables loading of this file using the C++ pre-processor's #include (C++11 standard raw string
// literal). Comment-out this line for syntax-highlighting when developing.
R"(

// =================================================================================================
// Parameters set by the tuner or by the database. Here they are given a basic default value in case
// this kernel file is used outside of the CLBlast library.
#ifndef TRA_DIM
  #define TRA_DIM 8    // Number of local threads in the two dimensions (x,y)
#endif
#ifndef TRA_WPT
  #define TRA_WPT 1    // Work per thread in one dimension and vector-width in the other
#endif
#ifndef TRA_PAD
  #define TRA_PAD 0    // Padding of the local memory to avoid bank-conflicts
#endif

// =================================================================================================

// Data-widths
#if TRA_WPT == 1
  typedef real realT;
#elif TRA_WPT == 2
  typedef real2 realT;
#elif TRA_WPT == 4
  typedef real4 realT;
#elif TRA_WPT == 8
  typedef real8 realT;
#elif TRA_WPT == 16
  typedef real16 realT;
#endif

// =================================================================================================

// Transposes and copies a matrix. Requires both matrices to be of the same dimensions and without
// offset. A more general version is available in 'padtranspose.opencl'.
__attribute__((reqd_work_group_size(TRA_DIM, TRA_DIM, 1)))
__kernel void TransposeMatrix(const int ld,
                              __global const realT* restrict src,
                              __global realT* dest) {

  // Local memory to store a tile of the matrix (for coalescing)
  __local realT tile[TRA_WPT*TRA_DIM][TRA_DIM + TRA_PAD];

  // Loop over the work per thread
  #pragma unroll
  for (int w_one=0; w_one<TRA_WPT; ++w_one) {

    // Computes the identifiers for the source matrix. Note that the local and global dimensions
    // do not correspond to each other!
    const int id_one = get_group_id(1) * TRA_DIM + get_local_id(0);
    const int id_two = (get_group_id(0) * TRA_DIM + get_local_id(1))*TRA_WPT + w_one;

    // Loads data into the local memory
    realT value = src[id_two*(ld/TRA_WPT) + id_one];
    tile[get_local_id(0)*TRA_WPT + w_one][get_local_id(1)] = value;
  }

  // Synchronizes all threads in a workgroup
  barrier(CLK_LOCAL_MEM_FENCE);

  // Loads transposed data from the local memory
  realT v[TRA_WPT];
  #pragma unroll
  for (int w_one=0; w_one<TRA_WPT; ++w_one) {
    v[w_one] = tile[get_local_id(1)*TRA_WPT + w_one][get_local_id(0)];
  }

  // Performs the register-level transpose of the vectorized data
  realT results[TRA_WPT];
  #if TRA_WPT == 1
    results[0] = v[0];
  #elif TRA_WPT == 2
    results[0] = (realT) (v[0].x, v[1].x);
    results[1] = (realT) (v[0].y, v[1].y);
  #elif TRA_WPT == 4
    results[0] = (realT) (v[0].x, v[1].x, v[2].x, v[3].x);
    results[1] = (realT) (v[0].y, v[1].y, v[2].y, v[3].y);
    results[2] = (realT) (v[0].z, v[1].z, v[2].z, v[3].z);
    results[3] = (realT) (v[0].w, v[1].w, v[2].w, v[3].w);
  #elif TRA_WPT == 8
    results[0] = (realT) (v[0].s0, v[1].s0, v[2].s0, v[3].s0, v[4].s0, v[5].s0, v[6].s0, v[7].s0);
    results[1] = (realT) (v[0].s1, v[1].s1, v[2].s1, v[3].s1, v[4].s1, v[5].s1, v[6].s1, v[7].s1);
    results[2] = (realT) (v[0].s2, v[1].s2, v[2].s2, v[3].s2, v[4].s2, v[5].s2, v[6].s2, v[7].s2);
    results[3] = (realT) (v[0].s3, v[1].s3, v[2].s3, v[3].s3, v[4].s3, v[5].s3, v[6].s3, v[7].s3);
    results[4] = (realT) (v[0].s4, v[1].s4, v[2].s4, v[3].s4, v[4].s4, v[5].s4, v[6].s4, v[7].s4);
    results[5] = (realT) (v[0].s5, v[1].s5, v[2].s5, v[3].s5, v[4].s5, v[5].s5, v[6].s5, v[7].s5);
    results[6] = (realT) (v[0].s6, v[1].s6, v[2].s6, v[3].s6, v[4].s6, v[5].s6, v[6].s6, v[7].s6);
    results[7] = (realT) (v[0].s7, v[1].s7, v[2].s7, v[3].s7, v[4].s7, v[5].s7, v[6].s7, v[7].s7);
  #elif TRA_WPT == 16
    results[ 0] = (realT) (v[0].s0, v[1].s0, v[2].s0, v[3].s0, v[4].s0, v[5].s0, v[6].s0, v[7].s0, v[8].s0, v[9].s0, v[10].s0, v[11].s0, v[12].s0, v[13].s0, v[14].s0, v[15].s0);
    results[ 1] = (realT) (v[0].s1, v[1].s1, v[2].s1, v[3].s1, v[4].s1, v[5].s1, v[6].s1, v[7].s1, v[8].s1, v[9].s1, v[10].s1, v[11].s1, v[12].s1, v[13].s1, v[14].s1, v[15].s1);
    results[ 2] = (realT) (v[0].s2, v[1].s2, v[2].s2, v[3].s2, v[4].s2, v[5].s2, v[6].s2, v[7].s2, v[8].s2, v[9].s2, v[10].s2, v[11].s2, v[12].s2, v[13].s2, v[14].s2, v[15].s2);
    results[ 3] = (realT) (v[0].s3, v[1].s3, v[2].s3, v[3].s3, v[4].s3, v[5].s3, v[6].s3, v[7].s3, v[8].s3, v[9].s3, v[10].s3, v[11].s3, v[12].s3, v[13].s3, v[14].s3, v[15].s3);
    results[ 4] = (realT) (v[0].s4, v[1].s4, v[2].s4, v[3].s4, v[4].s4, v[5].s4, v[6].s4, v[7].s4, v[8].s4, v[9].s4, v[10].s4, v[11].s4, v[12].s4, v[13].s4, v[14].s4, v[15].s4);
    results[ 5] = (realT) (v[0].s5, v[1].s5, v[2].s5, v[3].s5, v[4].s5, v[5].s5, v[6].s5, v[7].s5, v[8].s5, v[9].s5, v[10].s5, v[11].s5, v[12].s5, v[13].s5, v[14].s5, v[15].s5);
    results[ 6] = (realT) (v[0].s6, v[1].s6, v[2].s6, v[3].s6, v[4].s6, v[5].s6, v[6].s6, v[7].s6, v[8].s6, v[9].s6, v[10].s6, v[11].s6, v[12].s6, v[13].s6, v[14].s6, v[15].s6);
    results[ 7] = (realT) (v[0].s7, v[1].s7, v[2].s7, v[3].s7, v[4].s7, v[5].s7, v[6].s7, v[7].s7, v[8].s7, v[9].s7, v[10].s7, v[11].s7, v[12].s7, v[13].s7, v[14].s7, v[15].s7);
    results[ 8] = (realT) (v[0].s8, v[1].s8, v[2].s8, v[3].s8, v[4].s8, v[5].s8, v[6].s8, v[7].s8, v[8].s8, v[9].s8, v[10].s8, v[11].s8, v[12].s8, v[13].s8, v[14].s8, v[15].s8);
    results[ 9] = (realT) (v[0].s9, v[1].s9, v[2].s9, v[3].s9, v[4].s9, v[5].s9, v[6].s9, v[7].s9, v[8].s9, v[9].s9, v[10].s9, v[11].s9, v[12].s9, v[13].s9, v[14].s9, v[15].s9);
    results[10] = (realT) (v[0].sA, v[1].sA, v[2].sA, v[3].sA, v[4].sA, v[5].sA, v[6].sA, v[7].sA, v[8].sA, v[9].sA, v[10].sA, v[11].sA, v[12].sA, v[13].sA, v[14].sA, v[15].sA);
    results[11] = (realT) (v[0].sB, v[1].sB, v[2].sB, v[3].sB, v[4].sB, v[5].sB, v[6].sB, v[7].sB, v[8].sB, v[9].sB, v[10].sB, v[11].sB, v[12].sB, v[13].sB, v[14].sB, v[15].sB);
    results[12] = (realT) (v[0].sC, v[1].sC, v[2].sC, v[3].sC, v[4].sC, v[5].sC, v[6].sC, v[7].sC, v[8].sC, v[9].sC, v[10].sC, v[11].sC, v[12].sC, v[13].sC, v[14].sC, v[15].sC);
    results[13] = (realT) (v[0].sD, v[1].sD, v[2].sD, v[3].sD, v[4].sD, v[5].sD, v[6].sD, v[7].sD, v[8].sD, v[9].sD, v[10].sD, v[11].sD, v[12].sD, v[13].sD, v[14].sD, v[15].sD);
    results[14] = (realT) (v[0].sE, v[1].sE, v[2].sE, v[3].sE, v[4].sE, v[5].sE, v[6].sE, v[7].sE, v[8].sE, v[9].sE, v[10].sE, v[11].sE, v[12].sE, v[13].sE, v[14].sE, v[15].sE);
    results[15] = (realT) (v[0].sF, v[1].sF, v[2].sF, v[3].sF, v[4].sF, v[5].sF, v[6].sF, v[7].sF, v[8].sF, v[9].sF, v[10].sF, v[11].sF, v[12].sF, v[13].sF, v[14].sF, v[15].sF);
  #endif

  // Stores the results into the destination matrix
  #pragma unroll
  for (int w_two=0; w_two<TRA_WPT; ++w_two) {
    const int id_one = get_global_id(0);
    const int id_two = get_global_id(1)*TRA_WPT + w_two;
    dest[id_two*(ld/TRA_WPT) + id_one] = results[w_two];
  }
}

// =================================================================================================

// End of the C++11 raw string literal
)"

// =================================================================================================

// =================================================================================================
// This file is part of the CLBlast project. The project is licensed under Apache Version 2.0. This
// project loosely follows the Google C++ styleguide and uses a tab-size of two spaces and a max-
// width of 100 characters per line.
//
// Author(s):
//   Cedric Nugteren <www.cedricnugteren.nl>
//
// This file contains the common kernels shared among different BLAS functions. This file contains
// kernels to transpose matrices.
//
// =================================================================================================

// Enables loading of this file using the C++ pre-processor's #include (C++11 standard raw string
// literal). Comment-out this line for syntax-highlighting when developing.
R"(

// =================================================================================================
// Parameters set by the tuner or by the database. Here they are given a basic default value in case
// this kernel file is used outside of the CLBlast library.
#ifndef TRA_DIM
  #define TRA_DIM 8    // Number of local threads in the two dimensions (x,y)
#endif
#ifndef TRA_WPT
  #define TRA_WPT 1    // Work per thread in one dimension and vector-width in the other
#endif
#ifndef TRA_PAD
  #define TRA_PAD 0    // Padding of the local memory to avoid bank-conflicts
#endif

// =================================================================================================

// Data-widths
#if TRA_WPT == 1
  typedef real realT;
#elif TRA_WPT == 2
  typedef real2 realT;
#elif TRA_WPT == 4
  typedef real4 realT;
#elif TRA_WPT == 8
  typedef real8 realT;
#elif TRA_WPT == 16
  typedef real16 realT;
#endif

// =================================================================================================

// Transposes and copies a matrix. Requires both matrices to be of the same dimensions and without
// offset. A more general version is available in 'padtranspose.opencl'.
__attribute__((reqd_work_group_size(TRA_DIM, TRA_DIM, 1)))
__kernel void TransposeMatrix(const int ld,
                              __global const realT* restrict src,
                              __global realT* dest) {

  // Local memory to store a tile of the matrix (for coalescing)
  __local real tile[TRA_WPT*TRA_DIM][TRA_WPT*TRA_DIM + TRA_PAD];

  // Loop over the work per thread
  #pragma unroll
  for (int w_one=0; w_one<TRA_WPT; ++w_one) {

    // Computes the identifiers for the source matrix. Note that the local and global dimensions
    // do not correspond to each other!
    const int id_one = get_group_id(1) * TRA_DIM + get_local_id(0);
    const int id_two = (get_group_id(0) * TRA_DIM + get_local_id(1))*TRA_WPT + w_one;

    // Loads data into the local memory
    realT value = src[id_two*(ld/TRA_WPT) + id_one];
    #if TRA_WPT == 1
      tile[get_local_id(1)*TRA_WPT + 0][get_local_id(0)*TRA_WPT + w_one] = value;
    #elif TRA_WPT == 2
      tile[get_local_id(1)*TRA_WPT + 0][get_local_id(0)*TRA_WPT + w_one] = value.x;
      tile[get_local_id(1)*TRA_WPT + 1][get_local_id(0)*TRA_WPT + w_one] = value.y;
    #elif TRA_WPT == 4
      tile[get_local_id(1)*TRA_WPT + 0][get_local_id(0)*TRA_WPT + w_one] = value.x;
      tile[get_local_id(1)*TRA_WPT + 1][get_local_id(0)*TRA_WPT + w_one] = value.y;
      tile[get_local_id(1)*TRA_WPT + 2][get_local_id(0)*TRA_WPT + w_one] = value.z;
      tile[get_local_id(1)*TRA_WPT + 3][get_local_id(0)*TRA_WPT + w_one] = value.w;
    #elif TRA_WPT == 8
      tile[get_local_id(1)*TRA_WPT + 0][get_local_id(0)*TRA_WPT + w_one] = value.s0;
      tile[get_local_id(1)*TRA_WPT + 1][get_local_id(0)*TRA_WPT + w_one] = value.s1;
      tile[get_local_id(1)*TRA_WPT + 2][get_local_id(0)*TRA_WPT + w_one] = value.s2;
      tile[get_local_id(1)*TRA_WPT + 3][get_local_id(0)*TRA_WPT + w_one] = value.s3;
      tile[get_local_id(1)*TRA_WPT + 4][get_local_id(0)*TRA_WPT + w_one] = value.s4;
      tile[get_local_id(1)*TRA_WPT + 5][get_local_id(0)*TRA_WPT + w_one] = value.s5;
      tile[get_local_id(1)*TRA_WPT + 6][get_local_id(0)*TRA_WPT + w_one] = value.s6;
      tile[get_local_id(1)*TRA_WPT + 7][get_local_id(0)*TRA_WPT + w_one] = value.s7;
    #elif TRA_WPT == 16
      tile[get_local_id(1)*TRA_WPT +  0][get_local_id(0)*TRA_WPT + w_one] = value.s0;
      tile[get_local_id(1)*TRA_WPT +  1][get_local_id(0)*TRA_WPT + w_one] = value.s1;
      tile[get_local_id(1)*TRA_WPT +  2][get_local_id(0)*TRA_WPT + w_one] = value.s2;
      tile[get_local_id(1)*TRA_WPT +  3][get_local_id(0)*TRA_WPT + w_one] = value.s3;
      tile[get_local_id(1)*TRA_WPT +  4][get_local_id(0)*TRA_WPT + w_one] = value.s4;
      tile[get_local_id(1)*TRA_WPT +  5][get_local_id(0)*TRA_WPT + w_one] = value.s5;
      tile[get_local_id(1)*TRA_WPT +  6][get_local_id(0)*TRA_WPT + w_one] = value.s6;
      tile[get_local_id(1)*TRA_WPT +  7][get_local_id(0)*TRA_WPT + w_one] = value.s7;
      tile[get_local_id(1)*TRA_WPT +  8][get_local_id(0)*TRA_WPT + w_one] = value.s8;
      tile[get_local_id(1)*TRA_WPT +  9][get_local_id(0)*TRA_WPT + w_one] = value.s9;
      tile[get_local_id(1)*TRA_WPT + 10][get_local_id(0)*TRA_WPT + w_one] = value.sA;
      tile[get_local_id(1)*TRA_WPT + 11][get_local_id(0)*TRA_WPT + w_one] = value.sB;
      tile[get_local_id(1)*TRA_WPT + 12][get_local_id(0)*TRA_WPT + w_one] = value.sC;
      tile[get_local_id(1)*TRA_WPT + 13][get_local_id(0)*TRA_WPT + w_one] = value.sD;
      tile[get_local_id(1)*TRA_WPT + 14][get_local_id(0)*TRA_WPT + w_one] = value.sE;
      tile[get_local_id(1)*TRA_WPT + 15][get_local_id(0)*TRA_WPT + w_one] = value.sF;
    #endif
  }

  // Synchronizes all threads in a workgroup
  barrier(CLK_LOCAL_MEM_FENCE);

  // Loop over the work per thread
  #pragma unroll
  for (int w_two=0; w_two<TRA_WPT; ++w_two) {

    // Computes the identifiers for the destination matrix
    const int id_one = get_global_id(0);
    const int id_two = get_global_id(1)*TRA_WPT + w_two;

    // Stores the transposed value in the destination matrix
    realT value;
    #if TRA_WPT == 1
      value = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 0];
    #elif TRA_WPT == 2
      value.x = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 0];
      value.y = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 1];
    #elif TRA_WPT == 4
      value.x = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 0];
      value.y = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 1];
      value.z = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 2];
      value.w = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 3];
    #elif TRA_WPT == 8
      value.s0 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 0];
      value.s1 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 1];
      value.s2 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 2];
      value.s3 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 3];
      value.s4 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 4];
      value.s5 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 5];
      value.s6 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 6];
      value.s7 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 7];
    #elif TRA_WPT == 16
      value.s0 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT +  0];
      value.s1 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT +  1];
      value.s2 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT +  2];
      value.s3 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT +  3];
      value.s4 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT +  4];
      value.s5 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT +  5];
      value.s6 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT +  6];
      value.s7 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT +  7];
      value.s8 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT +  8];
      value.s9 = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT +  9];
      value.sA = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 10];
      value.sB = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 11];
      value.sC = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 12];
      value.sD = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 13];
      value.sE = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 14];
      value.sF = tile[get_local_id(0)*TRA_WPT + w_two][get_local_id(1)*TRA_WPT + 15];
    #endif
    dest[id_two*(ld/TRA_WPT) + id_one] = value;
  }
}

// =================================================================================================

// End of the C++11 raw string literal
)"

// =================================================================================================