import sys
import json
from rectpack import newPacker,MaxRectsBl

# Input data
input_data = json.load(sys.stdin)

# Extract margins and input details
rect_margin = input_data["rect_margin"]
bin_margin = input_data["bin_margin"]
bin_sizes = sorted(input_data["bin_sizes"], key=lambda b: b["bin_height"], reverse=True)

# Prepare rectangles with a new index
rectangles = []
Origrectangles = []
index1 = 0  # New index to track each rectangle placement
for rect in input_data["rectangles"]:
    width = rect["width"]
    height = rect["height"]
    qty = rect["qty"]
    # original_index = rect["index"]
    original_index = rect.get('index', None)
    orignal_url =  rect.get('orignal_url', None)
    Origrectangles.append((width, height, original_index,qty,orignal_url))
    for _ in range(qty):
        index1 += 1
        rectangles.append((width, height, original_index, index1,orignal_url))

def pack_rectangles_with_rotation(rotation, rects, bin_width, bin_height, rect_margin, bin_margin):
    packer = newPacker(rotation=rotation,pack_algo=MaxRectsBl)
    sorted_rects = sorted(rects, key=lambda r: r[0], reverse=True)
    
    for r in sorted_rects:
        packer.add_rect(r[0] + rect_margin, r[1] + rect_margin, rid=r)
    
    packer.add_bin(bin_width - bin_margin, bin_height - bin_margin)
    
    packer.pack()
    
    total_bin_area = sum(bin.width * bin.height for bin in packer)
    total_rect_area = sum(r[0] * r[1] for r in rects)
    wasted_area = total_bin_area - total_rect_area
    
    return packer, wasted_area

# Function to choose the best packing strategy (rotation vs no rotation)
def choose_best_packing_strategy(rects, bin_width, bin_height, rect_margin, bin_margin):
    packer_no_rotation, wasted_no_rotation = pack_rectangles_with_rotation(False, rects, bin_width, bin_height, rect_margin, bin_margin)
    packer_with_rotation, wasted_with_rotation = pack_rectangles_with_rotation(True, rects, bin_width, bin_height, rect_margin, bin_margin)

    packed_no_rotation = sum(len(bin) for bin in packer_no_rotation)
    packed_with_rotation = sum(len(bin) for bin in packer_with_rotation)

    if packed_with_rotation > packed_no_rotation:
        return packer_with_rotation, "With Rotation"
    elif packed_no_rotation > packed_with_rotation:
        return packer_no_rotation, "No Rotation"
    else:
        if wasted_with_rotation < wasted_no_rotation:
            return packer_with_rotation, "With Rotation"
        else:
            return packer_no_rotation, "No Rotation"        

# Function to pack rectangles into bins
def pack_rectangles(rects, bin_width, bin_height, rect_margin, bin_margin,Origrectangles,bin_id):
    packer, chosen_strategy = choose_best_packing_strategy(rects, bin_width, bin_height, rect_margin, bin_margin)

    # Collect packing results
    packed_bins = []
    placed_rects = []

    # Process each bin and collect data
    for i, bin in enumerate(packer):
        bin_data = {
            "bin_id" : bin_id,
            "bin_number": i + 1,
            "bin_width": bin.width,
            "bin_height": bin.height,
            "rotation_strategy": chosen_strategy,
            "rectangles": []
        }
        for rect in bin:
            x = rect.x + bin_margin
            y = rect.y + bin_margin
            w = rect.width
            h = rect.height
            original_width, original_height, original_index, index1,orignal_url = rect.rid
            rotated = (w, h) != (original_width + rect_margin, original_height + rect_margin)
            real_width = w - rect_margin
            real_height = h - rect_margin

            bin_data["rectangles"].append({
                "x": x,
                "y": y,
                "width": real_width,
                "height": real_height,
                "rotated": rotated,
                "index" : original_index,
                "index1": index1,  # Use new index1
                "orignal_url": orignal_url
            })

            # Track placed rectangles by their original dimensions and index1
            placed_rects.append((original_width, original_height, original_index, index1,orignal_url))
        
        packed_bins.append(bin_data)

    # Calculate remaining rectangles by checking how many of each we still need to place
    # Calculate placed rectangle counts
    placed_count = {}
    for placed in placed_rects:
        key = (placed[0], placed[1], placed[2])  # Use original dimensions and index
        placed_count[key] = placed_count.get(key, 0) + 1

    #print(placed_count)
    # print(rects)
    # sfs    
    # Calculate remaining rectangles
    remaining_rects = []
    #print(Origrectangles)
    #sfs
    for i, rect in enumerate(Origrectangles):
        key = (rect[0], rect[1], rect[2])  # Use original dimensions and index
        #print(key)
        placed_qty = placed_count.get(key, 0)
        #print(placed_qty)
        remaining_qty = rect[3] - placed_qty
        #print(remaining_qty)
        #Origrectangles[i] = (rect[0], rect[1], rect[2], remaining_qty)
        for _ in range(remaining_qty):
            remaining_rects.append(rect)

            
    #print(remaining_rects)
    #sfs
    return packed_bins, remaining_rects,Origrectangles,placed_rects

# Function to choose the best bin size based on least remaining rectangles
def choose_best_bin_size(rects, bin_sizes, rect_margin, bin_margin,Origrectangles):
    best_bin = None
    best_remaining = float('inf')
    best_packed_bins = []
    best_remaining_rects = []
    placed_rects1 = []

    # Try all available bin sizes and return the one with least remaining rectangles
    for bin in bin_sizes:
        bin_width = bin["bin_width"] + bin_margin
        bin_height = bin["bin_height"]
        bin_id = bin["bin_id"]
        packed_bins, remaining_rects,Origrectangles,placed_rects = pack_rectangles(rects, bin_width, bin_height, rect_margin, bin_margin,Origrectangles,bin_id)

        # If fewer remaining rects, or same remaining rects but smaller bin height
        if len(remaining_rects) < best_remaining or (len(remaining_rects) == best_remaining and bin_height < best_bin["bin_height"]):
            best_bin = bin
            best_remaining = len(remaining_rects)
            best_packed_bins = packed_bins
            best_remaining_rects = remaining_rects
            placed_rects1 = placed_rects
    
    #print(best_remaining_rects)
    #print(best_packed_bins)
    #print(placed_rects1)
    #sdf

    placed_count = {}
    for placed in placed_rects1:
        key = (placed[0], placed[1], placed[2])  # Use original dimensions and index
        placed_count[key] = placed_count.get(key, 0) + 1

    for i, rect in enumerate(Origrectangles):
        key = (rect[0], rect[1], rect[2])  # Use original dimensions and index
        #print(key)
        placed_qty = placed_count.get(key, 0)
        #print(placed_qty)
        remaining_qty = rect[3] - placed_qty
        #print(remaining_qty)
        Origrectangles[i] = (rect[0], rect[1], rect[2], remaining_qty,rect[4])    

    return best_packed_bins, best_remaining_rects, Origrectangles

# Iterative packing process
final_bins = []
remaining_rectangles = rectangles

while remaining_rectangles:
    # Try packing the remaining rectangles into bins
    packed_bins, remaining_rects, Origrectangles = choose_best_bin_size(remaining_rectangles, bin_sizes, rect_margin, bin_margin,Origrectangles)

    if remaining_rects == remaining_rectangles:
        print(json.dumps({"error": "No rectangle fits in any bin."}, indent=4))
        break
    # Add the packed bins to the final results
    final_bins.extend(packed_bins)

    # Update remaining rectangles
    remaining_rectangles = remaining_rects
    #print('3423')
    #print(remaining_rectangles)
    
    #sd
    #Origrectangles = Origrectangles

    # If no rectangles are remaining, stop the process
    if not remaining_rectangles:
        break

# Prepare the final response
response = {
    "bins": final_bins
}

# Output the result
response_json = json.dumps(response, indent=4)
print(response_json)