import pathlib
import tempfile
import unittest
import zipfile
from layout import extract, apply, effective_properties
from renderer import A, P, fromstring

class LayoutTests(unittest.TestCase):
    def source(self, directory):
        path = pathlib.Path(directory) / 'source.pptx'
        with zipfile.ZipFile(path,'w') as z:
            z.writestr('ppt/presentation.xml',f'<p:presentation xmlns:p="{P[1:-1]}" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><p:sldIdLst><p:sldId id="256" r:id="rId1"/></p:sldIdLst><p:sldSz cx="1000000" cy="800000"/></p:presentation>')
            z.writestr('ppt/_rels/presentation.xml.rels','<Relationships><Relationship Id="rId1" Target="slides/slide1.xml"/></Relationships>')
            z.writestr('ppt/slides/slide1.xml',f'<p:sld xmlns:p="{P[1:-1]}" xmlns:a="{A[1:-1]}"><p:cSld><p:spTree><p:sp><p:nvSpPr><p:cNvPr id="4" name="Text"/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="400000" cy="100000"/></a:xfrm></p:spPr><p:txBody><a:bodyPr wrap="none"/><a:p><a:pPr rtl="1"/><a:r><a:rPr lang="ar-SA" sz="2400"/><a:t>نص عربي</a:t></a:r><a:r><a:rPr lang="en-US" sz="1800"/><a:t>ABC</a:t></a:r></a:p></p:txBody></p:sp></p:spTree></p:cSld></p:sld>')
            z.writestr('unchanged.bin',b'unchanged bytes')
        return path
    def test_geometry_preserves_text_runs_and_other_parts(self):
        with tempfile.TemporaryDirectory() as d:
            source=self.source(d); result=extract(source); target=pathlib.Path(d)/'output.pptx'
            identity=result['slides'][0]['elements'][0]['id']
            apply(source,target,{identity:{'x':100,'width':500000}})
            after=extract(target)
            self.assertEqual(after['slides'][0]['elements'][0]['text'],'نص عربيABC')
            self.assertEqual(after['slides'][0]['elements'][0]['bounds']['x'],100)
            with zipfile.ZipFile(target) as z:
                self.assertEqual(z.read('unchanged.bin'),b'unchanged bytes')
                xml=z.read('ppt/slides/slide1.xml')
                self.assertIn(b'lang="ar-SA"',xml); self.assertIn(b'sz="1800"',xml)
    def test_wrap_is_targeted_and_invalid_identity_rejected(self):
        with tempfile.TemporaryDirectory() as d:
            source=self.source(d); target=pathlib.Path(d)/'output.pptx'; identity=extract(source)['slides'][0]['elements'][0]['id']
            apply(source,target,{identity:{'wrap':True}})
            self.assertTrue(extract(target)['slides'][0]['elements'][0]['properties']['wrap'])
            with self.assertRaises(ValueError): apply(source,target,{'foreign#5':{'x':1}})
    def test_paragraph_levels_and_missing_runs_resolve_independently(self):
        shape=fromstring(f'<p:sp xmlns:p="{P[1:-1]}" xmlns:a="{A[1:-1]}"><p:nvSpPr><p:nvPr><p:ph type="body"/></p:nvPr></p:nvSpPr><p:txBody><a:bodyPr lIns="0"/><a:lstStyle><a:lvl1pPr algn="r"><a:defRPr sz="2400"><a:latin typeface="Face A"/></a:defRPr></a:lvl1pPr><a:lvl2pPr algn="l"><a:defRPr sz="1800"><a:latin typeface="Face B"/></a:defRPr></a:lvl2pPr></a:lstStyle><a:p><a:pPr lvl="0"/><a:r><a:rPr sz="3000"/><a:t>Explicit</a:t></a:r><a:r><a:t>Inherited</a:t></a:r></a:p><a:p><a:pPr lvl="1"/><a:r><a:t>Level two</a:t></a:r></a:p></p:txBody></p:sp>')
        presentation=fromstring(f'<p:presentation xmlns:p="{P[1:-1]}"/>')
        values,states,paragraphs=effective_properties(shape.find(P+'txBody'),[shape,None,None],None,presentation,None)
        self.assertEqual(states['font_size']['state'],'mixed')
        self.assertIsNone(values['font_size'])
        self.assertEqual([r['font_size'] for p in paragraphs for r in p['runs']],[30,24,18])
        self.assertEqual([r['font_family'] for p in paragraphs for r in p['runs']],['Face A','Face A','Face B'])
        self.assertEqual(states['alignment']['state'],'mixed')
        self.assertEqual(values['margin_left'],0)
        self.assertEqual(values['margin_right'],91440)
    def test_spacing_and_font_style_use_valid_child_order(self):
        with tempfile.TemporaryDirectory() as d:
            source=self.source(d);target=pathlib.Path(d)/'output.pptx';identity=extract(source)['slides'][0]['elements'][0]['id']
            apply(source,target,{identity:{'space_before':10,'line_spacing':120,'font_family':'Face','font_style':'Bold Italic'}})
            with zipfile.ZipFile(target) as z:
                root=fromstring(z.read('ppt/slides/slide1.xml'))
                props=root.find('.//'+A+'pPr')
                self.assertEqual([n.tag for n in props],[A+'lnSpc',A+'spcBef'])
                self.assertEqual(root.find('.//'+A+'rPr').get('b'),'1')
                self.assertEqual(root.find('.//'+A+'rPr').get('lang'),'ar-SA')
    def test_placeholder_levels_master_and_theme_aliases(self):
        shape=fromstring(f'<p:sp xmlns:p="{P[1:-1]}" xmlns:a="{A[1:-1]}"><p:nvSpPr><p:nvPr><p:ph/></p:nvPr></p:nvSpPr><p:txBody><a:bodyPr/><a:p><a:pPr lvl="1"/><a:r><a:t>العربية</a:t></a:r></a:p></p:txBody></p:sp>')
        master=fromstring(f'<p:sldMaster xmlns:p="{P[1:-1]}" xmlns:a="{A[1:-1]}"><p:txStyles><p:bodyStyle><a:lvl1pPr><a:defRPr sz="4000"/></a:lvl1pPr><a:lvl2pPr algn="r"><a:defRPr sz="2200"><a:cs typeface="+mn-cs"/></a:defRPr></a:lvl2pPr></p:bodyStyle></p:txStyles></p:sldMaster>')
        theme=fromstring(f'<a:theme xmlns:a="{A[1:-1]}"><a:themeElements><a:fontScheme><a:minorFont><a:cs typeface=""/><a:font script="Arab" typeface="Arabic Face"/></a:minorFont></a:fontScheme></a:themeElements></a:theme>')
        values,states,_=effective_properties(shape.find(P+'txBody'),[shape,None,None],master,fromstring(f'<p:presentation xmlns:p="{P[1:-1]}"/>'),theme)
        self.assertEqual(values['font_size'],22)
        self.assertEqual(values['font_family'],'Arabic Face')
        self.assertEqual(states['font_size']['origins'],['master'])
        self.assertEqual(values['alignment'],'r')
    def test_unresolved_size_is_not_invented(self):
        shape=fromstring(f'<p:sp xmlns:p="{P[1:-1]}" xmlns:a="{A[1:-1]}"><p:nvSpPr><p:nvPr/></p:nvSpPr><p:txBody><a:bodyPr/><a:p><a:r><a:t>Text</a:t></a:r></a:p></p:txBody></p:sp>')
        values,states,_=effective_properties(shape.find(P+'txBody'),[shape,None,None],None,fromstring(f'<p:presentation xmlns:p="{P[1:-1]}"/>'),None)
        self.assertIsNone(values['font_size'])
        self.assertEqual(states['font_size']['state'],'unresolved')
if __name__=='__main__': unittest.main()
