//
// DO NOT EDIT THIS FILE!
// THIS FILE IS AUTOMATICALLY EXTRACTED FROM A LITERATE PROGRAM SOURCE FILE.
//
// This software is copyrighted 2023 by G. Andrew Mangogna.
// The following terms apply to all files associated with the software unless
// explicitly disclaimed in individual files.
// 
// The author hereby grants permission to use, copy, modify, distribute,
// and license this software and its documentation for any purpose, provided
// that existing copyright notices are retained in all copies and that this
// notice is included verbatim in any distributions. No written agreement,
// license, or royalty fee is required for any of the authorized uses.
// Modifications to this software may be copyrighted by their authors and
// need not follow the licensing terms described here, provided that the
// new terms are clearly indicated on the first page of each file where
// they apply.
// 
// IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
// DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
// OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES
// THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
// 
// THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
// IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
// NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
// OR MODIFICATIONS.
// 
// GOVERNMENT USE: If you are acquiring this software on behalf of the
// U.S. government, the Government shall have only "Restricted Rights"
// in the software and related documentation as defined in the Federal
// Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
// are acquiring the software on behalf of the Department of Defense,
// the software shall be classified as "Commercial Computer Software"
// and the Government shall have only "Restricted Rights" as defined in
// Clause 252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing,
// the authors grant the U.S. Government and others acting in its behalf
// permission to use and distribute the software in accordance with the
// terms specified in this license.

const std = @import("std");
const builtin = @import("builtin");
const rship = @import("rship_adj_matrix.zig");
const log = std.log;
const mem = std.mem;
const assert = std.debug.assert;

pub const std_options = struct {
    pub const log_level = .info;
};

const SupplierNumber = []const u8;
const PartNumber = []const u8;
const Name = []const u8;
const Status = usize;
const Qty = usize;
const Weight = f32;
const PartColor = enum {
    Red,
    Blue,
    Green,
};
const Supplier = struct {
    s_num: SupplierNumber,
    s_name: Name,
    s_status: Status,
    s_city: Name,
};
const Part = struct {
    p_num: PartNumber,
    p_name: Name,
    p_color: PartColor,
    p_weight: Weight,
    p_city: Name,
};
const SuppliedPart = struct {
    s_num: SupplierNumber,
    p_num: PartNumber,
    qty: Qty,
};
const supplier_count: usize = 5;

var suppliers = [supplier_count]Supplier {
    .{.s_num = "S1", .s_name = "Smith", .s_status = 20, .s_city = "London"},    // 0
    .{.s_num = "S2", .s_name = "Jones", .s_status = 10, .s_city = "Paris"},     // 1
    .{.s_num = "S3", .s_name = "Blake", .s_status = 30, .s_city = "Paris"},     // 2
    .{.s_num = "S4", .s_name = "Clark", .s_status = 20, .s_city = "London"},    // 3
    .{.s_num = "S5", .s_name = "Adams", .s_status = 30, .s_city = "Athens"},    // 4
};
const part_count: usize = 6;

var parts = [part_count]Part {
    .{.p_num = "P1", .p_name = "Nut", .p_color = .Red, .p_weight = 12.0,
            .p_city = "London"},  // 0
    .{.p_num = "P2", .p_name = "Bolt", .p_color = .Green, .p_weight = 17.0,
            .p_city = "Paris"},// 1
    .{.p_num = "P3", .p_name = "Screw", .p_color = .Blue, .p_weight = 17.0,
            .p_city = "Oslo"}, // 2
    .{.p_num = "P4", .p_name = "Screw", .p_color = .Red, .p_weight = 14.0,
            .p_city = "London"},// 3
    .{.p_num = "P5", .p_name = "Cam", .p_color = .Blue, .p_weight = 12.0,
            .p_city = "Paris"},  // 4
    .{.p_num = "P6", .p_name = "Cog", .p_color = .Red, .p_weight = 19.0,
            .p_city = "London"},  // 5
};
const supplied_part_count: usize = 12;

var supplied_parts = [supplied_part_count]SuppliedPart {
    .{.s_num = "S1", .p_num = "P1", .qty = 300,},   // 0
    .{.s_num = "S1", .p_num = "P2", .qty = 200,},   // 1
    .{.s_num = "S1", .p_num = "P3", .qty = 400,},   // 2
    .{.s_num = "S1", .p_num = "P4", .qty = 200,},   // 3
    .{.s_num = "S1", .p_num = "P5", .qty = 100,},   // 4
    .{.s_num = "S1", .p_num = "P6", .qty = 100,},   // 5
    .{.s_num = "S2", .p_num = "P1", .qty = 300,},   // 6
    .{.s_num = "S2", .p_num = "P2", .qty = 400,},   // 7
    .{.s_num = "S3", .p_num = "P2", .qty = 200,},   // 8
    .{.s_num = "S4", .p_num = "P2", .qty = 200,},   // 9
    .{.s_num = "S4", .p_num = "P4", .qty = 300,},   // 10
    .{.s_num = "S4", .p_num = "P5", .qty = 400,},   // 11
};
const SuppliersToPartsAssociation = struct {
    // Use two adjacency matrices to handle the decomposition.
    // Supplied Part is the domain in both cases.
    pub const SuppliedPartToSupplier =
        rship.AssociationAdjMatrix(supplied_part_count, .any, .one, supplier_count);
    pub const SuppliedPartToPart =
        rship.AssociationAdjMatrix(supplied_part_count, .at_least_one, .one, part_count);

    sp_to_s: SuppliedPartToSupplier,
    sp_to_p: SuppliedPartToPart,

    // All domain and codomain elements are in play since the population is static.
    const sp_to_s_domain = SuppliedPartToSupplier.DomainSet.initFull();
    const sp_to_s_codomain = SuppliedPartToSupplier.CodomainSet.initFull();
    const sp_to_p_domain = SuppliedPartToPart.DomainSet.initFull();
    const sp_to_p_codomain = SuppliedPartToPart.CodomainSet.initFull();

    const Self = @This();

    // Since this is a fixed population, we can specify the association
    // matrices completely at initialization time.
    fn init(
        self: *Self,
    ) void {
        self.sp_to_s = SuppliedPartToSupplier.init();
        self.sp_to_p = SuppliedPartToPart.init();

        self.sp_to_s.relatePairs(&[_]rship.AdjacentPair {
            .{.domain_element = 0, .codomain_element = 0},
            .{.domain_element = 1, .codomain_element = 0},
            .{.domain_element = 2, .codomain_element = 0},
            .{.domain_element = 3, .codomain_element = 0},
            .{.domain_element = 4, .codomain_element = 0},
            .{.domain_element = 5, .codomain_element = 0},
            .{.domain_element = 6, .codomain_element = 1},
            .{.domain_element = 7, .codomain_element = 1},
            .{.domain_element = 8, .codomain_element = 2},
            .{.domain_element = 9, .codomain_element = 3},
            .{.domain_element = 10, .codomain_element = 3},
            .{.domain_element = 11, .codomain_element = 3},
        });
        assert(self.sp_to_s.isAssociationConsistent(
                sp_to_s_domain, sp_to_s_codomain));
        self.sp_to_s.printAssociationMatrix(sp_to_s_domain, sp_to_s_codomain,
                "Supplied Parts to Suppliers association matrix");

        self.sp_to_p.relatePairs(&[_]rship.AdjacentPair {
            .{.domain_element = 0, .codomain_element = 0},
            .{.domain_element = 1, .codomain_element = 1},
            .{.domain_element = 2, .codomain_element = 2},
            .{.domain_element = 3, .codomain_element = 3},
            .{.domain_element = 4, .codomain_element = 4},
            .{.domain_element = 5, .codomain_element = 5},
            .{.domain_element = 6, .codomain_element = 0},
            .{.domain_element = 7, .codomain_element = 1},
            .{.domain_element = 8, .codomain_element = 1},
            .{.domain_element = 9, .codomain_element = 1},
            .{.domain_element = 10, .codomain_element = 3},
            .{.domain_element = 11, .codomain_element = 4},
        });
        assert(self.sp_to_p.isAssociationConsistent(
                sp_to_p_domain, sp_to_p_codomain));
        self.sp_to_p.printAssociationMatrix(sp_to_p_domain, sp_to_p_codomain,
                "Supplied Parts to Parts association matrix");
    }

    // To navigate from Supplier to Supplied Part we start with a
    // subset of the codomain of R1a, i.e. a subset of Supplier.
    // The preimage under the Supplied Part to Supplier adjacency matrix
    // is the set of Supplied Parts associated with the Supplier subset.
    fn navigateSupplierToSuppliedPart(
        self: Self,
        s_inst_set: SuppliedPartToSupplier.CodomainSet
    ) SuppliedPartToSupplier.DomainSet {
        return self.sp_to_s.preimage(s_inst_set);
    }

    // In the other direction from Part to Supplied Part, we start with
    // a subset of the codomain of R1b, i.e. a subset of Part.
    // The preimage under the Supplied Part to Part adjacency matrix
    // is the set of Supplied Parts associated with the Part subset.
    fn navigatePartToSuppliedPart(
        self: Self,
        p_inst_set: SuppliedPartToPart.CodomainSet
    ) SuppliedPartToPart.DomainSet {
        return self.sp_to_p.preimage(p_inst_set);
    }

    // Two more functions are used to navigate from Supplied Part to
    // either Supplier are Part.
    fn navigateSuppliedPartToSupplier(
        self: Self,
        sp_inst_set: SuppliedPartToSupplier.DomainSet
    ) SuppliedPartToSupplier.CodomainSet {
        return self.sp_to_s.image(sp_inst_set);
    }

    fn navigateSuppliedPartToPart(
        self: Self,
        sp_inst_set: SuppliedPartToPart.DomainSet
    ) SuppliedPartToPart.CodomainSet {
        return self.sp_to_p.image(sp_inst_set);
    }

    // To navigate both decomposed association from Supplier to Part,
    // the result is a lovely composition of navigating each of the
    // decomposed associations. Closure and composition properties are amazing.
    fn navigateSupplierToPart(
        self: Self,
        s_inst_set: SuppliedPartToSupplier.CodomainSet,
    ) SuppliedPartToPart.CodomainSet {
        return self.navigateSuppliedPartToPart(
                self.navigateSupplierToSuppliedPart(s_inst_set));
    }

    // The navigation from Part to Supplier is just the reverse composition.
    fn navigatePartToSupplier(
        self: Self,
        p_inst_set: SuppliedPartToPart.CodomainSet,
    ) SuppliedPartToSupplier.CodomainSet {
        return self.navigateSuppliedPartToSupplier(
                self.navigatePartToSuppliedPart(p_inst_set));
    }
};

pub fn main() !void {
    log.info("starting main", .{}) ;
    var suppliers_to_parts_assoc: SuppliersToPartsAssociation = undefined;
    
    suppliers_to_parts_assoc.init();
    log.info("Supplier = {d} bytes", .{@sizeOf(Supplier)});
    log.info("Part = {d} bytes", .{@sizeOf(Part)});
    log.info("SuppliedPart = {d} bytes", .{@sizeOf(SuppliedPart)});
    log.info("SuppliedPartToSupplier = {d} bytes",
        .{@sizeOf(SuppliersToPartsAssociation.SuppliedPartToSupplier)});
    log.info("SuppliedPartToPart = {d} bytes",
        .{@sizeOf(SuppliersToPartsAssociation.SuppliedPartToPart)});
    log.info("==========================", .{});
    // First question.
    log.info("1. What parts are supplied by Smith?", .{});
    
    // Given the population and for brevity, we just hard code the supplier as Smith.
    const smith_element: usize = 0;
    const smith_set = SuppliersToPartsAssociation.SuppliedPartToSupplier.
            makeSingularCodomainSet(smith_element);
    
    const smith_parts = suppliers_to_parts_assoc.navigateSupplierToPart(smith_set);
    var parts_iter = smith_parts.iterator(.{});
    while (parts_iter.next()) |p_index| {
        log.info("    {s} | {s: <5} | {any} | {d:2.1} | {s}", parts[p_index]);
    }
    // Second question.
    log.info("2. What parts are supplied by Suppliers located in Paris?", .{}) ;
    
    // In this case, we construct a sequential search of the Supplier class.
    var paris_suppliers =
            SuppliersToPartsAssociation.SuppliedPartToSupplier.emptyCodomainSet;
    for (&suppliers, 0..) |*supplier, s_index| {
        if (mem.eql(u8, supplier.s_city, "Paris")) {
            paris_suppliers.set(s_index);
        }
    }
    
    const paris_parts = suppliers_to_parts_assoc.navigateSupplierToPart(paris_suppliers);
    parts_iter = paris_parts.iterator(.{});
    while (parts_iter.next()) |p_index| {
        log.info("    {s} | {s: <5} | {any} | {d:2.1} | {s}", parts[p_index]);
    }
    // Third question.
    log.info("3. What is the quantity of all parts supplied by Jones?", .{}) ;
    
    // Again, we'll just hard code the starting set knowing the how the
    // population is stored in the array.
    const jones_element: usize = 1;
    const jones_set = SuppliersToPartsAssociation.SuppliedPartToSupplier.
            makeSingularCodomainSet(jones_element);
    
    const jones_supplied_parts =
        suppliers_to_parts_assoc.navigateSupplierToSuppliedPart(jones_set);
    
    // With the supplied parts that Jones supplies, we can add up the QTY values.
    var quantity: usize = 0;
    var supplied_parts_iter = jones_supplied_parts.iterator(.{});
    while (supplied_parts_iter.next()) |sp_index| {
        quantity += supplied_parts[sp_index].qty;
    }
    
    log.info("    Jones supplies {d} parts", .{quantity});
    // Fourth question.
    log.info("4. Which suppliers supply Red parts?", .{});
    
    // For this case, we also perform a sequential search for the Red parts.
    var red_parts =
            SuppliersToPartsAssociation.SuppliedPartToPart.emptyCodomainSet;
    for (&parts, 0..) |*part, p_index| {
        if (part.p_color == .Red) {
            red_parts.set(p_index);
        }
    }
    
    const red_part_suppliers = suppliers_to_parts_assoc.navigatePartToSupplier(red_parts);
    var supplier_iter = red_part_suppliers.iterator(.{});
    while (supplier_iter.next()) |s_index| {
        log.info("    {s} | {s: <5} | {d} | {s}", suppliers[s_index]);
    }
    // Fifth question.
    log.info("5. Which suppliers supply parts that are co-located with the supplier?", .{});
    
    // In relational algebraic terms, this query can be satisfied by a semijoin
    // operation. If the Supplier and Part relations are joined across Supplier.CITY
    // and Part.CITY and then the attributes of Supplier are projected from the
    // result, we obtain the Supplier tuples of interest.
    // We're going to simulate that algebra using simple brute force iteration and
    // comparison which works fine for a small population.
    
    // The strategy used here is to iterate across the supplier tuples and for each tuple
    // compute the related parts. For each related part, we compare the CITY attribute
    // values, adding any match to an accumulating bit set of Supplier.
    
    // The result is a set of suppliers that are co-located with the parts they supply.
    var colocated_supplier =
            SuppliersToPartsAssociation.SuppliedPartToSupplier.emptyCodomainSet;
    
    for (&suppliers, 0..) |*supplier, supplier_element| {
        var supplier_set = SuppliersToPartsAssociation.SuppliedPartToSupplier.
                makeSingularCodomainSet(supplier_element);
        var a_suppliers_parts = suppliers_to_parts_assoc.navigateSupplierToPart(supplier_set);
        parts_iter = a_suppliers_parts.iterator(.{});
        while (parts_iter.next()) |p_index| {
            if (mem.eql(u8, supplier.s_city, parts[p_index].p_city)) {
                colocated_supplier.set(supplier_element);
            }
        }
    }
    
    var colocated_iter = colocated_supplier.iterator(.{});
    while (colocated_iter.next()) |supplier_element| {
        log.info("    {s} | {s: <5} | {d} | {s}", suppliers[supplier_element]);
    }
    log.info("==========================", .{});
}
