#!/usr/bin/perl

use strict;
use POSIX qw( strftime );

# https://www2.swift.com/knowledgecentre/publications/us9m_20190719/2.0?topic=mt940-example-0.htm 
# https://www2.swift.com/knowledgecentre/publications/us9m_20190719/2.0?topic=mt940.htm

sub ST_WF_20       () {  qw( ST_WF_20       ) };
sub ST_WF_25       () {  qw( ST_WF_25       ) };
sub ST_WF_28C      () {  qw( ST_WF_28C      ) };
sub ST_WF_60       () {  qw( ST_WF_60       ) };
sub ST_WF_61       () {  qw( ST_WF_61       ) };
sub ST_WF_86       () {  qw( ST_WF_86       ) };
sub ST_WF_61_OR_62 () {  qw( ST_WF_61_OR_62 ) };
sub ST_WF_DASH     () {  qw( ST_WF_DASH     ) };

my $swift_charset = '[A-Za-z0-9-,+.() ]';
                                            
sub decode_61 ($) {                         
	my ( $line ) = @_;                      
	print "*** decode_61()\n";
	print "*** '$line'\n";

	# MT940 Field Specifications /  MT940 - 6. Field 61: Statement Line
	# 61 Kontoauszug Zeile/Transaktionsdetails '2208120812DR47,54NDDTKREF+'
	# :61: 090123 0122 C3500,25FCHK304955//4958843 ADDITIONAL INFORMATION
	my ( $valuedate, $entrydate, $cdmark, $fundscode, $amount, $transactiontype, $identificationcode, $ref_f_a_owner, $slashes, $ref_acct_srv_inst, $suppl_details, $r );

	my $map_cdmark= { 
		"C" => "credit",
		"D" => "debit",
		"RC" => "reversal of credit (debit entry)",
		"RD" => "reversal of debit (credit entry)",
	};

	my $map_transactiontype = {
		"S" => "Swift transfer",
		"N" => "Non-Swift transfer",
		"F" => "First advice",
	};

	# In subfield 6, when Transaction Type is 'N' or 'F', Identification Code may contain one of the following codes:
	my $map_identificationcode = {
		"BNK" => "Securities Related Item - Bank Fees", 
		"BOE" => "Bill of Exchange", 
		"BRF" => "Brokerage Fee", 
		"CAR" => "Securities Related Item - Corporate Actions Related (should only be used when no specific corporate action event code is available)",
		"CAS" => "Securities Related Item - Cash in Lieu",
		"CHG" => "Charges and Other Expenses",
		"CHK" => "Cheques",
		"CLR" => "Cash Letters/Cheques Remittance",
		"CMI" => "Cash Management Item - No Detail",
		"CMN" => "Cash Management Item - Notional Pooling",
		"CMP" => "Compensation Claims",
		"CMS" => "Cash Management Item - Sweeping",
		"CMT" => "Cash Management Item - Topping",
		"CMZ" => "Cash Management Item - Zero Balancing",
		"COL" => "Collections (used when entering a principal amount)",
		"COM" => "Commission",
		"CPN" => "Securities Related Item - Coupon Payments",
		"DCR" => "Documentary Credit (used when entering a principal amount)",
		"DDT" => "Direct Debit Item",
		"DIS" => "Securities Related Item - Gains Disbursement",
		"DIV" => "Securities Related Item - Dividends",
		"EQA" => "Equivalent Amount",
		"EXT" => "Securities Related Item - External Transfer for Own Account",
		"FEX" => "Foreign Exchange",
		"INT" => "Interest Related Amount",
		"LBX" => "Lock Box",
		"LDP" => "Loan Deposit",
		"MAR" => "Securities Related Item - Margin Payments/Receipts",
		"MAT" => "Securities Related Item - Maturity",
		"MGT" => "Securities Related Item - Management Fees",
		"MSC" => "Miscellaneous",
		"NWI" => "Securities Related Item - New Issues Distribution",
		"ODC" => "Overdraft Charge",
		"OPT" => "Securities Related Item - Options",
		"PCH" => "Securities Related Item - Purchase (including STIF and Time deposits)",
		"POP" => "Securities Related Item - Pair-off Proceeds",
		"PRN" => "Securities Related Item - Principal Pay-down/Pay-up",
		"REC" => "Securities Related Item - Tax Reclaim",
		"RED" => "Securities Related Item - Redemption/Withdrawal",
		"RIG" => "Securities Related Item - Rights",
		"RTI" => "Returned Item",
		"SAL" => "Securities Related Item - Sale (including STIF and Time deposits)",
		"SEC" => "Securities (used when entering a principal amount)",
		"SLE" => "Securities Related Item - Securities Lending Related",
		"STO" => "Standing Order",
		"STP" => "Securities Related Item - Stamp Duty",
		"SUB" => "Securities Related Item - Subscription",
		"SWP" => "Securities Related Item - SWAP Payment",
		"TAX" => "Securities Related Item - Withholding Tax Payment",
		"TCK" => "Travellers Cheques",
		"TCM" => "Securities Related Item - Tripartite Collateral Management",
		"TRA" => "Securities Related Item - Internal Transfer for Own Account",
		"TRF" => "Transfer",
		"TRN" => "Securities Related Item - Transaction Fee",
		"UWC" => "Securities Related Item - Underwriting Commission",
		"VDA" => "Value Date Adjustment (used with an entry made to withdraw an incorrectly dated entry - it will be followed by the correct entry with the relevant code)",
		"WAR" => "Securities Related Item - Warrant",
	};

	# SF 1  6!n  (Value Date)
	# genau 6 Ziffern
	if ( $line =~ s/^(\d{6})// ) {
		$valuedate = $1;
		print "*** '$valuedate'\n";
	} else {
		die "unrecognized line part '$line'";
	}

	# SF 2  [4!n]  (Entry Date)
	# genau 4 Ziffern, optional
	if ( $line =~ s/^(\d{4})// ) {
		$entrydate = $1;
		print "*** '$entrydate'\n";
	}

	# SF 3 2a (Debit/Credit Mark)
	# ein oder zwei Buchstaben aus 'C', 'D', 'RC' oder 'RD':
	# - C  Credit
    # - D  Debit
    # - RC Reversal of Credit (debit entry)
    # - RD Reversal of Debit (credit entry)
	if ( $line =~ s/^(C|D|RC|RD)// ) {
		$cdmark = $1;
		print "*** '$cdmark'\n";
	} else {
		die "unrecognized line part '$line'";
	}

	# SF 4 [1!a] (Funds Code)
	# ein Buchstabe, optional
	if ( $line =~ s/^([A-Z])// ) {
		$fundscode = $1;
		print "*** '$fundscode'\n";
	}

	# SF 5 15d (Amount)
	# bis zu 15-stelliger Dezimalwert
	if ( $line =~ s/^([0-9]{1,12},[0-9]{2})// ) {
		$amount = $1;
		print "*** '$amount'\n";
	} else {
		die "unrecognized line part '$line'";
	}

	# SF 6 1!a3!c (Transaction Type)(Identification Code)
	# ein Buchstabe, drei Zeichen (wofür genau steht 'c' ?)
	if ( $line =~ s/^([SNF])([A-Z]{3})// ) {
		$transactiontype    = $1;
		$identificationcode = $2;
		print "*** '$transactiontype - $identificationcode' : '$map_transactiontype->{$transactiontype}' - '$map_identificationcode->{$identificationcode}'\n";
	} else {
		die "unrecognized line part '$line'";
	}

	# SF 7 16x (Reference for the Account Owner)
	# ein bis 16 Zeichen (wofür genau steht 'x' ?)
	if ( $line =~ s/^([A-Z0-9\+ ]{1,16})// ) {
		$ref_f_a_owner = $1;
		print "*** '$ref_f_a_owner'\n";
	} else {
		die "unrecognized line part '$line'";
	}

	# SF 8 [//16x] (Reference of the Account Servicing Institution)
	# zwei '/' plus 1 bis 16 Zeichen (wofür genau steht 'x' ?), optional
	if ( $line =~ s/^\/\/([A-Z0-9\+ ]{1,16})(\s)/$2/x ) {
		$ref_acct_srv_inst = $1;
		print "*** '$ref_acct_srv_inst'\n";
	}

	# SF 9 [34x] <crlf>(Supplementary Details)
	# <crlf>plus 1 bis 34 Zeichen (wofür genau steht 'x' ?), optional
	if ( $line =~ s/^\s([A-Z0-9\+ ]{1,34})$// ) {
		$suppl_details = $1;
		print "*** '$suppl_details'\n";
	}

	return ( $valuedate, $entrydate, $cdmark, $fundscode, $amount, $transactiontype, $ref_f_a_owner, $slashes, $ref_acct_srv_inst, $suppl_details, $r );
	
}

sub decode_86 ($) {
	my ( $line ) = @_;
	print "*** decode_86()\n";
	print "*** line: '$line'\n";

	#  MT940 Field Specifications /  MT940 - 7. Field 86: Information to Account Owner
	# Format: 6*65x (Narrative) - das waere also ein Freitextfeld
	# weitere Doku: https://docplayer.org/15211095-Mt940-kontoauszug-feld-86-verwendungszweck-in-strukturierter-oder-unstrukturierter-form.html
	#  :86:166?00Uberweisungsgutschr.?10931?20EREF+1222230037810
	#  :86:105?00Basislastschrift?10931?20EREF+R2022006603199
	#  :86:105?00Basislastschrift?10931?20EREF+R2022006603199


	# gem. deutscher Norm
	my $map_geschaeftsvorfallcode = { 
		# 0xx INLANDSZAHLUNGSVERKEHR
		"001" => "Inhaberscheck (nicht Euroscheck)",
		"002" => "Orderscheck",
		"003" => "DM-Reisescheck",
		"004" => "Lastschrift (Abbuchungsverf ahren)",
		"005" => "Lastschrift (Einzugsermächtigungsverfahren)",
		"006" => "Sonstige Einzugspapiere",
		"007" => "Auszahlung freizügiger Sparverkehr",
		"008" => "Dauerauftrag Belastung",
		"009" => "Rücklastschrift aus Datenträgeraustausch, Lastschrift (Rückbelastung) - DTA -",
		"010" => "Wechselrückrechnung ",
		"011" => "Euroscheck",
		"012" => "Zahlungsanweisung zur Verrechnung",
		"013" => "EU-Standardüberweisung",
		"014" => "Lastschrift für Fremdwährungs - eurocheque / Lastschrift für über die GZS abgewickelte Auslandsschecks",
		"015" => "Auslandsüberweisung ohne Meldeteil",
		"017" => "Überweisung beim neutralen Überweisungs-/Zahlscheinvordruck mit prüfziffergesicherten Zuordnungsdaten",
		"018" => "Überweisung beim neutralen Überweisungs - /Zahlscheinvordruck ",
		"019" => "Überweisung beim neutralen Spenden - Überweisungs - /Zahlscheinvordruck ",
		"020" => "Überweisung 051 Überweisungsgutschrift",
		"052" => "Dauerauftragsgutschrift",
		"053" => "Lohn - , Gehalts - , Rentengutschrift",
		"054" => "Vermögenswirksame Leistungen",
		"056" => "Überweisung öffentlicher Kassen ",
		"058" => "Bank - an - Bank - Zahlung (Überweisungsgutschrift)",
		"059" => "Retourenhülle (Gutschrift) für unanbringliche Überweisung, Gutschrift (Rücküberweisung) - DTA -",
		"063" => "Überweisungsgutschrift  - EU - Standardüberweisung",
		"065" => "Überweisungsgutschrift (Auslandsüberweisung ohne Meldeteil)",
		"066" => "Gutschrift aus Scheckeinreichung E.v. (Exportscheckabwicklung über GZS)",
		"067" => "Gutschrift beim neutralen Überweisungs - /Zahlscheinvordruck mit prüfziffergesicherten internen Zuordnungsdaten",
		"068" => "Gutschrift beim neutralen Überweisungs - /Zahlscheinvordruck EZÜ 069 Gutschrift beim neutralen Spenden - Überweisungs - /Zahlscheinvordruck EZÜ",
		"070" => "Scheckeinreichung",
		"071" => "Lastschrifteinreichung",
		"072" => "Wechseleinreichung",
		"073" => "Wechsel",
		"074" => "TC (Scheckbelastung)",
		"075" => "Scheck BSE",
		"076" => "Telefonauftrag",
		"077" => "Online - Überweisung",
		"078" => "Überweisung (Versorgungsbezüge)",
		"079" => "Sammler",
		"080" => "Gehalt",
		"081" => "Vergütung",
		"082" => "Einzahlungen",
		"083" => "Auszahlungen",
		"084" => "Online - Einzugsauftrag",
		"087" => "Überweisung mit Festvaluta",
		"088" => "Überweisungsgutschrift mit Festvaluta",
		"089" => "drahtliche Überweisung mit Festvaluta",
		"090" => "drahtliche Überweisungsgutschrift mit Festvaluta",
		"091" => "DATA - Einreichung Überweisungen",
		"092" => "DATA - Einreichung Lastschriften",
		"093" => "Diskont - Wechsel",
		"094" => "Rediskont - Wechsel",
		"095" => "Aval (Inland)",
		"096" => "Kontoübertrag (Soll)",
		"097" => "Kontoübertrag (Haben)",
		"098" => "GeldKarte (Umsatz Elektronische Geldbörse)",
		"099" => "GeldKarte (Händlerprovision für Zahlungsgarantie)",
		# 1xx SEPA ZAHLUNGSVERKEHR
		# Aus EDIFACT Aufträgen nicht möglich !
		# Reserve
		# 2xx AUSLANDSGESCHÄFT
		"201" => "Zahlungsauftrag",
		"202" => "Auslandsvergütung",
		"203" => "Inkasso",
		"204" => "Akkreditiv",
		"205" => "Aval",
		"206" => "Auslandsüberweisung",
		"207" => "Zunächst frei",
		"208" => "Rembourse",
		"209" => "Zahlung per Scheck",
		"210" => "Zahlung über elektronische Medien",
		"211" => "Zahlungseingang über elektronische Medien",
		"212" => "Dauerauftrag",
		"213" => "Lastschrift - Einzug aus dem Ausland ",
		"214" => "Dokumenten - Inkasso (Import)",
		"215" => "Dokumenten - Inkasso (Export)",
		"216" => "Wechsel - Inkasso (Import)",
		"217" => "Wechsel - Inkasso (Export)",
		"218" => "Import - Akkreditiv",
		"219" => "Export - Akkreditiv",
		"220" => "Gutschrift e.V. eines Auslands - Schecks",
		"221" => "Gutschrift Auslands - Scheck - Inkasso",
		"222" => "Belastung Auslands - Scheck",
		"223" => "Belastung Auslands - ec - Scheck",
		"224" => "Sorten - Ankauf",
		"225" => "Sorten - Verkauf",
		# 3xx WERTPAPIERGESCHÄFT
		"301" => "Inkasso",
		"302" => "Kupon / Dividenden",
		"303" => "Effekten",
		"304" => "Übertrag",
		"305" => "Namensschuldverschreibung",
		"306" => "Schuldschein",
		"307" => "Wertpapierzeichnung",
		"308" => "Handel von Bezugsrechten",
		"309" => "Handel von Bonusrechten",
		"310" => "Handel von Optionen",
		"311" => "Termingeschäfte",
		"320" => "Gebühren für Wertpapiergeschäfte",
		"321" => "Depotgebühren",
		"330" => "Erträge aus Wertpapieren",
		"340" => "Gutschrift für fällige Wertpapiere",
		"399" => "Storno",
		# 4xx DEVISENGESCHÄFT
		"401" => "Kassedevisen",
		"402" => "Termindevisen",
		"403" => "Reisedevisen",
		"404" => "Devisenschecks",
		"405" => "Finanzinnovationen",
		"411" => "Devisenkassa - Kauf",
		"412" => "Devisenkassa - Verkauf",
		"413" => "Devisentermin - Kauf",
		"414" => "Devisentermin - Verkauf",
		"415" => "FW - Tagegeld - Aktiv",
		"416" => "FW - Tagegeld - Passiv",
		"417" => "FW - Termingeld - Aktiv",
		"418" => "FW - Termingeld - Passiv",
		"419" => "Call - Geld - Aktiv",
		"420" => "Call - Geld - Passiv",
		"421" => "Optionen",
		"422" => "Swap",
		"423" => "Edelmetall - Ankauf",
		"424" => "Edelmetall - Verkauf",
		# 5xx MAOBE
		# 6xx KREDITGESCHÄFT
		"601" => "Einzug von Raten/Annuitäten",
		"602" => "Überweisung von Raten/Annuitäten",
		"603" => "Tilgung",
		"604" => "Darlehenszinsen",
		"605" => "Darlehenszinsen mit Nebenleistungen",
		# 7xx RESERVE
		# 8xx SONSTIGE
		"801" => "Scheckkarte",
		"802" => "Scheckheft",
		"803" => "Depotverwahrung",
		"804" => "Dauerauftragsgebühren",
		"805" => "Abschluß",
		"806" => "Porto / Zustellgebühren",
		"807" => "Preise / Spesen",
		"808" => "Gebühren",
		"809" => "Provisionen",
		"810" => "Mahngebühren",
		"811" => "Kreditkosten",
		"812" => "Stundungszinsen",
		"813" => "Disagio",
		"814" => "Zinsen",
		"815" => "Kapitalisierte Zinsen",
		"816" => "Zinssatzänderung",
		"817" => "Zinsberichtigungen",
		"818" => "Abbuchung",
		"819" => "Bezüge",
		"820" => "Übertrag",
		"821" => "Telefon",
		"822" => "Auszahlplan",
		"823" => "Festgeld",
		"824" => "Leihgeld",
		"825" => "Universaldarlehen",
		"826" => "Dynamisches Sparen",
		"827" => "Überschußsparen",
		"828" => "Sparbrief",
		"829" => "Sparplan",
		"830" => "Bonus",
		"831" => "Alte Rechnung",
		"832" => "Hypothek",
		"833" => "Cash Concentrating : Buchung Hauptkonten",
		"834" => "Cash Concentrating : Avisinformation für Nebenkonten",
		"835" => "Sonstige nicht definierte GV - Arten",
		"836" => "Reklamationsbuchung",
		"888" => "Umbuchung wegen Euro - Umstellung",
		"899" => "Storno",
		# 9xx UNSTRUKTURIERTE INHALTE
		"997" => "Depotaufstellung -> MT571",
		"999" => "Unstrukturierte Belegung des Mehrzweckfeldes :86:",
		# Gschäftsvorfallcodes für SEPA - Zahlungen
		# 1xx SEPA ZAHLUNGSVERKEHR
		"105" => "SEPA Direct Debit (Einzelbuchung - Soll, B2C)",
		"109" => "SEPA Direct Debit (Soll; Rückbelastung)",
		"116" => "SEPA - Überweisung (Einzelbuchung - Soll)",
		"159" => "SEPA - Überweisung Retoure (Haben) für unanbringlich e Überweisung, (Rücküberweisung)",
		"166" => "SEPA - Überweisung (Einzelbuchung - Haben)",
		"171" => "SEPA Direct Debit Einreichung (Haben)",
		"177" => "SEPA - Online - Überweisung (Soll)",
		"181" => "SEPA Direct Debit (Haben; Wiedergutschrift)",
		"191" => "SEPA - Überweisung (Sammler - Soll)",
		"192" => "SEPA Direct Debit (Sammler - Haben)",
		"193" => "SEPA Direct Debit (Soll, Reversal)",
		"194" => "SEPA - Überweisung (Sammler - Haben",
		"195" => "SEPA Direct Debit (Sammler - Soll)",
	};

	my $dataset_86 = {};
	my ( $geschaeftsvorfallcode, $buchungstext, $primanota, $___nnummer, $verwendungzweck, $bicauftraggeber, $ibanauftraggeber, $nameauftraggeber, $separueckgabecode  );
	my $sep = '\?';

	# Feld 0 | dreistellig numerisch
	# genau 3 Ziffern - Geschäftsvorfallcode
	if ( $line =~ s/^(\d{3})($|${sep})// ) {
		$dataset_86->{geschaeftsvorfallcode} = $1;
		print "*** GVC '$dataset_86->{geschaeftsvorfallcode}' '$map_geschaeftsvorfallcode->{$dataset_86->{geschaeftsvorfallcode}}'\n";
	} else {
		die "unrecognized line part '$line'";
	}

	# Feld 00 27var char
	if ( $line =~ s/^00([^${sep}]*)($|${sep})// ) {
		$dataset_86->{buchungstext} = $1;
		print "***  00 '$dataset_86->{buchungstext}'\n";
	} else {
		die "unrecognized line part '$line'";
	}

	# Feld 10 10var numerisch
	if ( $line =~ s/^10(\d*)($|${sep})// ) {
		$dataset_86->{primanota} = $1;
		print "***  10 '$dataset_86->{primanota}'\n";
	} else {
		die "unrecognized line part '$line'";
	}

	# Feld 20..29 27var char
	if ( $line =~ s/^20([^${sep}]*)($|${sep})// ) {
		$dataset_86->{verwendungzweck} = [ $1 ];
		print "   ***  20 '$1'\n";
	}
	if ( $line =~ s/^21([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  21 '$1'\n";
	}
	if ( $line =~ s/^22([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  22 '$1'\n";
	}
	if ( $line =~ s/^23([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  23 '$1'\n";
	}
	if ( $line =~ s/^24([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  24 '$1'\n";
	}
	if ( $line =~ s/^25([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  25 '$1'\n";
	}
	if ( $line =~ s/^26([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  26 '$1'\n";
	}
	if ( $line =~ s/^27([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  27 '$1'\n";
	}
	if ( $line =~ s/^28([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  28 '$1'\n";
	}
	if ( $line =~ s/^29([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  29 '$1'\n";
	}
	print "*** :86:?2x: '@{$dataset_86->{verwendungzweck}}'\n";

	# der Geschaeftsvorfallcode "805" / "Abschluss" hat keine Felder > 29
	# /!\ das könnte noch für weitere Geschaeftsvorfallcodes zutreffen /!\
	if ( $dataset_86->{geschaeftsvorfallcode} ne "805" ) {
		# Feld 30 12var BIC Auftraggeber
		if ( $line =~ s/^30([A-Z0-9]*)($|${sep})// ) {
			$dataset_86->{bicauftraggeber} = $1;
			print "***  30 '$dataset_86->{bicauftraggeber}'\n";
		} else {
			die "unrecognized line part '$line'";
		}

		# Feld 31 34var IBAN Auftraggeber
		if ( $line =~ s/^31([A-Z0-9]*)($|${sep})// ) {
			$dataset_86->{ibanauftraggeber} = $1;
			print "***  31 '$dataset_86->{ibanauftraggeber}'\n";
		} else {
			die "unrecognized line part '$line'";
		}

		# Feld 32..33 27var Name Auftraggeber
		if ( $line =~ s/^32(${swift_charset}*)($|${sep})// ) {
			$dataset_86->{nameauftraggeber} = $1;
			print "   ***  32 '$1'\n";
		} else {
			die "unrecognized line part '$line'";
		}
		if ( $line =~ s/^33(${swift_charset}*)($|${sep})// ) {
			$dataset_86->{nameauftraggeber} .= $1;
			print "   ***  33 '$1'\n";
		}
		print "***  32,33 '$nameauftraggeber'\n";

		# Feld 34 3num SEPA Rueckgabecodes
		if ( $line =~ s/^34(\d{3})($|${sep})// ) {
			$dataset_86->{separueckgabecode} = $1;
			print "***  34 '$dataset_86->{separueckgabecode}'\n";
		}
	} else {
		print "*** skipped 20..34 due to GVC '$dataset_86->{geschaeftsvorfallcode}' '$map_geschaeftsvorfallcode->{$dataset_86->{geschaeftsvorfallcode}}'\n";
	}


	# Feld 60..63
	if ( $line =~ s/^60([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  60 '$1'\n";
	}
	if ( $line =~ s/^61([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  61 '$1'\n";
	}
	if ( $line =~ s/^62([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  62 '$1'\n";
	}
	if ( $line =~ s/^63([^${sep}]*)($|${sep})// ) {
		push @{$dataset_86->{verwendungzweck}} , $1;
		print "   ***  63 '$1'\n";
	}
	print "***  20..29,60..63 '@{$dataset_86->{verwendungzweck}}'\n";

	sub decode_verwendungzweck ($) {
		print "      decode_verwendungzweck()\n";
		my ( $a ) = @_;

		# sub ST_WF_20  () {  1 };
		# sub ST_WF_25  () {  2 };
		# sub ST_WF_28C () {  3 };
		# sub ST_WF_60  () {  4 };
		# sub ST_WF_61  () {  5 };
		# sub ST_WF_86  () {  6 };
		# sub ST_WF_62  () {  7 };

		my ( $EREF, $MREF, $KREF, $CRED, $DEBT, $SVWZ, $ABWA );
		my $cur_bez = "";
		my $bezeichner = {};
		my $bez_order = [];

		#dev# use Data::Dumper; print Dumper($a); 
		for my $i ( @$a ) {
			print "      ### '$i'\n";
			if ( $i =~ /^([A-Z]{4})\+(.*)$/ ) {
				print "      ### -> '$1' '$2'\n";
				$cur_bez = $1;
				push @$bez_order, $cur_bez;
				$bezeichner->{$cur_bez} = $2;
			} else {
				print "      ### --> '$i'\n";
				$bezeichner->{$cur_bez} .= $i;
			}
		}
		#dev# use Data::Dumper; print Dumper($bezeichner); exit;
		for my $i ( @$bez_order ) {
			print "         *** $i '$bezeichner->{$i}'\n";
		}

		return $bezeichner;
	}
	$dataset_86->{Bezeichner} = decode_verwendungzweck( $dataset_86->{verwendungzweck} );
	print "   ***  :86: 2x SVWZ '$dataset_86->{Bezeichner}->{SVWZ}'\n";
	return $dataset_86;

}


##
## main parser state engine
##

my $state = ST_WF_20;

my $_86_text = "";
my $data = [];
my $dataset = undef;
my $datasetcnt = 0;

while (my $l = <>) {
	#dev# print "L0: '$l'\n";
	chomp($l);
	$l =~ s/\r$//;
	print "L1: '$l'\n";
	my ( $cdflag, $yy, $mm, $dd, $currency, $amount );
	SWITCH: {
		( $state eq ST_WF_20 ) and do {
			( $l =~ /^:20:(.*)$/ ) and do {
				print "20 Referenznummer der Transaktion '$1'\n";
				push @$data, { "ReferenznummerDerTransaktion" => $1 };
				$state = ST_WF_25;
				last SWITCH;
			};
			die "unrecognized line '$l' in state '$state'";
		}; 
		( $state eq ST_WF_25 ) and do {
			( $l =~ /^:25:(.*)$/ ) and do {
				print "25 Kontobezeichnung [BLZ/Konto] '$1'\n";
				push @$data, { "KontobezeichnungBLZKonto" => $1 };
				$state = ST_WF_28C;
				last SWITCH;
			};
			die "unrecognized line '$l' in state '$state'";
		}; 
		( $state eq ST_WF_28C ) and do {
			( $l =~ /^:28C:(.*)$/ ) and do {
				print "28C Kontoauszugsnummer '$1'\n";
				push @$data, {"Kontoauszugsnummer" => $1 };
				$state = ST_WF_60;
				last SWITCH;
			};
			die "unrecognized line '$l' in state '$state'";
		}; 
		( $state eq ST_WF_60 ) and do {
			( ( $cdflag, $yy, $mm, $dd, $currency, $amount ) = $l =~ /^:60F:([CD])([0-9]{2})([0-9]{2})([0-9]{2})([A-Z]{3})(.*)$/ ) and do {
				print "60F Anfangssaldo [Credit/Debit Datum Waehrung Betrag] $cdflag $yy-$mm-$dd '$currency' '$amount'\n";
				push @$data, { "StartAnfangsSaldo" => {
					"AnfangsSaldoCD"       => $cdflag,
					"AnfangsSaldoDateYYYY" => ($yy > 79)?($yy+1900):($yy+2000),
					"AnfangsSaldoDateMM"   => $mm,
					"AnfangsSaldoDateDD"   => $dd,
					"AnfangsSaldoCurrency" => $currency,
					"AnfangsSaldoAmount"   => $amount,
				}};
				$state = ST_WF_61;
				last SWITCH;
			};
			( ( $cdflag, $yy, $mm, $dd, $currency, $amount ) = $l =~ /^:60M:([CD])([0-9]{2})([0-9]{2})([0-9]{2})([A-Z]{3})(.*)$/ ) and do {
				print "60M Zwischensaldo [Credit/Debit Datum Waehrung Betrag] $cdflag $yy-$mm-$dd '$currency' '$amount'\n";
				push @$data, { "StartZwischenSaldo" => {
					"ZwischenSaldoCD"       => $cdflag,
					"ZwischenSaldoDateYYYY" => ($yy > 79)?($yy+1900):($yy+2000),
					"ZwischenSaldoDateMM"   => $mm,
					"ZwischenSaldoDateDD"   => $dd,
					"ZwischenSaldoCurrency" => $currency,
					"ZwischenSaldoAmount"   => $amount,
				}};
				$state = ST_WF_61;
				last SWITCH;
			};
			die "unrecognized line '$l' in state '$state'";
		};
		# MT940 Field Specifications /  MT940 - 6. Field 61: Statement Line
		# 61 Kontoauszug Zeile/Transaktionsdetails '2208120812DR47,54NDDTKREF+'
		# :61: 090123 0122 C3500,25FCHK304955//4958843 ADDITIONAL INFORMATION - das scheint so nicht zu passen
		( $state eq ST_WF_61  ) and do {
			( $l =~ /^:61:(.*)$/ ) and do {
				$dataset = {};
				$datasetcnt ++;
				my ( $valuedate, $entrydate, $cdmark, $fundscode, $amount, $transactiontype, $identificationcode, $ref_f_a_owner, $slashes, $ref_acct_srv_inst, $suppl_details, $r ) = decode_61 ($1);
				print "61 Kontoauszug Zeile/Transaktionsdetails ( $valuedate, $entrydate, $cdmark, $fundscode, $amount, $transactiontype, $identificationcode, $ref_f_a_owner, $slashes, $ref_acct_srv_inst, $suppl_details, $r )\n";
				$dataset->{_datasetcnt}        = $datasetcnt;
				$dataset->{valuedate}          = $valuedate;
				$dataset->{entrydate}          = $entrydate;
				$dataset->{cdmark}             = $cdmark;
				$dataset->{fundscode}          = $fundscode;
				$dataset->{amount}             = $amount;
				$dataset->{transactiontype}    = $transactiontype;
				$dataset->{identificationcode} = $identificationcode;
				$dataset->{ref_f_a_owner}      = $ref_f_a_owner;
				$dataset->{ref_acct_srv_inst}  = $ref_acct_srv_inst;
				$dataset->{suppl_details}      = $suppl_details;
				push @$data, { "Transaktiondetails" => $dataset };
				use Data::Dumper; print "l.",__LINE__," \$data ",Dumper($data),"\n";
				$state = ST_WF_86;
				last SWITCH;
			};
			die "unrecognized line '$l' in state '$state'";
		};
		#  MT940 Field Specifications /  MT940 - 7. Field 86: Information to Account Owner
		# Format: 6*65x (Narrative) - das waere also ein Freitextfeld
		# weitere Doku: https://docplayer.org/15211095-Mt940-kontoauszug-feld-86-verwendungszweck-in-strukturierter-oder-unstrukturierter-form.html
		( $state eq ST_WF_86  ) and do {
			( $l =~ /^:86:(.*)$/ ) and do {
				my $len = length ($1);
				print "86 Info f. Kontoinhaber ($len) '$1'";
				$_86_text = "$1";
				$state = ST_WF_61_OR_62;
				last SWITCH;
			};
			die "unrecognized line '$l' in state '$state'";
		};
		( $state eq ST_WF_61_OR_62  ) and do {
			# catch trailing content lines for ST_WF_86
		    if ( $l !~ /^:/ ) {
				$_86_text .= $l;
				print "\$l: '$l'\n";
				last SWITCH;
			} 
			# ab hier folgen weitere :xx:-Records, daher wird zunaechst der :86:-Record abgeschlossen
			#print "'\n";
			print $_86_text,"\n";
			my $_86_text_umbr = $_86_text;
			$_86_text_umbr =~ s/\?/\n\t?/g;
			$_86_text_umbr =~ s/^/\t/g;
			print $_86_text_umbr,"\n";
			$dataset->{InformationToAccountOwner} = decode_86($_86_text);
			# add to last $dataset
			my $tmp_idx = $#$data;
			$data->[$tmp_idx]->{Transaktiondetails}->{bezeichner} = $dataset->{bezeichner};

			# print "*** Resetting data structures\n";
			$dataset  = {};
			$_86_text = "";
			use Data::Dumper; print "\$data: ",Dumper($data);

			# handle next of more than one data set:
			if ( $l =~ /^:61:(.*)$/ ) {
				$state = ST_WF_61;
				print "*** subsequent record :61:\n";
				redo SWITCH;
			# ab hier folgen :62x:-Records, daher wird zunaechst der :86:-Record abgeschlossen
			}
			( ( $cdflag, $yy, $mm, $dd, $currency, $amount ) = $l =~ /^:62F:([CD])([0-9]{2})([0-9]{2})([0-9]{2})([A-Z]{3})(.*)$/ ) and do {
				print "62F Schlusssaldo [Credit/Debit Datum Waehrung Betrag] $cdflag $yy-$mm-$dd '$currency' '$amount'\n";
				push @$data, { "EndSchlussSaldo" => {
					"SchlussSaldoCD"       => $cdflag,
					"SchlussSaldoDateYYYY" => ($yy > 79)?($yy+1900):($yy+2000),
					"SchlussSaldoDateMM"   => $mm,
					"SchlussSaldoDateDD"   => $dd,
					"SchlussSaldoCurrency" => $currency,
					"SchlussSaldoAmount"   => $amount,
				}};
				$state = ST_WF_DASH;
				last SWITCH;
			};
			( ( $cdflag, $yy, $mm, $dd, $currency, $amount ) = $l =~ /^:62M:([CD])([0-9]{2})([0-9]{2})([0-9]{2})([A-Z]{3})(.*)$/ ) and do {
				print "62M Zwischensaldo [Credit Datum Waehrung Betrag] C $yy-$mm-$dd '$currency' '$amount'\n";
				push @$data, { "EndZwischenSaldo" => {
					"ZwischenSaldoCD"       => $cdflag,
					"ZwischenSaldoDateYYYY" => ($yy > 79)?($yy+1900):($yy+2000),
					"ZwischenSaldoDateMM"   => $mm,
					"ZwischenSaldoDateDD"   => $dd,
					"ZwischenSaldoCurrency" => $currency,
					"ZwischenSaldoAmount"   => $amount,
				}};
				$state = ST_WF_DASH;
				last SWITCH;
			};
			( ( $yy, $mm, $dd, $currency, $amount ) = $l =~ /^:62M:D([0-9]{2})([0-9]{2})([0-9]{2})([A-Z]{3})(.*)$/ ) and do {
				print "62M Zwischensaldo [Debit Datum Waehrung Betrag] C $yy-$mm-$dd '$currency' '$amount'\n";
				$state = ST_WF_DASH;
				last SWITCH;
			};
			die "unrecognized line '$l' in state '$state'";
		};
		( $state eq ST_WF_DASH  ) and do {
		    if ( $l =~ /^-$/ ) {
				print "************ NEXT ***************\n";
				$state = ST_WF_20;
				last SWITCH;
			}
			die "unrecognized line '$l' in state '$state'";
		};
			
	# sample:
	#  :20:STARTUMS
	#  :25:43060967/7016809500
	#  :28C:0
	#  :60F:C220810EUR12692,91
	#  :61:2208100810CR50,00NTRFKREF+
	#  :86:166?00Uberweisungsgutschr.?10931?20EREF+1222230037810
	#  ?21KREF+2022081027396282090800?22000000001138?23PURP+COMC
	#  ?24SVWZ+SPENDE, BEVORZUGT FOER?25 OSZ ILL OSKOP EREF: 122223
	#  ?260037810?30DRESDEFF600?31DE09600800000914921100
	#  ?32RECKTENWALD CHRISTIAN
	#  :62F:C220810EUR12742,91
	#  -
	} # /SWITCH
}
use Data::Dumper; print Dumper($data);

sub KontoFromIBAN ($) {
	my ( $iban ) = @_;
	if ( $iban !~ /^DE\d{20}$/ ) {
		die "IBAN '$iban' does not match /^DE\\d{20}\$/";
	}
	my $konto = substr $iban,12,10;
	return $konto;
}

sub BLZFromIBAN ($) {
	my ( $iban ) = @_;
	if ( $iban !~ /^DE\d{20}$/ ) {
		die "IBAN '$iban' does not match /^DE\d{20}$/";
	}
	my $blz = substr $iban,4,8;
	return $blz;
}

sub SHfromDC ($) {
	my ($f) = @_;

	if ( $f eq "C" ) {
		return "H";
	} elsif ( $f eq "D" ) {
		return "S";
	} elsif ( $f eq "RC" ) { # Rücklastschriften verursachen Kosten
		return "S";
	#} elsif ( $f eq "RD" ) {
	#	return "H";
	} else {
		die "illegal C/D flag '$f'";
	}
}

my $currency = undef;

##
## printout
##
use Data::Dumper; print '$data: ',Dumper($data);

print "\n";
print "*** print out CSV ###\n";

my $isodate  = strftime("%Y-%m-%dT%H:%M:%S%z",localtime);
my $filename = "from_MT940_" . $isodate . ".csv";

print "*** output filename: '$filename' ###\n";
print "\n";

open CSV,">",$filename or die "Can't open output file '$filename': $!";

# Encoding: ISO-8859-1
my $csv_line = join(";",
	"Buchungstag",
	"Valuta",
	"Buchungstext",
	"Primanota",
	"Auftraggeber/Empf\xE4nger",
	"Zahlungsempf\xE4ngerKto",
	"Zahlungsempf\xE4ngerIBAN",
	"Zahlungsempf\xE4ngerBLZ",
	"Zahlungsempf\xE4ngerBIC",
	"VWZ1",
	"Kundenreferenz",
	"W\xE4hrung",
	"Betrag",
	"Soll/Haben",
	) . "\n";

print "### ",$csv_line;
print CSV    $csv_line;


PRINTOUT: for my $set ( @$data ) {
	use Data::Dumper; print "\$set: ",Dumper($set);
	my @Keys = keys %$set;
	print "Keys: @Keys\n";
	if ( scalar @Keys != 1 ) {
		die "invalid amount of key values";
	}
	my $key = shift @Keys;
	print "key: ",$key,"\n";
	( $key eq "ReferenznummerDerTransaktion" ) and do {
		; # NOOP
		next PRINTOUT;
	};
	( $key eq "KontobezeichnungBLZKonto" ) and do {
		; # NOOP
		next PRINTOUT;
	};
	( $key eq "Kontoauszugsnummer" ) and do {
		; # NOOP
		next PRINTOUT;
	};
	( $key eq "StartAnfangsSaldo" ) and do {
		if ( defined $set->{StartAnfangsSaldo}->{AnfangsSaldoCurrency} ) {
			$currency = $set->{StartAnfangsSaldo}->{AnfangsSaldoCurrency};
			print "currency: '$currency'\n";
		} else {
			use Data::Dumper; print Dumper($set);
			die "missing AnfangsSaldoCurrency in \$data->[0]->{StartAnfangsSaldo}";
		}
		next PRINTOUT;
	};
	( $key eq "StartZwischenSaldo" ) and do {
		if ( defined $set->{StartZwischenSaldo}->{ZwischenSaldoCurrency} ) {
			$currency = $set->{StartZwischenSaldo}->{ZwischenSaldoCurrency};
			print "currency: '$currency'\n";
		} else {
			die "missing ZwischenSaldoCurrency in \$data->[0]->{StartZwischenSaldo}";
		}
		next PRINTOUT;
	};
	( $key eq "Transaktiondetails" ) and do {
		my $geschaeftsvorfallcode = $set->{Transaktiondetails}->{InformationToAccountOwner}->{geschaeftsvorfallcode};
		my $blz  ;
		my $konto;
		if ( $geschaeftsvorfallcode eq "805" ) { # 805 := Abschluss
			$blz   = "";
			$konto = "";
		} else {
			$blz   = BLZFromIBAN  ($set->{Transaktiondetails}->{InformationToAccountOwner}->{ibanauftraggeber});
			$konto = KontoFromIBAN($set->{Transaktiondetails}->{InformationToAccountOwner}->{ibanauftraggeber});
		} 
		# derive YY for $entrydate from value date:
		my $entrydate_YYMMDD = substr($set->{Transaktiondetails}->{valuedate},0,2) . $set->{Transaktiondetails}->{entrydate};

		my $csv_line = join(";",
			$entrydate_YYMMDD,
			$set->{Transaktiondetails}->{valuedate},
			$set->{Transaktiondetails}->{InformationToAccountOwner}->{buchungstext},
			$set->{Transaktiondetails}->{InformationToAccountOwner}->{primanota},
			$set->{Transaktiondetails}->{InformationToAccountOwner}->{nameauftraggeber},
			$konto,
			$set->{Transaktiondetails}->{InformationToAccountOwner}->{ibanauftraggeber},
			$blz,
			$set->{Transaktiondetails}->{InformationToAccountOwner}->{bicauftraggeber},
			$set->{Transaktiondetails}->{InformationToAccountOwner}->{Bezeichner}->{SVWZ},
			"-Kundenreferenz-",
			$currency,
			$set->{Transaktiondetails}->{amount},
			SHfromDC($set->{Transaktiondetails}->{cdmark}),
			)."\n";
		print "### ",$csv_line;
		print CSV    $csv_line;
		next PRINTOUT;
	};
	( $key eq "EndZwischenSaldo" ) and do {
		if ( defined $set->{EndZwischenSaldo}->{ZwischenSaldoCurrency} ) {
			$currency = $set->{EndZwischenSaldo}->{ZwischenSaldoCurrency};
			print "currency: '$currency'\n";
		} else {
			use Data::Dumper; print Dumper($set);
			die "missing ZwischenSaldoCurrency in \$data->[0]->{EndZwischenSaldo}";
		}
		next PRINTOUT;
	};
	( $key eq "EndSchlussSaldo" ) and do {
		if ( defined $set->{EndSchlussSaldo}->{SchlussSaldoCurrency} ) {
			$currency = $set->{EndSchlussSaldo}->{SchlussSaldoCurrency};
			print "currency: '$currency'\n";
		} else {
			use Data::Dumper; print Dumper($set);
			die "missing SchlussSaldoCurrency in \$data->[0]->{EndSchlussSaldo}";
		}
		next PRINTOUT;
	};
	die "unknown record '$key'";
}

close CSV;
print "*** output filename: '$filename' ###\n";

