# Path: pym2149/buf.py
# class BufType:
# 
#     def __init__(self, _, dtype):
#         self.dtype = dtype
# 
#     def __call__(self):
#         return MasterBuf(self.dtype)
# 
# Path: pym2149/mix.py
# class IdealMixer(BufNode):
# 
#     def __init__(self, container, log2maxpeaktopeak, chipamps):
#         log.debug("Mix is trivial: %s", not chipamps.nontrivial)
#         buftype = BufType.float
#         if chipamps.nontrivial:
#             if len(container) != chipamps.size():
#                 raise Exception(f"Expected {len(container)} chipamps but got: {chipamps.size()}")
#             self.contrib = buftype()
#             self.callimpl = self.nontrivialcallimpl
#         else:
#             self.callimpl = self.trivialcallimpl
#         super().__init__(buftype)
#         self.datum = buftype.dtype(2 ** (log2maxpeaktopeak - 1.5)) # Half power point, very close to -3 dB.
#         self.container = container
#         self.chipamps = chipamps
# 
#     def nontrivialcallimpl(self):
#         self.blockbuf.fill_same(self.datum)
#         contrib = self.contrib.ensureandcrop(self.block.framecount)
#         for buf, amp in zip(self.chain(self.container), self.chain(self.chipamps)):
#             if amp:
#                 contrib.copybuf(buf)
#                 contrib.mul(amp)
#                 self.blockbuf.subbuf(contrib)
# 
#     def trivialcallimpl(self):
#         self.blockbuf.fill_same(self.datum)
#         for buf in self.chain(self.container):
#             self.blockbuf.subbuf(buf)
# 
# class Multiplexer(Node):
# 
#     def __init__(self, buftype, streams):
#         super().__init__()
#         self.multi = buftype()
#         self.channels = len(streams)
#         self.streams = streams
# 
#     def callimpl(self):
#         for i, stream in enumerate(self.streams):
#             buf = self.chain(stream)
#             if not i:
#                 # BufNode can't do this because size is not framecount:
#                 size = len(buf)
#                 multi = self.multi.ensureandcrop(size * self.channels)
#             multi.putstrided(i, i + self.channels * size, self.channels, buf.buf)
#         return multi
# 
# Path: pym2149/nod.py
# class Block:
# 
#     def __init__(self, framecount):
#         # If it's a numpy type it can cause overflow in osc module, so ensure arbitrary precision:
#         self.framecount = int(framecount)
# 
#     def __repr__(self):
#         return f"{type(self).__name__}({self.framecount!r})"
# 
# class BufNode(Node):
# 
#     def __init__(self, buftype, channels = 1):
#         super().__init__()
#         self.masterbuf = buftype()
#         self.channels = channels
#         self.realcallimpl = self.callimpl
#         self.callimpl = self.makeresult
# 
#     def makeresult(self):
#         self.blockbuf = self.masterbuf.ensureandcrop(self.block.framecount * self.channels)
#         resultornone = self.realcallimpl()
#         if resultornone is not None:
#             return resultornone
#         return self.blockbuf
# 
# class Container(Node):
# 
#     def __init__(self, nodes):
#         super().__init__()
#         self.nodes = nodes
# 
#     def callimpl(self):
#         return [self.chain(node) for node in self.nodes]
# 
#     def __len__(self):
#         return len(self.nodes)
# 
# Path: pym2149/out.py
# class TrivialOutChannel:
# 
#     nontrivial = False


from .buf import BufType
from .mix import IdealMixer, Multiplexer
from .nod import Block, BufNode, Container
from .out import TrivialOutChannel
from unittest import TestCase
import numpy as np
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pym2149.  If not, see <http://www.gnu.org/licenses/>.


class Counter(BufNode):

    buftype = BufType(None, np.int64) # Closest thing to int.

    def __init__(self, x = 0):
        super().__init__(self.buftype)
        self.x = self.buftype.dtype(x)

    def callimpl(self):
        for frameindex in range(self.block.framecount):
            self.blockbuf.fillpart(frameindex, frameindex + 1, self.x)
            self.x += 1

class TestIdealMixer(TestCase):

    def expect(self, m, values, actual):
        self.assertEqual(len(values), len(actual))
        for i in range(len(values)):
            self.assertAlmostEqual(m.datum - values[i], actual.buf[i])

    def test_works(self):
        c = Container([Counter(10), Counter()])
        m = IdealMixer(c, 16, TrivialOutChannel)

# Path: pym2149/buf.py
# class BufType:
# 
#     def __init__(self, _, dtype):
#         self.dtype = dtype
# 
#     def __call__(self):
#         return MasterBuf(self.dtype)
# 
# Path: pym2149/mix.py
# class IdealMixer(BufNode):
# 
#     def __init__(self, container, log2maxpeaktopeak, chipamps):
#         log.debug("Mix is trivial: %s", not chipamps.nontrivial)
#         buftype = BufType.float
#         if chipamps.nontrivial:
#             if len(container) != chipamps.size():
#                 raise Exception(f"Expected {len(container)} chipamps but got: {chipamps.size()}")
#             self.contrib = buftype()
#             self.callimpl = self.nontrivialcallimpl
#         else:
#             self.callimpl = self.trivialcallimpl
#         super().__init__(buftype)
#         self.datum = buftype.dtype(2 ** (log2maxpeaktopeak - 1.5)) # Half power point, very close to -3 dB.
#         self.container = container
#         self.chipamps = chipamps
# 
#     def nontrivialcallimpl(self):
#         self.blockbuf.fill_same(self.datum)
#         contrib = self.contrib.ensureandcrop(self.block.framecount)
#         for buf, amp in zip(self.chain(self.container), self.chain(self.chipamps)):
#             if amp:
#                 contrib.copybuf(buf)
#                 contrib.mul(amp)
#                 self.blockbuf.subbuf(contrib)
# 
#     def trivialcallimpl(self):
#         self.blockbuf.fill_same(self.datum)
#         for buf in self.chain(self.container):
#             self.blockbuf.subbuf(buf)
# 
# class Multiplexer(Node):
# 
#     def __init__(self, buftype, streams):
#         super().__init__()
#         self.multi = buftype()
#         self.channels = len(streams)
#         self.streams = streams
# 
#     def callimpl(self):
#         for i, stream in enumerate(self.streams):
#             buf = self.chain(stream)
#             if not i:
#                 # BufNode can't do this because size is not framecount:
#                 size = len(buf)
#                 multi = self.multi.ensureandcrop(size * self.channels)
#             multi.putstrided(i, i + self.channels * size, self.channels, buf.buf)
#         return multi
# 
# Path: pym2149/nod.py
# class Block:
# 
#     def __init__(self, framecount):
#         # If it's a numpy type it can cause overflow in osc module, so ensure arbitrary precision:
#         self.framecount = int(framecount)
# 
#     def __repr__(self):
#         return f"{type(self).__name__}({self.framecount!r})"
# 
# class BufNode(Node):
# 
#     def __init__(self, buftype, channels = 1):
#         super().__init__()
#         self.masterbuf = buftype()
#         self.channels = channels
#         self.realcallimpl = self.callimpl
#         self.callimpl = self.makeresult
# 
#     def makeresult(self):
#         self.blockbuf = self.masterbuf.ensureandcrop(self.block.framecount * self.channels)
#         resultornone = self.realcallimpl()
#         if resultornone is not None:
#             return resultornone
#         return self.blockbuf
# 
# class Container(Node):
# 
#     def __init__(self, nodes):
#         super().__init__()
#         self.nodes = nodes
# 
#     def callimpl(self):
#         return [self.chain(node) for node in self.nodes]
# 
#     def __len__(self):
#         return len(self.nodes)
# 
# Path: pym2149/out.py
# class TrivialOutChannel:
# 
#     nontrivial = False


# Path: pym2149/test_mix.py
from .buf import BufType
from .mix import IdealMixer, Multiplexer
from .nod import Block, BufNode, Container
from .out import TrivialOutChannel
from unittest import TestCase
import numpy as np

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pym2149.  If not, see <http://www.gnu.org/licenses/>.


class Counter(BufNode):

    buftype = BufType(None, np.int64) # Closest thing to int.

    def __init__(self, x = 0):
        super().__init__(self.buftype)
        self.x = self.buftype.dtype(x)

    def callimpl(self):
        for frameindex in range(self.block.framecount):
            self.blockbuf.fillpart(frameindex, frameindex + 1, self.x)
            self.x += 1

class TestIdealMixer(TestCase):

    def expect(self, m, values, actual):
        self.assertEqual(len(values), len(actual))
        for i in range(len(values)):
            self.assertAlmostEqual(m.datum - values[i], actual.buf[i])

    def test_works(self):
        c = Container([Counter(10), Counter()])
        m = IdealMixer(c, 16, TrivialOutChannel)

        self.expect(m, [10, 12, 14, 16, 18], m.call(Block(5)))