using System.IO;

namespace MyNes.Core
{
    internal class MMC5Sqr
    {
    	private byte[][] duty_cycle_sequences = new byte[4][]
    	{
    		new byte[8] { 0, 0, 0, 0, 0, 0, 0, 1 },
    		new byte[8] { 0, 0, 0, 0, 0, 0, 1, 1 },
    		new byte[8] { 0, 0, 0, 0, 1, 1, 1, 1 },
    		new byte[8] { 1, 1, 1, 1, 1, 1, 0, 0 }
    	};

    	private byte[] duration_table = new byte[32]
    	{
    		10, 254, 20, 2, 40, 4, 80, 6, 160, 8,
    		60, 10, 14, 12, 26, 14, 12, 16, 24, 18,
    		48, 20, 96, 22, 192, 24, 72, 26, 16, 28,
    		32, 30
    	};

    	private byte duty_cycle;

    	private bool length_halt;

    	private bool constant_volume_envelope;

    	private byte volume_devider_period;

    	private int timer;

    	private int period_devider;

    	private byte seqencer;

    	private bool length_enabled;

    	private int length_counter;

    	private bool envelope_start_flag;

    	private byte envelope_devider;

    	private byte envelope_decay_level_counter;

    	private byte envelope;

    	internal int output;

    	internal bool Outputable;

    	internal void HardReset()
    	{
    		duty_cycle = 0;
    		length_halt = false;
    		constant_volume_envelope = false;
    		volume_devider_period = 0;
    		timer = 0;
    		period_devider = 0;
    		seqencer = 0;
    		length_enabled = false;
    		length_counter = 0;
    		envelope_start_flag = false;
    		envelope_devider = 0;
    		envelope_decay_level_counter = 0;
    		envelope = 0;
    	}

    	internal void SoftReset()
    	{
    		HardReset();
    	}

    	internal void Clock()
    	{
    		period_devider--;
    		if (period_devider > 0)
    		{
    			return;
    		}
    		period_devider = timer + 1;
    		if (length_counter > 0)
    		{
    			if (Outputable)
    			{
    				output = duty_cycle_sequences[duty_cycle][seqencer] * envelope;
    			}
    		}
    		else
    		{
    			output = 0;
    		}
    		if (seqencer == 0)
    		{
    			seqencer = 7;
    		}
    		else
    		{
    			seqencer--;
    		}
    	}

    	internal void ClockLength()
    	{
    		if (length_counter > 0 && !length_halt)
    		{
    			length_counter--;
    		}
    	}

    	internal void ClockEnvelope()
    	{
    		if (envelope_start_flag)
    		{
    			envelope_start_flag = false;
    			envelope_decay_level_counter = 15;
    			envelope_devider = (byte)(volume_devider_period + 1);
    		}
    		else if (envelope_devider > 0)
    		{
    			envelope_devider--;
    		}
    		else
    		{
    			envelope_devider = (byte)(volume_devider_period + 1);
    			if (envelope_decay_level_counter > 0)
    			{
    				envelope_decay_level_counter--;
    			}
    			else if (length_halt)
    			{
    				envelope_decay_level_counter = 15;
    			}
    		}
    		envelope = (constant_volume_envelope ? volume_devider_period : envelope_decay_level_counter);
    	}

    	internal void Write0(ref byte value)
    	{
    		duty_cycle = (byte)((value & 0xC0) >> 6);
    		volume_devider_period = (byte)(value & 0xFu);
    		length_halt = (value & 0x20) != 0;
    		constant_volume_envelope = (value & 0x10) != 0;
    		envelope = (constant_volume_envelope ? volume_devider_period : envelope_decay_level_counter);
    	}

    	internal void Write2(ref byte value)
    	{
    		timer = (timer & 0xFF00) | value;
    	}

    	internal void Write3(ref byte value)
    	{
    		timer = (timer & 0xFF) | ((value & 7) << 8);
    		if (length_enabled)
    		{
    			length_counter = duration_table[value >> 3];
    		}
    		seqencer = 0;
    		envelope_start_flag = true;
    	}

    	internal void WriteEnabled(bool enabled)
    	{
    		length_enabled = enabled;
    		if (!length_enabled)
    		{
    			length_counter = 0;
    		}
    	}

    	internal bool ReadEnable()
    	{
    		return length_counter > 0;
    	}

    	internal void WriteStateData(ref BinaryWriter bin)
    	{
    		bin.Write(duty_cycle);
    		bin.Write(length_halt);
    		bin.Write(constant_volume_envelope);
    		bin.Write(volume_devider_period);
    		bin.Write(timer);
    		bin.Write(period_devider);
    		bin.Write(seqencer);
    		bin.Write(length_enabled);
    		bin.Write(length_counter);
    		bin.Write(envelope_start_flag);
    		bin.Write(envelope_devider);
    		bin.Write(envelope_decay_level_counter);
    		bin.Write(envelope);
    		bin.Write(output);
    	}

    	internal void ReadStateData(ref BinaryReader bin)
    	{
    		duty_cycle = bin.ReadByte();
    		length_halt = bin.ReadBoolean();
    		constant_volume_envelope = bin.ReadBoolean();
    		volume_devider_period = bin.ReadByte();
    		timer = bin.ReadInt32();
    		period_devider = bin.ReadInt32();
    		seqencer = bin.ReadByte();
    		length_enabled = bin.ReadBoolean();
    		length_counter = bin.ReadInt32();
    		envelope_start_flag = bin.ReadBoolean();
    		envelope_devider = bin.ReadByte();
    		envelope_decay_level_counter = bin.ReadByte();
    		envelope = bin.ReadByte();
    		output = bin.ReadInt32();
    	}
    }
}