"""Original bounded Unicode BWT rotations and independent table inversion."""
from collections import Counter
from itertools import product
import json

def encode(s):
 if type(s) is not str or len(s)>12:raise ValueError('literal Unicode string<=12codepoints')
 if not s:return '',0,[]
 rotations=sorted(s[i:]+s[:i] for i in range(len(s)));return ''.join(r[-1] for r in rotations),rotations.index(s),rotations

def decode(last,index):
 if type(last) is not str or len(last)>12 or type(index) is not int or (not last and index!=0) or (last and not 0<=index<len(last)):raise ValueError('bounded L and valid zero-based first-original row')
 if not last:return '',[],[]
 first=sorted(last);buckets={c:[i for i,v in enumerate(first) if v==c] for c in set(last)};counts=Counter();mapping=[]
 for c in last:mapping.append(buckets[c][counts[c]]);counts[c]+=1
 row=index;reverse=[];trace=[]
 for step in range(len(last)):
  reverse.append(last[row]);trace.append({'step':step,'row':row,'character':last[row],'next_row':mapping[row]});row=mapping[row]
 s=''.join(reversed(reverse));l2,i2,_=encode(s)
 if (l2,i2)!=(last,index):raise ValueError('noncanonical/malformed L,I pair under first-original-row contract')
 return s,mapping,trace

def table_oracle(last,index):
 if not last:return '',[]
 table=['']*len(last);stages=[]
 for _ in range(len(last)):table=sorted(c+r for c,r in zip(last,table));stages.append(list(table))
 return table[index],stages

def demo():
 # banana is a standard public pedagogic regression, not claimed novel.
 last,index,rotations=encode('banana');s,mapping,trace=decode(last,index);oracle,stages=table_oracle(last,index);assert (last,index)==('nnbaaa',3) and s==oracle=='banana'
 checks=0;periodic=[]
 for n in range(7):
  for word in product('ab',repeat=n):
   value=''.join(word);l,i,_=encode(value);decoded,_,_=decode(l,i);ref,_=table_oracle(l,i);assert decoded==ref==value and Counter(l)==Counter(value) and len(l)==len(value);checks+=1
 for value in ['abab','aaaa','کارکار','a a']:
  l,i,_=encode(value);back,mp,tr=decode(l,i);assert back==value;periodic.append({'source':value,'L':l,'I':i,'mapping':mp,'decode':back})
 alternate=[]
 for i in range(len(last)):
  try:value,_,_=decode(last,i);alternate.append({'I':i,'decoded':value})
  except ValueError:pass
 assert len({r['decoded'] for r in alternate})>1
 rejected=[]
 for label,fn in [('negative_index',lambda:decode(last,-1)),('out_of_range',lambda:decode(last,6)),('bool_index',lambda:decode(last,True)),('empty_nonzero_index',lambda:decode('',1)),('not_string',lambda:encode([])),('over_budget',lambda:encode('a'*13)),('noncanonical_periodic_primary',lambda:decode('bbaa',1)),('malformed_pair',lambda:decode('ab',0))]:
  try:fn()
  except ValueError:rejected.append(label)
  else:raise AssertionError('expected rejection: '+label)
 return {'standard_banana_regression':{'rotations':rotations,'L':last,'I':index,'sorted_L_not_original':''.join(sorted(last)),'occurrence_rank_mapping':mapping,'backward_decode_trace':trace,'decoded':s,'independent_table_stages':stages},'all127_binary_roundtrips':checks,'periodic_and_original_Unicode_cases':periodic,'same_L_different_valid_I':alternate,'invalid_cases_rejected':rejected,'scope':'literal Unicode block permutation, banana/aaaa/abab standard regressions explicitly not original invention; own Persian/space cases. BWT alone does not compress bytes. No sentinel, bzip2/MTF/Huffman codec, security or compression-ratio claim. Periodic mapping need not one length-n cycle.'}
if __name__=='__main__':print(json.dumps(demo(),ensure_ascii=True,indent=2))
