1 module iban.constructor;
2 
3 import std.typecons : Nullable, nullable;
4 import std.format : format;
5 import std.stdio;
6 
7 import iban.validation;
8 import iban.structures;
9 import iban.ibans;
10 
11 @safe:
12 
13 Nullable!IBAN ibanFromString(string s) {
14 	s = removeWhite(s);
15 	Nullable!string specKey = extractCountryPrefix(s);
16 
17 	if(specKey.isNull()) {
18 		return Nullable!(IBAN).init;
19 	}
20 
21 	auto spec = specKey.get() in getIBANs();
22 
23 	if(spec is null) {
24 		return Nullable!(IBAN).init;
25 	}
26 
27 	if(!isValidIBAN(s, *spec)) {
28 		return Nullable!(IBAN).init;
29 	}
30 
31 	if(s.length < 4) {
32 		return Nullable!(IBAN).init;
33 	}
34 
35 	IBAN ret;
36 	ret.countryCode = s[0 .. 2];
37 	s = s[4 .. $];
38 	static foreach(it;
39 			[ [ "accountCode", "account_code" ]
40 			, [ "bankCode", "bank_code" ]
41 			, [ "branchCode", "branch_code" ]
42 			])
43 	{{
44 		long[]* pos = it[1] in spec.positions;
45 		if(pos !is null && (*pos).length > 1) {
46 			long[] ppos = *pos;
47 			ulong left = ppos[0];
48 			ulong right = ppos[1];
49 			if(left > s.length) {
50 				return Nullable!(IBAN).init;
51 			}
52 			if(right > s.length) {
53 				return Nullable!(IBAN).init;
54 			}
55 			__traits(getMember, ret, it[0]) = s[left .. right];
56 		}
57 	}}
58 
59 	return nullable(ret);
60 }
61 
62 unittest {
63 	import iban.testdata;
64 
65 	foreach(it; valid) {
66 		Nullable!IBAN t = ibanFromString(it);
67 		assert(!t.isNull(), it);
68 	}
69 }
70 
71 unittest {
72 	import iban.testdata;
73 
74 	foreach(it; invalid) {
75 		Nullable!IBAN t = ibanFromString(it);
76 		assert(t.isNull(), it);
77 	}
78 }
79 
80 Nullable!string buildIBANFromParts(string isoTwoDigitCountryCode, string bankCode
81 		, string branchCode , string accountCode)
82 {
83 	import std.algorithm.iteration : map, joiner;
84 	import std.bigint;
85 	import std.conv : to;
86 
87 	auto spec = isoTwoDigitCountryCode in getIBANs();
88 
89 	if(spec is null) {
90 		return Nullable!(string).init;
91 	}
92 
93 	char[] tmp = new char[](spec.bbanLength);
94 	tmp[] = '0';
95 
96 	// The three parts of the IBAN according to the countries spec
97 	static foreach(it;
98 			[ [ "accountCode", "account_code" ]
99 			, [ "bankCode", "bank_code" ]
100 			, [ "branchCode", "branch_code" ]
101 			])
102 	{{
103 		long[]* pos = it[1] in spec.positions;
104 		if(pos !is null && (*pos).length > 1) {
105 			long[] ppos = *pos;
106 			ulong left = ppos[0];
107 			ulong right = ppos[1];
108 			if(left > tmp.length) {
109 				return Nullable!(string).init;
110 			}
111 			if(right > tmp.length) {
112 				return Nullable!(string).init;
113 			}
114 			mixin(format("tmp[left .. right] = %s;\n", it[0]));
115 		}
116 	}}
117 
118 	// Computing the checksum
119 
120 	string bban = tmp
121 		.map!(it => it >= 'A'
122 				? to!string(to!int(it - 'A' + 10))
123 				: to!string(it)
124 		)
125 		.joiner("")
126 		.to!string();
127 
128 	string prefix = isoTwoDigitCountryCode
129 		.map!(it => it >= 'A'
130 				? to!int(it - 'A' + 10)
131 				: 0
132 		)
133 		.map!(it => to!string(it))
134 		.joiner("")
135 		.to!string();
136 
137 	string cc = bban ~ prefix ~ "00";
138 	BigInt num = cc;
139 	long mod = num % 97;
140 	long chkSumTT = 98 - mod;
141 
142 	string ret = isoTwoDigitCountryCode ~ format("%02d", chkSumTT)
143 		~ tmp.to!string();
144 
145 	return nullable(ret);
146 }
147 
148 IBAN ibanFromDetails(string isoTwoDigitCountryCode, string bankCode
149 		, string branchCode , string accountCode)
150 {
151 	IBAN ret;
152 	ret.countryCode = isoTwoDigitCountryCode;
153 	ret.bankCode = bankCode;
154 	ret.branchCode = branchCode;
155 	ret.accountCode = accountCode;
156 
157 	Nullable!string data = buildIBANFromParts(isoTwoDigitCountryCode, bankCode
158 			, branchCode, accountCode);
159 	if(!data.isNull()) {
160 		ret.iban = data.get();
161 	}
162 
163 	return ret;
164 }
165 
166 unittest {
167 	import iban.testdata;
168 
169 	foreach(it; valid) {
170 		it = it.removeWhite();
171 		Nullable!IBAN t = ibanFromString(it);
172 		assert(!t.isNull(), it);
173 		IBAN tNN = t.get();
174 		auto r = buildIBANFromParts(tNN.countryCode, tNN.bankCode
175 				, tNN.branchCode, tNN.accountCode);
176 		assert(!r.isNull());
177 		auto rNN = r.get();
178 		if(rNN != it) {
179 			writefln("\nexp: %s\ngot: %s", it, rNN);
180 		}
181 	}
182 }