# problem1solution.py
#
# ICS H32 Fall 2025
# Exercise Set 4
# INSTRUCTOR SOLUTION



def _make_location(location_parts: list[str]) -> str:
    return '.'.join(location_parts)


assert _make_location(['boo', 'is', 'happy', 'today']) == 'boo.is.happy.today'
assert _make_location(['boo']) == 'boo'
assert _make_location([]) == ''



def _make_resource(resource_parts: list[str]) -> str:
    return '/' + '/'.join(resource_parts)


assert _make_resource(['boo', 'is', 'happy', 'today']) == '/boo/is/happy/today'
assert _make_resource(['boo']) == '/boo'
assert _make_resource([]) == '/'



def _make_srl(kind: str, location_parts: list[str], resource_parts: list[str]) -> str:
    return kind + '://' + _make_location(location_parts) + _make_resource(resource_parts)


assert _make_srl('http', ['boo', 'com'], ['abc', 'def', 'ghi']) == 'http://boo.com/abc/def/ghi'



class Locator:
    def __init__(self, srl: str):
        def invalid():
            return ValueError('invalid SRL format')
        
        colon_pos = srl.find(':')

        if colon_pos == -1 or colon_pos == 0:
            raise invalid()

        self._kind = srl[:colon_pos]

        if not self._kind.isalpha() or not self._kind.islower():
            raise invalid()

        srl = srl[colon_pos:]

        if not srl.startswith('://'):
            raise invalid()

        srl = srl[3:]

        slash_pos = srl.find('/')

        if slash_pos == -1 or slash_pos == 0:
            raise invalid()

        location = srl[:slash_pos]
        self._location_parts = location.split('.')

        for location_part in self._location_parts:
            if not location_part.isalnum():
                raise invalid()
        
        resource = srl[slash_pos:]
        self._resource_parts = resource.split('/')[1:]

        for resource_part in self._resource_parts:
            if not resource_part.isalnum():
                raise invalid()


    def srl(self) -> str:
        return _make_srl(self._kind, self._location_parts, self._resource_parts)


    def kind(self) -> str:
        return self._kind


    def location(self) -> str:
        return _make_location(self._location_parts)


    def location_parts(self) -> list[str]:
        return self._location_parts


    def resource(self) -> str:
        return _make_resource(self._resource_parts)


    def resource_parts(self) -> str:
        return self._resource_parts


    def parent(self) -> 'Locator':
        if len(self._resource_parts) == 1:
            return self

        new_resource_parts = self._resource_parts[:-1]
        
        return Locator(_make_srl(self._kind, self._location_parts, new_resource_parts))


    def within(self, resource_part: str) -> 'Locator':
        new_resource_parts = self._resource_parts + [resource_part]

        return Locator(_make_srl(self._kind, self._location_parts, new_resource_parts))


# Locators understand their component parts
assert Locator('xyz://abc.def/ghi').srl() == 'xyz://abc.def/ghi'
assert Locator('xyz://abc.def/ghi/jkl/mno').srl() == 'xyz://abc.def/ghi/jkl/mno'
assert Locator('xyz://abc.def/ghi').kind() == 'xyz'
assert Locator('x://abc.def/ghi').kind() == 'x'
assert Locator('xyz://abc.def/ghi').location() == 'abc.def'
assert Locator('xyz://a1c.def/ghi').location() == 'a1c.def'
assert Locator('xyz://A1C.def/ghi').location() == 'A1C.def'
assert Locator('xyz://abc.def/ghi').location_parts() == ['abc', 'def']
assert Locator('xyz://abc/ghi').location() == 'abc'
assert Locator('xyz://abc/ghi').location_parts() == ['abc']
assert Locator('xyz://abc.def/ghi/jkl/mno').resource() == '/ghi/jkl/mno'
assert Locator('xyz://abc.def/ghi/jkl/m2o').resource_parts() == ['ghi', 'jkl', 'm2o']
assert Locator('xyz://abc.def/ghi/jkl/M3O').resource_parts() == ['ghi', 'jkl', 'M3O']
assert Locator('xyz://abc.def/ghi').resource() == '/ghi'
assert Locator('xyz://abc.def/ghi').resource_parts() == ['ghi']

def _srl_invalid(srl: str) -> bool:
    try:
        Locator(srl)
        return False
    except ValueError:
        return True

# Locators cannot be constructed from invalid SRLs
assert _srl_invalid('xyz//abc.def/ghi')
assert _srl_invalid('XYZ://abc.def/ghi')
assert _srl_invalid('B^C://abc.def/ghi')
assert _srl_invalid('b0c://abc.def/ghi')
assert _srl_invalid('abc.def/ghi:xyz')
assert _srl_invalid('xyz:abc.def/ghi')
assert _srl_invalid('xyz:/abc.def/ghi')
assert _srl_invalid('xyz://.abc.def/ghi')
assert _srl_invalid('xyz://abc.def./ghi')
assert _srl_invalid('xyz://abc012def.ghi_mno/jkl')
assert _srl_invalid('xyz://abc.def/')
assert _srl_invalid('xyz://a_b.cde/fgh')
assert _srl_invalid('xyz://abc.def/f_g')

# Locators can tell you their parent or a locator within them
assert Locator('xyz://abc.def/ghi/jkl/mno').parent().srl() == 'xyz://abc.def/ghi/jkl'
assert Locator('xyz://abc.def/ghi').parent().srl() == 'xyz://abc.def/ghi'
assert Locator('xyz://abc.def/ghi').within('jkl').srl() == 'xyz://abc.def/ghi/jkl'
