#!/usr/bin/env perl
# convert a LDraw file to BSX.
# this program will parse the LDraw file format v1.0.2 http://ldraw.org/article/218.html
# it will produce BrickStock/Brickstore BSX files, but is optimized to create BSX files for createing a wanted list on Bricklink.
#
# $Id: ldr2bsx.pl 973 2023-11-08 17:36:14Z doj $
#
# Copyright (c) 2016..2023 Dirk Jagdmann <doj@cubic.org>
#
# This software is provided 'as-is', without any express or implied
# warranty. In no event will the authors be held liable for any damages
# arising from the use of this software.
#
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it
# freely, subject to the following restrictions:
#
#     1. The origin of this software must not be misrepresented; you
#        must not claim that you wrote the original software. If you use
#        this software in a product, an acknowledgment in the product
#        documentation would be appreciated but is not required.
#
#     2. Altered source versions must be plainly marked as such, and
#        must not be misrepresented as being the original software.
#
#     3. This notice may not be removed or altered from any source
#        distribution.

use strict;
use Getopt::Std;

# parse command line
my %opts;
getopts('a', \%opts) or usage('invalid command line argument');

my $ldraw_fn = $ARGV[0];
usage("no LDraw file given on command line") unless -f $ldraw_fn;


if (0) {
# download LDraw mapping file if not present
my $ldraw_xml = 'ldraw.xml';
unless(-f $ldraw_xml) {
  my $url = "http://slswww.free.fr/ldraw.xml";
  download($url, $ldraw_xml) or die "download of $url failed";
}
die "could not get $ldraw_xml file" unless -f $ldraw_xml;

# parse LDraw mapping file
}

# parse Bricklink parts file
# TODO: check timestamp of Parts.txt and if too old download again from Bricklink
my %bricklink_parts; # key: item id, value: description
my $bricklink_parts = 'Parts.txt';
if (! -f $bricklink_parts) {
    my $url = 'https://www.bricklink.com/catalogDownload.asp?a=a&viewType=0&itemType=P&itemTypeInv=S&downloadType=T';
    print "You have to download the Bricklink parts file from $url\n";
    #download($url, $bricklink_parts) or die "download of $url failed";
    exit 1;
}
die "could not find Bricklink parts file $bricklink_parts" unless -f $bricklink_parts;
open(A, $bricklink_parts) or die "could not open $bricklink_parts: $!";
while(<A>) {
  next unless /^\d+/; # only process lines starting with digit characters
  s/\r?\n$//; # remove CR,NL characters
  @_ = split(/\t/);
  # 0: Category ID
  # 1: Category Name
  # 2: Part Number
  # 3: Part Name
  $bricklink_parts{$_[2]} = $_[3];
}
close(A);

# LDraw to Bricklink color conversion table http://www.peeron.com/inv/colors
# key: LDraw color ID, value: hash reference with Bricklink name and Bricklink color ID
my %LDrawColor2BricklinkColor = (
0 => { name => 'Black', bricklink_id => 11 },
1 => { name => 'Blue', bricklink_id => 7 },
2 => { name => 'Green', bricklink_id => 6 },
3 => { name => 'Dark Turquoise', bricklink_id => 39 },
4 => { name => 'Red', bricklink_id => 5 },
5 => { name => 'Dark Pink', bricklink_id => 47 },
6 => { name => 'Brown', bricklink_id => 8 },
7 => { name => 'Light Gray', bricklink_id => 9 },
8 => { name => 'Dark Gray', bricklink_id => 10 },
9 => { name => 'Light Blue', bricklink_id => 62 },
10 => { name => 'Bright Green', bricklink_id => 36 },
11 => { name => 'Light Turquoise', bricklink_id => 40 },
12 => { name => 'Salmon', bricklink_id => 25 },
13 => { name => 'Pink', bricklink_id => 23 },
14 => { name => 'Yellow', bricklink_id => 3 },
15 => { name => 'White', bricklink_id => 1 },
# 16 is the inherited LDraw color
17 => { name => 'Light Green', bricklink_id => 38 },
18 => { name => 'Light Yellow', bricklink_id => 33 },
19 => { name => 'Tan', bricklink_id => 2 },
20 => { name => 'Light Violet', bricklink_id => 44 },
21 => { name => 'Glow In Dark Opaque', bricklink_id => 46 },
22 => { name => 'Purple', bricklink_id => 24 },
23 => { name => 'Dark Blue-Violet', bricklink_id => 109 },
25 => { name => 'Orange', bricklink_id => 4 },
26 => { name => 'Magenta', bricklink_id => 71 },
27 => { name => 'Lime', bricklink_id => 34 },
28 => { name => 'Dark Tan', bricklink_id => 69 },
29 => { name => 'Bright Pink', bricklink_id => 104 },
33 => { name => 'Trans-Dark Blue', bricklink_id => 14 },
34 => { name => 'Trans-Green', bricklink_id => 20 },
35 => { name => 'Trans-Bright Green', bricklink_id => 108 },
36 => { name => 'Trans-Red', bricklink_id => 17 },
37 => { name => 'Trans-Purple', bricklink_id => 51 },
40 => { name => 'Trans-Black', bricklink_id => 13 },
41 => { name => 'Trans-Light Blue', bricklink_id => 15 },
42 => { name => 'Trans-Neon Green', bricklink_id => 16 },
43 => { name => 'Trans-Very Lt Blue', bricklink_id => 113 },
45 => { name => 'Trans-Dark Pink', bricklink_id => 50 },
46 => { name => 'Trans-Yellow', bricklink_id => 19 },
47 => { name => 'Trans-Clear', bricklink_id => 12 },
54 => { name => 'Trans-Neon Yellow', bricklink_id => 121 },
57 => { name => 'Trans-Neon Orange', bricklink_id => 18 },
60 => { name => 'Chrome Antique Brass', bricklink_id => 57 },
61 => { name => 'Chrome Blue', bricklink_id => 52 },
62 => { name => 'Chrome Green', bricklink_id => 64 },
63 => { name => 'Chrome Pink', bricklink_id => 82 },
64 => { name => 'Chrome Black', bricklink_id => 122 },
68 => { name => 'Very Light Orange', bricklink_id => 96 },
69 => { name => 'Light Purple', bricklink_id => 93 },
70 => { name => 'Reddish Brown', bricklink_id => 88 },
71 => { name => 'Light Bluish Gray', bricklink_id => 86 },
72 => { name => 'Dark Bluish Gray', bricklink_id => 85 },
73 => { name => 'Medium Blue', bricklink_id => 42 },
74 => { name => 'Medium Green', bricklink_id => 37 },
75 => { name => 'Speckle Black-Copper', bricklink_id => 116 },
76 => { name => 'Speckle DBGray-Silver', bricklink_id => 117 },
77 => { name => 'Light Pink', bricklink_id => 56 },
78 => { name => 'Light Flesh', bricklink_id => 90 },
79 => { name => 'Milky White', bricklink_id => 60 },
84 => { name => 'Medium Dark Flesh', bricklink_id => 150 },
85 => { name => 'Dark Purple', bricklink_id => 89 },
86 => { name => 'Dark Flesh', bricklink_id => 91 },
92 => { name => 'Flesh', bricklink_id => 28 },
100 => { name => 'Light Salmon', bricklink_id => 26 },
110 => { name => 'Violet', bricklink_id => 43 },
112 => { name => 'Blue-Violet', bricklink_id => 97 },
114 => { name => 'Glitter Trans-Dark Pink', bricklink_id => 100 },
115 => { name => 'Medium Lime', bricklink_id => 76 },
117 => { name => 'Glitter Trans-Clear', bricklink_id => 101 },
118 => { name => 'Aqua', bricklink_id => 41 },
120 => { name => 'Light Lime', bricklink_id => 35 },
125 => { name => 'Light Orange', bricklink_id => 32 },
129 => { name => 'Glitter Trans-Purple', bricklink_id => 102 },
132 => { name => 'Speckle Black-Silver', bricklink_id => 111 },
134 => { name => 'Copper', bricklink_id => 84 },
135 => { name => 'Pearl Light Gray', bricklink_id => 66 },
137 => { name => 'Metal Blue', bricklink_id => 78 },
142 => { name => 'Pearl Light Gold', bricklink_id => 61 },
143 => { name => 'Trans-Medium Blue', bricklink_id => 74 },
148 => { name => 'Pearl Dark Gray', bricklink_id => 77 },
150 => { name => 'Pearl Very Light Gray', bricklink_id => 119 },
151 => { name => 'Very Light Bluish Gray', bricklink_id => 99 },
178 => { name => 'Flat Dark Gold', bricklink_id => 81 },
179 => { name => 'Flat Silver', bricklink_id => 95 },
182 => { name => 'Trans-Orange', bricklink_id => 98 },
183 => { name => 'Pearl White', bricklink_id => 83 },
191 => { name => 'Bright Light Orange', bricklink_id => 110 },
212 => { name => 'Bright Light Blue', bricklink_id => 105 },
216 => { name => 'Rust', bricklink_id => 27 },
226 => { name => 'Bright Light Yellow', bricklink_id => 103 },
230 => { name => 'Trans-Pink', bricklink_id => 107 },
232 => { name => 'Sky Blue', bricklink_id => 87 },
236 => { name => 'Trans-Light Purple', bricklink_id => 114 },
272 => { name => 'Dark Blue', bricklink_id => 63 },
288 => { name => 'Dark Green', bricklink_id => 80 },
294 => { name => 'Glow In Dark Trans', bricklink_id => 118 },
297 => { name => 'Pearl Gold', bricklink_id => 115 },
308 => { name => 'Dark Brown', bricklink_id => 120 },
313 => { name => 'Maersk Blue', bricklink_id => 72 },
320 => { name => 'Dark Red', bricklink_id => 59 },
321 => { name => 'Dark Azure', bricklink_id => 153 },
323 => { name => 'Light Aqua', bricklink_id => 152 },
334 => { name => 'Chrome Gold', bricklink_id => 21 },
335 => { name => 'Sand Red', bricklink_id => 58 },
351 => { name => 'Medium Dark Pink', bricklink_id => 94 },
366 => { name => 'Earth Orange', bricklink_id => 29 },
373 => { name => 'Sand Purple', bricklink_id => 54 },
378 => { name => 'Sand Green', bricklink_id => 48 },
379 => { name => 'Sand Blue', bricklink_id => 55 },
383 => { name => 'Chrome Silver', bricklink_id => 22 },
450 => { name => 'Fabuland Brown', bricklink_id => 106 },
462 => { name => 'Medium Orange', bricklink_id => 31 },
484 => { name => 'Dark Orange', bricklink_id => 68 },
503 => { name => 'Very Light Gray', bricklink_id => 49 },
);

# manual substitutions from LDraw to Bricklink item numbers
my %LDraw2Bricklink = (
		       "0\t4180c02" => '4180c01', # black train wheels
		       "4\t4180c02" => '4180c04', # red train wheels
		       "6141" => '4073', # Plate, Round 1 x 1 Straight Side
		       "3324dc01" => '3324c01', # Hinge Plate 2 x 9 complete assembly
		       "u8200" => '3020', # 2x4 plate
		       "3046" => '3046a', # Slope 45 2 x 2 Double Concave
		       '3149dc01' => '3149c01', # Hinge Plate 2x5, complete assembly
		       '9352' => '3680c02', # Turntable 2 x 2 Plate, Complete Assembly with Light Bluish Gray Top
		       '502' => 'x460', # Light Gray Train Motor 12V Connecting Rod (7 studs long)
		       '947a' => 'x461', # Black Plate, Modified 1 x 2 with Steam Engine Cylinder, Round Surfaces
		       '501bc01' => 'bb12v', # Electric, Train Motor 12V Modern
		       '501ac01' => 'bb12v', # Electric, Train Motor 12V Modern
		       '266bc01' => '08010bc01', # Electric, Light Brick 12V 2 x 2 with 3 Plug Holes, Trans-Clear Diffuser Lens
		       '60897' => '4085', # Plate, Modified 1 x 1 with Clip Vertical
		       '3004p18' => '3004pb003', # Brick 1 x 2 with Black 'POLICE' Pattern
		       '3008p06' => '3008pb021', # LegoBrick 1 x 8 with White 'GARAGE' Pattern
		       '6264' => '3899', # Minifig, Utensil Cup
		       '3950' => '3959', # Minifig, Utensil Space Gun / Torch
		       '3712c01' => '9244c01', # Technic Universal Joint 4L
		       '89' => '9244c01', # Technic Universal Joint 4L
		       '122c03' => '122c01assy2', # Plate, Modified 2 x 2 with Wheels Red, with Black Tires Offset Tread Small (122c01 / 3641)
);

# load LDraw file

# key: string formatted with LDraw color ID, tab character, LDraw item ID
# value: item count
my %p;

open(A,'<:encoding(UTF-8)', $ldraw_fn) or usage("Can't open $ldraw_fn for reading: $!");
my $skipline = 0;
while(<A>) {
  chomp;

  # skip LeoCAD mini figues
  if (/^0 !LEOCAD GROUP BEGIN Minifig/)
  {
      $skipline = 1;
      next;
  }
  if (/^0 !LEOCAD GROUP END/)
  {
      $skipline = 0;
      next;
  }
  next if $skipline;

  # read all LDraw Line Type 1
  if (/^1 \s+ (\d+) \s+ (?:-?[\d.]+\s+){12} (.+)\.dat/x) {
    $p{"$1\t$2"}++;
  }
}
close(A);

# print Bricklink XML
my $exit_status = 0;
print<<'EOT';
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE BrickStockXML>
<!-- this BSX file was created by ldr2bsx.pl -->
<!-- See http://cubic.org/~doj/lego/7777-1/ for details -->
<BrickStockXML>
<Inventory>
EOT
while(my ($k,$qty) = each %p) {
  if ($k =~ /(\d+)\t(.+)/) {
    my $colorID = $1; # LDraw color ID
    my $itemid = $2; # LDraw item ID

    if (! exists($LDrawColor2BricklinkColor{$colorID})) {
      print STDERR "unknown LDraw color: $colorID\n";
      $LDrawColor2BricklinkColor{$colorID} = $LDrawColor2BricklinkColor{0}; # set to black
    }
    my $colorName = $LDrawColor2BricklinkColor{$colorID}->{name};
    $colorID = $LDrawColor2BricklinkColor{$colorID}->{bricklink_id};

    if (exists($LDraw2Bricklink{$k})) {
	$itemid = $LDraw2Bricklink{$k};
    }
    elsif (exists($LDraw2Bricklink{$itemid})) {
	$itemid = $LDraw2Bricklink{$itemid};
    }

    if (! exists($bricklink_parts{$itemid})) {
	print STDERR "unknown Bricklink part: $itemid\n";

	my $found_alternative = 0;
	while (length($itemid) > 0 && $itemid =~ /^\d+\D/) {
	  chop($itemid);
	  if (exists($bricklink_parts{$itemid})) {
	    if ($opts{a}) {
	      print STDERR "using alternative: $itemid\n";
	      $found_alternative = 1;
	      last;
	    } else {
	      print STDERR "possible alternative: $itemid\n";
	    }
	  }
	}

	$exit_status = 1 unless $found_alternative;
    }

    if (exists($bricklink_parts{$itemid})) {
    my $description = $bricklink_parts{$itemid};
    print<<"EOF";
 <Item>
  <ItemTypeID>P</ItemTypeID>
  <ItemID>$itemid</ItemID>
  <ColorID>$colorID</ColorID> <!-- $colorName -->
  <Qty>$qty</Qty>
  <ItemName>$description</ItemName>
  <REMARKS>$ldraw_fn</REMARKS>
 </Item>
EOF
    } else {
      print "<!-- unknown Bricklink part: $itemid -->\n";
    }
  } else {
    print STDERR "invalid key: '$k'. This is a software bug!\n";
    $exit_status = 1;
  }
}
print<<'EOT';
</Inventory>
</BrickStockXML>
EOT

if ($exit_status == 0) {
  print STDERR "\nconversion successful.\n";
} else {
  print STDERR "\ncould not convert all items or colors!\n";
}
exit $exit_status;

sub usage
  {
    my ($msg) = @_;
    print STDERR "$msg\n" if length($msg) > 0;
    print STDERR "usage: ldr2bsx.pl [-a] <LDraw file>\n";
    print STDERR "-a  accept any alternative part found\n";
    exit 1;
  }

sub download
    {
      my ($url, $fn) = @_;
      return unless $url;
      return unless $fn;
      system("wget -O '$fn' '$url'");
      return $fn if -f $fn;
      system("curl '$url' > '$fn'");
      return $fn if -f $fn;
      return undef;
    }
