-- Id: 19182
-- More information about this indicator can be found at:
-- http://fxcodebase.com/code/viewtopic.php?f=17&t=65142

--+------------------------------------------------------------------+
--|                               Copyright © 2018, Gehtsoft USA LLC |
--|                                            http://fxcodebase.com |
--+------------------------------------------------------------------+
--|                                      Developed by : Mario Jemic  |
--|                                          mario.jemic@gmail.com   |
--+------------------------------------------------------------------+
--|                                 Support our efforts by donating  |
--|                                 Patreon : https://goo.gl/GdXWeN  |
--|                                  Paypal : https://goo.gl/9Rj74e  |
--|                    BitCoin : 15VCJTLaz12Amr7adHSBtL9v8XomURo9RF  |
--|               BitCoin Cash : 1BEtS465S3Su438Kc58h2sqvVvHK9Mijtg  |
--|           Ethereum : 0x8C110cD61538fb6d7A2B47858F0c0AaBd663068D  |
--|                   LiteCoin : LLU8PSY2vsq7B9kRELLZQcKf5nJQrdeqwD  |
--+------------------------------------------------------------------+

function Init()
    indicator:name("MA Horizontal Lines");
    indicator:description("");
    indicator:requiredSource(core.Bar);
    indicator:type(core.Indicator);

 
 

   Add(1, "m1", true);
   Add(2,"m5", true);
   Add(3,"m15", true);
   Add(4,"m30", true);
   Add(5,"H1",  false);
   Add(6,"H2", false);
   Add(7, "H3", false);
   Add(8,"H4", false);
   Add(9,"H6", false);
   Add(10,"H8",  false);
   Add(11,"D1", false);
   Add(12,"W1", false);
   Add(13,"M1",  false);
   
   indicator.parameters:addGroup("Alerts Sound");   
    indicator.parameters:addBoolean("PlaySound", "Play Sound", "", true);	
    indicator.parameters:addBoolean("RecurrentSound", "Recurrent Sound", "", false);
	
	indicator.parameters:addFile("Sound",   "Alert Sound", "", "");
    indicator.parameters:setFlag("Sound", core.FLAG_SOUND);
	indicator.parameters:addGroup("Alerts Dialog box");  
	indicator.parameters:addBoolean("Show", "Show Dialog box Alert", "", true);
	
	indicator.parameters:addGroup("Alerts Email");   
	indicator.parameters:addBoolean("SendEmail", "Send Email", "", true);
    indicator.parameters:addString("Email", "Email", "", "");
    indicator.parameters:setFlag("Email", core.FLAG_EMAIL); 
   
end

function Add(id, TF,  On)

indicator.parameters:addGroup(id ..". Line");
indicator.parameters:addBoolean("on"..id , "Show  This Line", "", On);


indicator.parameters:addString("TF" .. id, "Time Frame ", "", TF);
 indicator.parameters:setFlag("TF" .. id, core.FLAG_PERIODS);

indicator.parameters:addString("Price"..id, "MA Price Source", "", "close");
indicator.parameters:addStringAlternative("Price"..id, "OPEN", "", "open");
indicator.parameters:addStringAlternative("Price"..id, "HIGH", "", "high");
indicator.parameters:addStringAlternative("Price"..id, "LOW", "", "low");
indicator.parameters:addStringAlternative("Price"..id,"CLOSE", "", "close");
indicator.parameters:addStringAlternative("Price"..id, "MEDIAN", "", "median");
indicator.parameters:addStringAlternative("Price"..id, "TYPICAL", "", "typical");
indicator.parameters:addStringAlternative("Price"..id, "WEIGHTED", "", "weighted");	 

indicator.parameters:addInteger("Period"..id, "MA Period", "Period" , 50);

indicator.parameters:addString("Method"..id, "MA Method", "Method" , "MVA");
indicator.parameters:addStringAlternative("Method"..id, "MVA", "MVA" , "MVA");
indicator.parameters:addStringAlternative("Method"..id, "EMA", "EMA" , "EMA");
indicator.parameters:addStringAlternative("Method"..id, "LWMA", "LWMA" , "LWMA");
indicator.parameters:addStringAlternative("Method"..id, "TMA", "TMA" , "TMA");
indicator.parameters:addStringAlternative("Method"..id, "SMMA", "SMMA" , "SMMA");
indicator.parameters:addStringAlternative("Method"..id, "KAMA", "KAMA" , "KAMA");
indicator.parameters:addStringAlternative("Method"..id, "VIDYA", "VIDYA" , "VIDYA");
indicator.parameters:addStringAlternative("Method"..id, "WMA", "WMA" , "WMA");


indicator.parameters:addColor("color"..id , "Line Color", "", getRainbowColour(id/13));
indicator.parameters:addInteger("width"..id , "Indicator Line Width", "", 1, 1, 5);
indicator.parameters:addInteger("style"..id , "Indicator Line Style", "", core.LINE_SOLID);
indicator.parameters:setFlag("style"..id , core.FLAG_LINE_STYLE);

indicator.parameters:addInteger("Size"..id , "Font Size", "", 10);
end

function convertHSVtoRGB(hue, saturation, value)
    -- http://en.wikipedia.org/wiki/HSL_and_HSV
    -- Hue is an angle (0..360), Saturation and Value are both in the range 0..1
    
    local hi = (math.floor(hue / 60)) % 6;
    local f = hue / 60 - math.floor(hue / 60);

    value = value * 255;
    local v = math.floor(value);
    local p = math.floor(value * (1 - saturation));
    local q = math.floor(value * (1 - f * saturation));
    local t = math.floor(value * (1 - (1 - f) * saturation));

    if hi == 0 then
        return core.rgb(v, t, p);
    elseif hi == 1 then
        return core.rgb(q, v, p);
    elseif hi == 2 then
        return core.rgb(p, v, t);
    elseif hi == 3 then
        return core.rgb(p, q, v);
    elseif hi == 4 then
        return core.rgb(t, p, v);
    else
        return core.rgb(v, p, q);
    end
end

function getRainbowColour(ratio)
    -- Using HSV colour system, where Hue is changed in proportion to ratio
    
    ratio = math.max(0, math.min(1, ratio));
    
    -- NOTE: hue is an angular value and can be 0..360, but it wraps around back to 'red' again
    -- which looks a bit weird so I limit to 300 degrees
    local hue = 300 * (1 - ratio);
    
    -- Convert back to RGB
    return convertHSVtoRGB(hue, 1, 1);
end
 
local color={};
local style={};
local width={};
local Size={};

local Price={};

local first;
local source;
local Source={};
local loading={};
local Number;

local Period={};
local MA={};
local Method={};
local TF={};

local Email;
local SendEmail; 
local Sound;
local  RecurrentSound ,SoundFile  ;
local Show;
local PlaySound;
local Last={};
function Prepare(onlyName)
     
	source = instance.source; 
	first=source:first();
    local name;
    name = profile:id() .. "(" .. instance.source:name()  .. ")";
    instance:name(name);

    if onlyName then
        return ;
    end 
	

	Number=0;
	
	for i= 1, 13 , 1 do
		if instance.parameters:getBoolean("on" .. i) then
		Number=Number+1;
		color[Number]=instance.parameters:getColor("color" .. i);
		width[Number]=instance.parameters:getInteger("width" .. i);
		style[Number]=instance.parameters:getInteger("style" .. i);
		Price[Number]=instance.parameters:getString("Price" .. i);	
		Method[Number]=instance.parameters:getString("Method" .. i);	
		Period[Number]=instance.parameters:getInteger("Period" .. i);	
		Size[Number]=instance.parameters:getInteger("Size" .. i);	
		TF[Number]=instance.parameters:getString("TF" .. i);	
		Source[Number] = core.host:execute("getSyncHistory", source:instrument(), TF[Number], source:isBid(), math.min(300,Period[Number]), 200+Number, 100+Number);
		loading[Number]=true;
    assert(core.indicators:findIndicator(Method[Number]) ~= nil, Method[Number] .. " indicator must be installed");
		MA[Number] = core.indicators:create(Method[Number], Source[Number][Price[Number]] ,  Period[Number]); 
		end
	end
	 
   SendEmail = instance.parameters.SendEmail;
	 
	if SendEmail then
        Email = instance.parameters.Email;
    else
        Email = nil;
    end
    assert(not(SendEmail) or (SendEmail and Email ~= ""), "E-mail address must be specified");
	

	
	RecurrentSound= instance.parameters.RecurrentSound;
    Show= instance.parameters.Show; 
	PlaySound = instance.parameters.PlaySound;
	
    if PlaySound then 
	  Sound=instance.parameters.Sound; 
    else  
      Sound=nil;
	 end
	 
	
	 
	 
	  assert(not(PlaySound) or (PlaySound and Sound ~= "") or (PlaySound and Sound ~= ""), "Sound file must be chosen"); 
	  
	  
   instance:ownerDrawn(true);
    Last={};
   core.host:execute ("setTimer", 1, 1);
   

end

function ReleaseInstance()
core.host:execute ("killTimer", 1);
end 


function Evaluate(j)	

	 
	 local   p=Source[j].close:size()-1;
 
		 
		 if not MA[j].DATA:hasData(p) 
		 or not p
		 then
		 return;
		 end
		 
		if Source[j].close[p]>  MA[j].DATA[p] 
		and Source[j].close[p-1]<=  MA[j].DATA[p-1] 
		and Last[j]~=Source[j].close:serial(p)
		then
						 GiveAlert("Up Trend",j);
						 Last[j]=Source[j].close:serial(p); 
		 
		elseif Source[j].close[p]<  MA[j].DATA[p] 
		and Source[j].close[p-1]>=  MA[j].DATA[p-1] 
		and Last[j]~=Source[j].close:serial(p) 
		then   
		                 GiveAlert("Down Trend",j);
						 Last[j]=Source[j].close:serial(p); 
		end
		  	         	 
	 
 
end


function GiveAlert(Label,i)
 
	SoundAlert(Sound);
	Pop(  Label , i  ); 
	EmailAlert(    Label,i  );
end


function EmailAlert( Label ,i  )

if not SendEmail then
return
end

 
   
	local DATA = core.dateToTable (core.now());
	
    
   local delim = "\013\010";  
  
   local Time =  " Date : " .. DATA.month.." / ".. DATA.day .. delim .." Time:  ".. DATA.hour  .." / ".. DATA.min .." / ".. DATA.sec; 
   
     local text = " Instrument : " .. source:instrument() .. delim  .. " Time Frame : " .. TF[i] .. delim..  Time.. delim  .. "Alert" .. " : " ..Label;
	 
   terminal:alertEmail(Email, "MA Horizontal Lines"  , text);
end
 
function Pop(Label ,i )

   if not Show then
   return;
   end
   
  	local DATA = core.dateToTable (core.now());
	
    
   local delim = "\013\010";  
  
   local Time =  " Date : " .. DATA.month.." / ".. DATA.day.. delim .." Time:  ".. DATA.hour  .." / ".. DATA.min .." / ".. DATA.sec; 
   
    local text = " Instrument : " .. source:instrument() .. delim  .. " Time Frame : " .. TF[i] .. delim..  Time.. delim  .. "Alert" .. " : " ..Label;

   core.host:execute ("prompt", 1,  "MA Horizontal Lines"   ,   text );


end

function SoundAlert(iAlert )
 if not PlaySound then
 return;
 end 
  
     terminal:alertSound(iAlert, RecurrentSound);
end

	 



-- the function is called when the async operation is finished
function AsyncOperationFinished(cookie)

local j;
local FLAG=false; 
local Num=0;

    for j = 1, Number, 1 do
		       
			  if cookie == (100 + j) then
			  loading[j]  = true;
		      elseif  cookie == (200 + j ) then
			  loading[j]  = false;         
			  end
		 
		       
                 if loading[j] then
				 FLAG= true;
				 Num=Num+1;
				 end
	end    
   
   
	if not FLAG and cookie== 1 then
			for i= 1, Number , 1 do
				  MA[i]:update(core.UpdateLast );
				  Evaluate(i);
			end
	 end
           
	
	if FLAG then
	 core.host:execute ("setStatus", "  Loading "..((Number) - Num) .. " / " .. (Number) );	 
	else
	 core.host:execute ("setStatus", "Loaded");
	  instance:updateFrom(0);
	end
	
 
   
        
    return core.ASYNC_REDRAW ;
	
	
end


function Update(period)     
end 


local init = false;

function Draw(stage, context)

   local j;
local FLAG=false; 
local Num=0;

    for j = 1, Number, 1 do
 	 
		       
                 if loading[j] then
				 FLAG= true;
				 end
	end    
   
   
   if FLAG then
   return;
   end
   

    if stage ~= 2 	
	then
	return;
	end
	
	
	local Shift= (context:right ()-context:left ())/Number;
	
        if not init then
		    for i=1, Number , 1 do
            context:createPen (i, context:convertPenStyle (style[i]), width[i], color[i]);
			context:createFont (20, "Arial", Size[i], Size[i], 0);
			end
            init = true;
         end
	
	
	
	for i=1, Number , 1 do
	
		if  MA[i].DATA:hasData(MA[i].DATA:size()-1) then
		visible, y = context:pointOfPrice (MA[i].DATA[MA[i].DATA:size()-1]);		 
		context:drawLine (i,  context:left (), y, context:right (), y);
		
		width, height = context:measureText (20, TF[i], 0)
		context:drawText (20, TF[i], color[i], -1, context:right ()-(i)*Shift, y-height, context:right ()-(i)*Shift+width, y, 0);
		end 
    end
	
end